1 /*
  2  * Copyright (c) 2016, 2020, 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.Name;
 41 import jdk.jfr.Percentage;
 42 import jdk.jfr.ValueDescriptor;
 43 import jdk.jfr.consumer.RecordedClass;
 44 import jdk.jfr.consumer.RecordedClassLoader;
 45 import jdk.jfr.consumer.RecordedEvent;
 46 import jdk.jfr.consumer.RecordedFrame;
 47 import jdk.jfr.consumer.RecordedMethod;
 48 import jdk.jfr.consumer.RecordedObject;
 49 import jdk.jfr.consumer.RecordedStackTrace;
 50 import jdk.jfr.consumer.RecordedThread;
 51 import jdk.jfr.internal.PrivateAccess;
 52 import jdk.jfr.internal.Type;
 53 import jdk.jfr.internal.Utils;
 54 
 55 /**
 56  * Print events in a human-readable format.
 57  *
 58  * This class is also used by {@link RecordedObject#toString()}
 59  */
 60 public final class PrettyWriter extends EventPrintWriter {
 61     private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject";
 62     private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS (YYYY-MM-dd)");
 63     private static final Long ZERO = 0L;
 64     private boolean showIds;
 65     private RecordedEvent currentEvent;
 66 
 67     public PrettyWriter(PrintWriter destination) {
 68         super(destination);
 69     }
 70 
 71     @Override
 72     protected void print(List<RecordedEvent> events) {
 73         for (RecordedEvent e : events) {
 74             print(e);
 75             flush(false);
 76         }
 77     }
 78 
 79     public void printType(Type t) {
 80         if (showIds) {
 81             print("// id: ");
 82             println(String.valueOf(t.getId()));
 83         }
 84         int commentIndex = t.getName().length() + 10;
 85         String typeName = t.getName();
 86         int index = typeName.lastIndexOf(".");
 87         if (index != -1) {
 88             println("@Name(\"" + typeName + "\")");
 89         }
 90         printAnnotations(commentIndex, t.getAnnotationElements());
 91         print("class " + typeName.substring(index + 1));
 92         String superType = t.getSuperType();
 93         if (superType != null) {
 94             print(" extends " + superType);
 95         }
 96         println(" {");
 97         indent();
 98         boolean first = true;
 99         for (ValueDescriptor v : t.getFields()) {
100             printField(commentIndex, v, first);
101             first = false;
102         }
103         retract();
104         println("}");
105         println();
106     }
107 
108     private void printField(int commentIndex, ValueDescriptor v, boolean first) {
109         if (!first) {
110             println();
111         }
112         printAnnotations(commentIndex, v.getAnnotationElements());
113         printIndent();
114         Type vType = PrivateAccess.getInstance().getType(v);
115         if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) {
116             print("static ");
117         }
118         print(makeSimpleType(v.getTypeName()));
119         if (v.isArray()) {
120             print("[]");
121         }
122         print(" ");
123         print(v.getName());
124         print(";");
125         printCommentRef(commentIndex, v.getTypeId());
126     }
127 
128     private void printCommentRef(int commentIndex, long typeId) {
129         if (showIds) {
130             int column = getColumn();
131             if (column > commentIndex) {
132                 print("  ");
133             } else {
134                 while (column < commentIndex) {
135                     print(" ");
136                     column++;
137                 }
138             }
139             println(" // id=" + typeId);
140         } else {
141             println();
142         }
143     }
144 
145     private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) {
146         for (AnnotationElement a : annotations) {
147             if (!Name.class.getName().equals(a.getTypeName())) {
148                 printIndent();
149                 print("@");
150                 print(makeSimpleType(a.getTypeName()));
151                 List<ValueDescriptor> vs = a.getValueDescriptors();
152                 if (!vs.isEmpty()) {
153                     printAnnotation(a);
154                     printCommentRef(commentIndex, a.getTypeId());
155                 } else {
156                     println();
157                 }
158             }
159         }
160     }
161 
162     private void printAnnotation(AnnotationElement a) {
163         StringJoiner sj = new StringJoiner(", ", "(", ")");
164         List<ValueDescriptor> vs = a.getValueDescriptors();
165         for (ValueDescriptor v : vs) {
166             Object o = a.getValue(v.getName());
167             if (vs.size() == 1 && v.getName().equals("value")) {
168                 sj.add(textify(o));
169             } else {
170                 sj.add(v.getName() + "=" + textify(o));
171             }
172         }
173         print(sj.toString());
174     }
175 
176     private String textify(Object o) {
177         if (o.getClass().isArray()) {
178             Object[] array = (Object[]) o;
179             if (array.length == 1) {
180                 return quoteIfNeeded(array[0]);
181             }
182             StringJoiner s = new StringJoiner(", ", "{", "}");
183             for (Object ob : array) {
184                 s.add(quoteIfNeeded(ob));
185             }
186             return s.toString();
187         } else {
188             return quoteIfNeeded(o);
189         }
190     }
191 
192     private String quoteIfNeeded(Object o) {
193         if (o instanceof String) {
194             return "\"" + o + "\"";
195         } else {
196             return String.valueOf(o);
197         }
198     }
199 
200     private String makeSimpleType(String typeName) {
201         int index = typeName.lastIndexOf(".");
202         return typeName.substring(index + 1);
203     }
204 
205     public void print(RecordedEvent event) {
206         currentEvent = event;
207         print(event.getEventType().getName(), " ");
208         println("{");
209         indent();
210         for (ValueDescriptor v : event.getFields()) {
211             String name = v.getName();
212             if (!isZeroDuration(event, name) && !isLateField(name)) {
213                 printFieldValue(event, v);
214             }
215         }
216         if (event.getThread() != null) {
217             printIndent();
218             print(EVENT_THREAD_FIELD + " = ");
219             printThread(event.getThread(), "");
220         }
221         if (event.getStackTrace() != null) {
222             printIndent();
223             print(STACK_TRACE_FIELD + " = ");
224             printStackTrace(event.getStackTrace());
225         }
226         retract();
227         printIndent();
228         println("}");
229         println();
230     }
231 
232     private boolean isZeroDuration(RecordedEvent event, String name) {
233         return name.equals("duration") && ZERO.equals(event.getValue("duration"));
234     }
235 
236     private void printStackTrace(RecordedStackTrace stackTrace) {
237         println("[");
238         List<RecordedFrame> frames = stackTrace.getFrames();
239         indent();
240         int i = 0;
241         int depth = 0;
242         while (i < frames.size() && depth < getStackDepth()) {
243             RecordedFrame frame = frames.get(i);
244             if (frame.isJavaFrame() && !frame.getMethod().isHidden()) {
245                 printIndent();
246                 printValue(frame, null, "");
247                 println();
248                 depth++;
249             }
250             i++;
251         }
252         if (stackTrace.isTruncated() || i == getStackDepth()) {
253             printIndent();
254             println("...");
255         }
256         retract();
257         printIndent();
258         println("]");
259     }
260 
261     public void print(RecordedObject struct, String postFix) {
262         println("{");
263         indent();
264         for (ValueDescriptor v : struct.getFields()) {
265             printFieldValue(struct, v);
266         }
267         retract();
268         printIndent();
269         println("}" + postFix);
270     }
271 
272     private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
273         printIndent();
274         print(v.getName(), " = ");
275         printValue(getValue(struct, v), v, "");
276     }
277 
278     private void printArray(Object[] array) {
279         println("[");
280         indent();
281         for (int i = 0; i < array.length; i++) {
282             printIndent();
283             printValue(array[i], null, i + 1 < array.length ? ", " : "");
284         }
285         retract();
286         printIndent();
287         println("]");
288     }
289 
290     private void printValue(Object value, ValueDescriptor field, String postFix) {
291         if (value == null) {
292             println("N/A" + postFix);
293             return;
294         }
295         if (value instanceof RecordedObject) {
296             if (value instanceof RecordedThread rt) {
297                 printThread(rt, postFix);
298                 return;
299             }
300             if (value instanceof RecordedClass rc) {
301                 printClass(rc, postFix);
302                 return;
303             }
304             if (value instanceof RecordedClassLoader rcl) {
305                 printClassLoader(rcl, postFix);
306                 return;
307             }
308             if (value instanceof RecordedFrame frame) {
309                 if (frame.isJavaFrame()) {
310                     printJavaFrame((RecordedFrame) value, postFix);
311                     return;
312                 }
313             }
314             if (value instanceof RecordedMethod rm) {
315                 println(formatMethod(rm));
316                 return;
317             }
318             if (field.getTypeName().equals(TYPE_OLD_OBJECT)) {
319                 printOldObject((RecordedObject) value);
320                 return;
321             }
322              print((RecordedObject) value, postFix);
323             return;
324         }
325         if (value.getClass().isArray()) {
326             printArray((Object[]) value);
327             return;
328         }
329 
330         if (value instanceof Double d) {
331             if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
332                 println("N/A");
333                 return;
334             }
335         }
336         if (value instanceof Float f) {
337             if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
338                 println("N/A");
339                 return;
340             }
341         }
342         if (value instanceof Long l) {
343             if (l == Long.MIN_VALUE) {
344                 println("N/A");
345                 return;
346             }
347         }
348         if (value instanceof Integer i) {
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 
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 
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 
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 
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         if (clazz != null) {
436             print(clazz.getName());
437             print(" (");
438             print("id = ");
439             print(String.valueOf(cl.getId()));
440             print(")");
441         } else {
442             print("null");
443         }
444         println(postFix);
445     }
446 
447     private void printJavaFrame(RecordedFrame f, String postFix) {
448         print(formatMethod(f.getMethod()));
449         int line = f.getLineNumber();
450         if (line >= 0) {
451             print(" line: " + line);
452         }
453         print(postFix);
454     }
455 
456     private String formatMethod(RecordedMethod m) {
457         StringBuilder sb = new StringBuilder();
458         sb.append(m.getType().getName());
459         sb.append(".");
460         sb.append(m.getName());
461         sb.append("(");
462         StringJoiner sj = new StringJoiner(", ");
463         String md = m.getDescriptor().replace("/", ".");
464         String parameter = md.substring(1, md.lastIndexOf(")"));
465         for (String qualifiedName : decodeDescriptors(parameter, "")) {
466             String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
467             sj.add(typeName);
468         }
469         sb.append(sj);
470         sb.append(")");
471         return sb.toString();
472     }
473 
474     private void printClass(RecordedClass clazz, String postFix) {
475         RecordedClassLoader classLoader = clazz.getClassLoader();
476         String classLoaderName = "null";
477         if (classLoader != null) {
478             if (classLoader.getName() != null) {
479                 classLoaderName = classLoader.getName();
480             } else {
481                 classLoaderName = classLoader.getType().getName();
482             }
483         }
484         String className = clazz.getName();
485         if (className.startsWith("[")) {
486             className = decodeDescriptors(className, "").get(0);
487         }
488         println(className + " (classLoader = " + classLoaderName + ")" + postFix);
489     }
490 
491     List<String> decodeDescriptors(String descriptor, String arraySize) {
492         List<String> descriptors = new ArrayList<>();
493         for (int index = 0; index < descriptor.length(); index++) {
494             String arrayBrackets = "";
495             while (descriptor.charAt(index) == '[') {
496                 arrayBrackets = arrayBrackets +  "[" + arraySize + "]" ;
497                 arraySize = "";
498                 index++;
499             }
500             char c = descriptor.charAt(index);
501             String type;
502             switch (c) {
503             case 'L':
504                 int endIndex = descriptor.indexOf(';', index);
505                 type = descriptor.substring(index + 1, endIndex);
506                 index = endIndex;
507                 break;
508             case 'I':
509                 type = "int";
510                 break;
511             case 'J':
512                 type = "long";
513                 break;
514             case 'Z':
515                 type = "boolean";
516                 break;
517             case 'D':
518                 type = "double";
519                 break;
520             case 'F':
521                 type = "float";
522                 break;
523             case 'S':
524                 type = "short";
525                 break;
526             case 'C':
527                 type = "char";
528                 break;
529             case 'B':
530                 type = "byte";
531                 break;
532             default:
533                 type = "<unknown-descriptor-type>";
534             }
535             descriptors.add(type + arrayBrackets);
536         }
537         return descriptors;
538     }
539 
540     private void printThread(RecordedThread thread, String postFix) {
541         long javaThreadId = thread.getJavaThreadId();
542         if (javaThreadId > 0) {
543             println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix);
544         } else {
545             println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix);
546         }
547     }
548 
549     private boolean printFormatted(ValueDescriptor field, Object value) {
550         if (value instanceof Duration d) {
551             if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0)  {
552                 println("N/A");
553                 return true;
554             }
555             println(Utils.formatDuration(d));
556             return true;
557         }
558         if (value instanceof OffsetDateTime odt) {
559             if (odt.equals(OffsetDateTime.MIN))  {
560                 println("N/A");
561                 return true;
562             }
563             println(TIME_FORMAT.format(odt));
564             return true;
565         }
566         Percentage percentage = field.getAnnotation(Percentage.class);
567         if (percentage != null) {
568             if (value instanceof Number n) {
569                 double d = n.doubleValue();
570                 println(String.format("%.2f", d * 100) + "%");
571                 return true;
572             }
573         }
574         DataAmount dataAmount = field.getAnnotation(DataAmount.class);
575         if (dataAmount != null) {
576             if (value instanceof Number n) {
577                 long amount = n.longValue();
578                 if (field.getAnnotation(Frequency.class) != null) {
579                     if (dataAmount.value().equals(DataAmount.BYTES)) {
580                         println(Utils.formatBytesPerSecond(amount));
581                         return true;
582                     }
583                     if (dataAmount.value().equals(DataAmount.BITS)) {
584                         println(Utils.formatBitsPerSecond(amount));
585                         return true;
586                     }
587                 } else {
588                     if (dataAmount.value().equals(DataAmount.BYTES)) {
589                         println(Utils.formatBytes(amount));
590                         return true;
591                     }
592                     if (dataAmount.value().equals(DataAmount.BITS)) {
593                         println(Utils.formatBits(amount));
594                         return true;
595                     }
596                 }
597             }
598         }
599         MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
600         if (memoryAddress != null) {
601             if (value instanceof Number n) {
602                 long d = n.longValue();
603                 println(String.format("0x%08X", d));
604                 return true;
605             }
606         }
607         Frequency frequency = field.getAnnotation(Frequency.class);
608         if (frequency != null) {
609             if (value instanceof Number) {
610                 println(value + " Hz");
611                 return true;
612             }
613         }
614 
615         return false;
616     }
617 
618     public void setShowIds(boolean showIds) {
619         this.showIds = showIds;
620     }
621 }