Skip to content

Commit a2d7f44

Browse files
committed
8295651: JFR: 'jfr scrub' should summarize what was removed
Reviewed-by: mgronlun
1 parent 0c34bf0 commit a2d7f44

File tree

9 files changed

+136
-15
lines changed

9 files changed

+136
-15
lines changed

src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,13 +28,15 @@
2828
import java.io.IOException;
2929
import java.io.PrintWriter;
3030
import java.io.StringWriter;
31+
import java.nio.file.Path;
3132
import java.time.Duration;
3233
import java.time.Instant;
3334
import java.time.OffsetDateTime;
3435
import java.time.temporal.ChronoUnit;
3536
import java.util.Comparator;
3637
import java.util.List;
3738
import java.util.Objects;
39+
import java.util.function.Predicate;
3840

3941
import jdk.jfr.Configuration;
4042
import jdk.jfr.EventType;
@@ -46,6 +48,7 @@
4648
import jdk.jfr.internal.consumer.JdkJfrConsumer;
4749
import jdk.jfr.internal.consumer.ObjectContext;
4850
import jdk.jfr.internal.consumer.ObjectFactory;
51+
import jdk.jfr.internal.consumer.filter.ChunkWriter.RemovedEvents;
4952
import jdk.jfr.internal.tool.PrettyWriter;
5053

