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