Skip to content
Open
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
240 changes: 240 additions & 0 deletions bin/parser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3


import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordingFile;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

/**
* A helper class to parse Java Flight Recorder (JFR) files
* and extract insights from custom Infinispan ProtoStream events.
*
* It specifically looks for 'org.infinispan.protostream.ResizeEvent'
* and 'org.infinispan.protostream.AllocateEvent' to generate a
* summary about buffer allocation and resizing patterns.
*/
@Command(name = "parser", mixinStandardHelpOptions = true, version = "parser 0.1",
description = "parser made with jbang")
class parser implements Callable<Integer> {

@Parameters(index = "0", description = "Path to JFR file")
private File file;

public static void main(String... args) {
int exitCode = new CommandLine(new parser()).execute(args);
System.exit(exitCode);
}

@Override
public Integer call() throws Exception {
Path p = file.toPath();
try (PrintWriter writer = new PrintWriter(System.out)) {
parseAndSummarize(p, writer);
}
return 0;
}

private static final String RESIZE_EVENT_NAME = "org.infinispan.protostream.ResizeEvent";
private static final String ALLOCATE_EVENT_NAME = "org.infinispan.protostream.AllocateEvent";

/**
* Parses a JFR file to analyze ProtoStream buffer events and prints a summary.
*
* @param jfrFilePath The path to the JFR file.
* @param writer The PrintWriter to which the summary report will be written.
* @throws IOException If an error occurs while reading the JFR file.
*/
public void parseAndSummarize(Path jfrFilePath, PrintWriter writer) throws IOException {
if (jfrFilePath == null || writer == null) {
throw new IllegalArgumentException("JFR file path and PrintWriter cannot be null.");
}

// --- Data Collectors ---
// Metrics for Resize Events
long resizeEventCount = 0;
long totalBytesResized = 0;
int maxResizeFrom = 0;
int maxResizeTo = 0;
Map<String, HotSpot> resizeHotspots = new HashMap<>();

// Metrics for Allocate Events
long allocateEventCount = 0;
long totalBytesAllocated = 0;
int maxAllocationSize = 0;
Map<String, HotSpot> allocationHotspots = new HashMap<>();

try (RecordingFile recordingFile = new RecordingFile(jfrFilePath)) {
while (recordingFile.hasMoreEvents()) {
RecordedEvent event = recordingFile.readEvent();

switch (event.getEventType().getName()) {
case RESIZE_EVENT_NAME:
resizeEventCount++;
int fromSize = event.getValue("before");
int toSize = event.getValue("after");
totalBytesResized += (long) toSize - fromSize;

if (toSize > maxResizeTo) {
maxResizeTo = toSize;
maxResizeFrom = fromSize;
}

// Use the first frame of the stack trace as the hotspot identifier
Optional.ofNullable(event.getStackTrace()).ifPresent(stackTrace -> {
if (!stackTrace.getFrames().isEmpty()) {
String topFrame = stackTrace.getFrames().get(0).toString();
resizeHotspots.computeIfAbsent(topFrame, k -> new HotSpot(stackTrace)).inc();
}
});
break;

case ALLOCATE_EVENT_NAME:
allocateEventCount++;
int newSize = event.getValue("size");
totalBytesAllocated += newSize;

if (newSize > maxAllocationSize) {
maxAllocationSize = newSize;
}

Optional.ofNullable(event.getStackTrace()).ifPresent(stackTrace -> {
if (!stackTrace.getFrames().isEmpty()) {
String topFrame = stackTrace.getFrames().get(0).toString();
allocationHotspots.computeIfAbsent(topFrame, k -> new HotSpot(stackTrace)).inc();
}
});
break;
}
}
}

// --- Generate Summary Report ---
generateReport(
writer,
resizeEventCount, totalBytesResized, maxResizeFrom, maxResizeTo, resizeHotspots,
allocateEventCount, totalBytesAllocated, maxAllocationSize, allocationHotspots
);
}

private void generateReport(PrintWriter writer,
long resizeEventCount, long totalBytesResized, int maxResizeFrom, int maxResizeTo, Map<String, HotSpot> resizeHotspots,
long allocateEventCount, long totalBytesAllocated, int maxAllocationSize, Map<String, HotSpot> allocationHotspots) {

writer.println("=========================================================");
writer.println(" Infinispan ProtoStream Buffer Events JFR Summary ");
writer.println("=========================================================");
writer.println();

// --- Resize Events Section ---
writer.println("--- Buffer Resize Events (" + RESIZE_EVENT_NAME + ") ---");
if (resizeEventCount > 0) {
writer.printf("Total Resize Events: %,d%n", resizeEventCount);
writer.printf("Total Bytes Added by Resizing: %,d bytes%n", totalBytesResized);
writer.printf("Average Resize Increase: %,.2f bytes%n", (double) totalBytesResized / resizeEventCount);
writer.printf("Largest Single Resize: from %,d to %,d bytes (an increase of %,d bytes)%n", maxResizeFrom, maxResizeTo, maxResizeTo - maxResizeFrom);
writer.println();
writer.println("Top 5 Most Common Resize Locations (Stack Trace):");
printTopHotspots(writer, resizeHotspots, 5);
} else {
writer.println("No resize events found in this recording.");
}
writer.println();

// --- Allocate Events Section ---
writer.println("--- Buffer Allocate Events (" + ALLOCATE_EVENT_NAME + ") ---");
if (allocateEventCount > 0) {
writer.printf("Total Allocation Events: %,d%n", allocateEventCount);
writer.printf("Total Bytes Allocated: %,d bytes%n", totalBytesAllocated);
writer.printf("Average Allocation Size: %,.2f bytes%n", (double) totalBytesAllocated / allocateEventCount);
writer.printf("Largest Single Allocation: %,d bytes%n", maxAllocationSize);
writer.println();
writer.println("Top 5 Most Common Allocation Locations (Stack Trace):");
printTopHotspots(writer, allocationHotspots, 5);
} else {
writer.println("No allocation events found in this recording.");
}
writer.println();
writer.println("=========================================================");
writer.flush();
}

/**
* Helper to sort and print the most frequent call sites.
*/
private void printTopHotspots(PrintWriter writer, Map<String, HotSpot> hotspots, int limit) {
if (hotspots.isEmpty()) {
writer.println(" (No stack trace information available)");
return;
}

// Sort the map by value (count) in descending order
LinkedHashMap<String, HotSpot> sortedHotspots = hotspots.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue((a, b) -> Integer.compare(b.times(), a.times())))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));

