1 /*
2 * Copyright 2004-2007 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 *
23 */
24
25 package sun.jvm.hotspot.utilities;
26
27 import java.io.*;
28 import java.util.*;
29 import sun.jvm.hotspot.oops.*;
30 import 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
72 public 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 = OopUtilities.classOopToKlass(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 sizeInBytes &= ~Bits.rightNBits(Klass.LH_SIZE_LOW_BITS);
180 writeAttribute("object-size", "int",
181 Long.toString(sizeInBytes));
182 // write static fields of this class.
183 writeObjectFields(reflectedType);
184 }
185 }
186 out.println("</node>");
187
188 // write edges for super class and direct interfaces
189 if (reflectedType != null) {
190 Klass superType = reflectedType.getSuper();
191 Oop superMirror = (superType == null)?
192 null : superType.getJavaMirror();
193 writeEdge(instance, superMirror, "extends");
194 if (isInstanceKlass) {
195 // write edges for directly implemented interfaces
196 InstanceKlass ik = (InstanceKlass) reflectedType;
197 ObjArray interfaces = ik.getLocalInterfaces();
198 final int len = (int) interfaces.getLength();
199 for (int i = 0; i < len; i++) {
200 Klass k = (Klass) interfaces.getObjAt(i);
201 writeEdge(instance, k.getJavaMirror(), "implements");
202 }
203
204 // write loader
205 Oop loader = ik.getClassLoader();
206 writeEdge(instance, loader, "loaded-by");
207
208 // write signers
209 Oop signers = ik.getSigners();
210 writeEdge(instance, signers, "signed-by");
211
212 // write protection domain
213 Oop protectionDomain = ik.getProtectionDomain();
214 writeEdge(instance, protectionDomain, "protection-domain");
215
216 // write edges for static reference fields from this class
217 for (Iterator itr = refFields.iterator(); itr.hasNext();) {
218 OopField field = (OopField) itr.next();
219 Oop ref = field.getValue(reflectedType);
220 String name = field.getID().getName();
221 writeEdge(instance, ref, identifierToXMLName(name));
222 }
223 }
224 }
225 refFields = null;
226 }
227
228 protected void writeReferenceField(Oop oop, OopField field)
229 throws IOException {
230 refFields.add(field);
231 }
232
233 protected void writeByteField(Oop oop, ByteField field)
234 throws IOException {
235 writeField(field, "int", "B", Byte.toString(field.getValue(oop)));
236 }
237
238 protected void writeCharField(Oop oop, CharField field)
239 throws IOException {
240 writeField(field, "string", "C",
241 escapeXMLChars(Character.toString(field.getValue(oop))));
242 }
243
244 protected void writeBooleanField(Oop oop, BooleanField field)
245 throws IOException {
246 writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop)));
247 }
248
249 protected void writeShortField(Oop oop, ShortField field)
250 throws IOException {
251 writeField(field, "int", "S", Short.toString(field.getValue(oop)));
252 }
253
254 protected void writeIntField(Oop oop, IntField field)
255 throws IOException {
256 writeField(field, "int", "I", Integer.toString(field.getValue(oop)));
257 }
258
259 protected void writeLongField(Oop oop, LongField field)
260 throws IOException {
261 writeField(field, "int", "J", Long.toString(field.getValue(oop)));
262 }
263
264 protected void writeFloatField(Oop oop, FloatField field)
265 throws IOException {
266 writeField(field, "float", "F", Float.toString(field.getValue(oop)));
267 }
268
269 protected void writeDoubleField(Oop oop, DoubleField field)
270 throws IOException {
271 writeField(field, "float", "D", Double.toString(field.getValue(oop)));
272 }
273
274 protected void writeHeapFooter() throws IOException {
275 out.println("</graph>");
276 out.println("</gxl>");
277 }
278
279 //-- Internals only below this point
280
281 // Java identifier to XML NMTOKEN type string
282 private static String identifierToXMLName(String name) {
283 // for now, just replace '$' with '_'
284 return name.replace('$', '_');
285 }
286
287 // escapes XML meta-characters and illegal characters
288 private static String escapeXMLChars(String s) {
289 // FIXME: is there a better way or API?
290 StringBuffer result = null;
291 for(int i = 0, max = s.length(), delta = 0; i < max; i++) {
292 char c = s.charAt(i);
293 String replacement = null;
294 if (c == '&') {
295 replacement = "&";
296 } else if (c == '<') {
297 replacement = "<";
298 } else if (c == '>') {
299 replacement = ">";
300 } else if (c == '"') {
301 replacement = """;
302 } else if (c == '\'') {
303 replacement = "'";
304 } else if (c < '\u0020' || (c > '\ud7ff' && c < '\ue000') ||
305 c == '\ufffe' || c == '\uffff') {
306 // These are illegal in XML -- put these in a CDATA section.
307 // Refer to section 2.2 Characters in XML specification at
308 // http://www.w3.org/TR/2004/REC-xml-20040204/
309 replacement = "<![CDATA[&#x" +
310 Integer.toHexString((int)c) + ";]]>";
311 }
312
313 if (replacement != null) {
314 if (result == null) {
315 result = new StringBuffer(s);
316 }
317 result.replace(i + delta, i + delta + 1, replacement);
318 delta += (replacement.length() - 1);
319 }
320 }
321 if (result == null) {
322 return s;
323 }
324 return result.toString();
325 }
326
327 private static String getID(Oop oop) {
328 // address as unique id for node -- prefixed by "ID_".
329 if (oop == null) {
330 return "ID_NULL";
331 } else {
332 return "ID_" + oop.getHandle().toString();
333 }
334 }
335
336 private void writeArrayLength(Array array) throws IOException {
337 writeAttribute("length", "int",
338 Integer.toString((int) array.getLength()));
339 }
340
341 private void writeAttribute(String name, String type, String value) {
342 out.print("\t<attr name='");
343 out.print(name);
344 out.print("'><");
345 out.print(type);
346 out.print('>');
347 out.print(value);
348 out.print("</");
349 out.print(type);
350 out.println("></attr>");
351 }
352
353 private void writeEdge(Oop from, Oop to, String name) throws IOException {
354 out.print("<edge from='");
355 out.print(getID(from));
356 out.print("' to='");
357 out.print(getID(to));
358 out.println("'>");
359 writeAttribute("name", "string", name);
360 out.println("</edge>");
361 }
362
363 private void writeField(Field field, String type, String kind,
364 String value) throws IOException {
365 // 'type' is GXL type of the attribute
366 // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D")
367 if (isArray) {
368 out.print('\t');
369 } else {
370 out.print("\t<attr name='");
371 String name = field.getID().getName();
372 out.print(identifierToXMLName(name));
373 out.print("' kind='");
374 out.print(kind);
375 out.print("'>");
376 }
377 out.print('<');
378 out.print(type);
379 out.print('>');
380 out.print(value);
381 out.print("</");
382 out.print(type);
383 out.print('>');
384 if (isArray) {
385 out.println();
386 } else {
387 out.println("</attr>");
388 }
389 }
390
391 private void writeVMInfo() throws IOException {
392 VM vm = VM.getVM();
393 writeAttribute("vm-version", "string", vm.getVMRelease());
394 writeAttribute("vm-type", "string",
395 (vm.isClientCompiler())? "client" :
396 ((vm.isServerCompiler())? "server" : "core"));
397 writeAttribute("os", "string", vm.getOS());
398 writeAttribute("cpu", "string", vm.getCPU());
399 writeAttribute("pointer-size", "string",
400 Integer.toString((int)vm.getOopSize() * 8));
401 }
402
403 // XML encoding that we'll use
404 private static final String ENCODING = "UTF-8";
405
406 // reference fields of currently visited object
407 private List/*<OopField>*/ refFields;
408 // are we writing an array now?
409 private boolean isArray;
410 private PrintWriter out;
411 }