Skip to content

Commit 7dcbbbd

Browse files
authored
Merge 7ae393e into 8b7e489
2 parents 8b7e489 + 7ae393e commit 7dcbbbd

File tree

18 files changed

+590
-182
lines changed

18 files changed

+590
-182
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ private void stop(final boolean restartProfiler) {
301301
endData.measurementsMap,
302302
endData.traceFile,
303303
startProfileChunkTimestamp,
304-
ProfileChunk.Platform.ANDROID));
304+
ProfileChunk.PLATFORM_ANDROID));
305305
}
306306
}
307307

sentry-async-profiler/src/main/java/io/sentry/asyncprofiler/convert/JfrAsyncProfilerToSentryProfileConverter.java

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,28 @@
88
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.JfrReader;
99
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.StackTrace;
1010
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.Event;
11+
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.EventAggregator;
12+
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.EventCollector;
1113
import io.sentry.protocol.SentryStackFrame;
1214
import io.sentry.protocol.profiling.SentryProfile;
1315
import io.sentry.protocol.profiling.SentrySample;
1416
import io.sentry.protocol.profiling.SentryThreadMetadata;
1517
import java.io.IOException;
1618
import java.nio.file.Path;
1719
import java.util.ArrayList;
20+
import java.util.HashMap;
1821
import java.util.List;
22+
import java.util.Map;
1923
import org.jetbrains.annotations.NotNull;
2024
import org.jetbrains.annotations.Nullable;
2125

