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 final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); 63 private final static Long ZERO = 0L; 64 private boolean showIds; 65 private RecordedEvent currentEvent; 66 PrettyWriter(PrintWriter destination)67 public PrettyWriter(PrintWriter destination) { 68 super(destination); 69 } 70 71 @Override print(List<RecordedEvent> events)72 protected void print(List<RecordedEvent> events) { 73 for (RecordedEvent e : events) { 74 print(e); 75 flush(false); 76 } 77 } 78 printType(Type t)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 printField(int commentIndex, ValueDescriptor v, boolean first)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 printCommentRef(int commentIndex, long typeId)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 printAnnotations(int commentIndex, List<AnnotationElement> annotations)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 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 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 print(RecordedObject struct, String postFix)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 printFieldValue(RecordedObject struct, ValueDescriptor v)272 private void printFieldValue(RecordedObject struct, ValueDescriptor v) { 273 printIndent(); 274 print(v.getName(), " = "); 275 printValue(getValue(struct, v), v, ""); 276 } 277 printArray(Object[] array)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 printValue(Object value, ValueDescriptor field, String postFix)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) { 297 printThread((RecordedThread) value, postFix); 298 return; 299 } 300 if (value instanceof RecordedClass) { 301 printClass((RecordedClass) value, postFix); 302 return; 303 } 304 if (value instanceof RecordedClassLoader) { 305 printClassLoader((RecordedClassLoader) value, postFix); 306 return; 307 } 308 if (value instanceof RecordedFrame) { 309 RecordedFrame frame = (RecordedFrame) value; 310 if (frame.isJavaFrame()) { 311 printJavaFrame((RecordedFrame) value, postFix); 312 return; 313 } 314 } 315 if (value instanceof RecordedMethod) { 316 println(formatMethod((RecordedMethod) value)); 317 return; 318 } 319 if (field.getTypeName().equals(TYPE_OLD_OBJECT)) { 320 printOldObject((RecordedObject) value); 321 return; 322 } 323 print((RecordedObject) value, postFix); 324 return; 325 } 326 if (value.getClass().isArray()) { 327 printArray((Object[]) value); 328 return; 329 } 330 331 if (value instanceof Double) { 332 Double d = (Double) value; 333 if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) { 334 println("N/A"); 335 return; 336 } 337 } 338 if (value instanceof Float) { 339 Float f = (Float) value; 340 if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) { 341 println("N/A"); 342 return; 343 } 344 } 345 if (value instanceof Long) { 346 Long l = (Long) value; 347 if (l == Long.MIN_VALUE) { 348 println("N/A"); 349 return; 350 } 351 } 352 if (value instanceof Integer) { 353 Integer i = (Integer) value; 354 if (i == Integer.MIN_VALUE) { 355 println("N/A"); 356 return; 357 } 358 } 359 360 if (field.getContentType() != null) { 361 if (printFormatted(field, value)) { 362 return; 363 } 364 } 365 366 String text = String.valueOf(value); 367 if (value instanceof String) { 368 text = "\"" + text + "\""; 369 } 370 println(text); 371 } 372 printOldObject(RecordedObject object)373 private void printOldObject(RecordedObject object) { 374 println(" ["); 375 indent(); 376 printIndent(); 377 try { 378 printReferenceChain(object); 379 } catch (IllegalArgumentException iae) { 380 // Could not find a field 381 // Not possible to validate fields beforehand using RecordedObject#hasField 382 // since nested objects, for example object.referrer.array.index, requires 383 // an actual array object (which may be null). 384 } 385 retract(); 386 printIndent(); 387 println("]"); 388 } 389 printReferenceChain(RecordedObject object)390 private void printReferenceChain(RecordedObject object) { 391 printObject(object, currentEvent.getLong("arrayElements")); 392 for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) { 393 long skip = ref.getLong("skip"); 394 if (skip > 0) { 395 printIndent(); 396 println("..."); 397 } 398 String objectHolder = ""; 399 long size = Long.MIN_VALUE; 400 RecordedObject array = ref.getValue("array"); 401 if (array != null) { 402 long index = array.getLong("index"); 403 size = array.getLong("size"); 404 objectHolder = "[" + index + "]"; 405 } 406 RecordedObject field = ref.getValue("field"); 407 if (field != null) { 408 objectHolder = field.getString("name"); 409 } 410 printIndent(); 411 print(objectHolder); 412 print(" : "); 413 object = ref.getValue("object"); 414 if (object != null) { 415 printObject(object, size); 416 } 417 } 418 } 419 printObject(RecordedObject object, long arraySize)420 void printObject(RecordedObject object, long arraySize) { 421 RecordedClass clazz = object.getClass("type"); 422 if (clazz != null) { 423 String className = clazz.getName(); 424 if (className!= null && className.startsWith("[")) { 425 className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0); 426 } 427 print(className); 428 String description = object.getString("description"); 429 if (description != null) { 430 print(" "); 431 print(description); 432 } 433 } 434 println(); 435 } 436 printClassLoader(RecordedClassLoader cl, String postFix)437 private void printClassLoader(RecordedClassLoader cl, String postFix) { 438 // Purposely not printing class loader name to avoid cluttered output 439 RecordedClass clazz = cl.getType(); 440 print(clazz == null ? "null" : clazz.getName()); 441 if (clazz != null) { 442 print(" ("); 443 print("id = "); 444 print(String.valueOf(cl.getId())); 445 println(")"); 446 } 447 } 448 printJavaFrame(RecordedFrame f, String postFix)449 private void printJavaFrame(RecordedFrame f, String postFix) { 450 print(formatMethod(f.getMethod())); 451 int line = f.getLineNumber(); 452 if (line >= 0) { 453 print(" line: " + line); 454 } 455 print(postFix); 456 } 457 formatMethod(RecordedMethod m)458 private String formatMethod(RecordedMethod m) { 459 StringBuilder sb = new StringBuilder(); 460 sb.append(m.getType().getName()); 461 sb.append("."); 462 sb.append(m.getName()); 463 sb.append("("); 464 StringJoiner sj = new StringJoiner(", "); 465 String md = m.getDescriptor().replace("/", "."); 466 String parameter = md.substring(1, md.lastIndexOf(")")); 467 for (String qualifiedName : decodeDescriptors(parameter, "")) { 468 String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); 469 sj.add(typeName); 470 } 471 sb.append(sj); 472 sb.append(")"); 473 return sb.toString(); 474 } 475 printClass(RecordedClass clazz, String postFix)476 private void printClass(RecordedClass clazz, String postFix) { 477 RecordedClassLoader classLoader = clazz.getClassLoader(); 478 String classLoaderName = "null"; 479 if (classLoader != null) { 480 if (classLoader.getName() != null) { 481 classLoaderName = classLoader.getName(); 482 } else { 483 classLoaderName = classLoader.getType().getName(); 484 } 485 } 486 String className = clazz.getName(); 487 if (className.startsWith("[")) { 488 className = decodeDescriptors(className, "").get(0); 489 } 490 println(className + " (classLoader = " + classLoaderName + ")" + postFix); 491 } 492 decodeDescriptors(String descriptor, String arraySize)493 List<String> decodeDescriptors(String descriptor, String arraySize) { 494 List<String> descriptors = new ArrayList<>(); 495 for (int index = 0; index < descriptor.length(); index++) { 496 String arrayBrackets = ""; 497 while (descriptor.charAt(index) == '[') { 498 arrayBrackets = arrayBrackets + "[" + arraySize + "]" ; 499 arraySize = ""; 500 index++; 501 } 502 char c = descriptor.charAt(index); 503 String type; 504 switch (c) { 505 case 'L': 506 int endIndex = descriptor.indexOf(';', index); 507 type = descriptor.substring(index + 1, endIndex); 508 index = endIndex; 509 break; 510 case 'I': 511 type = "int"; 512 break; 513 case 'J': 514 type = "long"; 515 break; 516 case 'Z': 517 type = "boolean"; 518 break; 519 case 'D': 520 type = "double"; 521 break; 522 case 'F': 523 type = "float"; 524 break; 525 case 'S': 526 type = "short"; 527 break; 528 case 'C': 529 type = "char"; 530 break; 531 case 'B': 532 type = "byte"; 533 break; 534 default: 535 type = "<unknown-descriptor-type>"; 536 } 537 descriptors.add(type + arrayBrackets); 538 } 539 return descriptors; 540 } 541 printThread(RecordedThread thread, String postFix)542 private void printThread(RecordedThread thread, String postFix) { 543 long javaThreadId = thread.getJavaThreadId(); 544 if (javaThreadId > 0) { 545 println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix); 546 } else { 547 println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix); 548 } 549 } 550 printFormatted(ValueDescriptor field, Object value)551 private boolean printFormatted(ValueDescriptor field, Object value) { 552 if (value instanceof Duration) { 553 Duration d = (Duration) value; 554 if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0) { 555 println("N/A"); 556 return true; 557 } 558 println(Utils.formatDuration(d)); 559 return true; 560 } 561 if (value instanceof OffsetDateTime) { 562 OffsetDateTime odt = (OffsetDateTime) value; 563 if (odt.equals(OffsetDateTime.MIN)) { 564 println("N/A"); 565 return true; 566 } 567 println(TIME_FORMAT.format(odt)); 568 return true; 569 } 570 Percentage percentage = field.getAnnotation(Percentage.class); 571 if (percentage != null) { 572 if (value instanceof Number) { 573 double d = ((Number) value).doubleValue(); 574 println(String.format("%.2f", d * 100) + "%"); 575 return true; 576 } 577 } 578 DataAmount dataAmount = field.getAnnotation(DataAmount.class); 579 if (dataAmount != null) { 580 if (value instanceof Number) { 581 Number n = (Number) value; 582 long amount = n.longValue(); 583 if (field.getAnnotation(Frequency.class) != null) { 584 if (dataAmount.value().equals(DataAmount.BYTES)) { 585 println(Utils.formatBytesPerSecond(amount)); 586 return true; 587 } 588 if (dataAmount.value().equals(DataAmount.BITS)) { 589 println(Utils.formatBitsPerSecond(amount)); 590 return true; 591 } 592 } else { 593 if (dataAmount.value().equals(DataAmount.BYTES)) { 594 println(Utils.formatBytes(amount)); 595 return true; 596 } 597 if (dataAmount.value().equals(DataAmount.BITS)) { 598 println(Utils.formatBits(amount)); 599 return true; 600 } 601 } 602 } 603 } 604 MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class); 605 if (memoryAddress != null) { 606 if (value instanceof Number) { 607 long d = ((Number) value).longValue(); 608 println(String.format("0x%08X", d)); 609 return true; 610 } 611 } 612 Frequency frequency = field.getAnnotation(Frequency.class); 613 if (frequency != null) { 614 if (value instanceof Number) { 615 println(value + " Hz"); 616 return true; 617 } 618 } 619 620 return false; 621 } 622 setShowIds(boolean showIds)623 public void setShowIds(boolean showIds) { 624 this.showIds = showIds; 625 } 626 } 627