1/* 2 * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25package sun.jvm.hotspot.utilities; 26 27import java.io.*; 28import java.util.*; 29import sun.jvm.hotspot.oops.*; 30import sun.jvm.hotspot.runtime.*; 31 32/** 33 * <p>This class writes Java heap in Graph eXchange Language (GXL) 34 * format. GXL is an open standard for serializing arbitrary graphs in 35 * XML syntax.</p> 36 * 37 * <p>A GXL document contains one or more graphs. A graph contains 38 * nodes and edges. Both nodes and edges can have attributes. graphs, 39 * nodes, edges and attributes are represented by XML elements graph, 40 * node, edge and attr respectively. Attributes can be typed. GXL 41 * supports locator, bool, int, float, bool, string, enum as well as 42 * set, seq, bag, tup types. Nodes must have a XML attribute 'id' that 43 * is unique id of the node in the GXL document. Edges must have 44 * 'from' and 'to' XML attributes that are ids of from and to nodes.</p> 45 * 46 * <p>Java heap to GXL document mapping:</p> 47 * <ul> 48 * <li>Java object - GXL node. 49 * <li>Java primitive field - GXL attribute (type mapping below). 50 * <li>Java reference field - GXL edge from referee to referent node. 51 * <li>Java primitive array - GXL node with seq type attribute. 52 * <li>Java char array - GXL node with one attribute of string type. 53 * <li>Java object array - GXL node and 'length' edges. 54 * </ul> 55 * 56 * <p>Java primitive to GXL type mapping:</p> 57 * <ul> 58 * <li>Java byte, int, short, long - GXL int attribute 59 * <li>Java float, double - GXL float attribute 60 * <li>Java boolean - GXL bool atttribute 61 * <li>Java char - GXL string attribute 62 * </ul> 63 * 64 * Exact Java primitive type code is written in 'kind' attribute of 65 * 'attr' element. Type code is specified in JVM spec. second edition 66 * section 4.3.2 (Field Descriptor). 67 * 68 * @see <a href="http://www.gupro.de/GXL/">GXL</a> 69 * @see <a href="http://www.gupro.de/GXL/dtd/dtd.html">GXL DTD</a> 70 */ 71 72public class HeapGXLWriter extends AbstractHeapGraphWriter { 73 public void write(String fileName) throws IOException { 74 out = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); 75 super.write(); 76 if (out.checkError()) { 77 throw new IOException(); 78 } 79 out.flush(); 80 } 81 82 protected void writeHeapHeader() throws IOException { 83 // XML processing instruction 84 out.print("<?xml version='1.0' encoding='"); 85 out.print(ENCODING); 86 out.println("'?>"); 87 88 out.println("<gxl>"); 89 out.println("<graph id='JavaHeap'>"); 90 91 // document properties 92 writeAttribute("creation-date", "string", new Date().toString()); 93 94 // write VM info 95 writeVMInfo(); 96 97 // emit a node for null 98 out.print("<node id='"); 99 out.print(getID(null)); 100 out.println("'/>"); 101 } 102 103 protected void writeObjectHeader(Oop oop) throws IOException { 104 refFields = new ArrayList(); 105 isArray = oop.isArray(); 106 107 // generate an edge for instanceof relation 108 // between object node and it's class node. 109 writeEdge(oop, oop.getKlass().getJavaMirror(), "instanceof"); 110 111 out.print("<node id='"); 112 out.print(getID(oop)); 113 out.println("'>"); 114 } 115 116 protected void writeObjectFooter(Oop oop) throws IOException { 117 out.println("</node>"); 118 119 // write the reference fields as edges 120 for (Iterator itr = refFields.iterator(); itr.hasNext();) { 121 OopField field = (OopField) itr.next(); 122 Oop ref = field.getValue(oop); 123 124 String name = field.getID().getName(); 125 if (isArray) { 126 // for arrays elements we use element<index> pattern 127 name = "element" + name; 128 } else { 129 name = identifierToXMLName(name); 130 } 131 writeEdge(oop, ref, name); 132 } 133 refFields = null; 134 } 135 136 protected void writeObjectArray(ObjArray array) throws IOException { 137 writeObjectHeader(array); 138 writeArrayLength(array); 139 writeObjectFields(array); 140 writeObjectFooter(array); 141 } 142 143 protected void writePrimitiveArray(TypeArray array) 144 throws IOException { 145 writeObjectHeader(array); 146 // write array length 147 writeArrayLength(array); 148 // write array elements 149 out.println("\t<attr name='elements'>"); 150 TypeArrayKlass klass = (TypeArrayKlass) array.getKlass(); 151 if (klass.getElementType() == TypeArrayKlass.T_CHAR) { 152 // char[] special treatment -- write it as string 153 out.print("\t<string>"); 154 out.print(escapeXMLChars(OopUtilities.charArrayToString(array))); 155 out.println("</string>"); 156 } else { 157 out.println("\t<seq>"); 158 writeObjectFields(array); 159 out.println("\t</seq>"); 160 } 161 out.println("\t</attr>"); 162 writeObjectFooter(array); 163 } 164 165 protected void writeClass(Instance instance) throws IOException { 166 writeObjectHeader(instance); 167 Klass reflectedType = java_lang_Class.asKlass(instance); 168 boolean isInstanceKlass = (reflectedType instanceof InstanceKlass); 169 // reflectedType is null for primitive types (int.class etc). 170 if (reflectedType != null) { 171 Symbol name = reflectedType.getName(); 172 if (name != null) { 173 // write class name as an attribute 174 writeAttribute("class-name", "string", name.asString()); 175 } 176 if (isInstanceKlass) { 177 // write object-size as an attribute 178 long sizeInBytes = reflectedType.getLayoutHelper(); 179 writeAttribute("object-size", "int", 180 Long.toString(sizeInBytes)); 181 // write static fields of this class. 182 writeObjectFields((InstanceKlass)reflectedType); 183 } 184 } 185 out.println("</node>"); 186 187 // write edges for super class and direct interfaces 188 if (reflectedType != null) { 189 Klass superType = reflectedType.getSuper(); 190 Oop superMirror = (superType == null)? 191 null : superType.getJavaMirror(); 192 writeEdge(instance, superMirror, "extends"); 193 if (isInstanceKlass) { 194 // write edges for directly implemented interfaces 195 InstanceKlass ik = (InstanceKlass) reflectedType; 196 KlassArray interfaces = ik.getLocalInterfaces(); 197 final int len = interfaces.length(); 198 for (int i = 0; i < len; i++) { 199 Klass k = interfaces.getAt(i); 200 writeEdge(instance, k.getJavaMirror(), "implements"); 201 } 202 203 // write loader 204 Oop loader = ik.getClassLoader(); 205 writeEdge(instance, loader, "loaded-by"); 206 207 // write signers NYI 208 // Oop signers = ik.getJavaMirror().getSigners(); 209 writeEdge(instance, null, "signed-by"); 210 211 // write protection domain NYI 212 // Oop protectionDomain = ik.getJavaMirror().getProtectionDomain(); 213 writeEdge(instance, null, "protection-domain"); 214 215 // write edges for static reference fields from this class 216 for (Iterator itr = refFields.iterator(); itr.hasNext();) { 217 OopField field = (OopField) itr.next(); 218 Oop ref = field.getValue(reflectedType); 219 String name = field.getID().getName(); 220 writeEdge(instance, ref, identifierToXMLName(name)); 221 } 222 } 223 } 224 refFields = null; 225 } 226 227 protected void writeReferenceField(Oop oop, OopField field) 228 throws IOException { 229 refFields.add(field); 230 } 231 232 protected void writeByteField(Oop oop, ByteField field) 233 throws IOException { 234 writeField(field, "int", "B", Byte.toString(field.getValue(oop))); 235 } 236 237 protected void writeCharField(Oop oop, CharField field) 238 throws IOException { 239 writeField(field, "string", "C", 240 escapeXMLChars(Character.toString(field.getValue(oop)))); 241 } 242 243 protected void writeBooleanField(Oop oop, BooleanField field) 244 throws IOException { 245 writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop))); 246 } 247 248 protected void writeShortField(Oop oop, ShortField field) 249 throws IOException { 250 writeField(field, "int", "S", Short.toString(field.getValue(oop))); 251 } 252 253 protected void writeIntField(Oop oop, IntField field) 254 throws IOException { 255 writeField(field, "int", "I", Integer.toString(field.getValue(oop))); 256 } 257 258 protected void writeLongField(Oop oop, LongField field) 259 throws IOException { 260 writeField(field, "int", "J", Long.toString(field.getValue(oop))); 261 } 262 263 protected void writeFloatField(Oop oop, FloatField field) 264 throws IOException { 265 writeField(field, "float", "F", Float.toString(field.getValue(oop))); 266 } 267 268 protected void writeDoubleField(Oop oop, DoubleField field) 269 throws IOException { 270 writeField(field, "float", "D", Double.toString(field.getValue(oop))); 271 } 272 273 protected void writeHeapFooter() throws IOException { 274 out.println("</graph>"); 275 out.println("</gxl>"); 276 } 277 278 //-- Internals only below this point 279 280 // Java identifier to XML NMTOKEN type string 281 private static String identifierToXMLName(String name) { 282 // for now, just replace '$' with '_' 283 return name.replace('$', '_'); 284 } 285 286 // escapes XML meta-characters and illegal characters 287 private static String escapeXMLChars(String s) { 288 // FIXME: is there a better way or API? 289 StringBuffer result = null; 290 for(int i = 0, max = s.length(), delta = 0; i < max; i++) { 291 char c = s.charAt(i); 292 String replacement = null; 293 if (c == '&') { 294 replacement = "&"; 295 } else if (c == '<') { 296 replacement = "<"; 297 } else if (c == '>') { 298 replacement = ">"; 299 } else if (c == '"') { 300 replacement = """; 301 } else if (c == '\'') { 302 replacement = "'"; 303 } else if (c < '\u0020' || (c > '\ud7ff' && c < '\ue000') || 304 c == '\ufffe' || c == '\uffff') { 305 // These are illegal in XML -- put these in a CDATA section. 306 // Refer to section 2.2 Characters in XML specification at 307 // http://www.w3.org/TR/2004/REC-xml-20040204/ 308 replacement = "<![CDATA[&#x" + 309 Integer.toHexString((int)c) + ";]]>"; 310 } 311 312 if (replacement != null) { 313 if (result == null) { 314 result = new StringBuffer(s); 315 } 316 result.replace(i + delta, i + delta + 1, replacement); 317 delta += (replacement.length() - 1); 318 } 319 } 320 if (result == null) { 321 return s; 322 } 323 return result.toString(); 324 } 325 326 private static String getID(Oop oop) { 327 // address as unique id for node -- prefixed by "ID_". 328 if (oop == null) { 329 return "ID_NULL"; 330 } else { 331 return "ID_" + oop.getHandle().toString(); 332 } 333 } 334 335 private void writeArrayLength(Array array) throws IOException { 336 writeAttribute("length", "int", 337 Integer.toString((int) array.getLength())); 338 } 339 340 private void writeAttribute(String name, String type, String value) { 341 out.print("\t<attr name='"); 342 out.print(name); 343 out.print("'><"); 344 out.print(type); 345 out.print('>'); 346 out.print(value); 347 out.print("</"); 348 out.print(type); 349 out.println("></attr>"); 350 } 351 352 private void writeEdge(Oop from, Oop to, String name) throws IOException { 353 out.print("<edge from='"); 354 out.print(getID(from)); 355 out.print("' to='"); 356 out.print(getID(to)); 357 out.println("'>"); 358 writeAttribute("name", "string", name); 359 out.println("</edge>"); 360 } 361 362 private void writeField(Field field, String type, String kind, 363 String value) throws IOException { 364 // 'type' is GXL type of the attribute 365 // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D") 366 if (isArray) { 367 out.print('\t'); 368 } else { 369 out.print("\t<attr name='"); 370 String name = field.getID().getName(); 371 out.print(identifierToXMLName(name)); 372 out.print("' kind='"); 373 out.print(kind); 374 out.print("'>"); 375 } 376 out.print('<'); 377 out.print(type); 378 out.print('>'); 379 out.print(value); 380 out.print("</"); 381 out.print(type); 382 out.print('>'); 383 if (isArray) { 384 out.println(); 385 } else { 386 out.println("</attr>"); 387 } 388 } 389 390 private void writeVMInfo() throws IOException { 391 VM vm = VM.getVM(); 392 writeAttribute("vm-version", "string", vm.getVMRelease()); 393 writeAttribute("vm-type", "string", 394 (vm.isClientCompiler())? "client" : 395 ((vm.isServerCompiler())? "server" : "core")); 396 writeAttribute("os", "string", vm.getOS()); 397 writeAttribute("cpu", "string", vm.getCPU()); 398 writeAttribute("pointer-size", "string", 399 Integer.toString((int)vm.getOopSize() * 8)); 400 } 401 402 // XML encoding that we'll use 403 private static final String ENCODING = "UTF-8"; 404 405 // reference fields of currently visited object 406 private List/*<OopField>*/ refFields; 407 // are we writing an array now? 408 private boolean isArray; 409 private PrintWriter out; 410} 411