int count = 0;
for (Map.Entry<String, HotSpot> entry : sortedHotspots.entrySet()) {
if (count++ >= limit) {
break;
}
HotSpot hs = entry.getValue();
StringBuilder sb = new StringBuilder();
boolean first = true;
for (RecordedFrame frame : hs.stackTrace.getFrames()) {
if (first) {
sb.append(frameToString(frame));
first = false;
} else {
sb.append('\t').append("at ").append(frameToString(frame));
}
sb.append(System.lineSeparator());
}
writer.printf(" - [%,d times]:%n%s%n", hs.times(), sb);
}
}

private String frameToString(RecordedFrame frame) {
RecordedMethod method = frame.getMethod();
return String.format("%s.%s:%d [%s]", method.getType().getName(), method.getName(), frame.getLineNumber(), frame.getType());
}

private static class HotSpot {
private final RecordedStackTrace stackTrace;
private int times;

private HotSpot(RecordedStackTrace stackTrace) {
this.stackTrace = stackTrace;
}

public void inc() {
times++;
}

public int times() {
return times;
}
}
}
1 change: 1 addition & 0 deletions core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
requires static jcip.annotations;
requires org.jboss.logging;
requires static org.jboss.logging.annotations;
requires jdk.jfr;
opens org.infinispan.protostream;
exports org.infinispan.protostream;
exports org.infinispan.protostream.annotations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.infinispan.protostream.descriptors.MapDescriptor;
import org.infinispan.protostream.descriptors.Type;
import org.infinispan.protostream.descriptors.WireType;
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
import org.jboss.logging.Logger;

