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 final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); 61 private final static Long ZERO = 0L; 62 private boolean showIds; 63 PrettyWriter(PrintWriter destination)64 public PrettyWriter(PrintWriter destination) { 65 super(destination); 66 } 67 68 @Override print(List<RecordedEvent> events)69 protected void print(List<RecordedEvent> events) { 70 for (RecordedEvent e : events) { 71 print(e); 72 flush(false); 73 } 74 } 75 printType(Type t)76 public void printType(Type t) { 77 if (showIds) { 78 print("// id: "); 79 println(String.valueOf(t.getId())); 80 } 81 int commentIndex = t.getName().length() + 10; 82 String typeName = t.getName(); 83 int index = typeName.lastIndexOf("."); 84 if (index != -1) { 85 println("@Name(\"" + typeName + "\")"); 86 } 87 printAnnotations(commentIndex, t.getAnnotationElements()); 88 print("class " + typeName.substring(index + 1)); 89 String superType = t.getSuperType(); 90 if (superType != null) { 91 print(" extends " + superType); 92 } 93 println(" {"); 94 indent(); 95 boolean first = true; 96 for (ValueDescriptor v : t.getFields()) { 97 printField(commentIndex, v, first); 98 first = false; 99 } 100 retract(); 101 println("}"); 102 println(); 103 } 104 printField(int commentIndex, ValueDescriptor v, boolean first)105 private void printField(int commentIndex, ValueDescriptor v, boolean first) { 106 if (!first) { 107 println(); 108 } 109 printAnnotations(commentIndex, v.getAnnotationElements()); 110 printIndent(); 111 Type vType = PrivateAccess.getInstance().getType(v); 112 if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) { 113 print("static "); 114 } 115 print(makeSimpleType(v.getTypeName())); 116 if (v.isArray()) { 117 print("[]"); 118 } 119 print(" "); 120 print(v.getName()); 121 print(";"); 122 printCommentRef(commentIndex, v.getTypeId()); 123 } 124 printCommentRef(int commentIndex, long typeId)125 private void printCommentRef(int commentIndex, long typeId) { 126 if (showIds) { 127 int column = getColumn(); 128 if (column > commentIndex) { 129 print(" "); 130 } else { 131 while (column < commentIndex) { 132 print(" "); 133 column++; 134 } 135 } 136 println(" // id=" + typeId); 137 } else { 138 println(); 139 } 140 } 141 printAnnotations(int commentIndex, List<AnnotationElement> annotations)142 private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) { 143 for (AnnotationElement a : annotations) { 144 printIndent(); 145 print("@"); 146 print(makeSimpleType(a.getTypeName())); 147 List<ValueDescriptor> vs = a.getValueDescriptors(); 148 if (!vs.isEmpty()) { 149 printAnnotation(a); 150 printCommentRef(commentIndex, a.getTypeId()); 151 } else { 152 println(); 153 } 154 } 155 } 156 printAnnotation(AnnotationElement a)157 private void printAnnotation(AnnotationElement a) { 158 StringJoiner sj = new StringJoiner(", ", "(", ")"); 159 List<ValueDescriptor> vs = a.getValueDescriptors(); 160 for (ValueDescriptor v : vs) { 161 Object o = a.getValue(v.getName()); 162 if (vs.size() == 1 && v.getName().equals("value")) { 163 sj.add(textify(o)); 164 } else { 165 sj.add(v.getName() + "=" + textify(o)); 166 } 167 } 168 print(sj.toString()); 169 } 170 textify(Object o)171 private String textify(Object o) { 172 if (o.getClass().isArray()) { 173 Object[] array = (Object[]) o; 174 if (array.length == 1) { 175 return quoteIfNeeded(array[0]); 176 } 177 StringJoiner s = new StringJoiner(", ", "{", "}"); 178 for (Object ob : array) { 179 s.add(quoteIfNeeded(ob)); 180 } 181 return s.toString(); 182 } else { 183 return quoteIfNeeded(o); 184 } 185 } 186 quoteIfNeeded(Object o)187 private String quoteIfNeeded(Object o) { 188 if (o instanceof String) { 189 return "\"" + o + "\""; 190 } else { 191 return String.valueOf(o); 192 } 193 } 194 makeSimpleType(String typeName)195 private String makeSimpleType(String typeName) { 196 int index = typeName.lastIndexOf("."); 197 return typeName.substring(index + 1); 198 } 199 print(RecordedEvent event)200 public void print(RecordedEvent event) { 201 print(event.getEventType().getName(), " "); 202 println("{"); 203 indent(); 204 for (ValueDescriptor v : event.getFields()) { 205 String name = v.getName(); 206 if (!isZeroDuration(event, name) && !isLateField(name)) { 207 printFieldValue(event, v); 208 } 209 } 210 if (event.getThread() != null) { 211 printIndent(); 212 print(EVENT_THREAD_FIELD + " = "); 213 printThread(event.getThread(), ""); 214 } 215 if (event.getStackTrace() != null) { 216 printIndent(); 217 print(STACK_TRACE_FIELD + " = "); 218 printStackTrace(event.getStackTrace()); 219 } 220 retract(); 221 printIndent(); 222 println("}"); 223 println(); 224 } 225 isZeroDuration(RecordedEvent event, String name)226 private boolean isZeroDuration(RecordedEvent event, String name) { 227 return name.equals("duration") && ZERO.equals(event.getValue("duration")); 228 } 229 printStackTrace(RecordedStackTrace stackTrace)230 private void printStackTrace(RecordedStackTrace stackTrace) { 231 println("["); 232 List<RecordedFrame> frames = stackTrace.getFrames(); 233 indent(); 234 int i = 0; 235 while (i < frames.size() && i < getStackDepth()) { 236 RecordedFrame frame = frames.get(i); 237 if (frame.isJavaFrame()) { 238 printIndent(); 239 printValue(frame, null, ""); 240 println(); 241 i++; 242 } 243 } 244 if (stackTrace.isTruncated() || i == getStackDepth()) { 245 printIndent(); 246 println("..."); 247 } 248 retract(); 249 printIndent(); 250 println("]"); 251 } 252 print(RecordedObject struct, String postFix)253 public void print(RecordedObject struct, String postFix) { 254 println("{"); 255 indent(); 256 for (ValueDescriptor v : struct.getFields()) { 257 printFieldValue(struct, v); 258 } 259 retract(); 260 printIndent(); 261 println("}" + postFix); 262 } 263 printFieldValue(RecordedObject struct, ValueDescriptor v)264 private void printFieldValue(RecordedObject struct, ValueDescriptor v) { 265 printIndent(); 266 print(v.getName(), " = "); 267 printValue(getValue(struct, v), v, ""); 268 } 269 printArray(Object[] array)270 private void printArray(Object[] array) { 271 println("["); 272 indent(); 273 for (int i = 0; i < array.length; i++) { 274 printIndent(); 275 printValue(array[i], null, i + 1 < array.length ? ", " : ""); 276 } 277 retract(); 278 printIndent(); 279 println("]"); 280 } 281 printValue(Object value, ValueDescriptor field, String postFix)282 private void printValue(Object value, ValueDescriptor field, String postFix) { 283 if (value == null) { 284 println("N/A" + postFix); 285 return; 286 } 287 if (value instanceof RecordedObject) { 288 if (value instanceof RecordedThread) { 289 printThread((RecordedThread) value, postFix); 290 return; 291 } 292 if (value instanceof RecordedClass) { 293 printClass((RecordedClass) value, postFix); 294 return; 295 } 296 if (value instanceof RecordedClassLoader) { 297 printClassLoader((RecordedClassLoader) value, postFix); 298 return; 299 } 300 if (value instanceof RecordedFrame) { 301 RecordedFrame frame = (RecordedFrame) value; 302 if (frame.isJavaFrame()) { 303 printJavaFrame((RecordedFrame) value, postFix); 304 return; 305 } 306 } 307 if (value instanceof RecordedMethod) { 308 println(formatMethod((RecordedMethod) value)); 309 return; 310 } 311 print((RecordedObject) value, postFix); 312 return; 313 } 314 if (value.getClass().isArray()) { 315 printArray((Object[]) value); 316 return; 317 } 318 319 if (value instanceof Double) { 320 Double d = (Double) value; 321 if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) { 322 println("N/A"); 323 return; 324 } 325 } 326 if (value instanceof Float) { 327 Float f = (Float) value; 328 if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) { 329 println("N/A"); 330 return; 331 } 332 } 333 if (value instanceof Long) { 334 Long l = (Long) value; 335 if (l == Long.MIN_VALUE) { 336 println("N/A"); 337 return; 338 } 339 } 340 if (value instanceof Integer) { 341 Integer i = (Integer) value; 342 if (i == Integer.MIN_VALUE) { 343 println("N/A"); 344 return; 345 } 346 } 347 348 if (field.getContentType() != null) { 349 if (printFormatted(field, value)) { 350 return; 351 } 352 } 353 354 String text = String.valueOf(value); 355 if (value instanceof String) { 356 text = "\"" + text + "\""; 357 } 358 println(text); 359 } 360 printClassLoader(RecordedClassLoader cl, String postFix)361 private void printClassLoader(RecordedClassLoader cl, String postFix) { 362 // Purposely not printing class loader name to avoid cluttered output 363 RecordedClass clazz = cl.getType(); 364 print(clazz == null ? "null" : clazz.getName()); 365 if (clazz != null) { 366 print(" ("); 367 print("id = "); 368 print(String.valueOf(cl.getId())); 369 println(")"); 370 } 371 } 372 printJavaFrame(RecordedFrame f, String postFix)373 private void printJavaFrame(RecordedFrame f, String postFix) { 374 print(formatMethod(f.getMethod())); 375 int line = f.getLineNumber(); 376 if (line >= 0) { 377 print(" line: " + line); 378 } 379 print(postFix); 380 } 381 formatMethod(RecordedMethod m)382 private String formatMethod(RecordedMethod m) { 383 StringBuilder sb = new StringBuilder(); 384 sb.append(m.getType().getName()); 385 sb.append("."); 386 sb.append(m.getName()); 387 sb.append("("); 388 StringJoiner sj = new StringJoiner(", "); 389 String md = m.getDescriptor().replace("/", "."); 390 String parameter = md.substring(1, md.lastIndexOf(")")); 391 for (String qualifiedName : decodeDescriptors(parameter)) { 392 String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); 393 sj.add(typeName); 394 } 395 sb.append(sj); 396 sb.append(")"); 397 return sb.toString(); 398 } 399 printClass(RecordedClass clazz, String postFix)400 private void printClass(RecordedClass clazz, String postFix) { 401 RecordedClassLoader classLoader = clazz.getClassLoader(); 402 String classLoaderName = "null"; 403 if (classLoader != null) { 404 if (classLoader.getName() != null) { 405 classLoaderName = classLoader.getName(); 406 } else { 407 classLoaderName = classLoader.getType().getName(); 408 } 409 } 410 String className = clazz.getName(); 411 if (className.startsWith("[")) { 412 className = decodeDescriptors(className).get(0); 413 } 414 println(className + " (classLoader = " + classLoaderName + ")" + postFix); 415 } 416 decodeDescriptors(String descriptor)417 List<String> decodeDescriptors(String descriptor) { 418 List<String> descriptors = new ArrayList<>(); 419 for (int index = 0; index < descriptor.length(); index++) { 420 String arrayBrackets = ""; 421 while (descriptor.charAt(index) == '[') { 422 arrayBrackets += "[]"; 423 index++; 424 } 425 char c = descriptor.charAt(index); 426 String type; 427 switch (c) { 428 case 'L': 429 int endIndex = descriptor.indexOf(';', index); 430 type = descriptor.substring(index + 1, endIndex); 431 index = endIndex; 432 break; 433 case 'I': 434 type = "int"; 435 break; 436 case 'J': 437 type = "long"; 438 break; 439 case 'Z': 440 type = "boolean"; 441 break; 442 case 'D': 443 type = "double"; 444 break; 445 case 'F': 446 type = "float"; 447 break; 448 case 'S': 449 type = "short"; 450 break; 451 case 'C': 452 type = "char"; 453 break; 454 case 'B': 455 type = "byte"; 456 break; 457 default: 458 type = "<unknown-descriptor-type>"; 459 } 460 descriptors.add(type + arrayBrackets); 461 } 462 return descriptors; 463 } 464 printThread(RecordedThread thread, String postFix)465 private void printThread(RecordedThread thread, String postFix) { 466 long javaThreadId = thread.getJavaThreadId(); 467 if (javaThreadId > 0) { 468 println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix); 469 } else { 470 println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix); 471 } 472 } 473 printFormatted(ValueDescriptor field, Object value)474 private boolean printFormatted(ValueDescriptor field, Object value) { 475 if (value instanceof Duration) { 476 Duration d = (Duration) value; 477 if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0) { 478 println("N/A"); 479 return true; 480 } 481 double s = d.toNanosPart() / 1000_000_000.0 + d.toSecondsPart(); 482 if (s < 1.0) { 483 if (s < 0.001) { 484 println(String.format("%.3f", s * 1_000_000) + " us"); 485 } else { 486 println(String.format("%.3f", s * 1_000) + " ms"); 487 } 488 } else { 489 if (s < 1000.0) { 490 println(String.format("%.3f", s) + " s"); 491 } else { 492 println(String.format("%.0f", s) + " s"); 493 } 494 } 495 return true; 496 } 497 if (value instanceof OffsetDateTime) { 498 OffsetDateTime odt = (OffsetDateTime) value; 499 if (odt.equals(OffsetDateTime.MIN)) { 500 println("N/A"); 501 return true; 502 } 503 println(TIME_FORMAT.format(odt)); 504 return true; 505 } 506 Percentage percentage = field.getAnnotation(Percentage.class); 507 if (percentage != null) { 508 if (value instanceof Number) { 509 double d = ((Number) value).doubleValue(); 510 println(String.format("%.2f", d * 100) + "%"); 511 return true; 512 } 513 } 514 DataAmount dataAmount = field.getAnnotation(DataAmount.class); 515 if (dataAmount != null) { 516 if (value instanceof Number) { 517 Number n = (Number) value; 518 long amount = n.longValue(); 519 if (field.getAnnotation(Frequency.class) != null) { 520 if (dataAmount.value().equals(DataAmount.BYTES)) { 521 println(Utils.formatBytesPerSecond(amount)); 522 return true; 523 } 524 if (dataAmount.value().equals(DataAmount.BITS)) { 525 println(Utils.formatBitsPerSecond(amount)); 526 return true; 527 } 528 } else { 529 if (dataAmount.value().equals(DataAmount.BYTES)) { 530 println(Utils.formatBytes(amount)); 531 return true; 532 } 533 if (dataAmount.value().equals(DataAmount.BITS)) { 534 println(Utils.formatBits(amount)); 535 return true; 536 } 537 } 538 } 539 } 540 MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class); 541 if (memoryAddress != null) { 542 if (value instanceof Number) { 543 long d = ((Number) value).longValue(); 544 println(String.format("0x%08X", d)); 545 return true; 546 } 547 } 548 Frequency frequency = field.getAnnotation(Frequency.class); 549 if (frequency != null) { 550 if (value instanceof Number) { 551 println(value + " Hz"); 552 return true; 553 } 554 } 555 556 return false; 557 } 558 setShowIds(boolean showIds)559 public void setShowIds(boolean showIds) { 560 this.showIds = showIds; 561 } 562 } 563