Skip to content

Commit 066d89d

Browse files
authored
Merge 30cb14b into 8b7e489
2 parents 8b7e489 + 30cb14b commit 066d89d

File tree

20 files changed

+613
-183
lines changed

20 files changed

+613
-183
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/api/sentry-async-profiler.api

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ public final class io/sentry/asyncprofiler/convert/JfrAsyncProfilerToSentryProfi
88
public static fun convertFromFileStatic (Ljava/nio/file/Path;)Lio/sentry/protocol/profiling/SentryProfile;
99
}
1010

11+
public final class io/sentry/asyncprofiler/convert/NonAggregatingEventCollector : io/sentry/asyncprofiler/vendor/asyncprofiler/jfr/event/EventCollector {
12+
public fun <init> ()V
13+
public fun afterChunk ()V
14+
public fun beforeChunk ()V
15+
public fun collect (Lio/sentry/asyncprofiler/vendor/asyncprofiler/jfr/event/Event;)V
16+
public fun finish ()Z
17+
public fun forEach (Lio/sentry/asyncprofiler/vendor/asyncprofiler/jfr/event/EventCollector$Visitor;)V
18+
}
19+
1120
public final class io/sentry/asyncprofiler/profiling/JavaContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver {
1221
public fun <init> (Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/ISentryExecutorService;)V
1322
public fun close (Z)V

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

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,27 @@
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.EventCollector;
1112
import io.sentry.protocol.SentryStackFrame;
1213
import io.sentry.protocol.profiling.SentryProfile;
1314
import io.sentry.protocol.profiling.SentrySample;
1415
import io.sentry.protocol.profiling.SentryThreadMetadata;
1516
import java.io.IOException;
1617
import java.nio.file.Path;
1718
import java.util.ArrayList;
19+
import java.util.HashMap;
1820
import java.util.List;
21+
import java.util.Map;
1922
import org.jetbrains.annotations.NotNull;
2023
import org.jetbrains.annotations.Nullable;
2124

2225
public final class JfrAsyncProfilerToSentryProfileConverter extends JfrConverter {
23-
private static final long NANOS_PER_SECOND = 1_000_000_000L;
26+
private static final double NANOS_PER_SECOND = 1_000_000_000.0;
2427

2528
private final @NotNull SentryProfile sentryProfile = new SentryProfile();
2629
private final @NotNull SentryStackTraceFactory stackTraceFactory;
30+
private final @NotNull Map<SentryStackFrame, Integer> frameDeduplicationMap = new HashMap<>();
31+
private final @NotNull Map<List<Integer>, Integer> stackDeduplicationMap = new HashMap<>();
2732

2833
public JfrAsyncProfilerToSentryProfileConverter(
2934
JfrReader jfr, Arguments args, @NotNull SentryStackTraceFactory stackTraceFactory) {
@@ -36,12 +41,18 @@ protected void convertChunk() {
3641
collector.forEach(new ProfileEventVisitor(sentryProfile, stackTraceFactory, jfr, args));
3742
}
3843

44+
@Override
45+
protected EventCollector createCollector(Arguments args) {
46+
return new NonAggregatingEventCollector();
47+
}
48+
3949
public static @NotNull SentryProfile convertFromFileStatic(@NotNull Path jfrFilePath)
4050
throws IOException {
4151
JfrAsyncProfilerToSentryProfileConverter converter;
4252
try (JfrReader jfrReader = new JfrReader(jfrFilePath.toString())) {
4353
Arguments args = new Arguments();
4454
args.cpu = false;
55+
args.wall = true;
4556
args.alloc = false;
4657
args.threads = true;
4758
args.lines = true;
@@ -56,11 +67,12 @@ protected void convertChunk() {
5667
return converter.sentryProfile;
5768
}
5869

59-
private class ProfileEventVisitor extends AggregatedEventVisitor {
70+
private class ProfileEventVisitor implements EventCollector.Visitor {
6071
private final @NotNull SentryProfile sentryProfile;
6172
private final @NotNull SentryStackTraceFactory stackTraceFactory;
6273
private final @NotNull JfrReader jfr;
6374
private final @NotNull Arguments args;
75+
private final double ticksPerNanosecond;
6476

6577
public ProfileEventVisitor(
6678
@NotNull SentryProfile sentryProfile,
@@ -71,10 +83,11 @@ public ProfileEventVisitor(
7183
this.stackTraceFactory = stackTraceFactory;
7284
this.jfr = jfr;
7385
this.args = args;
86+
ticksPerNanosecond = jfr.ticksPerSec / NANOS_PER_SECOND;
7487
}
7588

7689
@Override
77-
public void visit(Event event, long value) {
90+
public void visit(Event event, long samples, long value) {
7891
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
7992
long threadId = resolveThreadId(event.tid);
8093

@@ -83,12 +96,16 @@ public void visit(Event event, long value) {
8396
processThreadMetadata(event, threadId);
8497
}
8598

86-
createSample(event, threadId);
87-
88-
buildStackTraceAndFrames(stackTrace);
99+
processSampleWithStack(event, threadId, stackTrace);
89100
}
90101
}
91102

103+
private long resolveThreadId(int eventThreadId) {
104+
return jfr.threads.get(eventThreadId) != null
105+
? jfr.javaThreads.get(eventThreadId)
106+
: eventThreadId;
107+
}
108+
92109
private void processThreadMetadata(Event event, long threadId) {
93110
final String threadName = getPlainThreadName(event.tid);
94111
sentryProfile
@@ -103,28 +120,73 @@ private void processThreadMetadata(Event event, long threadId) {
103120
});
104121
}
105122

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

110159
long[] methods = stackTrace.methods;
111160
byte[] types = stackTrace.types;
112161
int[] locations = stackTrace.locations;
113162

114163
for (int i = 0; i < methods.length; i++) {
115164
StackTraceElement element = getStackTraceElement(methods[i], types[i], locations[i]);
116-
if (element.isNativeMethod()) {
165+
if (element.isNativeMethod() || isNativeFrame(types[i])) {
117166
continue;
118167
}
119168

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

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

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

130192
private SentryStackFrame createStackFrame(StackTraceElement element) {
@@ -176,36 +238,12 @@ private boolean isRegularClassWithoutPackage(String className) {
176238
return !className.startsWith("[");
177239
}
178240

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-
197241
private boolean shouldMarkAsSystemFrame(StackTraceElement element, String className) {
198242
return element.isNativeMethod() || className.isEmpty();
199243
}
200244

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

0 commit comments

Comments
 (0)