1 /*
2  * Copyright (c) 2016, 2018, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.jfr.internal.tool;
27 
28 import java.io.PrintWriter;
29 import java.time.Duration;
30 import java.time.OffsetDateTime;
31 import java.time.format.DateTimeFormatter;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.StringJoiner;
35 
36 import jdk.jfr.AnnotationElement;
37 import jdk.jfr.DataAmount;
38 import jdk.jfr.Frequency;
39 import jdk.jfr.MemoryAddress;
40 import jdk.jfr.Percentage;
41 import jdk.jfr.ValueDescriptor;
42 import jdk.jfr.consumer.RecordedClass;
43 import jdk.jfr.consumer.RecordedClassLoader;
44 import jdk.jfr.consumer.RecordedEvent;
45 import jdk.jfr.consumer.RecordedFrame;
46 import jdk.jfr.consumer.RecordedMethod;
47 import jdk.jfr.consumer.RecordedObject;
48 import jdk.jfr.consumer.RecordedStackTrace;
49 import jdk.jfr.consumer.RecordedThread;
50 import jdk.jfr.internal.PrivateAccess;
51 import jdk.jfr.internal.Type;
52 import jdk.jfr.internal.Utils;
53 
54 /**
55  * Print events in a human-readable format.
56  *
57  * This class is also used by {@link RecordedObject#toString()}
58  */
59 public final class PrettyWriter extends EventPrintWriter {
60     private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
61     private final static Long ZERO = 0L;
62     private boolean showIds;
63 
PrettyWriter(PrintWriter destination)64     public PrettyWriter(PrintWriter destination) {
65         super(destination);
66     }
67 
68     @Override
print(List<RecordedEvent> events)69     protected void print(List<RecordedEvent> events) {
70         for (RecordedEvent e : events) {
71             print(e);
72             flush(false);
73         }
74     }
75 
printType(Type t)76     public void printType(Type t) {
77         if (showIds) {
78             print("// id: ");
79             println(String.valueOf(t.getId()));
80         }
81         int commentIndex = t.getName().length() + 10;
82         String typeName = t.getName();
83         int index = typeName.lastIndexOf(".");
84         if (index != -1) {
85             println("@Name(\"" + typeName + "\")");
86         }
87         printAnnotations(commentIndex, t.getAnnotationElements());
88         print("class " + typeName.substring(index + 1));
89         String superType = t.getSuperType();
90         if (superType != null) {
91             print(" extends " + superType);
92         }
93         println(" {");
94         indent();
95         boolean first = true;
96         for (ValueDescriptor v : t.getFields()) {
97             printField(commentIndex, v, first);
98             first = false;
99         }
100         retract();
101         println("}");
102         println();
103     }
104 
printField(int commentIndex, ValueDescriptor v, boolean first)105     private void printField(int commentIndex, ValueDescriptor v, boolean first) {
106         if (!first) {
107             println();
108         }
109         printAnnotations(commentIndex, v.getAnnotationElements());
110         printIndent();
111         Type vType = PrivateAccess.getInstance().getType(v);
112         if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) {
113             print("static ");
114         }
115         print(makeSimpleType(v.getTypeName()));
116         if (v.isArray()) {
117             print("[]");
118         }
119         print(" ");
120         print(v.getName());
121         print(";");
122         printCommentRef(commentIndex, v.getTypeId());
123     }
124 
printCommentRef(int commentIndex, long typeId)125     private void printCommentRef(int commentIndex, long typeId) {
126         if (showIds) {
127             int column = getColumn();
128             if (column > commentIndex) {
129                 print("  ");
130             } else {
131                 while (column < commentIndex) {
132                     print(" ");
133                     column++;
134                 }
135             }
136             println(" // id=" + typeId);
137         } else {
138             println();
139         }
140     }
141 
printAnnotations(int commentIndex, List<AnnotationElement> annotations)142     private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) {
143         for (AnnotationElement a : annotations) {
144             printIndent();
145             print("@");
146             print(makeSimpleType(a.getTypeName()));
147             List<ValueDescriptor> vs = a.getValueDescriptors();
148             if (!vs.isEmpty()) {
149                 printAnnotation(a);
150                 printCommentRef(commentIndex, a.getTypeId());
151             } else {
152                 println();
153             }
154         }
155     }
156 
printAnnotation(AnnotationElement a)157     private void printAnnotation(AnnotationElement a) {
158         StringJoiner sj = new StringJoiner(", ", "(", ")");
159         List<ValueDescriptor> vs = a.getValueDescriptors();
160         for (ValueDescriptor v : vs) {
161             Object o = a.getValue(v.getName());
162             if (vs.size() == 1 && v.getName().equals("value")) {
163                 sj.add(textify(o));
164             } else {
165                 sj.add(v.getName() + "=" + textify(o));
166             }
167         }
168         print(sj.toString());
169     }
170 
textify(Object o)171     private String textify(Object o) {
172         if (o.getClass().isArray()) {
173             Object[] array = (Object[]) o;
174             if (array.length == 1) {
175                 return quoteIfNeeded(array[0]);
176             }
177             StringJoiner s = new StringJoiner(", ", "{", "}");
178             for (Object ob : array) {
179                 s.add(quoteIfNeeded(ob));
180             }
181             return s.toString();
182         } else {
183             return quoteIfNeeded(o);
184         }
185     }
186 
quoteIfNeeded(Object o)187     private String quoteIfNeeded(Object o) {
188         if (o instanceof String) {
189             return "\"" + o + "\"";
190         } else {
191             return String.valueOf(o);
192         }
193     }
194 
makeSimpleType(String typeName)195     private String makeSimpleType(String typeName) {
196         int index = typeName.lastIndexOf(".");
197         return typeName.substring(index + 1);
198     }
199 
print(RecordedEvent event)200     public void print(RecordedEvent event) {
201         print(event.getEventType().getName(), " ");
202         println("{");
203         indent();
204         for (ValueDescriptor v : event.getFields()) {
205             String name = v.getName();
206             if (!isZeroDuration(event, name) && !isLateField(name)) {
207                 printFieldValue(event, v);
208             }
209         }
210         if (event.getThread() != null) {
211             printIndent();
212             print(EVENT_THREAD_FIELD + " = ");
213             printThread(event.getThread(), "");
214         }
215         if (event.getStackTrace() != null) {
216             printIndent();
217             print(STACK_TRACE_FIELD + " = ");
218             printStackTrace(event.getStackTrace());
219         }
220         retract();
221         printIndent();
222         println("}");
223         println();
224     }
225 
isZeroDuration(RecordedEvent event, String name)226     private boolean isZeroDuration(RecordedEvent event, String name) {
227         return name.equals("duration") && ZERO.equals(event.getValue("duration"));
228     }
229 
printStackTrace(RecordedStackTrace stackTrace)230     private void printStackTrace(RecordedStackTrace stackTrace) {
231         println("[");
232         List<RecordedFrame> frames = stackTrace.getFrames();
233         indent();
234         int i = 0;
235         while (i < frames.size() && i < getStackDepth()) {
236             RecordedFrame frame = frames.get(i);
237             if (frame.isJavaFrame()) {
238                 printIndent();
239                 printValue(frame, null, "");
240                 println();
241                 i++;
242             }
243         }
244         if (stackTrace.isTruncated() || i == getStackDepth()) {
245             printIndent();
246             println("...");
247         }
248         retract();
249         printIndent();
250         println("]");
251     }
252 
print(RecordedObject struct, String postFix)253     public void print(RecordedObject struct, String postFix) {
254         println("{");
255         indent();
256         for (ValueDescriptor v : struct.getFields()) {
257             printFieldValue(struct, v);
258         }
259         retract();
260         printIndent();
261         println("}" + postFix);
262     }
263 
printFieldValue(RecordedObject struct, ValueDescriptor v)264     private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
265         printIndent();
266         print(v.getName(), " = ");
267         printValue(getValue(struct, v), v, "");
268     }
269 
printArray(Object[] array)270     private void printArray(Object[] array) {
271         println("[");
272         indent();
273         for (int i = 0; i < array.length; i++) {
274             printIndent();
275             printValue(array[i], null, i + 1 < array.length ? ", " : "");
276         }
277         retract();
278         printIndent();
279         println("]");
280     }
281 
printValue(Object value, ValueDescriptor field, String postFix)282     private void printValue(Object value, ValueDescriptor field, String postFix) {
283         if (value == null) {
284             println("N/A" + postFix);
285             return;
286         }
287         if (value instanceof RecordedObject) {
288             if (value instanceof RecordedThread) {
289                 printThread((RecordedThread) value, postFix);
290                 return;
291             }
292             if (value instanceof RecordedClass) {
293                 printClass((RecordedClass) value, postFix);
294                 return;
295             }
296             if (value instanceof RecordedClassLoader) {
297                 printClassLoader((RecordedClassLoader) value, postFix);
298                 return;
299             }
300             if (value instanceof RecordedFrame) {
301                 RecordedFrame frame = (RecordedFrame) value;
302                 if (frame.isJavaFrame()) {
303                     printJavaFrame((RecordedFrame) value, postFix);
304                     return;
305                 }
306             }
307             if (value instanceof RecordedMethod) {
308                 println(formatMethod((RecordedMethod) value));
309                 return;
310             }
311             print((RecordedObject) value, postFix);
312             return;
313         }
314         if (value.getClass().isArray()) {
315             printArray((Object[]) value);
316             return;
317         }
318 
319         if (value instanceof Double) {
320             Double d = (Double) value;
321             if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
322                 println("N/A");
323                 return;
324             }
325         }
326         if (value instanceof Float) {
327             Float f = (Float) value;
328             if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
329                 println("N/A");
330                 return;
331             }
332         }
333         if (value instanceof Long) {
334             Long l = (Long) value;
335             if (l == Long.MIN_VALUE) {
336                 println("N/A");
337                 return;
338             }
339         }
340         if (value instanceof Integer) {
341             Integer i = (Integer) value;
342             if (i == Integer.MIN_VALUE) {
343                 println("N/A");
344                 return;
345             }
346         }
347 
348         if (field.getContentType() != null) {
349             if (printFormatted(field, value)) {
350                 return;
351             }
352         }
353 
354         String text = String.valueOf(value);
355         if (value instanceof String) {
356             text = "\"" + text + "\"";
357         }
358         println(text);
359     }
360 
printClassLoader(RecordedClassLoader cl, String postFix)361     private void printClassLoader(RecordedClassLoader cl, String postFix) {
362         // Purposely not printing class loader name to avoid cluttered output
363         RecordedClass clazz = cl.getType();
364         print(clazz == null ? "null" : clazz.getName());
365         if (clazz != null) {
366             print(" (");
367             print("id = ");
368             print(String.valueOf(cl.getId()));
369             println(")");
370         }
371     }
372 
printJavaFrame(RecordedFrame f, String postFix)373     private void printJavaFrame(RecordedFrame f, String postFix) {
374         print(formatMethod(f.getMethod()));
375         int line = f.getLineNumber();
376         if (line >= 0) {
377             print(" line: " + line);
378         }
379         print(postFix);
380     }
381 
formatMethod(RecordedMethod m)382     private String formatMethod(RecordedMethod m) {
383         StringBuilder sb = new StringBuilder();
384         sb.append(m.getType().getName());
385         sb.append(".");
386         sb.append(m.getName());
387         sb.append("(");
388         StringJoiner sj = new StringJoiner(", ");
389         String md = m.getDescriptor().replace("/", ".");
390         String parameter = md.substring(1, md.lastIndexOf(")"));
391         for (String qualifiedName : decodeDescriptors(parameter)) {
392             String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
393             sj.add(typeName);
394         }
395         sb.append(sj);
396         sb.append(")");
397         return sb.toString();
398     }
399 
printClass(RecordedClass clazz, String postFix)400     private void printClass(RecordedClass clazz, String postFix) {
401         RecordedClassLoader classLoader = clazz.getClassLoader();
402         String classLoaderName = "null";
403         if (classLoader != null) {
404             if (classLoader.getName() != null) {
405                 classLoaderName = classLoader.getName();
406             } else {
407                 classLoaderName = classLoader.getType().getName();
408             }
409         }
410         String className = clazz.getName();
411         if (className.startsWith("[")) {
412             className = decodeDescriptors(className).get(0);
413         }
414         println(className + " (classLoader = " + classLoaderName + ")" + postFix);
415     }
416 
decodeDescriptors(String descriptor)417     List<String> decodeDescriptors(String descriptor) {
418         List<String> descriptors = new ArrayList<>();
419         for (int index = 0; index < descriptor.length(); index++) {
420             String arrayBrackets = "";
421             while (descriptor.charAt(index) == '[') {
422                 arrayBrackets += "[]";
423                 index++;
424             }
425             char c = descriptor.charAt(index);
426             String type;
427             switch (c) {
428             case 'L':
429                 int endIndex = descriptor.indexOf(';', index);
430                 type = descriptor.substring(index + 1, endIndex);
431                 index = endIndex;
432                 break;
433             case 'I':
434                 type = "int";
435                 break;
436             case 'J':
437                 type = "long";
438                 break;
439             case 'Z':
440                 type = "boolean";
441                 break;
442             case 'D':
443                 type = "double";
444                 break;
445             case 'F':
446                 type = "float";
447                 break;
448             case 'S':
449                 type = "short";
450                 break;
451             case 'C':
452                 type = "char";
453                 break;
454             case 'B':
455                 type = "byte";
456                 break;
457             default:
458                 type = "<unknown-descriptor-type>";
459             }
460             descriptors.add(type + arrayBrackets);
461         }
462         return descriptors;
463     }
464 
printThread(RecordedThread thread, String postFix)465     private void printThread(RecordedThread thread, String postFix) {
466         long javaThreadId = thread.getJavaThreadId();
467         if (javaThreadId > 0) {
468             println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix);
469         } else {
470             println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix);
471         }
472     }
473 
printFormatted(ValueDescriptor field, Object value)474     private boolean printFormatted(ValueDescriptor field, Object value) {
475         if (value instanceof Duration) {
476             Duration d = (Duration) value;
477             if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0)  {
478                 println("N/A");
479                 return true;
480             }
481             double s = d.toNanosPart() / 1000_000_000.0 + d.toSecondsPart();
482             if (s < 1.0) {
483                 if (s < 0.001) {
484                     println(String.format("%.3f", s * 1_000_000) + " us");
485                 } else {
486                     println(String.format("%.3f", s * 1_000) + " ms");
487                 }
488             } else {
489                 if (s < 1000.0) {
490                     println(String.format("%.3f", s) + " s");
491                 } else {
492                     println(String.format("%.0f", s) + " s");
493                 }
494             }
495             return true;
496         }
497         if (value instanceof OffsetDateTime) {
498             OffsetDateTime odt = (OffsetDateTime) value;
499             if (odt.equals(OffsetDateTime.MIN))  {
500                 println("N/A");
501                 return true;
502             }
503             println(TIME_FORMAT.format(odt));
504             return true;
505         }
506         Percentage percentage = field.getAnnotation(Percentage.class);
507         if (percentage != null) {
508             if (value instanceof Number) {
509                 double d = ((Number) value).doubleValue();
510                 println(String.format("%.2f", d * 100) + "%");
511                 return true;
512             }
513         }
514         DataAmount dataAmount = field.getAnnotation(DataAmount.class);
515         if (dataAmount != null) {
516             if (value instanceof Number) {
517                 Number n = (Number) value;
518                 long amount = n.longValue();
519                 if (field.getAnnotation(Frequency.class) != null) {
520                     if (dataAmount.value().equals(DataAmount.BYTES)) {
521                         println(Utils.formatBytesPerSecond(amount));
522                         return true;
523                     }
524                     if (dataAmount.value().equals(DataAmount.BITS)) {
525                         println(Utils.formatBitsPerSecond(amount));
526                         return true;
527                     }
528                 } else {
529                     if (dataAmount.value().equals(DataAmount.BYTES)) {
530                         println(Utils.formatBytes(amount));
531                         return true;
532                     }
533                     if (dataAmount.value().equals(DataAmount.BITS)) {
534                         println(Utils.formatBits(amount));
535                         return true;
536                     }
537                 }
538             }
539         }
540         MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
541         if (memoryAddress != null) {
542             if (value instanceof Number) {
543                 long d = ((Number) value).longValue();
544                 println(String.format("0x%08X", d));
545                 return true;
546             }
547         }
548         Frequency frequency = field.getAnnotation(Frequency.class);
549         if (frequency != null) {
550             if (value instanceof Number) {
551                 println(value + " Hz");
552                 return true;
553             }
554         }
555 
556         return false;
557     }
558 
setShowIds(boolean showIds)559     public void setShowIds(boolean showIds) {
560         this.showIds = showIds;
561     }
562 }
563