8353614: JFR: jfr print --exact

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2025-04-11 15:12:54 +00:00
parent e604bb9e94
commit 1d7138fe02
4 changed files with 172 additions and 35 deletions

View File

@ -59,13 +59,20 @@ import jdk.jfr.internal.util.ValueFormatter;
*/
public final class PrettyWriter extends EventPrintWriter {
private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject";
private static final DateTimeFormatter TIME_FORMAT_EXACT = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS (yyyy-MM-dd)");
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS (yyyy-MM-dd)");
private static final Long ZERO = 0L;
private final boolean showExact;
private boolean showIds;
private RecordedEvent currentEvent;
public PrettyWriter(PrintWriter destination) {
public PrettyWriter(PrintWriter destination, boolean showExact) {
super(destination);
this.showExact = showExact;
}
public PrettyWriter(PrintWriter destination) {
this(destination, false);
}
@Override
@ -508,7 +515,11 @@ public final class PrettyWriter extends EventPrintWriter {
println("Forever");
return true;
}
println(ValueFormatter.formatDuration(d));
if (showExact) {
println(String.format("%.9f s", (double) d.toNanos() / 1_000_000_000));
} else {
println(ValueFormatter.formatDuration(d));
}
return true;
}
if (value instanceof OffsetDateTime odt) {
@ -516,40 +527,34 @@ public final class PrettyWriter extends EventPrintWriter {
println("N/A");
return true;
}
println(TIME_FORMAT.format(odt));
if (showExact) {
println(TIME_FORMAT_EXACT.format(odt));
} else {
println(TIME_FORMAT.format(odt));
}
return true;
}
Percentage percentage = field.getAnnotation(Percentage.class);
if (percentage != null) {
if (value instanceof Number n) {
double d = n.doubleValue();
println(String.format("%.2f", d * 100) + "%");
double p = 100 * n.doubleValue();
if (showExact) {
println(String.format("%.9f%%", p));
} else {
println(String.format("%.2f%%", p));
}
return true;
}
}
DataAmount dataAmount = field.getAnnotation(DataAmount.class);
if (dataAmount != null) {
if (value instanceof Number n) {
long amount = n.longValue();
if (field.getAnnotation(Frequency.class) != null) {
if (dataAmount.value().equals(DataAmount.BYTES)) {
println(ValueFormatter.formatBytesPerSecond(amount));
return true;
}
if (dataAmount.value().equals(DataAmount.BITS)) {
println(ValueFormatter.formatBitsPerSecond(amount));
return true;
}
} else {
if (dataAmount.value().equals(DataAmount.BYTES)) {
println(ValueFormatter.formatBytes(amount));
return true;
}
if (dataAmount.value().equals(DataAmount.BITS)) {
println(ValueFormatter.formatBits(amount));
return true;
}
}
if (dataAmount != null && value instanceof Number number) {
boolean frequency = field.getAnnotation(Frequency.class) != null;
String unit = dataAmount.value();
boolean bits = unit.equals(DataAmount.BITS);
boolean bytes = unit.equals(DataAmount.BYTES);
if (bits || bytes) {
formatMemory(number.longValue(), bytes, frequency);
return true;
}
}
MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
@ -571,6 +576,35 @@ public final class PrettyWriter extends EventPrintWriter {
return false;
}
private void formatMemory(long value, boolean bytesUnit, boolean frequency) {
if (showExact) {
StringBuilder sb = new StringBuilder();
sb.append(value);
sb.append(bytesUnit ? " byte" : " bit");
if (value > 1) {
sb.append("s");
}
if (frequency) {
sb.append("/s");
}
println(sb.toString());
return;
}
if (frequency) {
if (bytesUnit) {
println(ValueFormatter.formatBytesPerSecond(value));
} else {
println(ValueFormatter.formatBitsPerSecond(value));
}
return;
}
if (bytesUnit) {
println(ValueFormatter.formatBytes(value));
} else {
println(ValueFormatter.formatBits(value));
}
}
public void setShowIds(boolean showIds) {
this.showIds = showIds;
}

View File

@ -49,7 +49,7 @@ final class Print extends Command {
@Override
public List<String> getOptionSyntax() {
List<String> list = new ArrayList<>();
list.add("[--xml|--json]");
list.add("[--xml|--json|--exact]");
list.add("[--categories <filter>]");
list.add("[--events <filter>]");
list.add("[--stack-depth <depth>]");
@ -73,6 +73,8 @@ final class Print extends Command {
stream.println();
stream.println(" --json Print recording in JSON format");
stream.println();
stream.println(" --exact Pretty-print numbers and timestamps with full precision.");
stream.println();
stream.println(" --categories <filter> Select events matching a category name.");
stream.println(" The filter is a comma-separated list of names,");
stream.println(" simple and/or qualified, and/or quoted glob patterns");
@ -95,7 +97,7 @@ final class Print extends Command {
char q = quoteCharacter();
stream.println(" jfr print --categories " + q + "GC,JVM,Java*" + q + " recording.jfr");
stream.println();
stream.println(" jfr print --events "+ q + "jdk.*" + q +" --stack-depth 64 recording.jfr");
stream.println(" jfr print --exact --events "+ q + "jdk.*" + q +" --stack-depth 64 recording.jfr");
stream.println();
stream.println(" jfr print --json --events CPULoad recording.jfr");
}
@ -140,6 +142,9 @@ final class Print extends Command {
throw new UserSyntaxException("not a valid value for --stack-depth");
}
}
if (acceptFormatterOption(options, eventWriter, "--exact")) {
eventWriter = new PrettyWriter(pw, true);;
}
if (acceptFormatterOption(options, eventWriter, "--json")) {
eventWriter = new JSONWriter(pw);
}
@ -155,7 +160,7 @@ final class Print extends Command {
optionCount = options.size();
}
if (eventWriter == null) {
eventWriter = new PrettyWriter(pw); // default to pretty printer
eventWriter = new PrettyWriter(pw, false); // default to pretty printer
}
eventWriter.setStackDepth(stackDepth);
if (!eventFilters.isEmpty()) {

View File

@ -106,7 +106,7 @@ Use `jfr print` to print the contents of a flight recording file to standard out
The syntax is:
`jfr print` \[`--xml`|`--json`\]
`jfr print` \[`--xml`|`--json`|`--exact`\]
\[`--categories` <*filters*>\]
\[`--events` <*filters*>\]
\[`--stack-depth` <*depth*>\]
@ -120,6 +120,9 @@ where:
<a id="print-option-json">`--json`</a>
: Print the recording in JSON format.
<a id="print-option-exact">`--exact`</a>
: Pretty-print numbers and timestamps with full precision.
<a id="print-option-categories">`--categories` <*filters*></a>
: Select events matching a category name.
The filter is a comma-separated list of names,

View File

@ -24,9 +24,19 @@
package jdk.jfr.tool;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import jdk.jfr.Recording;
import jdk.jfr.Event;
import jdk.jfr.Percentage;
import jdk.jfr.Timestamp;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.Timespan;
import jdk.jfr.DataAmount;
import jdk.jfr.Frequency;
import jdk.test.lib.Utils;
import jdk.test.lib.process.OutputAnalyzer;
@ -40,20 +50,105 @@ import jdk.test.lib.process.OutputAnalyzer;
*/
public class TestPrint {
public static void main(String[] args) throws Throwable {
static class ExactEvent extends Event {
@DataAmount(DataAmount.BITS)
long oneBit;
@DataAmount(DataAmount.BITS)
long bits;
@Frequency
@DataAmount(DataAmount.BITS)
long oneBitPerSecond;
@Frequency
@DataAmount(DataAmount.BITS)
long bitsPerSecond;
@DataAmount(DataAmount.BYTES)
long oneByte;
@DataAmount(DataAmount.BYTES)
long bytes;
@Frequency
@DataAmount(DataAmount.BYTES)
long oneBytePerSecond;
@Frequency
@DataAmount(DataAmount.BYTES)
long bytesPerSecond;
@Percentage
double percentage;
@Timestamp(Timestamp.MILLISECONDS_SINCE_EPOCH)
long timestamp;
@Timespan(Timespan.NANOSECONDS)
long timespan;
}
public static void main(String[] args) throws Throwable {
testNoFile();
testMissingFile();
testIncorrectOption();
testExact();
}
private static void testNoFile() throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr("print");
output.shouldContain("missing file");
}
output = ExecuteHelper.jfr("print", "missing.jfr");
private static void testMissingFile() throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr("print", "missing.jfr");
output.shouldContain("could not open file ");
}
Path file = Utils.createTempFile("faked-print-file", ".jfr");
private static void testIncorrectOption() throws Throwable {
Path file = Utils.createTempFile("faked-print-file", ".jfr");
FileWriter fw = new FileWriter(file.toFile());
fw.write('d');
fw.close();
output = ExecuteHelper.jfr("print", "--wrongOption", file.toAbsolutePath().toString());
OutputAnalyzer output = ExecuteHelper.jfr("print", "--wrongOption", file.toAbsolutePath().toString());
output.shouldContain("unknown option");
Files.delete(file);
}
private static void testExact() throws Throwable{
try (Recording r = new Recording()) {
r.start();
ExactEvent e = new ExactEvent();
e.begin();
e.oneBit = 1L;
e.bits = 222_222_222L;
e.oneBitPerSecond = 1L;
e.bitsPerSecond = 333_333_333L;
e.oneByte = 1L;
e.bytes = 444_444_444L;
e.oneBytePerSecond = 1L;
e.bytesPerSecond = 555_555_555L;
e.percentage = 0.666_666_666_66;
e.timestamp = 777;
e.timespan = 888_888_888L;
e.commit();
r.stop();
Path file = Path.of("exact.jfr");
r.dump(file);
OutputAnalyzer output = ExecuteHelper.jfr("print", "--exact", file.toAbsolutePath().toString());
output.shouldContain("oneBit = 1 bit");
output.shouldContain("bits = 222222222 bits");
output.shouldContain("oneBitPerSecond = 1 bit/s");
output.shouldContain("bitsPerSecond = 333333333 bits/s");
output.shouldContain("oneByte = 1 byte");
output.shouldContain("bytes = 444444444 bytes");
output.shouldContain("oneBytePerSecond = 1 byte/s");
output.shouldContain("bytesPerSecond = 555555555 bytes/s");
output.shouldContain(String.valueOf(100 * e.percentage) + "%");
output.shouldContain("00.777000000 (19");
output.shouldContain(String.valueOf(e.timespan) + " s");
Files.delete(file);
}
}
}