635 lines
21 KiB
Java
635 lines
21 KiB
Java
/*
|
|
* 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 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 && d.getNano() == 0) {
|
|
println("N/A");
|
|
return true;
|
|
}
|
|
double s = d.getNano() / 1000_000_000.0 + (int) (d.getSeconds() % 60);
|
|
if (s < 1.0) {
|
|
if (s < 0.001) {
|
|
println(String.format("%.3f", s * 1_000_000) + " us");
|
|
} else {
|
|
println(String.format("%.3f", s * 1_000) + " ms");
|
|
}
|
|
} else {
|
|
if (s < 1000.0) {
|
|
println(String.format("%.3f", s) + " s");
|
|
} else {
|
|
println(String.format("%.0f", s) + " s");
|
|
}
|
|
}
|
|
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;
|
|
long amount = n.longValue();
|
|
if (field.getAnnotation(Frequency.class) != null) {
|
|
if (dataAmount.value().equals(DataAmount.BYTES)) {
|
|
println(Utils.formatBytesPerSecond(amount));
|
|
return true;
|
|
}
|
|
if (dataAmount.value().equals(DataAmount.BITS)) {
|
|
println(Utils.formatBitsPerSecond(amount));
|
|
return true;
|
|
}
|
|
} else {
|
|
if (dataAmount.value().equals(DataAmount.BYTES)) {
|
|
println(Utils.formatBytes(amount));
|
|
return true;
|
|
}
|
|
if (dataAmount.value().equals(DataAmount.BITS)) {
|
|
println(Utils.formatBits(amount));
|
|
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;
|
|
}
|
|
}
|
|
Frequency frequency = field.getAnnotation(Frequency.class);
|
|
if (frequency != null) {
|
|
if (value instanceof Number) {
|
|
println(value + " Hz");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void setShowIds(boolean showIds) {
|
|
this.showIds = showIds;
|
|
}
|
|
}
|