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 Duration MILLSECOND = Duration.ofMillis(1); 61 private static final Duration SECOND = Duration.ofSeconds(1); 62 private static final Duration MINUTE = Duration.ofMinutes(1); 63 private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject"; 64 private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); 65 private final static Long ZERO = 0L; 66 private boolean showIds; 67 private RecordedEvent currentEvent; 68 PrettyWriter(PrintWriter destination)69 public PrettyWriter(PrintWriter destination) { 70 super(destination); 71 } 72 73 @Override print(List<RecordedEvent> events)74 protected void print(List<RecordedEvent> events) { 75 for (RecordedEvent e : events) { 76 print(e); 77 flush(false); 78 } 79 } 80 printType(Type t)81 public void printType(Type t) { 82 if (showIds) { 83 print("// id: "); 84 println(String.valueOf(t.getId())); 85 } 86 int commentIndex = t.getName().length() + 10; 87 String typeName = t.getName(); 88 int index = typeName.lastIndexOf("."); 89 if (index != -1) { 90 println("@Name(\"" + typeName + "\")"); 91 } 92 printAnnotations(commentIndex, t.getAnnotationElements()); 93 print("class " + typeName.substring(index + 1)); 94 String superType = t.getSuperType(); 95 if (superType != null) { 96 print(" extends " + superType); 97 } 98 println(" {"); 99 indent(); 100 boolean first = true; 101 for (ValueDescriptor v : t.getFields()) { 102 printField(commentIndex, v, first); 103 first = false; 104 } 105 retract(); 106 println("}"); 107 println(); 108 } 109 printField(int commentIndex, ValueDescriptor v, boolean first)110 private void printField(int commentIndex, ValueDescriptor v, boolean first) { 111 if (!first) { 112 println(); 113 } 114 printAnnotations(commentIndex, v.getAnnotationElements()); 115 printIndent(); 116 Type vType = PrivateAccess.getInstance().getType(v); 117 if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) { 118 print("static "); 119 } 120 print(makeSimpleType(v.getTypeName())); 121 if (v.isArray()) { 122 print("[]"); 123 } 124 print(" "); 125 print(v.getName()); 126 print(";"); 127 printCommentRef(commentIndex, v.getTypeId()); 128 } 129 printCommentRef(int commentIndex, long typeId)130 private void printCommentRef(int commentIndex, long typeId) { 131 if (showIds) { 132 int column = getColumn(); 133 if (column > commentIndex) { 134 print(" "); 135 } else { 136 while (column < commentIndex) { 137 print(" "); 138 column++; 139 } 140 } 141 println(" // id=" + typeId); 142 } else { 143 println(); 144 } 145 } 146 printAnnotations(int commentIndex, List<AnnotationElement> annotations)147 private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) { 148 for (AnnotationElement a : annotations) { 149 printIndent(); 150 print("@"); 151 print(makeSimpleType(a.getTypeName())); 152 List<ValueDescriptor> vs = a.getValueDescriptors(); 153 if (!vs.isEmpty()) { 154 printAnnotation(a); 155 printCommentRef(commentIndex, a.getTypeId()); 156 } else { 157 println(); 158 } 159 } 160 } 161 printAnnotation(AnnotationElement a)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 textify(Object o)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 quoteIfNeeded(Object o)192 private String quoteIfNeeded(Object o) { 193 if (o instanceof String) { 194 return "\"" + o + "\""; 195 } else { 196 return String.valueOf(o); 197 } 198 } 199 makeSimpleType(String typeName)200 private String makeSimpleType(String typeName) { 201 int index = typeName.lastIndexOf("."); 202 return typeName.substring(index + 1); 203 } 204 print(RecordedEvent event)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 isZeroDuration(RecordedEvent event, String name)232 private boolean isZeroDuration(RecordedEvent event, String name) { 233 return name.equals("duration") && ZERO.equals(event.getValue("duration")); 234 } 235 printStackTrace(RecordedStackTrace stackTrace)236 private void printStackTrace(RecordedStackTrace stackTrace) { 237 println("["); 238 List<RecordedFrame> frames = stackTrace.getFrames(); 239 indent(); 240 int i = 0; 241 while (i < frames.size() && i < getStackDepth()) { 242 RecordedFrame frame = frames.get(i); 243 if (frame.isJavaFrame()) { 244 printIndent(); 245 printValue(frame, null, ""); 246 println(); 247 i++; 248 } 249 } 250 if (stackTrace.isTruncated() || i == getStackDepth()) { 251 printIndent(); 252 println("..."); 253 } 254 retract(); 255 printIndent(); 256 println("]"); 257 } 258 print(RecordedObject struct, String postFix)259 public void print(RecordedObject struct, String postFix) { 260 println("{"); 261 indent(); 262 for (ValueDescriptor v : struct.getFields()) { 263 printFieldValue(struct, v); 264 } 265 retract(); 266 printIndent(); 267 println("}" + postFix); 268 } 269 printFieldValue(RecordedObject struct, ValueDescriptor v)270 private void printFieldValue(RecordedObject struct, ValueDescriptor v) { 271 printIndent(); 272 print(v.getName(), " = "); 273 printValue(getValue(struct, v), v, ""); 274 } 275 printArray(Object[] array)276 private void printArray(Object[] array) { 277 println("["); 278 indent(); 279 for (int i = 0; i < array.length; i++) { 280 printIndent(); 281 printValue(array[i], null, i + 1 < array.length ? ", " : ""); 282 } 283 retract(); 284 printIndent(); 285 println("]"); 286 } 287 printValue(Object value, ValueDescriptor field, String postFix)288 private void printValue(Object value, ValueDescriptor field, String postFix) { 289 if (value == null) { 290 println("N/A" + postFix); 291 return; 292 } 293 if (value instanceof RecordedObject) { 294 if (value instanceof RecordedThread) { 295 printThread((RecordedThread) value, postFix); 296 return; 297 } 298 if (value instanceof RecordedClass) { 299 printClass((RecordedClass) value, postFix); 300 return; 301 } 302 if (value instanceof RecordedClassLoader) { 303 printClassLoader((RecordedClassLoader) value, postFix); 304 return; 305 } 306 if (value instanceof RecordedFrame) { 307 RecordedFrame frame = (RecordedFrame) value; 308 if (frame.isJavaFrame()) { 309 printJavaFrame((RecordedFrame) value, postFix); 310 return; 311 } 312 } 313 if (value instanceof RecordedMethod) { 314 println(formatMethod((RecordedMethod) value)); 315 return; 316 } 317 if (field.getTypeName().equals(TYPE_OLD_OBJECT)) { 318 printOldObject((RecordedObject) value); 319 return; 320 } 321 print((RecordedObject) value, postFix); 322 return; 323 } 324 if (value.getClass().isArray()) { 325 printArray((Object[]) value); 326 return; 327 } 328 329 if (value instanceof Double) { 330 Double d = (Double) value; 331 if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) { 332 println("N/A"); 333 return; 334 } 335 } 336 if (value instanceof Float) { 337 Float f = (Float) value; 338 if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) { 339 println("N/A"); 340 return; 341 } 342 } 343 if (value instanceof Long) { 344 Long l = (Long) value; 345 if (l == Long.MIN_VALUE) { 346 println("N/A"); 347 return; 348 } 349 } 350 if (value instanceof Integer) { 351 Integer i = (Integer) value; 352 if (i == Integer.MIN_VALUE) { 353 println("N/A"); 354 return; 355 } 356 } 357 358 if (field.getContentType() != null) { 359 if (printFormatted(field, value)) { 360 return; 361 } 362 } 363 364 String text = String.valueOf(value); 365 if (value instanceof String) { 366 text = "\"" + text + "\""; 367 } 368 println(text); 369 } 370 printOldObject(RecordedObject object)371 private void printOldObject(RecordedObject object) { 372 println(" ["); 373 indent(); 374 printIndent(); 375 try { 376 printReferenceChain(object); 377 } catch (IllegalArgumentException iae) { 378 // Could not find a field 379 // Not possible to validate fields beforehand using RecordedObject#hasField 380 // since nested objects, for example object.referrer.array.index, requires 381 // an actual array object (which may be null). 382 } 383 retract(); 384 printIndent(); 385 println("]"); 386 } 387 printReferenceChain(RecordedObject object)388 private void printReferenceChain(RecordedObject object) { 389 printObject(object, currentEvent.getLong("arrayElements")); 390 for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) { 391 long skip = ref.getLong("skip"); 392 if (skip > 0) { 393 printIndent(); 394 println("..."); 395 } 396 String objectHolder = ""; 397 long size = Long.MIN_VALUE; 398 RecordedObject array = ref.getValue("array"); 399 if (array != null) { 400 long index = array.getLong("index"); 401 size = array.getLong("size"); 402 objectHolder = "[" + index + "]"; 403 } 404 RecordedObject field = ref.getValue("field"); 405 if (field != null) { 406 objectHolder = field.getString("name"); 407 } 408 printIndent(); 409 print(objectHolder); 410 print(" : "); 411 object = ref.getValue("object"); 412 if (object != null) { 413 printObject(object, size); 414 } 415 } 416 } 417 printObject(RecordedObject object, long arraySize)418 void printObject(RecordedObject object, long arraySize) { 419 RecordedClass clazz = object.getClass("type"); 420 if (clazz != null) { 421 String className = clazz.getName(); 422 if (className!= null && className.startsWith("[")) { 423 className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0); 424 } 425 print(className); 426 String description = object.getString("description"); 427 if (description != null) { 428 print(" "); 429 print(description); 430 } 431 } 432 println(); 433 } 434 printClassLoader(RecordedClassLoader cl, String postFix)435 private void printClassLoader(RecordedClassLoader cl, String postFix) { 436 // Purposely not printing class loader name to avoid cluttered output 437 RecordedClass clazz = cl.getType(); 438 print(clazz == null ? "null" : clazz.getName()); 439 if (clazz != null) { 440 print(" ("); 441 print("id = "); 442 print(String.valueOf(cl.getId())); 443 println(")"); 444 } 445 } 446 printJavaFrame(RecordedFrame f, String postFix)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 formatMethod(RecordedMethod m)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 printClass(RecordedClass clazz, String postFix)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 decodeDescriptors(String descriptor, String arraySize)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 printThread(RecordedThread thread, String postFix)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 printFormatted(ValueDescriptor field, Object value)549 private boolean printFormatted(ValueDescriptor field, Object value) { 550 if (value instanceof Duration) { 551 Duration d = (Duration) value; 552 if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0) { 553 println("N/A"); 554 return true; 555 } 556 if(d.compareTo(MILLSECOND) < 0){ 557 println(String.format("%.3f us", (double)d.toNanos() / 1_000)); 558 } else if(d.compareTo(SECOND) < 0){ 559 println(String.format("%.3f ms", (double)d.toNanos() / 1_000_000)); 560 } else if(d.compareTo(MINUTE) < 0){ 561 println(String.format("%.3f s", (double)d.toMillis() / 1_000)); 562 } else { 563 println(String.format("%d s", d.toSeconds())); 564 } 565 return true; 566 } 567 if (value instanceof OffsetDateTime) { 568 OffsetDateTime odt = (OffsetDateTime) value; 569 if (odt.equals(OffsetDateTime.MIN)) { 570 println("N/A"); 571 return true; 572 } 573 println(TIME_FORMAT.format(odt)); 574 return true; 575 } 576 Percentage percentage = field.getAnnotation(Percentage.class); 577 if (percentage != null) { 578 if (value instanceof Number) { 579 double d = ((Number) value).doubleValue(); 580 println(String.format("%.2f", d * 100) + "%"); 581 return true; 582 } 583 } 584 DataAmount dataAmount = field.getAnnotation(DataAmount.class); 585 if (dataAmount != null) { 586 if (value instanceof Number) { 587 Number n = (Number) value; 588 long amount = n.longValue(); 589 if (field.getAnnotation(Frequency.class) != null) { 590 if (dataAmount.value().equals(DataAmount.BYTES)) { 591 println(Utils.formatBytesPerSecond(amount)); 592 return true; 593 } 594 if (dataAmount.value().equals(DataAmount.BITS)) { 595 println(Utils.formatBitsPerSecond(amount)); 596 return true; 597 } 598 } else { 599 if (dataAmount.value().equals(DataAmount.BYTES)) { 600 println(Utils.formatBytes(amount)); 601 return true; 602 } 603 if (dataAmount.value().equals(DataAmount.BITS)) { 604 println(Utils.formatBits(amount)); 605 return true; 606 } 607 } 608 } 609 } 610 MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class); 611 if (memoryAddress != null) { 612 if (value instanceof Number) { 613 long d = ((Number) value).longValue(); 614 println(String.format("0x%08X", d)); 615 return true; 616 } 617 } 618 Frequency frequency = field.getAnnotation(Frequency.class); 619 if (frequency != null) { 620 if (value instanceof Number) { 621 println(value + " Hz"); 622 return true; 623 } 624 } 625 626 return false; 627 } 628 setShowIds(boolean showIds)629 public void setShowIds(boolean showIds) { 630 this.showIds = showIds; 631 } 632 } 633