2226
public final class JfrAsyncProfilerToSentryProfileConverter extends JfrConverter {
23-
private static final long NANOS_PER_SECOND = 1_000_000_000L;
27+
private static final double NANOS_PER_SECOND = 1_000_000_000.0;
2428

2529
private final @NotNull SentryProfile sentryProfile = new SentryProfile();
2630
private final @NotNull SentryStackTraceFactory stackTraceFactory;
31+
private final @NotNull Map<SentryStackFrame, Integer> frameDeduplicationMap = new HashMap<>();
32+
private final @NotNull Map<List<Integer>, Integer> stackDeduplicationMap = new HashMap<>();
2733

2834
public JfrAsyncProfilerToSentryProfileConverter(
2935
JfrReader jfr, Arguments args, @NotNull SentryStackTraceFactory stackTraceFactory) {
@@ -36,12 +42,18 @@ protected void convertChunk() {
3642
collector.forEach(new ProfileEventVisitor(sentryProfile, stackTraceFactory, jfr, args));
3743
}
3844

45+
@Override
46+
protected EventCollector createCollector(Arguments args) {
47+
return new NonAggregatingEventCollector();
48+
}
49+
3950
public static @NotNull SentryProfile convertFromFileStatic(@NotNull Path jfrFilePath)
4051
throws IOException {
4152
JfrAsyncProfilerToSentryProfileConverter converter;
4253
try (JfrReader jfrReader = new JfrReader(jfrFilePath.toString())) {
4354
Arguments args = new Arguments();
4455
args.cpu = false;
56+
args.wall = true;
4557
args.alloc = false;
4658
args.threads = true;
4759
args.lines = true;
@@ -56,11 +68,12 @@ protected void convertChunk() {
5668
return converter.sentryProfile;
5769
}
5870

59-
private class ProfileEventVisitor extends AggregatedEventVisitor {
71+
private class ProfileEventVisitor implements EventCollector.Visitor {
6072
private final @NotNull SentryProfile sentryProfile;
6173
private final @NotNull SentryStackTraceFactory stackTraceFactory;
6274
private final @NotNull JfrReader jfr;
6375
private final @NotNull Arguments args;
76+
private final double ticksPerNanosecond;
6477

6578
public ProfileEventVisitor(
6679
@NotNull SentryProfile sentryProfile,
@@ -71,10 +84,11 @@ public ProfileEventVisitor(
7184
this.stackTraceFactory = stackTraceFactory;
7285
this.jfr = jfr;
7386
this.args = args;
87+
ticksPerNanosecond = jfr.ticksPerSec / NANOS_PER_SECOND;
7488
}
7589

7690
@Override
77-
public void visit(Event event, long value) {
91+
public void visit(Event event, long samples, long value) {
7892
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
7993
long threadId = resolveThreadId(event.tid);
8094

@@ -83,12 +97,16 @@ public void visit(Event event, long value) {
8397
processThreadMetadata(event, threadId);
8498
}
8599

86-
createSample(event, threadId);
87-
88-
buildStackTraceAndFrames(stackTrace);
100+
processSampleWithStack(event, threadId, stackTrace);
89101
}
90102
}
91103

104+
private long resolveThreadId(int eventThreadId) {
105+
return jfr.threads.get(eventThreadId) != null
106+
? jfr.javaThreads.get(eventThreadId)
107+
: eventThreadId;
108+
}
109+
92110
private void processThreadMetadata(Event event, long threadId) {
93111
final String threadName = getPlainThreadName(event.tid);
94112
sentryProfile
@@ -103,28 +121,73 @@ private void processThreadMetadata(Event event, long threadId) {
103121
});
104122
}
105123

106-
private void buildStackTraceAndFrames(StackTrace stackTrace) {
107-
List<Integer> stack = new ArrayList<>();
108-
int currentFrame = sentryProfile.getFrames().size();
124+
private void processSampleWithStack(Event event, long threadId, StackTrace stackTrace) {
125+
int stackIndex = addStackTrace(stackTrace);
126+
127+
SentrySample sample = new SentrySample();
128+
sample.setTimestamp(calculateTimestamp(event));
129+
sample.setThreadId(String.valueOf(threadId));
130+
sample.setStackId(stackIndex);
131+
132+
sentryProfile.getSamples().add(sample);
133+
}
134+
135+
private double calculateTimestamp(Event event) {
136+
long nanosFromStart = (long) ((event.time - jfr.chunkStartTicks) / ticksPerNanosecond);
137+
138+
long timeNs = jfr.chunkStartNanos + nanosFromStart;
139+
140+
return DateUtils.nanosToSeconds(timeNs);
141+
}
142+
143+
private int addStackTrace(StackTrace stackTrace) {
144+
List<Integer> callStack = createFramesAndCallStack(stackTrace);
145+
146+
Integer existingIndex = stackDeduplicationMap.get(callStack);
147+
if (existingIndex != null) {
148+
return existingIndex;
149+
}
150+
151+
int stackIndex = sentryProfile.getStacks().size();
152+
sentryProfile.getStacks().add(callStack);
153+
stackDeduplicationMap.put(callStack, stackIndex);
154+
return stackIndex;
155+
}
156+
157+
private List<Integer> createFramesAndCallStack(StackTrace stackTrace) {
158+
List<Integer> callStack = new ArrayList<>();
109159

110160
long[] methods = stackTrace.methods;
111161
byte[] types = stackTrace.types;
112162
int[] locations = stackTrace.locations;
113163

114164
for (int i = 0; i < methods.length; i++) {
115165
StackTraceElement element = getStackTraceElement(methods[i], types[i], locations[i]);
116-
if (element.isNativeMethod()) {
166+
if (element.isNativeMethod() || isNativeFrame(types[i])) {
117167
continue;
118168
}
119169

120170
SentryStackFrame frame = createStackFrame(element);
121-
sentryProfile.getFrames().add(frame);
171+
frame.setNative(isNativeFrame(types[i]));
172+
int frameIndex = getOrAddFrame(frame);
173+
callStack.add(frameIndex);
174+
}
122175

123-
stack.add(currentFrame);
124-
currentFrame++;
176+
return callStack;
177+
}
178+
179+
// Get existing frame index or add new frame and return its index
180+
private int getOrAddFrame(SentryStackFrame frame) {
181+
Integer existingIndex = frameDeduplicationMap.get(frame);
182+
183+
if (existingIndex != null) {
184+
return existingIndex;
125185
}
126186

127-
sentryProfile.getStacks().add(stack);
187+
int newIndex = sentryProfile.getFrames().size();
188+
sentryProfile.getFrames().add(frame);
189+
frameDeduplicationMap.put(frame, newIndex);
190+
return newIndex;
128191
}
129192

130193
private SentryStackFrame createStackFrame(StackTraceElement element) {
@@ -176,36 +239,12 @@ private boolean isRegularClassWithoutPackage(String className) {
176239
return !className.startsWith("[");
177240
}
178241

179-
private void createSample(Event event, long threadId) {
180-
int stackId = sentryProfile.getStacks().size();
181-
SentrySample sample = new SentrySample();
182-
183-
// Calculate timestamp from JFR event time
184-
long nsFromStart =
185-
(event.time - jfr.chunkStartTicks)
186-
* JfrAsyncProfilerToSentryProfileConverter.NANOS_PER_SECOND
187-
/ jfr.ticksPerSec;
188-
long timeNs = jfr.chunkStartNanos + nsFromStart;
189-
sample.setTimestamp(DateUtils.nanosToSeconds(timeNs));
190-
191-
sample.setThreadId(String.valueOf(threadId));
192-
sample.setStackId(stackId);
193-
194-
sentryProfile.getSamples().add(sample);
195-
}
196-
197242
private boolean shouldMarkAsSystemFrame(StackTraceElement element, String className) {
198243
return element.isNativeMethod() || className.isEmpty();
199244
}
200245

201246
private @Nullable Integer extractLineNumber(StackTraceElement element) {
202247
return element.getLineNumber() != 0 ? element.getLineNumber() : null;
203248
}
204-
205-
private long resolveThreadId(int eventThreadId) {
206-
return jfr.threads.get(eventThreadId) != null
207-
? jfr.javaThreads.get(eventThreadId)
208-
: eventThreadId;
209-
}
210249
}
211250
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.sentry.asyncprofiler.convert;
2+
3+
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.Event;
4+
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.EventCollector;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
public final class NonAggregatingEventCollector implements EventCollector {
10+
final List<Event> events = new ArrayList<>();
11+
12+
@Override
13+
public void collect(Event e) {
14+
events.add(e);
15+
}
16+
17+
@Override
18+
public void beforeChunk() {
19+
// No-op
20+
}
21+
22+
@Override
23+
public void afterChunk() {
24+
// No-op
25+
}
26+
27+
@Override
28+
public boolean finish() {
29+
return true;
30+
}
31+
32+
@Override
33+
public void forEach(Visitor visitor) {
34+
for (Event event : events) {
35+
visitor.visit(event, event.samples(), event.value());
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)