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 static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject";
61     private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
62     private final static Long ZERO = 0L;
63     private boolean showIds;
64     private RecordedEvent currentEvent;
65 
PrettyWriter(PrintWriter destination)66     public PrettyWriter(PrintWriter destination) {
67         super(destination);
68     }
69 
70     @Override
print(List<RecordedEvent> events)71     protected void print(List<RecordedEvent> events) {
72         for (RecordedEvent e : events) {
73             print(e);
74             flush(false);
75         }
76     }
77 
printType(Type t)78     public void printType(Type t) {
79         if (showIds) {
80             print("// id: ");
81             println(String.valueOf(t.getId()));
82         }
83         int commentIndex = t.getName().length() + 10;
84         String typeName = t.getName();
85         int index = typeName.lastIndexOf(".");
86         if (index != -1) {
87             println("@Name(\"" + typeName + "\")");
88         }
89         printAnnotations(commentIndex, t.getAnnotationElements());
90         print("class " + typeName.substring(index + 1));
91         String superType = t.getSuperType();
92         if (superType != null) {
93             print(" extends " + superType);
94         }
95         println(" {");
96         indent();
97         boolean first = true;
98         for (ValueDescriptor v : t.getFields()) {
99             printField(commentIndex, v, first);
100             first = false;
101         }
102         retract();
103         println("}");
104         println();
105     }
106 
printField(int commentIndex, ValueDescriptor v, boolean first)107     private void printField(int commentIndex, ValueDescriptor v, boolean first) {
108         if (!first) {
109             println();
110         }
111         printAnnotations(commentIndex, v.getAnnotationElements());
112         printIndent();
113         Type vType = PrivateAccess.getInstance().getType(v);
114         if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) {
115             print("static ");
116         }
117         print(makeSimpleType(v.getTypeName()));
118         if (v.isArray()) {
119             print("[]");
120         }
121         print(" ");
122         print(v.getName());
123         print(";");
124         printCommentRef(commentIndex, v.getTypeId());
125     }
126 
printCommentRef(int commentIndex, long typeId)127     private void printCommentRef(int commentIndex, long typeId) {
128         if (showIds) {
129             int column = getColumn();
130             if (column > commentIndex) {
131                 print("  ");
132             } else {
133                 while (column < commentIndex) {
134                     print(" ");
135                     column++;
136                 }
137             }
138             println(" // id=" + typeId);
139         } else {
140             println();
141         }
142     }
143 
printAnnotations(int commentIndex, List<AnnotationElement> annotations)144     private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) {
145         for (AnnotationElement a : annotations) {
146             printIndent();
147             print("@");
148             print(makeSimpleType(a.getTypeName()));
149             List<ValueDescriptor> vs = a.getValueDescriptors();
150             if (!vs.isEmpty()) {
151                 printAnnotation(a);
152                 printCommentRef(commentIndex, a.getTypeId());
153             } else {
154                 println();
155             }
156         }
157     }
158 
printAnnotation(AnnotationElement a)159     private void printAnnotation(AnnotationElement a) {
160         StringJoiner sj = new StringJoiner(", ", "(", ")");
161         List<ValueDescriptor> vs = a.getValueDescriptors();
162         for (ValueDescriptor v : vs) {
163             Object o = a.getValue(v.getName());
164             if (vs.size() == 1 && v.getName().equals("value")) {
165                 sj.add(textify(o));
166             } else {
167                 sj.add(v.getName() + "=" + textify(o));
168             }
169         }
170         print(sj.toString());
171     }
172 
textify(Object o)173     private String textify(Object o) {
174         if (o.getClass().isArray()) {
175             Object[] array = (Object[]) o;
176             if (array.length == 1) {
177                 return quoteIfNeeded(array[0]);
178             }
179             StringJoiner s = new StringJoiner(", ", "{", "}");
180             for (Object ob : array) {
181                 s.add(quoteIfNeeded(ob));
182             }
183             return s.toString();
184         } else {
185             return quoteIfNeeded(o);
186         }
187     }
188 
quoteIfNeeded(Object o)189     private String quoteIfNeeded(Object o) {
190         if (o instanceof String) {
191             return "\"" + o + "\"";
192         } else {
193             return String.valueOf(o);
194         }
195     }
196 
makeSimpleType(String typeName)197     private String makeSimpleType(String typeName) {
198         int index = typeName.lastIndexOf(".");
199         return typeName.substring(index + 1);
200     }
201 
print(RecordedEvent event)202     public void print(RecordedEvent event) {
203         currentEvent = event;
204         print(event.getEventType().getName(), " ");
205         println("{");
206         indent();
207         for (ValueDescriptor v : event.getFields()) {
208             String name = v.getName();
209             if (!isZeroDuration(event, name) && !isLateField(name)) {
210                 printFieldValue(event, v);
211             }
212         }
213         if (event.getThread() != null) {
214             printIndent();
215             print(EVENT_THREAD_FIELD + " = ");
216             printThread(event.getThread(), "");
217         }
218         if (event.getStackTrace() != null) {
219             printIndent();
220             print(STACK_TRACE_FIELD + " = ");
221             printStackTrace(event.getStackTrace());
222         }
223         retract();
224         printIndent();
225         println("}");
226         println();
227     }
228 
isZeroDuration(RecordedEvent event, String name)229     private boolean isZeroDuration(RecordedEvent event, String name) {
230         return name.equals("duration") && ZERO.equals(event.getValue("duration"));
231     }
232 
printStackTrace(RecordedStackTrace stackTrace)233     private void printStackTrace(RecordedStackTrace stackTrace) {
234         println("[");
235         List<RecordedFrame> frames = stackTrace.getFrames();
236         indent();
237         int i = 0;
238         while (i < frames.size() && i < getStackDepth()) {
239             RecordedFrame frame = frames.get(i);
240             if (frame.isJavaFrame()) {
241                 printIndent();
242                 printValue(frame, null, "");
243                 println();
244                 i++;
245             }
246         }
247         if (stackTrace.isTruncated() || i == getStackDepth()) {
248             printIndent();
249             println("...");
250         }
251         retract();
252         printIndent();
253         println("]");
254     }
255 
print(RecordedObject struct, String postFix)256     public void print(RecordedObject struct, String postFix) {
257         println("{");
258         indent();
259         for (ValueDescriptor v : struct.getFields()) {
260             printFieldValue(struct, v);
261         }
262         retract();
263         printIndent();
264         println("}" + postFix);
265     }
266 
printFieldValue(RecordedObject struct, ValueDescriptor v)267     private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
268         printIndent();
269         print(v.getName(), " = ");
270         printValue(getValue(struct, v), v, "");
271     }
272 
printArray(Object[] array)273     private void printArray(Object[] array) {
274         println("[");
275         indent();
276         for (int i = 0; i < array.length; i++) {
277             printIndent();
278             printValue(array[i], null, i + 1 < array.length ? ", " : "");
279         }
280         retract();
281         printIndent();
282         println("]");
283     }
284 
printValue(Object value, ValueDescriptor field, String postFix)285     private void printValue(Object value, ValueDescriptor field, String postFix) {
286         if (value == null) {
287             println("N/A" + postFix);
288             return;
289         }
290         if (value instanceof RecordedObject) {
291             if (value instanceof RecordedThread) {
292                 printThread((RecordedThread) value, postFix);
293                 return;
294             }
295             if (value instanceof RecordedClass) {
296                 printClass((RecordedClass) value, postFix);
297                 return;
298             }
299             if (value instanceof RecordedClassLoader) {
300                 printClassLoader((RecordedClassLoader) value, postFix);
301                 return;
302             }
303             if (value instanceof RecordedFrame) {
304                 RecordedFrame frame = (RecordedFrame) value;
305                 if (frame.isJavaFrame()) {
306                     printJavaFrame((RecordedFrame) value, postFix);
307                     return;
308                 }
309             }
310             if (value instanceof RecordedMethod) {
311                 println(formatMethod((RecordedMethod) value));
312                 return;
313             }
314             if (field.getTypeName().equals(TYPE_OLD_OBJECT)) {
315                 printOldObject((RecordedObject) value);
316                 return;
317             }
318              print((RecordedObject) value, postFix);
319             return;
320         }
321         if (value.getClass().isArray()) {
322             printArray((Object[]) value);
323             return;
324         }
325 
326         if (value instanceof Double) {
327             Double d = (Double) value;
328             if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
329                 println("N/A");
330                 return;
331             }
332         }
333         if (value instanceof Float) {
334             Float f = (Float) value;
335             if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
336                 println("N/A");
337                 return;
338             }
339         }
340         if (value instanceof Long) {
341             Long l = (Long) value;
342             if (l == Long.MIN_VALUE) {
343                 println("N/A");
344                 return;
345             }
346         }
347         if (value instanceof Integer) {
348             Integer i = (Integer) value;
349             if (i == Integer.MIN_VALUE) {
350                 println("N/A");
351                 return;
352             }
353         }
354 
355         if (field.getContentType() != null) {
356             if (printFormatted(field, value)) {
357                 return;
358             }
359         }
360 
361         String text = String.valueOf(value);
362         if (value instanceof String) {
363             text = "\"" + text + "\"";
364         }
365         println(text);
366     }
367 
printOldObject(RecordedObject object)368     private void printOldObject(RecordedObject object) {
369         println(" [");
370         indent();
371         printIndent();
372         try {
373             printReferenceChain(object);
374         } catch (IllegalArgumentException iae) {
375            // Could not find a field
376            // Not possible to validate fields beforehand using RecordedObject#hasField
377            // since nested objects, for example object.referrer.array.index, requires
378            // an actual array object (which may be null).
379         }
380         retract();
381         printIndent();
382         println("]");
383     }
384 
printReferenceChain(RecordedObject object)385     private void printReferenceChain(RecordedObject object) {
386         printObject(object, currentEvent.getLong("arrayElements"));
387         for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) {
388             long skip = ref.getLong("skip");
389             if (skip > 0) {
390                 printIndent();
391                 println("...");
392             }
393             String objectHolder = "";
394             long size = Long.MIN_VALUE;
395             RecordedObject array = ref.getValue("array");
396             if (array != null) {
397                 long index = array.getLong("index");
398                 size = array.getLong("size");
399                 objectHolder = "[" + index + "]";
400             }
401             RecordedObject field = ref.getValue("field");
402             if (field != null) {
403                 objectHolder = field.getString("name");
404             }
405             printIndent();
406             print(objectHolder);
407             print(" : ");
408             object = ref.getValue("object");
409             if (object != null) {
410                 printObject(object, size);
411             }
412         }
413     }
414 
printObject(RecordedObject object, long arraySize)415     void printObject(RecordedObject object, long arraySize) {
416         RecordedClass clazz = object.getClass("type");
417         if (clazz != null) {
418             String className = clazz.getName();
419             if (className!= null && className.startsWith("[")) {
420                 className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0);
421             }
422             print(className);
423             String description = object.getString("description");
424             if (description != null) {
425                 print(" ");
426                 print(description);
427             }
428         }
429         println();
430     }
431 
printClassLoader(RecordedClassLoader cl, String postFix)432     private void printClassLoader(RecordedClassLoader cl, String postFix) {
433         // Purposely not printing class loader name to avoid cluttered output
434         RecordedClass clazz = cl.getType();
435         print(clazz == null ? "null" : clazz.getName());
436         if (clazz != null) {
437             print(" (");
438             print("id = ");
439             print(String.valueOf(cl.getId()));
440             println(")");
441         }
442     }
443 
printJavaFrame(RecordedFrame f, String postFix)444     private void printJavaFrame(RecordedFrame f, String postFix) {
445         print(formatMethod(f.getMethod()));
446         int line = f.getLineNumber();
447         if (line >= 0) {
448             print(" line: " + line);
449         }
450         print(postFix);
451     }
452 
formatMethod(RecordedMethod m)453     private String formatMethod(RecordedMethod m) {
454         StringBuilder sb = new StringBuilder();
455         sb.append(m.getType().getName());
456         sb.append(".");
457         sb.append(m.getName());
458         sb.append("(");
459         StringJoiner sj = new StringJoiner(", ");
460         String md = m.getDescriptor().replace("/", ".");
461         String parameter = md.substring(1, md.lastIndexOf(")"));
462         for (String qualifiedName : decodeDescriptors(parameter, "")) {
463             String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
464             sj.add(typeName);
465         }
466         sb.append(sj);
467         sb.append(")");
468         return sb.toString();
469     }
470 
printClass(RecordedClass clazz, String postFix)471     private void printClass(RecordedClass clazz, String postFix) {
472         RecordedClassLoader classLoader = clazz.getClassLoader();
473         String classLoaderName = "null";
474         if (classLoader != null) {
475             if (classLoader.getName() != null) {
476                 classLoaderName = classLoader.getName();
477             } else {
478                 classLoaderName = classLoader.getType().getName();
479             }
480         }
481         String className = clazz.getName();
482         if (className.startsWith("[")) {
483             className = decodeDescriptors(className, "").get(0);
484         }
485         println(className + " (classLoader = " + classLoaderName + ")" + postFix);
486     }
487 
decodeDescriptors(String descriptor, String arraySize)488     List<String> decodeDescriptors(String descriptor, String arraySize) {
489         List<String> descriptors = new ArrayList<>();
490         for (int index = 0; index < descriptor.length(); index++) {
491             String arrayBrackets = "";
492             while (descriptor.charAt(index) == '[') {
493                 arrayBrackets = arrayBrackets +  "[" + arraySize + "]" ;
494                 arraySize = "";
495                 index++;
496             }
497             char c = descriptor.charAt(index);
498             String type;
499             switch (c) {
500             case 'L':
501                 int endIndex = descriptor.indexOf(';', index);
502                 type = descriptor.substring(index + 1, endIndex);
503                 index = endIndex;
504                 break;
505             case 'I':
506                 type = "int";
507                 break;
508             case 'J':
509                 type = "long";
510                 break;
511             case 'Z':
512                 type = "boolean";
513                 break;
514             case 'D':
515                 type = "double";
516                 break;
517             case 'F':
518                 type = "float";
519                 break;
520             case 'S':
521                 type = "short";
522                 break;
523             case 'C':
524                 type = "char";
525                 break;
526             case 'B':
527                 type = "byte";
528                 break;
529             default:
530                 type = "<unknown-descriptor-type>";
531             }
532             descriptors.add(type + arrayBrackets);
533         }
534         return descriptors;
535     }
536 
printThread(RecordedThread thread, String postFix)537     private void printThread(RecordedThread thread, String postFix) {
538         long javaThreadId = thread.getJavaThreadId();
539         if (javaThreadId > 0) {
540             println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix);
541         } else {
542             println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix);
543         }
544     }
545 
printFormatted(ValueDescriptor field, Object value)546     private boolean printFormatted(ValueDescriptor field, Object value) {
547         if (value instanceof Duration) {
548             Duration d = (Duration) value;
549             if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0)  {
550                 println("N/A");
551                 return true;
552             }
553             double s = d.getNano() / 1000_000_000.0 + (int) (d.getSeconds() % 60);
554             if (s < 1.0) {
555                 if (s < 0.001) {
556                     println(String.format("%.3f", s * 1_000_000) + " us");
557                 } else {
558                     println(String.format("%.3f", s * 1_000) + " ms");
559                 }
560             } else {
561                 if (s < 1000.0) {
562                     println(String.format("%.3f", s) + " s");
563                 } else {
564                     println(String.format("%.0f", s) + " s");
565                 }
566             }
567             return true;
568         }
569         if (value instanceof OffsetDateTime) {
570             OffsetDateTime odt = (OffsetDateTime) value;
571             if (odt.equals(OffsetDateTime.MIN))  {
572                 println("N/A");
573                 return true;
574             }
575             println(TIME_FORMAT.format(odt));
576             return true;
577         }
578         Percentage percentage = field.getAnnotation(Percentage.class);
579         if (percentage != null) {
580             if (value instanceof Number) {
581                 double d = ((Number) value).doubleValue();
582                 println(String.format("%.2f", d * 100) + "%");
583                 return true;
584             }
585         }
586         DataAmount dataAmount = field.getAnnotation(DataAmount.class);
587         if (dataAmount != null) {
588             if (value instanceof Number) {
589                 Number n = (Number) value;
590                 long amount = n.longValue();
591                 if (field.getAnnotation(Frequency.class) != null) {
592                     if (dataAmount.value().equals(DataAmount.BYTES)) {
593                         println(Utils.formatBytesPerSecond(amount));
594                         return true;
595                     }
596                     if (dataAmount.value().equals(DataAmount.BITS)) {
597                         println(Utils.formatBitsPerSecond(amount));
598                         return true;
599                     }
600                 } else {
601                     if (dataAmount.value().equals(DataAmount.BYTES)) {
602                         println(Utils.formatBytes(amount));
603                         return true;
604                     }
605                     if (dataAmount.value().equals(DataAmount.BITS)) {
606                         println(Utils.formatBits(amount));
607                         return true;
608                     }
609                 }
610             }
611         }
612         MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
613         if (memoryAddress != null) {
614             if (value instanceof Number) {
615                 long d = ((Number) value).longValue();
616                 println(String.format("0x%08X", d));
617                 return true;
618             }
619         }
620         Frequency frequency = field.getAnnotation(Frequency.class);
621         if (frequency != null) {
622             if (value instanceof Number) {
623                 println(value + " Hz");
624                 return true;
625             }
626         }
627 
628         return false;
629     }
630 
setShowIds(boolean showIds)631     public void setShowIds(boolean showIds) {
632         this.showIds = showIds;
633     }
634 }
635