Skip to content

Commit 1d7138f

Browse files
committed
8353614: JFR: jfr print --exact
Reviewed-by: mgronlun
1 parent e604bb9 commit 1d7138f

File tree

4 files changed

+171
-34
lines changed

4 files changed

+171
-34
lines changed

src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,20 @@
5959
*/
6060
public final class PrettyWriter extends EventPrintWriter {
6161
private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject";
62+
private static final DateTimeFormatter TIME_FORMAT_EXACT = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS (yyyy-MM-dd)");
6263
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS (yyyy-MM-dd)");
6364
private static final Long ZERO = 0L;
65+
private final boolean showExact;
6466
private boolean showIds;
6567
private RecordedEvent currentEvent;
6668

67-
public PrettyWriter(PrintWriter destination) {
69+
public PrettyWriter(PrintWriter destination, boolean showExact) {
6870
super(destination);
71+
this.showExact = showExact;
72+
}
73+
74+
public PrettyWriter(PrintWriter destination) {
75+
this(destination, false);
6976
}
7077

7178
@Override
@@ -508,48 +515,46 @@ private boolean printFormatted(ValueDescriptor field, Object value) {
508515
println("Forever");
509516
return true;
510517
}
511-
println(ValueFormatter.formatDuration(d));
518+
if (showExact) {
519+
println(String.format("%.9f s", (double) d.toNanos() / 1_000_000_000));
520+
} else {
521+
println(ValueFormatter.formatDuration(d));
522+
}
512523
return true;
513524
}
514525
if (value instanceof OffsetDateTime odt) {
515526
if (odt.equals(OffsetDateTime.MIN)) {
516527
println("N/A");
517528
return true;
518529
}
519-
println(TIME_FORMAT.format(odt));
530+
if (showExact) {
531+
println(TIME_FORMAT_EXACT.format(odt));
532+
} else {
533+
println(TIME_FORMAT.format(odt));
534+
}
520535
return true;
521536
}
522537
Percentage percentage = field.getAnnotation(Percentage.class);
523538
if (percentage != null) {
524539
if (value instanceof Number n) {
525-
double d = n.doubleValue();
526-
println(String.format("%.2f", d * 100) + "%");
540+
double p = 100 * n.doubleValue();
541+
if (showExact) {
542+
println(String.format("%.9f%%", p));
543+
} else {
544+
println(String.format("%.2f%%", p));
545+
}
527546
return true;
528547
}
529548
}
530549
DataAmount dataAmount = field.getAnnotation(DataAmount.class);
531-
if (dataAmount != null) {
532-
if (value instanceof Number n) {
533-
long amount = n.longValue();
534-
if (field.getAnnotation(Frequency.class) != null) {
535-
if (dataAmount.value().equals(DataAmount.BYTES)) {
536-
println(ValueFormatter.formatBytesPerSecond(amount));
537-
return true;
538-
}
539-
if (dataAmount.value().equals(DataAmount.BITS)) {
540-
println(ValueFormatter.formatBitsPerSecond(amount));
541-
return true;
542-
}
543-
} else {
544-
if (dataAmount.value().equals(DataAmount.BYTES)) {
545-
println(ValueFormatter.formatBytes(amount));
546-
return true;
547-
}
548-
if (dataAmount.value().equals(DataAmount.BITS)) {
549-
println(ValueFormatter.formatBits(amount));
550-
return true;
551-
}
552-
}
550+
if (dataAmount != null && value instanceof Number number) {
551+
boolean frequency = field.getAnnotation(Frequency.class) != null;
552+
String unit = dataAmount.value();
553+
boolean bits = unit.equals(DataAmount.BITS);
554+
boolean bytes = unit.equals(DataAmount.BYTES);
555+
if (bits || bytes) {
556+
formatMemory(number.longValue(), bytes, frequency);
557+
return true;
553558
}
554559
}
555560
MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
@@ -571,6 +576,35 @@ private boolean printFormatted(ValueDescriptor field, Object value) {
571576
return false;
572577
}
573578

579+
private void formatMemory(long value, boolean bytesUnit, boolean frequency) {
580+
if (showExact) {
581+
StringBuilder sb = new StringBuilder();
582+
sb.append(value);
583+
sb.append(bytesUnit ? " byte" : " bit");
584+
if (value > 1) {
585+
sb.append("s");
586+
}
587+
if (frequency) {
588+
sb.append("/s");
589+
}
590+
println(sb.toString());
591+
return;
592+
}
593+
if (frequency) {
594+
if (bytesUnit) {
595+
println(ValueFormatter.formatBytesPerSecond(value));
596+
} else {
597+
println(ValueFormatter.formatBitsPerSecond(value));
598+
}
599+
return;
600+
}
601+
if (bytesUnit) {
602+
println(ValueFormatter.formatBytes(value));
603+
} else {
604+
println(ValueFormatter.formatBits(value));
605+
}
606+
}
607+
574608
public void setShowIds(boolean showIds) {
575609
this.showIds = showIds;
576610
}

src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public String getName() {
4949
@Override
5050
public List<String> getOptionSyntax() {
5151
List<String> list = new ArrayList<>();
52-
list.add("[--xml|--json]");
52+
list.add("[--xml|--json|--exact]");
5353
list.add("[--categories <filter>]");
5454
list.add("[--events <filter>]");
5555
list.add("[--stack-depth <depth>]");
@@ -73,6 +73,8 @@ public void displayOptionUsage(PrintStream stream) {
7373
stream.println();
7474
stream.println(" --json Print recording in JSON format");
7575
stream.println();
76+
stream.println(" --exact Pretty-print numbers and timestamps with full precision.");
77+
stream.println();
7678
stream.println(" --categories <filter> Select events matching a category name.");
7779
stream.println(" The filter is a comma-separated list of names,");
7880
stream.println(" simple and/or qualified, and/or quoted glob patterns");
@@ -95,7 +97,7 @@ public void displayOptionUsage(PrintStream stream) {
9597
char q = quoteCharacter();
9698
stream.println(" jfr print --categories " + q + "GC,JVM,Java*" + q + " recording.jfr");
9799
stream.println();
98-
stream.println(" jfr print --events "+ q + "jdk.*" + q +" --stack-depth 64 recording.jfr");
100+
stream.println(" jfr print --exact --events "+ q + "jdk.*" + q +" --stack-depth 64 recording.jfr");
99101
stream.println();
100102
stream.println(" jfr print --json --events CPULoad recording.jfr");
101103
}
@@ -140,6 +142,9 @@ public void execute(Deque<String> options) throws UserSyntaxException, UserDataE
140142
throw new UserSyntaxException("not a valid value for --stack-depth");
141143
}
142144
}
145+
if (acceptFormatterOption(options, eventWriter, "--exact")) {
146+
eventWriter = new PrettyWriter(pw, true);;
147+
}
143148
if (acceptFormatterOption(options, eventWriter, "--json")) {
144149
eventWriter = new JSONWriter(pw);
145150
}
@@ -155,7 +160,7 @@ public void execute(Deque<String> options) throws UserSyntaxException, UserDataE
155160
optionCount = options.size();
156161
}
157162
if (eventWriter == null) {
158-
eventWriter = new PrettyWriter(pw); // default to pretty printer
163+
eventWriter = new PrettyWriter(pw, false); // default to pretty printer
159164
}
160165
eventWriter.setStackDepth(stackDepth);
161166
if (!eventFilters.isEmpty()) {

src/jdk.jfr/share/man/jfr.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Use `jfr print` to print the contents of a flight recording file to standard out
106106

107107
The syntax is:
108108

109-
`jfr print` \[`--xml`|`--json`\]
109+
`jfr print` \[`--xml`|`--json`|`--exact`\]
110110
\[`--categories` <*filters*>\]
111111
\[`--events` <*filters*>\]
112112
\[`--stack-depth` <*depth*>\]
@@ -120,6 +120,9 @@ where:
120120
<a id="print-option-json">`--json`</a>
121121
: Print the recording in JSON format.
122122

123+
<a id="print-option-exact">`--exact`</a>
124+
: Pretty-print numbers and timestamps with full precision.
125+
123126
<a id="print-option-categories">`--categories` <*filters*></a>
124127
: Select events matching a category name.
125128
The filter is a comma-separated list of names,

test/jdk/jdk/jfr/tool/TestPrint.java

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,19 @@
2424
package jdk.jfr.tool;
2525

2626
import java.io.FileWriter;
27+
import java.io.IOException;
2728
import java.nio.file.Files;
2829
import java.nio.file.Path;
30+
import java.util.List;
2931

32+
import jdk.jfr.Recording;
33+
import jdk.jfr.Event;
34+
import jdk.jfr.Percentage;
35+
import jdk.jfr.Timestamp;
36+
import jdk.jfr.consumer.RecordedEvent;
37+
import jdk.jfr.Timespan;
38+
import jdk.jfr.DataAmount;
39+
import jdk.jfr.Frequency;
3040
import jdk.test.lib.Utils;
3141
import jdk.test.lib.process.OutputAnalyzer;
3242

@@ -40,20 +50,105 @@
4050
*/
4151
public class TestPrint {
4252

53+
static class ExactEvent extends Event {
54+
@DataAmount(DataAmount.BITS)
55+
long oneBit;
56+
57+
@DataAmount(DataAmount.BITS)
58+
long bits;
59+
60+
@Frequency
61+
@DataAmount(DataAmount.BITS)
62+
long oneBitPerSecond;
63+
64+
@Frequency
65+
@DataAmount(DataAmount.BITS)
66+
long bitsPerSecond;
67+
68+
@DataAmount(DataAmount.BYTES)
69+
long oneByte;
70+
71+
@DataAmount(DataAmount.BYTES)
72+
long bytes;
73+
74+
@Frequency
75+
@DataAmount(DataAmount.BYTES)
76+
long oneBytePerSecond;
77+
78+
@Frequency
79+
@DataAmount(DataAmount.BYTES)
80+
long bytesPerSecond;
81+
82+
@Percentage
83+
double percentage;
84+
85+
@Timestamp(Timestamp.MILLISECONDS_SINCE_EPOCH)
86+
long timestamp;
87+
88+
@Timespan(Timespan.NANOSECONDS)
89+
long timespan;
90+
}
91+
4392
public static void main(String[] args) throws Throwable {
93+
testNoFile();
94+
testMissingFile();
95+
testIncorrectOption();
96+
testExact();
97+
}
4498

99+
private static void testNoFile() throws Throwable {
45100
OutputAnalyzer output = ExecuteHelper.jfr("print");
46101
output.shouldContain("missing file");
102+
}
47103

48-
output = ExecuteHelper.jfr("print", "missing.jfr");
104+
private static void testMissingFile() throws Throwable {
105+
OutputAnalyzer output = ExecuteHelper.jfr("print", "missing.jfr");
49106
output.shouldContain("could not open file ");
107+
}
50108

51-
Path file = Utils.createTempFile("faked-print-file", ".jfr");
109+
private static void testIncorrectOption() throws Throwable {
110+
Path file = Utils.createTempFile("faked-print-file", ".jfr");
52111
FileWriter fw = new FileWriter(file.toFile());
53112
fw.write('d');
54113
fw.close();
55-
output = ExecuteHelper.jfr("print", "--wrongOption", file.toAbsolutePath().toString());
114+
OutputAnalyzer output = ExecuteHelper.jfr("print", "--wrongOption", file.toAbsolutePath().toString());
56115
output.shouldContain("unknown option");
57116
Files.delete(file);
58117
}
118+
119+
private static void testExact() throws Throwable{
120+
try (Recording r = new Recording()) {
121+
r.start();
122+
ExactEvent e = new ExactEvent();
123+
e.begin();
124+
e.oneBit = 1L;
125+
e.bits = 222_222_222L;
126+
e.oneBitPerSecond = 1L;
127+
e.bitsPerSecond = 333_333_333L;
128+
e.oneByte = 1L;
129+
e.bytes = 444_444_444L;
130+
e.oneBytePerSecond = 1L;
131+
e.bytesPerSecond = 555_555_555L;
132+
e.percentage = 0.666_666_666_66;
133+
e.timestamp = 777;
134+
e.timespan = 888_888_888L;
135+
e.commit();
136+
r.stop();
137+
Path file = Path.of("exact.jfr");
138+
r.dump(file);
139+
OutputAnalyzer output = ExecuteHelper.jfr("print", "--exact", file.toAbsolutePath().toString());
140+
output.shouldContain("oneBit = 1 bit");
141+
output.shouldContain("bits = 222222222 bits");
142+
output.shouldContain("oneBitPerSecond = 1 bit/s");
143+
output.shouldContain("bitsPerSecond = 333333333 bits/s");
144+
output.shouldContain("oneByte = 1 byte");
145+
output.shouldContain("bytes = 444444444 bytes");
146+
output.shouldContain("oneBytePerSecond = 1 byte/s");
147+
output.shouldContain("bytesPerSecond = 555555555 bytes/s");
148+
output.shouldContain(String.valueOf(100 * e.percentage) + "%");
149+
output.shouldContain("00.777000000 (19");
150+
output.shouldContain(String.valueOf(e.timespan) + " s");
151+
Files.delete(file);
152+
}
153+
}
59154
}

0 commit comments

Comments
 (0)