5154
/**
@@ -148,6 +151,11 @@ public MetadataEvent newMetadataEvent(List<EventType> previous, List<EventType>
148151
List<Configuration> configurations) {
149152
return new MetadataEvent(previous, current, configurations);
150153
}
154+
155+
@Override
156+
public List<RemovedEvents> write(RecordingFile file, Path output, Predicate<RecordedEvent> filter) throws IOException {
157+
return file.write(output, filter, true);
158+
}
151159
};
152160
JdkJfrConsumer.setAccess(access);
153161
}

src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import jdk.jfr.internal.consumer.ParserState;
4747
import jdk.jfr.internal.consumer.RecordingInput;
4848
import jdk.jfr.internal.consumer.filter.ChunkWriter;
49+
import jdk.jfr.internal.consumer.filter.ChunkWriter.RemovedEvents;
4950

5051
/**
5152
* A recording file.
@@ -229,12 +230,18 @@ public void close() throws IOException {
229230
public void write(Path destination, Predicate<RecordedEvent> filter) throws IOException {
230231
Objects.requireNonNull(destination, "destination");
231232
Objects.requireNonNull(filter, "filter");
232-
try (ChunkWriter cw = new ChunkWriter(file.toPath(), destination, filter)) {
233+
write(destination, filter, false);
234+
}
235+
236+
// package private
237+
List<RemovedEvents> write(Path destination, Predicate<RecordedEvent> filter, boolean collectResults) throws IOException {
238+
try (ChunkWriter cw = new ChunkWriter(file.toPath(), destination, filter, collectResults)) {
233239
try (RecordingFile rf = new RecordingFile(cw)) {
234240
while (rf.hasMoreEvents()) {
235241
rf.readEvent();
236242
}
237243
}
244+
return cw.getRemovedEventTypes();
238245
}
239246
}
240247

src/jdk.jfr/share/classes/jdk/jfr/internal/LongMap.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,9 @@
2525

2626
package jdk.jfr.internal;
2727

28+
import java.util.ArrayList;
2829
import java.util.BitSet;
30+
import java.util.List;
2931
import java.util.function.Consumer;
3032
import java.util.function.LongConsumer;
3133

@@ -265,4 +267,15 @@ public String toString() {
265267
}
266268
return sb.toString();
267269
}
270+
271+
public List<T> values() {
272+
List<T> list = new ArrayList<>(count);
273+
for (int i = 0; i < keys.length; i++) {
274+
T o = objects[i];
275+
if (o != null) {
276+
list.add(o);
277+
}
278+
}
279+
return list;
280+
}
268281
}

src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,10 @@
2626
package jdk.jfr.internal.consumer;
2727

2828
import java.io.IOException;
29+
import java.nio.file.Path;
2930
import java.util.Comparator;
3031
import java.util.List;
32+
import java.util.function.Predicate;
3133

3234
import jdk.jfr.Configuration;
3335
import jdk.jfr.EventType;
@@ -43,6 +45,7 @@
4345
import jdk.jfr.consumer.RecordedThreadGroup;
4446
import jdk.jfr.consumer.RecordingFile;
4547
import jdk.jfr.internal.Type;
48+
import jdk.jfr.internal.consumer.filter.ChunkWriter.RemovedEvents;;
4649

4750
/*
4851
* Purpose of this class is to give package private access to
@@ -105,4 +108,5 @@ public static JdkJfrConsumer instance() {
105108

106109
public abstract MetadataEvent newMetadataEvent(List<EventType> previous, List<EventType> current, List<Configuration> configuration);
107110

111+
public abstract List<RemovedEvents> write(RecordingFile file, Path output, Predicate<RecordedEvent> filter) throws IOException;
108112
}

src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/filter/ChunkWriter.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Deque;
3333
import java.util.Map;
3434
import java.util.HashMap;
35+
import java.util.List;
3536
import java.util.function.Predicate;
3637

3738
import jdk.jfr.consumer.RecordedEvent;
@@ -51,23 +52,48 @@
5152
* All positional values are relative to file start, not the chunk.
5253
*/
5354
public final class ChunkWriter implements Closeable {
55+
public static class RemovedEvents implements Comparable<RemovedEvents> {
56+
public final String name;
57+
private long count;
58+
private long removed;
59+
60+
private RemovedEvents(String name) {
61+
this.name = name;
62+
}
63+
64+
public String getName() {
65+
return name;
66+
}
67+
68+
public String share() {
69+
return removed + "/" + count;
70+
}
71+
72+
@Override
73+
public int compareTo(RemovedEvents that) {
74+
return this.getName().compareTo(that.getName());
75+
}
76+
}
5477
private LongMap<Constants> pools = new LongMap<>();
5578
private final Deque<CheckpointEvent> checkpoints = new ArrayDeque<>();
5679
private final Path destination;
5780
private final RecordingInput input;
5881
private final RecordingOutput output;
5982
private final Predicate<RecordedEvent> filter;
6083
private final Map<String, Long> waste = new HashMap<>();
84+
private final LongMap<RemovedEvents> removedEvents = new LongMap<>();
85+
private final boolean collectResults;
6186

6287
private long chunkStartPosition;
6388
private boolean chunkComplete;
6489
private long lastCheckpoint;
6590

66-
public ChunkWriter(Path source, Path destination, Predicate<RecordedEvent> filter) throws IOException {
91+
public ChunkWriter(Path source, Path destination, Predicate<RecordedEvent> filter, boolean collectResults) throws IOException {
6792
this.destination = destination;
6893
this.output = new RecordingOutput(destination.toFile());
6994
this.input = new RecordingInput(source.toFile());
7095
this.filter = filter;
96+
this.collectResults = collectResults;
7197
}
7298

7399
Constants getPool(Type type) {
@@ -87,7 +113,25 @@ public CheckpointEvent newCheckpointEvent(long startPosition) {
87113
}
88114

89115
public boolean accept(RecordedEvent event) {
90-
return filter.test(event);
116+
if (!collectResults) {
117+
return filter.test(event);
118+
}
119+
long id = event.getEventType().getId();
120+
RemovedEvents r = removedEvents.get(id);
121+
if (r == null) {
122+
r = new RemovedEvents(event.getEventType().getName());
123+
removedEvents.put(id, r);
124+
}
125+
r.count++;
126+
if (filter.test(event)) {
127+
return true;
128+
}
129+
r.removed++;
130+
return false;
131+
}
132+
133+
public List<RemovedEvents> getRemovedEventTypes() {
134+
return removedEvents.values().stream().filter(r -> r.removed > 0).sorted().toList();
91135
}
92136

93137
public void touch(Object object) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ protected final void println(String text) {
318318
System.out.println(text);
319319
}
320320

321+
protected final void printf(String text, Object ... args) {
322+
System.out.printf(text, args);
323+
}
324+
321325
public static void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException {
322326
if (typo.equals(options.peek())) {
323327
throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?");

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -37,6 +37,8 @@
3737
import jdk.jfr.EventType;
3838
import jdk.jfr.consumer.RecordedEvent;
3939
import jdk.jfr.consumer.RecordingFile;
40+
import jdk.jfr.internal.consumer.JdkJfrConsumer;
41+
import jdk.jfr.internal.consumer.filter.ChunkWriter.RemovedEvents;
4042
import jdk.jfr.internal.util.UserDataException;
4143
import jdk.jfr.internal.util.UserSyntaxException;
4244

@@ -141,12 +143,26 @@ public void execute(Deque<String> options) throws UserSyntaxException, UserDataE
141143
try (RecordingFile rf = new RecordingFile(input)) {
142144
List<EventType> types = rf.readEventTypes();
143145
Predicate<RecordedEvent> filter = createFilter(options, types);
144-
rf.write(output, filter);
146+
List<RemovedEvents> result = JdkJfrConsumer.instance().write(rf, output, filter);
147+
println("Scrubbed recording file written to:");
148+
println(output.toRealPath().toString());
149+
if (result.isEmpty()) {
150+
println("No events removed.");
151+
return;
152+
}
153+
int maxName = 0;
154+
int maxShare = 0;
155+
for (RemovedEvents re : result) {
156+
maxName = Math.max(maxName, re.getName().length());
157+
maxShare = Math.max(maxShare, re.share().length());
158+
}
159+
println("Removed events:");
160+
for (RemovedEvents re : result) {
161+
printf("%-" + maxName + "s %" + maxShare + "s\n", re.getName(), re.share());
162+
}
145163
} catch (IOException ioe) {
146164
couldNotReadError(input, ioe);
147165
}
148-
println("Scrubbed recording file written to:");
149-
println(output.toAbsolutePath().toString());
150166
}
151167

152168
private Predicate<RecordedEvent> createFilter(Deque<String> options, List<EventType> types) throws UserSyntaxException, UserDataException {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ private void printInformation(Path p) throws IOException {
152152
println(typeHeader + " ".repeat(minWidth - typeHeader.length()) + header);
153153
println("=".repeat(minWidth + header.length()));
154154
for (Statistics s : statsList) {
155-
System.out.printf(" %-" + minWidth + "s%10d %12d\n", s.name, s.count, s.size);
155+
printf(" %-" + minWidth + "s%10d %12d\n", s.name, s.count, s.size);
156156
}
157157
}
158158
}

test/jdk/jdk/jfr/tool/TestScrub.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -69,9 +69,16 @@ public static void main(String[] args) throws Throwable {
6969

7070
try (Recording r = new Recording()) {
7171
r.start();
72-
emit(100, "India", TigerEvent.class);
73-
emit(100, "Namibia", ZebraEvent.class);
74-
emit(10000, "Lake Tanganyika", TigerfishEvent.class);
72+
emit(50, "India", TigerEvent.class);
73+
emit(50, "Namibia", ZebraEvent.class);
74+
emit(5000, "Lake Tanganyika", TigerfishEvent.class);
75+
// Force rotation
76+
try (Recording s = new Recording()) {
77+
s.start();
78+
}
79+
emit(50, "India", TigerEvent.class);
80+
emit(50, "Namibia", ZebraEvent.class);
81+
emit(5000, "Lake Tanganyika", TigerfishEvent.class);
7582
r.stop();
7683
r.dump(file);
7784
}
@@ -89,6 +96,7 @@ public static void main(String[] args) throws Throwable {
8996
testThreadInclude(file);
9097

9198
testMissingEventType(file);
99+
testSummary(file);
92100
}
93101

94102
private static void testInputOutput(Path file) throws Throwable {
@@ -275,6 +283,23 @@ private static void testMissingEventType(Path input) throws Throwable {
275283
Files.delete(output);
276284
}
277285

286+
287+
private static void testSummary(Path file) throws Throwable {
288+
String inputFile = file.toAbsolutePath().toString();
289+
290+
String removedZebras = Path.of("removed-zebras.jfr").toAbsolutePath().toString();
291+
var outp = ExecuteHelper.jfr("scrub", "--exclude-events", "Zebra", inputFile, removedZebras);
292+
outp.shouldContain("Removed events:");
293+
outp.shouldContain("example.Zebra 100/100");
294+
outp.shouldNotContain("Tiger");
295+
outp.shouldNotContain("No events removed");
296+
297+
String noneRemoved = Path.of("none-removed.jfr").toAbsolutePath().toString();
298+
outp = ExecuteHelper.jfr("scrub", "--exclude-events", "jdk.JVMInformation", inputFile, noneRemoved);
299+
outp.shouldContain("No events removed");
300+
outp.shouldNotContain("jdk.JVMInformation");
301+
}
302+
278303
private static void assertNotThread(RecordedEvent event, String... threadNames) {
279304
String s = event.getThread().getJavaName();
280305
for (String threadName : threadNames) {

0 commit comments

Comments
 (0)