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 
  66     public PrettyWriter(PrintWriter destination) {
  67         super(destination);
  68     }
  69 
  70     @Override
  71     protected void print(List<RecordedEvent> events) {
  72         for (RecordedEvent e : events) {
  73             print(e);
  74             flush(false);
  75         }
  76     }
  77 
  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 
 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 
 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 
 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 
 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 
 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 
 189     private String quoteIfNeeded(Object o) {
 190         if (o instanceof String) {
 191             return "\"" + o + "\"";
 192         } else {
 193             return String.valueOf(o);
 194         }
 195     }
 196 
 197     private String makeSimpleType(String typeName) {
 198         int index = typeName.lastIndexOf(".");
 199         return typeName.substring(index + 1);
 200     }
 201 
 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 
 229     private boolean isZeroDuration(RecordedEvent event, String name) {
 230         return name.equals("duration") && ZERO.equals(event.getValue("duration"));
 231     }
 232 
 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 
 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 
 267     private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
 268         printIndent();
 269         print(v.getName(), " = ");
 270         printValue(getValue(struct, v), v, "");
 271     }
 272 
 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 
 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 
 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         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 
 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 
 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 
 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 
 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 
 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 
 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 
 631     public void setShowIds(boolean showIds) {
 632         this.showIds = showIds;
 633     }
 634 }