/**
Expand Down Expand Up @@ -187,6 +188,7 @@ public Integer readInt(String fieldName) throws IOException {
public int[] readInts(String fieldName) throws IOException {
List<Integer> values = readCollection(fieldName, new ArrayList<>(), Integer.class);
int[] result = new int[values.size()];
JfrEventPublisher.intBufferAllocateEvent(result.length);
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
Expand All @@ -202,6 +204,7 @@ public Long readLong(String fieldName) throws IOException {
public long[] readLongs(String fieldName) throws IOException {
List<Long> values = readCollection(fieldName, new ArrayList<>(), Long.class);
long[] result = new long[values.size()];
JfrEventPublisher.longBufferAllocateEvent(result.length);
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
Expand Down Expand Up @@ -229,6 +232,7 @@ public Float readFloat(String fieldName) throws IOException {
public float[] readFloats(String fieldName) throws IOException {
List<Float> values = readCollection(fieldName, new ArrayList<>(), Float.class);
float[] result = new float[values.size()];
JfrEventPublisher.floatBufferAllocateEvent(result.length);
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
Expand All @@ -244,6 +248,7 @@ public Double readDouble(String fieldName) throws IOException {
public double[] readDoubles(String fieldName) throws IOException {
List<Double> values = readCollection(fieldName, new ArrayList<>(), Double.class);
double[] result = new double[values.size()];
JfrEventPublisher.doubleBufferAllocateEvent(result.length);
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
Expand All @@ -259,6 +264,7 @@ public Boolean readBoolean(String fieldName) throws IOException {
public boolean[] readBooleans(String fieldName) throws IOException {
List<Boolean> values = readCollection(fieldName, new ArrayList<>(), Boolean.class);
boolean[] result = new boolean[values.size()];
JfrEventPublisher.bufferAllocateEvent(result.length);
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.infinispan.protostream.descriptors.MapDescriptor;
import org.infinispan.protostream.descriptors.Type;
import org.infinispan.protostream.descriptors.WireType;
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
import org.jboss.logging.Logger;

/**
Expand Down Expand Up @@ -452,10 +453,12 @@ public void writeBytes(String fieldName, InputStream input) throws IOException {
int len = 0;
List<byte[]> chunks = new LinkedList<>();
int bufLen;
JfrEventPublisher.bufferAllocateEvent(CHUNK_SIZE);
byte[] buffer = new byte[CHUNK_SIZE];
while ((bufLen = input.read(buffer)) != -1) {
chunks.add(buffer);
len += bufLen;
JfrEventPublisher.bufferAllocateEvent(CHUNK_SIZE);
buffer = new byte[CHUNK_SIZE];
}
input.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.infinispan.protostream.RandomAccessOutputStream;

import net.jcip.annotations.NotThreadSafe;
import org.infinispan.protostream.impl.jfr.JfrEventPublisher;

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

@Override
Expand Down Expand Up @@ -73,11 +75,16 @@ public void move(int startPos, int length, int newPos) {
@Override
public void ensureCapacity(int capacity) {
if (buf == null) {
buf = new byte[Math.max(MIN_SIZE, capacity)];
int cap = Math.max(MIN_SIZE, capacity);
buf = new byte[cap];
JfrEventPublisher.bufferAllocateEvent(cap);
} else if (capacity > buf.length) {
byte[] newbuf = new byte[getNewBufferSize(buf.length, capacity)];
int before = buf.length;
int cap = getNewBufferSize(buf.length, capacity);
byte[] newbuf = new byte[cap];
System.arraycopy(buf, 0, newbuf, 0, pos);
buf = newbuf;
JfrEventPublisher.bufferResizeEvent(before, cap);
}
}

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

@Override
public byte[] toByteArray() {
JfrEventPublisher.bufferAllocateEvent(pos);
return Arrays.copyOf(buf, pos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.Function;
import java.util.function.Predicate;

import org.infinispan.protostream.impl.jfr.JfrEventPublisher;
import sun.misc.Unsafe;

class StringUtil {
Expand Down Expand Up @@ -52,7 +53,9 @@ public boolean providesLatin1Bytes() {

@Override
public byte[] getBytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
byte[] b = s.getBytes(StandardCharsets.UTF_8);
JfrEventPublisher.bufferAllocateEvent(b.length);
return b;
}
};
}
Expand Down
Loading