Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 61 additions & 27 deletions src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,20 @@
*/
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
Expand Down Expand Up @@ -508,48 +515,46 @@ private boolean printFormatted(ValueDescriptor field, Object value) {
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) {
if (odt.equals(OffsetDateTime.MIN)) {
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);
Expand All @@ -571,6 +576,35 @@ private boolean printFormatted(ValueDescriptor field, Object value) {
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;
}
Expand Down
11 changes: 8 additions & 3 deletions src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public String getName() {
@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>]");
Expand All @@ -73,6 +73,8 @@ public void displayOptionUsage(PrintStream stream) {
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");
Expand All @@ -95,7 +97,7 @@ public void displayOptionUsage(PrintStream stream) {
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");
}
Expand Down Expand Up @@ -140,6 +142,9 @@ public void execute(Deque<String> options) throws UserSyntaxException, UserDataE
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);
}
Expand All @@ -155,7 +160,7 @@ public void execute(Deque<String> options) throws UserSyntaxException, UserDataE
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()) {
Expand Down
5 changes: 4 additions & 1 deletion src/jdk.jfr/share/man/jfr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*>\]
Expand All @@ -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,
Expand Down
101 changes: 98 additions & 3 deletions test/jdk/jdk/jfr/tool/TestPrint.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -40,20 +50,105 @@
*/
public class TestPrint {

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);
}
}
}