/* * Copyright (c) 2004, 2013, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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 = java_lang_Class.asKlass(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((InstanceKlass)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; KlassArray interfaces = ik.getLocalInterfaces(); final int len = interfaces.length(); for (int i = 0; i < len; i++) { Klass k = interfaces.getAt(i); writeEdge(instance, k.getJavaMirror(), "implements"); } // write loader Oop loader = ik.getClassLoader(); writeEdge(instance, loader, "loaded-by"); // write signers NYI // Oop signers = ik.getJavaMirror().getSigners(); writeEdge(instance, null, "signed-by"); // write protection domain NYI // Oop protectionDomain = ik.getJavaMirror().getProtectionDomain(); writeEdge(instance, null, "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 = "&"; } else if (c == '<') { replacement = "<"; } else if (c == '>') { replacement = ">"; } else if (c == '"') { replacement = """; } else if (c == '\'') { replacement = "'"; } 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; }