Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate JFR stacktrace symbols #109

Merged
merged 9 commits into from
Jul 1, 2024
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Dropping a requirement of a major version of a dependency is a new contract.
## [Unreleased]
[Unreleased]: https://github.com/atlassian/report/compare/release-4.4.0...master

### Added
- Add `MutableJvmSymbol`, `JfrFilter.Builder.symbolModifier` and `MultiJfrFilter.Builder.symbolModifier`.
They allow for mutating JVM symbols in JFR files, e.g. to normalize dynamic proxy names or lambda names.
This way, the same JVM code can be profiled multiple times and resulting JFRs can be merged or diffed.
- Add `DynamicProxyNormalization`.

## [4.4.0] - 2024-01-18
[4.4.0]: https://github.com/atlassian/report/compare/release-4.3.0...release-4.4.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@
package org.openjdk.jmc.flightrecorder.testutils.parser;

import jdk.jfr.consumer.RecordedEvent;

import java.nio.file.Path;
import tools.profiler.jfr.converter.CheckpointEvent;

/**
* A callback to be provided to {@linkplain StreamingChunkParser#parse(Path, ChunkParserListener)}
* A callback to be provided to {@linkplain StreamingChunkParser)}
*/
public interface ChunkParserListener {
/** Called when the recording starts to be processed */
Expand Down Expand Up @@ -74,7 +73,7 @@ default void onMetadata(EventHeader eventHeader, byte[] metadataPayload, Metadat
default void onEvent(RecordedEvent event, EventHeader eventHeader, byte[] eventPayload) {
}

default void onCheckpoint(EventHeader eventHeader, byte[] eventPayload) {
default void onCheckpoint(EventHeader eventHeader, byte[] eventPayload, CheckpointEvent constantPool) {
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,24 @@
*/
package org.openjdk.jmc.flightrecorder.testutils.parser;

import tools.profiler.jfr.converter.Element;
import tools.profiler.jfr.converter.JfrClass;
import tools.profiler.jfr.converter.JfrField;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static java.lang.Integer.parseInt;

/**
* JFR Chunk metadata
* <p>
* It contains the chunk specific type specifications
*/
public final class MetadataEvent {
private static final byte[] COMMON_BUFFER = new byte[4096]; // reusable byte buffer

public final int size;
public final long startTime;
Expand All @@ -56,37 +61,40 @@ public final class MetadataEvent {
*/
private int gmtOffset = 1;

private final Map<Long, String> eventTypeNameMapBacking = new HashMap<>(256);
public final Map<Integer, JfrClass> typesById = new HashMap<>(256);
public final Map<String, JfrClass> typesByName = new HashMap<>(256);

MetadataEvent(RecordingStream stream, int eventSize, long eventType) throws IOException {
size = eventSize;
if (eventType != 0) {
throw new IOException("Unexpected event type: " + eventType + " (should be 0). Stream at position: " + stream.position());
}
startTime = stream.readVarint();
duration = stream.readVarint();
metadataId = stream.readVarint();
readElements(stream, readStringTable(stream));
startTime = stream.readVarlong();
duration = stream.readVarlong();
metadataId = stream.readVarlong();
readElement(stream, readStringTable(stream));
}

private MetadataEvent(int size, long startTime, long duration, long metadataId, Map<Long, String> eventTypeNameMapBacking) {
private MetadataEvent(int size, long startTime, long duration, long metadataId) {
this.size = size;
this.startTime = startTime;
this.duration = duration;
this.metadataId = metadataId;
this.eventTypeNameMapBacking.putAll(eventTypeNameMapBacking);
}

public int getGmtOffset() {
return gmtOffset;
}

public JfrClass type(int id) {
return typesById.get(id);
}

public static class Builder {
private int size;
private long startTime;
private long duration;
private long metadataId;
private Map<Long, String> eventTypeNameMapBacking = new HashMap<>(256);

public Builder size(int size) {
this.size = size;
Expand All @@ -108,60 +116,55 @@ public Builder metadataId(long metadataId) {
return this;
}

public Builder eventTypeNameMapBacking(Map<Long, String> eventTypeNameMapBacking) {
this.eventTypeNameMapBacking.putAll(eventTypeNameMapBacking);
return this;
}

public MetadataEvent build() {
return new MetadataEvent(size, startTime, duration, metadataId, eventTypeNameMapBacking);
return new MetadataEvent(size, startTime, duration, metadataId);
}
}

private String[] readStringTable(RecordingStream stream) throws IOException {
int stringCnt = (int) stream.readVarint();
int stringCnt = stream.readVarint();
String[] stringConstants = new String[stringCnt];
for (int stringIdx = 0; stringIdx < stringCnt; stringIdx++) {
stringConstants[stringIdx] = readUTF8(stream);
}
return stringConstants;
}

private void readElements(RecordingStream stream, String[] stringConstants) throws IOException {
// get the element name
int stringPtr = (int) stream.readVarint();
boolean isClassElement = "class".equals(stringConstants[stringPtr]);

// process the attributes
int attrCount = (int) stream.readVarint();
String superType = null;
String name = null;
String id = null;
for (int i = 0; i < attrCount; i++) {
int keyPtr = (int) stream.readVarint();
int valPtr = (int) stream.readVarint();
// ignore anything but 'class' elements
if ("gmtOffset".equals(stringConstants[keyPtr])) {
gmtOffset = Integer.parseInt(stringConstants[valPtr]);
}
if (isClassElement) {
if ("superType".equals(stringConstants[keyPtr])) {
superType = stringConstants[valPtr];
} else if ("name".equals(stringConstants[keyPtr])) {
name = stringConstants[valPtr];
} else if ("id".equals(stringConstants[keyPtr])) {
id = stringConstants[valPtr];
}
}
private Element readElement(RecordingStream stream, String[] strings) throws IOException {
String name = strings[stream.readVarint()];

int attributeCount = stream.readVarint();
Map<String, String> attributes = new HashMap<>(attributeCount);
for (int i = 0; i < attributeCount; i++) {
attributes.put(strings[stream.readVarint()], strings[stream.readVarint()]);
}
// only event types are currently collected
if (name != null && id != null && "jdk.jfr.Event".equals(superType)) {
eventTypeNameMapBacking.put(Long.parseLong(id), name);

if (attributes.containsKey("gmtOffset")) {
gmtOffset = parseInt(attributes.get("gmtOffset"));
}

Element e = createElement(name, attributes);
int childCount = stream.readVarint();
for (int i = 0; i < childCount; i++) {
e.addChild(readElement(stream, strings));
}
// now inspect all the enclosed elements
int elemCount = (int) stream.readVarint();
for (int i = 0; i < elemCount; i++) {
readElements(stream, stringConstants);
return e;
}

private Element createElement(String name, Map<String, String> attributes) {
switch (name) {
case "class": {
JfrClass type = new JfrClass(attributes);
if (!attributes.containsKey("superType")) {
typesById.put(type.id, type);
}
typesByName.put(type.name, type);
return type;
}
case "field":
return new JfrField(attributes);
default:
return new Element();
}
}

Expand All @@ -172,15 +175,15 @@ private String readUTF8(RecordingStream stream) throws IOException {
} else if (id == 1) {
return "";
} else if (id == 3) {
int size = (int) stream.readVarint();
byte[] content = size <= COMMON_BUFFER.length ? COMMON_BUFFER : new byte[size];
int size = stream.readVarint();
byte[] content = new byte[size];
stream.read(content, 0, size);
return new String(content, 0, size, StandardCharsets.UTF_8);
} else if (id == 4) {
int size = (int) stream.readVarint();
int size = stream.readVarint();
char[] chars = new char[size];
for (int i = 0; i < size; i++) {
chars[i] = (char) stream.readVarint();
chars[i] = (char) stream.readVarlong();
}
return new String(chars);
} else {
Expand All @@ -191,7 +194,7 @@ private String readUTF8(RecordingStream stream) throws IOException {
@Override
public String toString() {
return "Metadata{" + "size=" + size + ", startTime=" + startTime + ", duration=" + duration + ", metadataId="
+ metadataId + '}' + eventTypeNameMapBacking;
+ metadataId + '}';
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class RecordingStream implements AutoCloseable {
private final DataInputStream delegate;
private long position = 0;
private final ByteArrayOutputStream toBytesLog = new ByteArrayOutputStream();
private DataOutputStream toBytesStream = new DataOutputStream(toBytesLog);
private final DataOutputStream toBytesStream = new DataOutputStream(toBytesLog);
private boolean isRecordingWrites = false;

public RecordingStream(InputStream is) {
Expand All @@ -48,33 +48,29 @@ public RecordingStream(InputStream is) {
delegate = new DataInputStream(bis);
}

long position() {
public long position() {
return position;
}

void startRecordingWrites() {
isRecordingWrites = true;
}

public byte[] stopRecordingWrites() {
byte[] stopRecordingWrites() {
isRecordingWrites = false;
byte[] result = toBytesLog.toByteArray();
toBytesLog.reset();
return result;
}

public void read(byte[] buffer, int offset, int length) throws IOException {
while (length > 0) {
int read = delegate.read(buffer, offset, length);
if (read == -1) {
throw new IOException("Unexpected EOF");
}
if (isRecordingWrites) {
toBytesStream.write(buffer, offset, read);
}
offset += read;
length -= read;
position += read;
int read = delegate.read(buffer, offset, length);
if (read == -1) {
throw new IOException("Unexpected EOF");
}
position += read;
if (isRecordingWrites) {
toBytesStream.write(buffer, offset, read);
}
}

Expand All @@ -100,7 +96,7 @@ public int readInt() throws IOException {
position += 4;
int result = delegate.readInt();
if (isRecordingWrites) {
toBytesStream.writeInt(delegate.readInt());
toBytesStream.writeInt(result);
}
return result;
}
Expand All @@ -114,15 +110,12 @@ long readLong() throws IOException {
return result;
}

public long readVarint() throws IOException {
public long readVarlong() throws IOException {
long value = 0;
int readValue = 0;
int i = 0;
do {
readValue = delegate.read();
if (isRecordingWrites) {
toBytesStream.writeByte(readValue);
}
readValue = read();
value |= (long) (readValue & 0x7F) << (7 * i);
i++;
} while ((readValue & 0x80) != 0
Expand All @@ -131,20 +124,26 @@ public long readVarint() throws IOException {
// However, eg. JMC parser will stop at 9 bytes, assuming that the compressed number is
// a Java unsigned long (therefore having only 63 bits and they all fit in 9 bytes).
&& i < 9);
position += i;
return value;
}

public int readVarint() throws IOException {
return (int) readVarlong();
}

public byte[] readVarbytes() throws IOException {
int byteCount = readVarint();
byte[] bytes = new byte[byteCount];
read(bytes, 0, byteCount);
return bytes;
}

public int available() throws IOException {
return delegate.available();
}

void skip(long bytes) throws IOException {
long toSkip = bytes;
while (toSkip > 0) {
toSkip -= delegate.skip(toSkip);
}
position += bytes;
public void skip(long bytes) throws IOException {
position += delegate.skip(bytes);
}

@Override
Expand Down
Loading
Loading