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 if (value instanceof Double) { 329 Double d = (Double) value; 330 if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) { 331 println("N/A"); 332 return; 333 } 334 } 335 if (value instanceof Float) { 336 Float f = (Float) value; 337 if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) { 338 println("N/A"); 339 return; 340 } 341 } 342 if (value instanceof Long) { 343 Long l = (Long) value; 344 if (l == Long.MIN_VALUE) { 345 println("N/A"); 346 return; 347 } 348 } 349 if (value instanceof Integer) { 350 Integer i = (Integer) value; 351 if (i == Integer.MIN_VALUE) { 352 println("N/A"); 353 return; 354 } 355 } 356 357 if (field.getContentType() != null) { 358 if (printFormatted(field, value)) { 359 return; 360 } 361 } 362 String text = String.valueOf(value); 363 if (value instanceof String) { 364 text = "\"" + text + "\""; 365 } 366 println(text); 367 } 368 printOldObject(RecordedObject object)369 private void printOldObject(RecordedObject object) { 370 println(" ["); 371 indent(); 372 printIndent(); 373 try { 374 printReferenceChain(object); 375 } catch (IllegalArgumentException iae) { 376 // Could not find a field 377 // Not possible to validate fields beforehand using RecordedObject#hasField 378 // since nested objects, for example object.referrer.array.index, requires 379 // an actual array object (which may be null). 380 } 381 retract(); 382 printIndent(); 383 println("]"); 384 } 385 printReferenceChain(RecordedObject object)386 private void printReferenceChain(RecordedObject object) { 387 printObject(object, currentEvent.getLong("arrayElements")); 388 for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) { 389 long skip = ref.getLong("skip"); 390 if (skip > 0) { 391 printIndent(); 392 println("..."); 393 } 394 String objectHolder = ""; 395 long size = Long.MIN_VALUE; 396 RecordedObject array = ref.getValue("array"); 397 if (array != null) { 398 long index = array.getLong("index"); 399 size = array.getLong("size"); 400 objectHolder = "[" + index + "]"; 401 } 402 RecordedObject field = ref.getValue("field"); 403 if (field != null) { 404 objectHolder = field.getString("name"); 405 } 406 printIndent(); 407 print(objectHolder); 408 print(" : "); 409 object = ref.getValue("object"); 410 if (object != null) { 411 printObject(object, size); 412 } 413 } 414 } 415 printObject(RecordedObject object, long arraySize)416 void printObject(RecordedObject object, long arraySize) { 417 RecordedClass clazz = object.getClass("type"); 418 if (clazz != null) { 419 String className = clazz.getName(); 420 if (className!= null && className.startsWith("[")) { 421 className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0); 422 } 423 print(className); 424 String description = object.getString("description"); 425 if (description != null) { 426 print(" "); 427 print(description); 428 } 429 } 430 println(); 431 } 432 printClassLoader(RecordedClassLoader cl, String postFix)433 private void printClassLoader(RecordedClassLoader cl, String postFix) { 434 // Purposely not printing class loader name to avoid cluttered output 435 RecordedClass clazz = cl.getType(); 436 print(clazz == null ? "null" : clazz.getName()); 437 if (clazz != null) { 438 print(" ("); 439 print("id = "); 440 print(String.valueOf(cl.getId())); 441 println(")"); 442 } 443 } 444 printJavaFrame(RecordedFrame f, String postFix)445 private void printJavaFrame(RecordedFrame f, String postFix) { 446 print(formatMethod(f.getMethod())); 447 int line = f.getLineNumber(); 448 if (line >= 0) { 449 print(" line: " + line); 450 } 451 print(postFix); 452 } 453 formatMethod(RecordedMethod m)454 private String formatMethod(RecordedMethod m) { 455 StringBuilder sb = new StringBuilder(); 456 sb.append(m.getType().getName()); 457 sb.append("."); 458 sb.append(m.getName()); 459 sb.append("("); 460 StringJoiner sj = new StringJoiner(", "); 461 String md = m.getDescriptor().replace("/", "."); 462 String parameter = md.substring(1, md.lastIndexOf(")")); 463 for (String qualifiedName : decodeDescriptors(parameter, "")) { 464 String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); 465 sj.add(typeName); 466 } 467 sb.append(sj); 468 sb.append(")"); 469 return sb.toString(); 470 } 471 printClass(RecordedClass clazz, String postFix)472 private void printClass(RecordedClass clazz, String postFix) { 473 RecordedClassLoader classLoader = clazz.getClassLoader(); 474 String classLoaderName = "null"; 475 if (classLoader != null) { 476 if (classLoader.getName() != null) { 477 classLoaderName = classLoader.getName(); 478 } else { 479 classLoaderName = classLoader.getType().getName(); 480 } 481 } 482 String className = clazz.getName(); 483 if (className.startsWith("[")) { 484 className = decodeDescriptors(className, "").get(0); 485 } 486 println(className + " (classLoader = " + classLoaderName + ")" + postFix); 487 } 488 decodeDescriptors(String descriptor, String arraySize)489 List<String> decodeDescriptors(String descriptor, String arraySize) { 490 List<String> descriptors = new ArrayList<>(); 491 for (int index = 0; index < descriptor.length(); index++) { 492 String arrayBrackets = ""; 493 while (descriptor.charAt(index) == '[') { 494 arrayBrackets = arrayBrackets + "[" + arraySize + "]" ; 495 arraySize = ""; 496 index++; 497 } 498 char c = descriptor.charAt(index); 499 String type; 500 switch (c) { 501 case 'L': 502 int endIndex = descriptor.indexOf(';', index); 503 type = descriptor.substring(index + 1, endIndex); 504 index = endIndex; 505 break; 506 case 'I': 507 type = "int"; 508 break; 509 case 'J': 510 type = "long"; 511 break; 512 case 'Z': 513 type = "boolean"; 514 break; 515 case 'D': 516 type = "double"; 517 break; 518 case 'F': 519 type = "float"; 520 break; 521 case 'S': 522 type = "short"; 523 break; 524 case 'C': 525 type = "char"; 526 break; 527 case 'B': 528 type = "byte"; 529 break; 530 default: 531 type = "<unknown-descriptor-type>"; 532 } 533 descriptors.add(type + arrayBrackets); 534 } 535 return descriptors; 536 } 537 printThread(RecordedThread thread, String postFix)538 private void printThread(RecordedThread thread, String postFix) { 539 long javaThreadId = thread.getJavaThreadId(); 540 if (javaThreadId > 0) { 541 println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix); 542 } else { 543 println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix); 544 } 545 } 546 printFormatted(ValueDescriptor field, Object value)547 private boolean printFormatted(ValueDescriptor field, Object value) { 548 if (value instanceof Duration) { 549 Duration d = (Duration) value; 550 if (d.getSeconds() == Long.MIN_VALUE) { 551 println("N/A"); 552 return true; 553 } 554 if(d.compareTo(MILLSECOND) < 0){ 555 println(String.format("%.3f us", (double)d.toNanos() / 1_000)); 556 } else if(d.compareTo(SECOND) < 0){ 557 println(String.format("%.3f ms", (double)d.toNanos() / 1_000_000)); 558 } else if(d.compareTo(MINUTE) < 0){ 559 println(String.format("%.3f s", (double)d.toMillis() / 1_000)); 560 } else { 561 println(String.format("%d s", d.toSeconds())); 562 } 563 return true; 564 } 565 if (value instanceof OffsetDateTime) { 566 OffsetDateTime odt = (OffsetDateTime) value; 567 if (odt.equals(OffsetDateTime.MIN)) { 568 println("N/A"); 569 return true; 570 } 571 println(TIME_FORMAT.format(odt)); 572 return true; 573 } 574 Percentage percentage = field.getAnnotation(Percentage.class); 575 if (percentage != null) { 576 if (value instanceof Number) { 577 double d = ((Number) value).doubleValue(); 578 println(String.format("%.2f", d * 100) + "%"); 579 return true; 580 } 581 } 582 DataAmount dataAmount = field.getAnnotation(DataAmount.class); 583 if (dataAmount != null) { 584 if (value instanceof Number) { 585 Number n = (Number) value; 586 String bytes = Utils.formatBytes(n.longValue()); 587 if (field.getAnnotation(Frequency.class) != null) { 588 bytes += "/s"; 589 } 590 println(bytes); 591 return true; 592 } 593 } 594 MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class); 595 if (memoryAddress != null) { 596 if (value instanceof Number) { 597 long d = ((Number) value).longValue(); 598 println(String.format("0x%08X", d)); 599 return true; 600 } 601 } 602 return false; 603 } 604 setShowIds(boolean showIds)605 public void setShowIds(boolean showIds) { 606 this.showIds = showIds; 607 } 608 } 609