| /* |
| * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.jfr.internal.tool; |
| |
| import java.io.PrintWriter; |
| import java.time.Duration; |
| import java.time.OffsetDateTime; |
| import java.time.format.DateTimeFormatter; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.StringJoiner; |
| |
| import jdk.jfr.AnnotationElement; |
| import jdk.jfr.DataAmount; |
| import jdk.jfr.Frequency; |
| import jdk.jfr.MemoryAddress; |
| import jdk.jfr.Percentage; |
| import jdk.jfr.ValueDescriptor; |
| import jdk.jfr.consumer.RecordedClass; |
| import jdk.jfr.consumer.RecordedClassLoader; |
| import jdk.jfr.consumer.RecordedEvent; |
| import jdk.jfr.consumer.RecordedFrame; |
| import jdk.jfr.consumer.RecordedMethod; |
| import jdk.jfr.consumer.RecordedObject; |
| import jdk.jfr.consumer.RecordedStackTrace; |
| import jdk.jfr.consumer.RecordedThread; |
| import jdk.jfr.internal.PrivateAccess; |
| import jdk.jfr.internal.Type; |
| import jdk.jfr.internal.Utils; |
| |
| /** |
| * Print events in a human-readable format. |
| * |
| * This class is also used by {@link RecordedObject#toString()} |
| */ |
| public final class PrettyWriter extends EventPrintWriter { |
| private static final Duration MILLSECOND = Duration.ofMillis(1); |
| private static final Duration SECOND = Duration.ofSeconds(1); |
| private static final Duration MINUTE = Duration.ofMinutes(1); |
| private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject"; |
| private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); |
| private final static Long ZERO = 0L; |
| private boolean showIds; |
| private RecordedEvent currentEvent; |
| |
| public PrettyWriter(PrintWriter destination) { |
| super(destination); |
| } |
| |
| @Override |
| protected void print(List<RecordedEvent> events) { |
| for (RecordedEvent e : events) { |
| print(e); |
| flush(false); |
| } |
| } |
| |
| public void printType(Type t) { |
| if (showIds) { |
| print("// id: "); |
| println(String.valueOf(t.getId())); |
| } |
| int commentIndex = t.getName().length() + 10; |
| String typeName = t.getName(); |
| int index = typeName.lastIndexOf("."); |
| if (index != -1) { |
| println("@Name(\"" + typeName + "\")"); |
| } |
| printAnnotations(commentIndex, t.getAnnotationElements()); |
| print("class " + typeName.substring(index + 1)); |
| String superType = t.getSuperType(); |
| if (superType != null) { |
| print(" extends " + superType); |
| } |
| println(" {"); |
| indent(); |
| boolean first = true; |
| for (ValueDescriptor v : t.getFields()) { |
| printField(commentIndex, v, first); |
| first = false; |
| } |
| retract(); |
| println("}"); |
| println(); |
| } |
| |
| private void printField(int commentIndex, ValueDescriptor v, boolean first) { |
| if (!first) { |
| println(); |
| } |
| printAnnotations(commentIndex, v.getAnnotationElements()); |
| printIndent(); |
| Type vType = PrivateAccess.getInstance().getType(v); |
| if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) { |
| print("static "); |
| } |
| print(makeSimpleType(v.getTypeName())); |
| if (v.isArray()) { |
| print("[]"); |
| } |
| print(" "); |
| print(v.getName()); |
| print(";"); |
| printCommentRef(commentIndex, v.getTypeId()); |
| } |
| |
| private void printCommentRef(int commentIndex, long typeId) { |
| if (showIds) { |
| int column = getColumn(); |
| if (column > commentIndex) { |
| print(" "); |
| } else { |
| while (column < commentIndex) { |
| print(" "); |
| column++; |
| } |
| } |
| println(" // id=" + typeId); |
| } else { |
| println(); |
| } |
| } |
| |
| private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) { |
| for (AnnotationElement a : annotations) { |
| printIndent(); |
| print("@"); |
| print(makeSimpleType(a.getTypeName())); |
| List<ValueDescriptor> vs = a.getValueDescriptors(); |
| if (!vs.isEmpty()) { |
| printAnnotation(a); |
| printCommentRef(commentIndex, a.getTypeId()); |
| } else { |
| println(); |
| } |
| } |
| } |
| |
| private void printAnnotation(AnnotationElement a) { |
| StringJoiner sj = new StringJoiner(", ", "(", ")"); |
| List<ValueDescriptor> vs = a.getValueDescriptors(); |
| for (ValueDescriptor v : vs) { |
| Object o = a.getValue(v.getName()); |
| if (vs.size() == 1 && v.getName().equals("value")) { |
| sj.add(textify(o)); |
| } else { |
| sj.add(v.getName() + "=" + textify(o)); |
| } |
| } |
| print(sj.toString()); |
| } |
| |
| private String textify(Object o) { |
| if (o.getClass().isArray()) { |
| Object[] array = (Object[]) o; |
| if (array.length == 1) { |
| return quoteIfNeeded(array[0]); |
| } |
| StringJoiner s = new StringJoiner(", ", "{", "}"); |
| for (Object ob : array) { |
| s.add(quoteIfNeeded(ob)); |
| } |
| return s.toString(); |
| } else { |
| return quoteIfNeeded(o); |
| } |
| } |
| |
| private String quoteIfNeeded(Object o) { |
| if (o instanceof String) { |
| return "\"" + o + "\""; |
| } else { |
| return String.valueOf(o); |
| } |
| } |
| |
| private String makeSimpleType(String typeName) { |
| int index = typeName.lastIndexOf("."); |
| return typeName.substring(index + 1); |
| } |
| |
| public void print(RecordedEvent event) { |
| currentEvent = event; |
| print(event.getEventType().getName(), " "); |
| println("{"); |
| indent(); |
| for (ValueDescriptor v : event.getFields()) { |
| String name = v.getName(); |
| if (!isZeroDuration(event, name) && !isLateField(name)) { |
| printFieldValue(event, v); |
| } |
| } |
| if (event.getThread() != null) { |
| printIndent(); |
| print(EVENT_THREAD_FIELD + " = "); |
| printThread(event.getThread(), ""); |
| } |
| if (event.getStackTrace() != null) { |
| printIndent(); |
| print(STACK_TRACE_FIELD + " = "); |
| printStackTrace(event.getStackTrace()); |
| } |
| retract(); |
| printIndent(); |
| println("}"); |
| println(); |
| } |
| |
| private boolean isZeroDuration(RecordedEvent event, String name) { |
| return name.equals("duration") && ZERO.equals(event.getValue("duration")); |
| } |
| |
| private void printStackTrace(RecordedStackTrace stackTrace) { |
| println("["); |
| List<RecordedFrame> frames = stackTrace.getFrames(); |
| indent(); |
| int i = 0; |
| while (i < frames.size() && i < getStackDepth()) { |
| RecordedFrame frame = frames.get(i); |
| if (frame.isJavaFrame()) { |
| printIndent(); |
| printValue(frame, null, ""); |
| println(); |
| i++; |
| } |
| } |
| if (stackTrace.isTruncated() || i == getStackDepth()) { |
| printIndent(); |
| println("..."); |
| } |
| retract(); |
| printIndent(); |
| println("]"); |
| } |
| |
| public void print(RecordedObject struct, String postFix) { |
| println("{"); |
| indent(); |
| for (ValueDescriptor v : struct.getFields()) { |
| printFieldValue(struct, v); |
| } |
| retract(); |
| printIndent(); |
| println("}" + postFix); |
| } |
| |
| private void printFieldValue(RecordedObject struct, ValueDescriptor v) { |
| printIndent(); |
| print(v.getName(), " = "); |
| printValue(getValue(struct, v), v, ""); |
| } |
| |
| private void printArray(Object[] array) { |
| println("["); |
| indent(); |
| for (int i = 0; i < array.length; i++) { |
| printIndent(); |
| printValue(array[i], null, i + 1 < array.length ? ", " : ""); |
| } |
| retract(); |
| printIndent(); |
| println("]"); |
| } |
| |
| private void printValue(Object value, ValueDescriptor field, String postFix) { |
| if (value == null) { |
| println("N/A" + postFix); |
| return; |
| } |
| if (value instanceof RecordedObject) { |
| if (value instanceof RecordedThread) { |
| printThread((RecordedThread) value, postFix); |
| return; |
| } |
| if (value instanceof RecordedClass) { |
| printClass((RecordedClass) value, postFix); |
| return; |
| } |
| if (value instanceof RecordedClassLoader) { |
| printClassLoader((RecordedClassLoader) value, postFix); |
| return; |
| } |
| if (value instanceof RecordedFrame) { |
| RecordedFrame frame = (RecordedFrame) value; |
| if (frame.isJavaFrame()) { |
| printJavaFrame((RecordedFrame) value, postFix); |
| return; |
| } |
| } |
| if (value instanceof RecordedMethod) { |
| println(formatMethod((RecordedMethod) value)); |
| return; |
| } |
| if (field.getTypeName().equals(TYPE_OLD_OBJECT)) { |
| printOldObject((RecordedObject) value); |
| return; |
| } |
| print((RecordedObject) value, postFix); |
| return; |
| } |
| if (value.getClass().isArray()) { |
| printArray((Object[]) value); |
| return; |
| } |
| if (value instanceof Double) { |
| Double d = (Double) value; |
| if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) { |
| println("N/A"); |
| return; |
| } |
| } |
| if (value instanceof Float) { |
| Float f = (Float) value; |
| if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) { |
| println("N/A"); |
| return; |
| } |
| } |
| if (value instanceof Long) { |
| Long l = (Long) value; |
| if (l == Long.MIN_VALUE) { |
| println("N/A"); |
| return; |
| } |
| } |
| if (value instanceof Integer) { |
| Integer i = (Integer) value; |
| if (i == Integer.MIN_VALUE) { |
| println("N/A"); |
| return; |
| } |
| } |
| |
| if (field.getContentType() != null) { |
| if (printFormatted(field, value)) { |
| return; |
| } |
| } |
| String text = String.valueOf(value); |
| if (value instanceof String) { |
| text = "\"" + text + "\""; |
| } |
| println(text); |
| } |
| |
| private void printOldObject(RecordedObject object) { |
| println(" ["); |
| indent(); |
| printIndent(); |
| try { |
| printReferenceChain(object); |
| } catch (IllegalArgumentException iae) { |
| // Could not find a field |
| // Not possible to validate fields beforehand using RecordedObject#hasField |
| // since nested objects, for example object.referrer.array.index, requires |
| // an actual array object (which may be null). |
| } |
| retract(); |
| printIndent(); |
| println("]"); |
| } |
| |
| private void printReferenceChain(RecordedObject object) { |
| printObject(object, currentEvent.getLong("arrayElements")); |
| for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) { |
| long skip = ref.getLong("skip"); |
| if (skip > 0) { |
| printIndent(); |
| println("..."); |
| } |
| String objectHolder = ""; |
| long size = Long.MIN_VALUE; |
| RecordedObject array = ref.getValue("array"); |
| if (array != null) { |
| long index = array.getLong("index"); |
| size = array.getLong("size"); |
| objectHolder = "[" + index + "]"; |
| } |
| RecordedObject field = ref.getValue("field"); |
| if (field != null) { |
| objectHolder = field.getString("name"); |
| } |
| printIndent(); |
| print(objectHolder); |
| print(" : "); |
| object = ref.getValue("object"); |
| if (object != null) { |
| printObject(object, size); |
| } |
| } |
| } |
| |
| void printObject(RecordedObject object, long arraySize) { |
| RecordedClass clazz = object.getClass("type"); |
| if (clazz != null) { |
| String className = clazz.getName(); |
| if (className!= null && className.startsWith("[")) { |
| className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0); |
| } |
| print(className); |
| String description = object.getString("description"); |
| if (description != null) { |
| print(" "); |
| print(description); |
| } |
| } |
| println(); |
| } |
| |
| private void printClassLoader(RecordedClassLoader cl, String postFix) { |
| // Purposely not printing class loader name to avoid cluttered output |
| RecordedClass clazz = cl.getType(); |
| print(clazz == null ? "null" : clazz.getName()); |
| if (clazz != null) { |
| print(" ("); |
| print("id = "); |
| print(String.valueOf(cl.getId())); |
| println(")"); |
| } |
| } |
| |
| private void printJavaFrame(RecordedFrame f, String postFix) { |
| print(formatMethod(f.getMethod())); |
| int line = f.getLineNumber(); |
| if (line >= 0) { |
| print(" line: " + line); |
| } |
| print(postFix); |
| } |
| |
| private String formatMethod(RecordedMethod m) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(m.getType().getName()); |
| sb.append("."); |
| sb.append(m.getName()); |
| sb.append("("); |
| StringJoiner sj = new StringJoiner(", "); |
| String md = m.getDescriptor().replace("/", "."); |
| String parameter = md.substring(1, md.lastIndexOf(")")); |
| for (String qualifiedName : decodeDescriptors(parameter, "")) { |
| String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); |
| sj.add(typeName); |
| } |
| sb.append(sj); |
| sb.append(")"); |
| return sb.toString(); |
| } |
| |
| private void printClass(RecordedClass clazz, String postFix) { |
| RecordedClassLoader classLoader = clazz.getClassLoader(); |
| String classLoaderName = "null"; |
| if (classLoader != null) { |
| if (classLoader.getName() != null) { |
| classLoaderName = classLoader.getName(); |
| } else { |
| classLoaderName = classLoader.getType().getName(); |
| } |
| } |
| String className = clazz.getName(); |
| if (className.startsWith("[")) { |
| className = decodeDescriptors(className, "").get(0); |
| } |
| println(className + " (classLoader = " + classLoaderName + ")" + postFix); |
| } |
| |
| List<String> decodeDescriptors(String descriptor, String arraySize) { |
| List<String> descriptors = new ArrayList<>(); |
| for (int index = 0; index < descriptor.length(); index++) { |
| String arrayBrackets = ""; |
| while (descriptor.charAt(index) == '[') { |
| arrayBrackets = arrayBrackets + "[" + arraySize + "]" ; |
| arraySize = ""; |
| index++; |
| } |
| char c = descriptor.charAt(index); |
| String type; |
| switch (c) { |
| case 'L': |
| int endIndex = descriptor.indexOf(';', index); |
| type = descriptor.substring(index + 1, endIndex); |
| index = endIndex; |
| break; |
| case 'I': |
| type = "int"; |
| break; |
| case 'J': |
| type = "long"; |
| break; |
| case 'Z': |
| type = "boolean"; |
| break; |
| case 'D': |
| type = "double"; |
| break; |
| case 'F': |
| type = "float"; |
| break; |
| case 'S': |
| type = "short"; |
| break; |
| case 'C': |
| type = "char"; |
| break; |
| case 'B': |
| type = "byte"; |
| break; |
| default: |
| type = "<unknown-descriptor-type>"; |
| } |
| descriptors.add(type + arrayBrackets); |
| } |
| return descriptors; |
| } |
| |
| private void printThread(RecordedThread thread, String postFix) { |
| long javaThreadId = thread.getJavaThreadId(); |
| if (javaThreadId > 0) { |
| println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix); |
| } else { |
| println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix); |
| } |
| } |
| |
| private boolean printFormatted(ValueDescriptor field, Object value) { |
| if (value instanceof Duration) { |
| Duration d = (Duration) value; |
| if (d.getSeconds() == Long.MIN_VALUE) { |
| println("N/A"); |
| return true; |
| } |
| if(d.compareTo(MILLSECOND) < 0){ |
| println(String.format("%.3f us", (double)d.toNanos() / 1_000)); |
| } else if(d.compareTo(SECOND) < 0){ |
| println(String.format("%.3f ms", (double)d.toNanos() / 1_000_000)); |
| } else if(d.compareTo(MINUTE) < 0){ |
| println(String.format("%.3f s", (double)d.toMillis() / 1_000)); |
| } else { |
| println(String.format("%d s", d.toSeconds())); |
| } |
| return true; |
| } |
| if (value instanceof OffsetDateTime) { |
| OffsetDateTime odt = (OffsetDateTime) value; |
| if (odt.equals(OffsetDateTime.MIN)) { |
| println("N/A"); |
| return true; |
| } |
| println(TIME_FORMAT.format(odt)); |
| return true; |
| } |
| Percentage percentage = field.getAnnotation(Percentage.class); |
| if (percentage != null) { |
| if (value instanceof Number) { |
| double d = ((Number) value).doubleValue(); |
| println(String.format("%.2f", d * 100) + "%"); |
| return true; |
| } |
| } |
| DataAmount dataAmount = field.getAnnotation(DataAmount.class); |
| if (dataAmount != null) { |
| if (value instanceof Number) { |
| Number n = (Number) value; |
| String bytes = Utils.formatBytes(n.longValue()); |
| if (field.getAnnotation(Frequency.class) != null) { |
| bytes += "/s"; |
| } |
| println(bytes); |
| return true; |
| } |
| } |
| MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class); |
| if (memoryAddress != null) { |
| if (value instanceof Number) { |
| long d = ((Number) value).longValue(); |
| println(String.format("0x%08X", d)); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void setShowIds(boolean showIds) { |
| this.showIds = showIds; |
| } |
| } |