hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapGXLWriter.java
changeset 1 489c9b5090e2
child 5547 f4b087cbb361
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapGXLWriter.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2004-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package sun.jvm.hotspot.utilities;
+
+import java.io.*;
+import java.util.*;
+import sun.jvm.hotspot.oops.*;
+import sun.jvm.hotspot.runtime.*;
+
+/**
+ * <p>This class writes Java heap in Graph eXchange Language (GXL)
+ * format. GXL is an open standard for serializing arbitrary graphs in
+ * XML syntax.</p>
+ *
+ * <p>A GXL document contains one or more graphs. A graph contains
+ * nodes and edges. Both nodes and edges can have attributes. graphs,
+ * nodes, edges and attributes are represented by XML elements graph,
+ * node, edge and attr respectively. Attributes can be typed. GXL
+ * supports locator, bool, int, float, bool, string, enum as well as
+ * set, seq, bag, tup types. Nodes must have a XML attribute 'id' that
+ * is unique id of the node in the GXL document. Edges must have
+ * 'from' and 'to' XML attributes that are ids of from and to nodes.</p>
+ *
+ * <p>Java heap to GXL document mapping:</p>
+ * <ul>
+ * <li>Java object - GXL node.
+ * <li>Java primitive field - GXL attribute (type mapping below).
+ * <li>Java reference field - GXL edge from referee to referent node.
+ * <li>Java primitive array - GXL node with seq type attribute.
+ * <li>Java char array - GXL node with one attribute of string type.
+ * <li>Java object array - GXL node and 'length' edges.
+ * </ul>
+ *
+ * <p>Java primitive to GXL type mapping:</p>
+ * <ul>
+ * <li>Java byte, int, short, long - GXL int attribute
+ * <li>Java float, double - GXL float attribute
+ * <li>Java boolean - GXL bool atttribute
+ * <li>Java char - GXL string attribute
+ * </ul>
+ *
+ * Exact Java primitive type code is written in 'kind' attribute of
+ * 'attr' element.  Type code is specified in JVM spec. second edition
+ * section 4.3.2 (Field Descriptor).
+ *
+ * @see <a href="http://www.gupro.de/GXL/">GXL</a>
+ * @see <a href="http://www.gupro.de/GXL/dtd/dtd.html">GXL DTD</a>
+ */
+
+public class HeapGXLWriter extends AbstractHeapGraphWriter {
+    public void write(String fileName) throws IOException {
+        out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
+        super.write();
+        if (out.checkError()) {
+            throw new IOException();
+        }
+        out.flush();
+    }
+
+    protected void writeHeapHeader() throws IOException {
+        // XML processing instruction
+        out.print("<?xml version='1.0' encoding='");
+        out.print(ENCODING);
+        out.println("'?>");
+
+        out.println("<gxl>");
+        out.println("<graph id='JavaHeap'>");
+
+        // document properties
+        writeAttribute("creation-date", "string", new Date().toString());
+
+        // write VM info
+        writeVMInfo();
+
+        // emit a node for null
+        out.print("<node id='");
+        out.print(getID(null));
+        out.println("'/>");
+    }
+
+    protected void writeObjectHeader(Oop oop) throws IOException  {
+        refFields = new ArrayList();
+        isArray = oop.isArray();
+
+        // generate an edge for instanceof relation
+        // between object node and it's class node.
+        writeEdge(oop, oop.getKlass().getJavaMirror(), "instanceof");
+
+        out.print("<node id='");
+        out.print(getID(oop));
+        out.println("'>");
+    }
+
+    protected void writeObjectFooter(Oop oop) throws IOException  {
+        out.println("</node>");
+
+        // write the reference fields as edges
+        for (Iterator itr = refFields.iterator(); itr.hasNext();) {
+            OopField field = (OopField) itr.next();
+            Oop ref = field.getValue(oop);
+
+            String name = field.getID().getName();
+            if (isArray) {
+                // for arrays elements we use element<index> pattern
+                name = "element" + name;
+            } else {
+                name = identifierToXMLName(name);
+            }
+            writeEdge(oop, ref, name);
+        }
+        refFields = null;
+    }
+
+    protected void writeObjectArray(ObjArray array) throws IOException {
+        writeObjectHeader(array);
+        writeArrayLength(array);
+        writeObjectFields(array);
+        writeObjectFooter(array);
+    }
+
+    protected void writePrimitiveArray(TypeArray array)
+        throws IOException  {
+        writeObjectHeader(array);
+        // write array length
+        writeArrayLength(array);
+        // write array elements
+        out.println("\t<attr name='elements'>");
+        TypeArrayKlass klass = (TypeArrayKlass) array.getKlass();
+        if (klass.getElementType() == TypeArrayKlass.T_CHAR) {
+            // char[] special treatment -- write it as string
+            out.print("\t<string>");
+            out.print(escapeXMLChars(OopUtilities.charArrayToString(array)));
+            out.println("</string>");
+        } else {
+            out.println("\t<seq>");
+            writeObjectFields(array);
+            out.println("\t</seq>");
+        }
+        out.println("\t</attr>");
+        writeObjectFooter(array);
+    }
+
+    protected void writeClass(Instance instance) throws IOException  {
+        writeObjectHeader(instance);
+        Klass reflectedType = OopUtilities.classOopToKlass(instance);
+        boolean isInstanceKlass = (reflectedType instanceof InstanceKlass);
+        // reflectedType is null for primitive types (int.class etc).
+        if (reflectedType != null) {
+            Symbol name = reflectedType.getName();
+            if (name != null) {
+                // write class name as an attribute
+                writeAttribute("class-name", "string", name.asString());
+            }
+            if (isInstanceKlass) {
+                // write object-size as an attribute
+                long sizeInBytes = reflectedType.getLayoutHelper();
+                writeAttribute("object-size", "int",
+                               Long.toString(sizeInBytes));
+                // write static fields of this class.
+                writeObjectFields(reflectedType);
+            }
+        }
+        out.println("</node>");
+
+        // write edges for super class and direct interfaces
+        if (reflectedType != null) {
+            Klass superType = reflectedType.getSuper();
+            Oop superMirror = (superType == null)?
+                              null : superType.getJavaMirror();
+            writeEdge(instance, superMirror, "extends");
+            if (isInstanceKlass) {
+                // write edges for directly implemented interfaces
+                InstanceKlass ik = (InstanceKlass) reflectedType;
+                ObjArray interfaces = ik.getLocalInterfaces();
+                final int len = (int) interfaces.getLength();
+                for (int i = 0; i < len; i++) {
+                    Klass k = (Klass) interfaces.getObjAt(i);
+                    writeEdge(instance, k.getJavaMirror(), "implements");
+                }
+
+                // write loader
+                Oop loader = ik.getClassLoader();
+                writeEdge(instance, loader, "loaded-by");
+
+                // write signers
+                Oop signers = ik.getSigners();
+                writeEdge(instance, signers, "signed-by");
+
+                // write protection domain
+                Oop protectionDomain = ik.getProtectionDomain();
+                writeEdge(instance, protectionDomain, "protection-domain");
+
+                // write edges for static reference fields from this class
+                for (Iterator itr = refFields.iterator(); itr.hasNext();) {
+                    OopField field = (OopField) itr.next();
+                    Oop ref = field.getValue(reflectedType);
+                    String name = field.getID().getName();
+                    writeEdge(instance, ref, identifierToXMLName(name));
+                }
+            }
+        }
+        refFields = null;
+    }
+
+    protected void writeReferenceField(Oop oop, OopField field)
+        throws IOException {
+        refFields.add(field);
+    }
+
+    protected void writeByteField(Oop oop, ByteField field)
+        throws IOException {
+        writeField(field, "int", "B", Byte.toString(field.getValue(oop)));
+    }
+
+    protected void writeCharField(Oop oop, CharField field)
+        throws IOException {
+        writeField(field, "string", "C",
+                   escapeXMLChars(Character.toString(field.getValue(oop))));
+    }
+
+    protected void writeBooleanField(Oop oop, BooleanField field)
+        throws IOException {
+        writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop)));
+    }
+
+    protected void writeShortField(Oop oop, ShortField field)
+        throws IOException {
+        writeField(field, "int", "S", Short.toString(field.getValue(oop)));
+    }
+
+    protected void writeIntField(Oop oop, IntField field)
+        throws IOException {
+        writeField(field, "int", "I", Integer.toString(field.getValue(oop)));
+    }
+
+    protected void writeLongField(Oop oop, LongField field)
+        throws IOException {
+        writeField(field, "int", "J", Long.toString(field.getValue(oop)));
+    }
+
+    protected void writeFloatField(Oop oop, FloatField field)
+        throws IOException {
+        writeField(field, "float", "F", Float.toString(field.getValue(oop)));
+    }
+
+    protected void writeDoubleField(Oop oop, DoubleField field)
+        throws IOException {
+        writeField(field, "float", "D", Double.toString(field.getValue(oop)));
+    }
+
+    protected void writeHeapFooter() throws IOException  {
+        out.println("</graph>");
+        out.println("</gxl>");
+    }
+
+    //-- Internals only below this point
+
+    // Java identifier to XML NMTOKEN type string
+    private static String identifierToXMLName(String name) {
+        // for now, just replace '$' with '_'
+        return name.replace('$', '_');
+    }
+
+    // escapes XML meta-characters and illegal characters
+    private static String escapeXMLChars(String s) {
+        // FIXME: is there a better way or API?
+        StringBuffer result = null;
+        for(int i = 0, max = s.length(), delta = 0; i < max; i++) {
+            char c = s.charAt(i);
+            String replacement = null;
+            if (c == '&') {
+                replacement = "&amp;";
+            } else if (c == '<') {
+                replacement = "&lt;";
+            } else if (c == '>') {
+                replacement = "&gt;";
+            } else if (c == '"') {
+                replacement = "&quot;";
+            } else if (c == '\'') {
+                replacement = "&apos;";
+            } else if (c <  '\u0020' || (c > '\ud7ff' && c < '\ue000') ||
+                       c == '\ufffe' || c == '\uffff') {
+                // These are illegal in XML -- put these in a CDATA section.
+                // Refer to section 2.2 Characters in XML specification at
+                // http://www.w3.org/TR/2004/REC-xml-20040204/
+                replacement = "<![CDATA[&#x" +
+                    Integer.toHexString((int)c) + ";]]>";
+            }
+
+            if (replacement != null) {
+                if (result == null) {
+                    result = new StringBuffer(s);
+                }
+                result.replace(i + delta, i + delta + 1, replacement);
+                delta += (replacement.length() - 1);
+            }
+        }
+        if (result == null) {
+            return s;
+        }
+        return result.toString();
+    }
+
+    private static String getID(Oop oop) {
+        // address as unique id for node -- prefixed by "ID_".
+        if (oop == null) {
+            return "ID_NULL";
+        } else {
+            return "ID_" + oop.getHandle().toString();
+        }
+    }
+
+    private void writeArrayLength(Array array) throws IOException {
+        writeAttribute("length", "int",
+                       Integer.toString((int) array.getLength()));
+    }
+
+    private void writeAttribute(String name, String type, String value) {
+        out.print("\t<attr name='");
+        out.print(name);
+        out.print("'><");
+        out.print(type);
+        out.print('>');
+        out.print(value);
+        out.print("</");
+        out.print(type);
+        out.println("></attr>");
+    }
+
+    private void writeEdge(Oop from, Oop to, String name) throws IOException {
+        out.print("<edge from='");
+        out.print(getID(from));
+        out.print("' to='");
+        out.print(getID(to));
+        out.println("'>");
+        writeAttribute("name", "string", name);
+        out.println("</edge>");
+    }
+
+    private void writeField(Field field, String type, String kind,
+                            String value) throws IOException  {
+        // 'type' is GXL type of the attribute
+        // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D")
+        if (isArray) {
+            out.print('\t');
+        } else {
+            out.print("\t<attr name='");
+            String name = field.getID().getName();
+            out.print(identifierToXMLName(name));
+            out.print("' kind='");
+            out.print(kind);
+            out.print("'>");
+        }
+        out.print('<');
+        out.print(type);
+        out.print('>');
+        out.print(value);
+        out.print("</");
+        out.print(type);
+        out.print('>');
+        if (isArray) {
+            out.println();
+        } else {
+            out.println("</attr>");
+        }
+    }
+
+    private void writeVMInfo() throws IOException {
+        VM vm = VM.getVM();
+        writeAttribute("vm-version", "string", vm.getVMRelease());
+        writeAttribute("vm-type", "string",
+                       (vm.isClientCompiler())? "client" :
+                       ((vm.isServerCompiler())? "server" : "core"));
+        writeAttribute("os", "string", vm.getOS());
+        writeAttribute("cpu", "string", vm.getCPU());
+        writeAttribute("pointer-size", "string",
+                       Integer.toString((int)vm.getOopSize() * 8));
+    }
+
+    // XML encoding that we'll use
+    private static final String ENCODING = "UTF-8";
+
+    // reference fields of currently visited object
+    private List/*<OopField>*/ refFields;
+    // are we writing an array now?
+    private boolean isArray;
+    private PrintWriter out;
+}