Skip to content

Commit 66201f9

Browse files
committed
[#531] JFR events for allocation and resizing
* Requires JFR package. * Covering read and write path.
1 parent b712306 commit 66201f9

File tree

15 files changed

+537
-3
lines changed

15 files changed

+537
-3
lines changed

bin/parser.java

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
//DEPS info.picocli:picocli:4.6.3
3+
4+
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.io.PrintWriter;
8+
import java.nio.file.Path;
9+
import java.util.HashMap;
10+
import java.util.LinkedHashMap;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.concurrent.Callable;
14+
import java.util.stream.Collectors;
15+
16+
import jdk.jfr.consumer.RecordedEvent;
17+
import jdk.jfr.consumer.RecordedFrame;
18+
import jdk.jfr.consumer.RecordedMethod;
19+
import jdk.jfr.consumer.RecordedStackTrace;
20+
import jdk.jfr.consumer.RecordingFile;
21+
import picocli.CommandLine;
22+
import picocli.CommandLine.Command;
23+
import picocli.CommandLine.Parameters;
24+
25+
/**
26+
* A helper class to parse Java Flight Recorder (JFR) files
27+
* and extract insights from custom Infinispan ProtoStream events.
28+
*
29+
* It specifically looks for 'org.infinispan.protostream.ResizeEvent'
30+
* and 'org.infinispan.protostream.AllocateEvent' to generate a
31+
* summary about buffer allocation and resizing patterns.
32+
*/
33+
@Command(name = "parser", mixinStandardHelpOptions = true, version = "parser 0.1",
34+
description = "parser made with jbang")
35+
class parser implements Callable<Integer> {
36+
37+
@Parameters(index = "0", description = "Path to JFR file")
38+
private File file;
39+
40+
public static void main(String... args) {
41+
int exitCode = new CommandLine(new parser()).execute(args);
42+
System.exit(exitCode);
43+
}
44+
45+
@Override
46+
public Integer call() throws Exception {
47+
Path p = file.toPath();
48+
try (PrintWriter writer = new PrintWriter(System.out)) {
49+
parseAndSummarize(p, writer);
50+
}
51+
return 0;
52+
}
53+
54+
private static final String RESIZE_EVENT_NAME = "org.infinispan.protostream.ResizeEvent";
55+
private static final String ALLOCATE_EVENT_NAME = "org.infinispan.protostream.AllocateEvent";
56+
57+
/**
58+
* Parses a JFR file to analyze ProtoStream buffer events and prints a summary.
59+
*
60+
* @param jfrFilePath The path to the JFR file.
61+
* @param writer The PrintWriter to which the summary report will be written.
62+
* @throws IOException If an error occurs while reading the JFR file.
63+
*/
64+
public void parseAndSummarize(Path jfrFilePath, PrintWriter writer) throws IOException {
65+
if (jfrFilePath == null || writer == null) {
66+
throw new IllegalArgumentException("JFR file path and PrintWriter cannot be null.");
67+
}
68+
69+
// --- Data Collectors ---
70+
// Metrics for Resize Events
71+
long resizeEventCount = 0;
72+
long totalBytesResized = 0;
73+
int maxResizeFrom = 0;
74+
int maxResizeTo = 0;
75+
Map<String, HotSpot> resizeHotspots = new HashMap<>();
76+
77+
// Metrics for Allocate Events
78+
long allocateEventCount = 0;
79+
long totalBytesAllocated = 0;
80+
int maxAllocationSize = 0;
81+
Map<String, HotSpot> allocationHotspots = new HashMap<>();
82+
83+
try (RecordingFile recordingFile = new RecordingFile(jfrFilePath)) {
84+
while (recordingFile.hasMoreEvents()) {
85+
RecordedEvent event = recordingFile.readEvent();
86+
87+
switch (event.getEventType().getName()) {
88+
case RESIZE_EVENT_NAME:
89+
resizeEventCount++;
90+
int fromSize = event.getValue("before");
91+
int toSize = event.getValue("after");
92+
totalBytesResized += (long) toSize - fromSize;
93+
94+
if (toSize > maxResizeTo) {
95+
maxResizeTo = toSize;
96+
maxResizeFrom = fromSize;
97+
}
98+
99+
// Use the first frame of the stack trace as the hotspot identifier
100+
Optional.ofNullable(event.getStackTrace()).ifPresent(stackTrace -> {
101+
if (!stackTrace.getFrames().isEmpty()) {
102+
String topFrame = stackTrace.getFrames().get(0).toString();
103+
resizeHotspots.computeIfAbsent(topFrame, k -> new HotSpot(stackTrace)).inc();
104+
}
105+
});
106+
break;
107+
108+
case ALLOCATE_EVENT_NAME:
109+
allocateEventCount++;
110+
int newSize = event.getValue("size");
111+
totalBytesAllocated += newSize;
112+
113+
if (newSize > maxAllocationSize) {
114+
maxAllocationSize = newSize;
115+
}
116+
117+
Optional.ofNullable(event.getStackTrace()).ifPresent(stackTrace -> {
118+
if (!stackTrace.getFrames().isEmpty()) {
119+
String topFrame = stackTrace.getFrames().get(0).toString();
120+
allocationHotspots.computeIfAbsent(topFrame, k -> new HotSpot(stackTrace)).inc();
121+
}
122+
});
123+
break;
124+
}
125+
}
126+
}
127+
128+
// --- Generate Summary Report ---
129+
generateReport(
130+
writer,
131+
resizeEventCount, totalBytesResized, maxResizeFrom, maxResizeTo, resizeHotspots,
132+
allocateEventCount, totalBytesAllocated, maxAllocationSize, allocationHotspots
133+
);
134+
}
135+
136+
private void generateReport(PrintWriter writer,
137+
long resizeEventCount, long totalBytesResized, int maxResizeFrom, int maxResizeTo, Map<String, HotSpot> resizeHotspots,
138+
long allocateEventCount, long totalBytesAllocated, int maxAllocationSize, Map<String, HotSpot> allocationHotspots) {
139+
140+
writer.println("=========================================================");
141+
writer.println(" Infinispan ProtoStream Buffer Events JFR Summary ");
142+
writer.println("=========================================================");
143+
writer.println();
144+
145+
// --- Resize Events Section ---
146+
writer.println("--- Buffer Resize Events (" + RESIZE_EVENT_NAME + ") ---");
147+
if (resizeEventCount > 0) {
148+
writer.printf("Total Resize Events: %,d%n", resizeEventCount);
149+
writer.printf("Total Bytes Added by Resizing: %,d bytes%n", totalBytesResized);
150+
writer.printf("Average Resize Increase: %,.2f bytes%n", (double) totalBytesResized / resizeEventCount);
151+
writer.printf("Largest Single Resize: from %,d to %,d bytes (an increase of %,d bytes)%n", maxResizeFrom, maxResizeTo, maxResizeTo - maxResizeFrom);
152+
writer.println();
153+
writer.println("Top 5 Most Common Resize Locations (Stack Trace):");
154+
printTopHotspots(writer, resizeHotspots, 5);
155+
} else {
156+
writer.println("No resize events found in this recording.");
157+
}
158+
writer.println();
159+
160+
// --- Allocate Events Section ---
161+
writer.println("--- Buffer Allocate Events (" + ALLOCATE_EVENT_NAME + ") ---");
162+
if (allocateEventCount > 0) {
163+
writer.printf("Total Allocation Events: %,d%n", allocateEventCount);
164+
writer.printf("Total Bytes Allocated: %,d bytes%n", totalBytesAllocated);
165+
writer.printf("Average Allocation Size: %,.2f bytes%n", (double) totalBytesAllocated / allocateEventCount);
166+
writer.printf("Largest Single Allocation: %,d bytes%n", maxAllocationSize);
167+
writer.println();
168+
writer.println("Top 5 Most Common Allocation Locations (Stack Trace):");
169+
printTopHotspots(writer, allocationHotspots, 5);
170+
} else {
171+
writer.println("No allocation events found in this recording.");
172+
}
173+
writer.println();
174+
writer.println("=========================================================");
175+
writer.flush();
176+
}
177+
178+
/**
179+
* Helper to sort and print the most frequent call sites.
180+
*/
181+
private void printTopHotspots(PrintWriter writer, Map<String, HotSpot> hotspots, int limit) {
182+
if (hotspots.isEmpty()) {
183+
writer.println(" (No stack trace information available)");
184+
return;
185+
}
186+
187+
// Sort the map by value (count) in descending order
188+
LinkedHashMap<String, HotSpot> sortedHotspots = hotspots.entrySet()
189+
.stream()
190+
.sorted(Map.Entry.comparingByValue((a, b) -> Integer.compare(b.times(), a.times())))
191+
.collect(Collectors.toMap(
192+
Map.Entry::getKey,
193+
Map.Entry::getValue,
194+
(e1, e2) -> e1,
195+
LinkedHashMap::new
196+
));
197+
198+
int count = 0;
199+
for (Map.Entry<String, HotSpot> entry : sortedHotspots.entrySet()) {
200+
if (count++ >= limit) {
201+
break;
202+
}
203+
HotSpot hs = entry.getValue();
204+
StringBuilder sb = new StringBuilder();
205+
boolean first = true;
206+
for (RecordedFrame frame : hs.stackTrace.getFrames()) {
207+
if (first) {
208+
sb.append(frameToString(frame));
209+
first = false;
210+
} else {
211+
sb.append('\t').append("at ").append(frameToString(frame));
212+
}
213+
sb.append(System.lineSeparator());
214+
}
215+
writer.printf(" - [%,d times]:%n%s%n", hs.times(), sb);
216+
}
217+
}
218+
219+
private String frameToString(RecordedFrame frame) {
220+
RecordedMethod method = frame.getMethod();
221+
return String.format("%s.%s:%d [%s]", method.getType().getName(), method.getName(), frame.getLineNumber(), frame.getType());
222+
}
223+
224+
private static class HotSpot {
225+
private final RecordedStackTrace stackTrace;
226+
private int times;
227+
228+
private HotSpot(RecordedStackTrace stackTrace) {
229+
this.stackTrace = stackTrace;
230+
}
231+
232+
public void inc() {
233+
times++;
234+
}
235+
236+
public int times() {
237+
return times;
238+
}
239+
}
240+
}

core/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
requires static jcip.annotations;
77
requires org.jboss.logging;
88
requires static org.jboss.logging.annotations;
9+
requires jdk.jfr;
910
opens org.infinispan.protostream;
1011
exports org.infinispan.protostream;
1112
exports org.infinispan.protostream.annotations;

core/src/main/java/org/infinispan/protostream/impl/ProtoStreamReaderImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.infinispan.protostream.descriptors.MapDescriptor;
2828
import org.infinispan.protostream.descriptors.Type;
2929
import org.infinispan.protostream.descriptors.WireType;
30+
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
3031
import org.jboss.logging.Logger;
3132

3233
/**
@@ -187,6 +188,7 @@ public Integer readInt(String fieldName) throws IOException {
187188
public int[] readInts(String fieldName) throws IOException {
188189
List<Integer> values = readCollection(fieldName, new ArrayList<>(), Integer.class);
189190
int[] result = new int[values.size()];
191+
JfrEventPublisher.intBufferAllocateEvent(result.length);
190192
for (int i = 0; i < values.size(); i++) {
191193
result[i] = values.get(i);
192194
}
@@ -202,6 +204,7 @@ public Long readLong(String fieldName) throws IOException {
202204
public long[] readLongs(String fieldName) throws IOException {
203205
List<Long> values = readCollection(fieldName, new ArrayList<>(), Long.class);
204206
long[] result = new long[values.size()];
207+
JfrEventPublisher.longBufferAllocateEvent(result.length);
205208
for (int i = 0; i < values.size(); i++) {
206209
result[i] = values.get(i);
207210
}
@@ -229,6 +232,7 @@ public Float readFloat(String fieldName) throws IOException {
229232
public float[] readFloats(String fieldName) throws IOException {
230233
List<Float> values = readCollection(fieldName, new ArrayList<>(), Float.class);
231234
float[] result = new float[values.size()];
235+
JfrEventPublisher.floatBufferAllocateEvent(result.length);
232236
for (int i = 0; i < values.size(); i++) {
233237
result[i] = values.get(i);
234238
}
@@ -244,6 +248,7 @@ public Double readDouble(String fieldName) throws IOException {
244248
public double[] readDoubles(String fieldName) throws IOException {
245249
List<Double> values = readCollection(fieldName, new ArrayList<>(), Double.class);
246250
double[] result = new double[values.size()];
251+
JfrEventPublisher.doubleBufferAllocateEvent(result.length);
247252
for (int i = 0; i < values.size(); i++) {
248253
result[i] = values.get(i);
249254
}
@@ -259,6 +264,7 @@ public Boolean readBoolean(String fieldName) throws IOException {
259264
public boolean[] readBooleans(String fieldName) throws IOException {
260265
List<Boolean> values = readCollection(fieldName, new ArrayList<>(), Boolean.class);
261266
boolean[] result = new boolean[values.size()];
267+
JfrEventPublisher.bufferAllocateEvent(result.length);
262268
for (int i = 0; i < values.size(); i++) {
263269
result[i] = values.get(i);
264270
}

core/src/main/java/org/infinispan/protostream/impl/ProtoStreamWriterImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.infinispan.protostream.descriptors.MapDescriptor;
2121
import org.infinispan.protostream.descriptors.Type;
2222
import org.infinispan.protostream.descriptors.WireType;
23+
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
2324
import org.jboss.logging.Logger;
2425

2526
/**
@@ -452,10 +453,12 @@ public void writeBytes(String fieldName, InputStream input) throws IOException {
452453
int len = 0;
453454
List<byte[]> chunks = new LinkedList<>();
454455
int bufLen;
456+
JfrEventPublisher.bufferAllocateEvent(CHUNK_SIZE);
455457
byte[] buffer = new byte[CHUNK_SIZE];
456458
while ((bufLen = input.read(buffer)) != -1) {
457459
chunks.add(buffer);
458460
len += bufLen;
461+
JfrEventPublisher.bufferAllocateEvent(CHUNK_SIZE);
459462
buffer = new byte[CHUNK_SIZE];
460463
}
461464
input.close();

core/src/main/java/org/infinispan/protostream/impl/RandomAccessOutputStreamImpl.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.infinispan.protostream.RandomAccessOutputStream;
1010

1111
import net.jcip.annotations.NotThreadSafe;
12+
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
1213

1314
@NotThreadSafe
1415
public class RandomAccessOutputStreamImpl extends OutputStream implements RandomAccessOutputStream {
@@ -27,6 +28,7 @@ public RandomAccessOutputStreamImpl(int capacity) {
2728
if (capacity < 0)
2829
throw new IllegalArgumentException("Negative initial capacity: " + capacity);
2930
this.buf = new byte[capacity];
31+
JfrEventPublisher.bufferAllocateEvent(capacity);
3032
}
3133

3234
@Override
@@ -73,11 +75,16 @@ public void move(int startPos, int length, int newPos) {
7375
@Override
7476
public void ensureCapacity(int capacity) {
7577
if (buf == null) {
76-
buf = new byte[Math.max(MIN_SIZE, capacity)];
78+
int cap = Math.max(MIN_SIZE, capacity);
79+
buf = new byte[cap];
80+
JfrEventPublisher.bufferAllocateEvent(cap);
7781
} else if (capacity > buf.length) {
78-
byte[] newbuf = new byte[getNewBufferSize(buf.length, capacity)];
82+
int before = buf.length;
83+
int cap = getNewBufferSize(buf.length, capacity);
84+
byte[] newbuf = new byte[cap];
7985
System.arraycopy(buf, 0, newbuf, 0, pos);
8086
buf = newbuf;
87+
JfrEventPublisher.bufferResizeEvent(before, cap);
8188
}
8289
}
8390

@@ -100,6 +107,7 @@ public void setPosition(int position) {
100107

101108
@Override
102109
public byte[] toByteArray() {
110+
JfrEventPublisher.bufferAllocateEvent(pos);
103111
return Arrays.copyOf(buf, pos);
104112
}
105113

core/src/main/java/org/infinispan/protostream/impl/StringUtil.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.function.Function;
1414
import java.util.function.Predicate;
1515

16+
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
1617
import sun.misc.Unsafe;
1718

1819
class StringUtil {
@@ -52,7 +53,9 @@ public boolean providesLatin1Bytes() {
5253

5354
@Override
5455
public byte[] getBytes(String s) {
55-
return s.getBytes(StandardCharsets.UTF_8);
56+
byte[] b = s.getBytes(StandardCharsets.UTF_8);
57+
JfrEventPublisher.bufferAllocateEvent(b.length);
58+
return b;
5659
}
5760
};
5861
}

0 commit comments

Comments
 (0)