Skip to content

Commit c21fbdb

Browse files
authored
Merge 34398f6 into 03a20dd
2 parents 03a20dd + 34398f6 commit c21fbdb

File tree

36 files changed

+904
-815
lines changed

36 files changed

+904
-815
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-
"android"));
304+
ProfileChunk.Platform.ANDROID));
305305
}
306306
}
307307

sentry-async-profiler/api/sentry-async-profiler.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public final class io/sentry/asyncprofiler/BuildConfig {
44
}
55

66
public final class io/sentry/asyncprofiler/convert/JfrAsyncProfilerToSentryProfileConverter : io/sentry/asyncprofiler/vendor/asyncprofiler/convert/JfrConverter {
7-
public fun <init> (Lio/sentry/asyncprofiler/vendor/asyncprofiler/jfr/JfrReader;Lio/sentry/asyncprofiler/vendor/asyncprofiler/convert/Arguments;)V
7+
public fun <init> (Lio/sentry/asyncprofiler/vendor/asyncprofiler/jfr/JfrReader;Lio/sentry/asyncprofiler/vendor/asyncprofiler/convert/Arguments;Lio/sentry/SentryStackTraceFactory;)V
88
public static fun convertFromFileStatic (Ljava/nio/file/Path;)Lio/sentry/protocol/profiling/SentryProfile;
99
}
1010

Lines changed: 167 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.asyncprofiler.convert;
22

3+
import io.sentry.DateUtils;
34
import io.sentry.Sentry;
45
import io.sentry.SentryStackTraceFactory;
56
import io.sentry.asyncprofiler.vendor.asyncprofiler.convert.Arguments;
@@ -8,145 +9,31 @@
89
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.StackTrace;
910
import io.sentry.asyncprofiler.vendor.asyncprofiler.jfr.event.Event;
1011
import io.sentry.protocol.SentryStackFrame;
11-
import io.sentry.protocol.profiling.JfrSample;
1212
import io.sentry.protocol.profiling.SentryProfile;
13-
import io.sentry.protocol.profiling.ThreadMetadata;
13+
import io.sentry.protocol.profiling.SentrySample;
14+
import io.sentry.protocol.profiling.SentryThreadMetadata;
1415
import java.io.IOException;
1516
import java.nio.file.Path;
16-
import java.time.Instant;
1717
import java.util.ArrayList;
18-
import java.util.HashMap;
1918
import java.util.List;
2019
import org.jetbrains.annotations.NotNull;
20+
import org.jetbrains.annotations.Nullable;
2121

2222
public final class JfrAsyncProfilerToSentryProfileConverter extends JfrConverter {
23+
private static final long NANOS_PER_SECOND = 1_000_000_000L;
24+
2325
private final @NotNull SentryProfile sentryProfile = new SentryProfile();
26+
private final @NotNull SentryStackTraceFactory stackTraceFactory;
2427

25-
public JfrAsyncProfilerToSentryProfileConverter(JfrReader jfr, Arguments args) {
28+
public JfrAsyncProfilerToSentryProfileConverter(
29+
JfrReader jfr, Arguments args, @NotNull SentryStackTraceFactory stackTraceFactory) {
2630
super(jfr, args);
31+
this.stackTraceFactory = stackTraceFactory;
2732
}
2833

2934
@Override
3035
protected void convertChunk() {
31-
final List<Event> events = new ArrayList<Event>();
32-
final List<List<Integer>> stacks = new ArrayList<>();
33-
34-
collector.forEach(
35-
new AggregatedEventVisitor() {
36-
37-
@Override
38-
public void visit(Event event, long value) {
39-
events.add(event);
40-
System.out.println(event);
41-
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
42-
43-
if (stackTrace != null) {
44-
Arguments args = JfrAsyncProfilerToSentryProfileConverter.this.args;
45-
long[] methods = stackTrace.methods;
46-
byte[] types = stackTrace.types;
47-
int[] locations = stackTrace.locations;
48-
49-
if (args.threads) {
50-
if (sentryProfile.threadMetadata == null) {
51-
sentryProfile.threadMetadata = new HashMap<>();
52-
}
53-
54-
long threadIdToUse =
55-
jfr.threads.get(event.tid) != null ? jfr.javaThreads.get(event.tid) : event.tid;
56-
57-
if (sentryProfile.threadMetadata != null) {
58-
final String threadName = getPlainThreadName(event.tid);
59-
sentryProfile.threadMetadata.computeIfAbsent(
60-
String.valueOf(threadIdToUse),
61-
k -> {
62-
ThreadMetadata metadata = new ThreadMetadata();
63-
metadata.name = threadName;
64-
metadata.priority = 0;
65-
return metadata;
66-
});
67-
}
68-
}
69-
70-
if (sentryProfile.samples == null) {
71-
sentryProfile.samples = new ArrayList<>();
72-
}
73-
74-
if (sentryProfile.frames == null) {
75-
sentryProfile.frames = new ArrayList<>();
76-
}
77-
78-
List<Integer> stack = new ArrayList<>();
79-
int currentStack = stacks.size();
80-
int currentFrame = sentryProfile.frames != null ? sentryProfile.frames.size() : 0;
81-
for (int i = 0; i < methods.length; i++) {
82-
// for (int i = methods.length; --i >= 0; ) {
83-
SentryStackFrame frame = new SentryStackFrame();
84-
StackTraceElement element =
85-
getStackTraceElement(methods[i], types[i], locations[i]);
86-
if (element.isNativeMethod()) {
87-
continue;
88-
}
89-
90-
final String classNameWithLambdas = element.getClassName().replace("/", ".");
91-
frame.setFunction(element.getMethodName());
92-
93-
int firstDollar = classNameWithLambdas.indexOf('$');
94-
String sanitizedClassName = classNameWithLambdas;
95-
if (firstDollar != -1) {
96-
sanitizedClassName = classNameWithLambdas.substring(0, firstDollar);
97-
}
98-
99-
int lastDot = sanitizedClassName.lastIndexOf('.');
100-
if (lastDot > 0) {
101-
frame.setModule(sanitizedClassName);
102-
} else if (!classNameWithLambdas.startsWith("[")) {
103-
frame.setModule("");
104-
}
105-
106-
if (element.isNativeMethod() || classNameWithLambdas.isEmpty()) {
107-
frame.setInApp(false);
108-
} else {
109-
frame.setInApp(
110-
new SentryStackTraceFactory(Sentry.getGlobalScope().getOptions())
111-
.isInApp(sanitizedClassName));
112-
}
113-
114-
frame.setLineno((element.getLineNumber() != 0) ? element.getLineNumber() : null);
115-
frame.setFilename(classNameWithLambdas);
116-
117-
if (sentryProfile.frames != null) {
118-
sentryProfile.frames.add(frame);
119-
}
120-
stack.add(currentFrame);
121-
currentFrame++;
122-
}
123-
124-
long divisor = jfr.ticksPerSec / 1000_000_000L;
125-
long myTimeStamp =
126-
jfr.chunkStartNanos + ((event.time - jfr.chunkStartTicks) / divisor);
127-
128-
JfrSample sample = new JfrSample();
129-
Instant instant = Instant.ofEpochSecond(0, myTimeStamp);
130-
double timestampDouble =
131-
instant.getEpochSecond() + instant.getNano() / 1_000_000_000.0;
132-
133-
sample.timestamp = timestampDouble;
134-
sample.threadId =
135-
String.valueOf(
136-
jfr.threads.get(event.tid) != null
137-
? jfr.javaThreads.get(event.tid)
138-
: event.tid);
139-
sample.stackId = currentStack;
140-
if (sentryProfile.samples != null) {
141-
sentryProfile.samples.add(sample);
142-
}
143-
144-
stacks.add(stack);
145-
}
146-
}
147-
});
148-
sentryProfile.stacks = stacks;
149-
System.out.println("Samples: " + events.size());
36+
collector.forEach(new ProfileEventVisitor(sentryProfile, stackTraceFactory, jfr, args));
15037
}
15138

15239
public static @NotNull SentryProfile convertFromFileStatic(@NotNull Path jfrFilePath)
@@ -160,10 +47,165 @@ public void visit(Event event, long value) {
16047
args.lines = true;
16148
args.dot = true;
16249

163-
converter = new JfrAsyncProfilerToSentryProfileConverter(jfrReader, args);
50+
SentryStackTraceFactory stackTraceFactory =
51+
new SentryStackTraceFactory(Sentry.getGlobalScope().getOptions());
52+
converter = new JfrAsyncProfilerToSentryProfileConverter(jfrReader, args, stackTraceFactory);
16453
converter.convert();
16554
}
16655

16756
return converter.sentryProfile;
16857
}
58+
59+
private class ProfileEventVisitor extends AggregatedEventVisitor {
60+
private final @NotNull SentryProfile sentryProfile;
61+
private final @NotNull SentryStackTraceFactory stackTraceFactory;
62+
private final @NotNull JfrReader jfr;
63+
private final @NotNull Arguments args;
64+
65+
public ProfileEventVisitor(
66+
@NotNull SentryProfile sentryProfile,
67+
@NotNull SentryStackTraceFactory stackTraceFactory,
68+
@NotNull JfrReader jfr,
69+
@NotNull Arguments args) {
70+
this.sentryProfile = sentryProfile;
71+
this.stackTraceFactory = stackTraceFactory;
72+
this.jfr = jfr;
73+
this.args = args;
74+
}
75+
76+
@Override
77+
public void visit(Event event, long value) {
78+
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
79+
long threadId = resolveThreadId(event.tid);
80+
81+
if (stackTrace != null) {
82+
if (args.threads) {
83+
processThreadMetadata(event, threadId);
84+
}
85+
86+
createSample(event, threadId);
87+
88+
buildStackTraceAndFrames(stackTrace);
89+
}
90+
}
91+
92+
private void processThreadMetadata(Event event, long threadId) {
93+
final String threadName = getPlainThreadName(event.tid);
94+
sentryProfile
95+
.getThreadMetadata()
96+
.computeIfAbsent(
97+
String.valueOf(threadId),
98+
k -> {
99+
SentryThreadMetadata metadata = new SentryThreadMetadata();
100+
metadata.setName(threadName);
101+
metadata.setPriority(0); // Default priority
102+
return metadata;
103+
});
104+
}
105+
106+
private void buildStackTraceAndFrames(StackTrace stackTrace) {
107+
List<Integer> stack = new ArrayList<>();
108+
int currentFrame = sentryProfile.getFrames().size();
109+
110+
long[] methods = stackTrace.methods;
111+
byte[] types = stackTrace.types;
112+
int[] locations = stackTrace.locations;
113+
114+
for (int i = 0; i < methods.length; i++) {
115+
StackTraceElement element = getStackTraceElement(methods[i], types[i], locations[i]);
116+
if (element.isNativeMethod()) {
117+
continue;
118+
}
119+
120+
SentryStackFrame frame = createStackFrame(element);
121+
sentryProfile.getFrames().add(frame);
122+
123+
stack.add(currentFrame);
124+
currentFrame++;
125+
}
126+
127+
sentryProfile.getStacks().add(stack);
128+
}
129+
130+
private SentryStackFrame createStackFrame(StackTraceElement element) {
131+
SentryStackFrame frame = new SentryStackFrame();
132+
final String classNameWithLambdas = element.getClassName().replace("/", ".");
133+
frame.setFunction(element.getMethodName());
134+
135+
String sanitizedClassName = extractSanitizedClassName(classNameWithLambdas);
136+
frame.setModule(extractModuleName(sanitizedClassName, classNameWithLambdas));
137+
138+
if (shouldMarkAsSystemFrame(element, classNameWithLambdas)) {
139+
frame.setInApp(false);
140+
} else {
141+
frame.setInApp(stackTraceFactory.isInApp(sanitizedClassName));
142+
}
143+
144+
frame.setLineno(extractLineNumber(element));
145+
frame.setFilename(classNameWithLambdas);
146+
147+
return frame;
148+
}
149+
150+
// Remove lambda suffix from class name
151+
private String extractSanitizedClassName(String classNameWithLambdas) {
152+
int firstDollar = classNameWithLambdas.indexOf('$');
153+
if (firstDollar != -1) {
154+
return classNameWithLambdas.substring(0, firstDollar);
155+
}
156+
return classNameWithLambdas;
157+
}
158+
159+
// TODO: test difference between null and empty string for module
160+
private @Nullable String extractModuleName(
161+
String sanitizedClassName, String classNameWithLambdas) {
162+
if (hasPackageStructure(sanitizedClassName)) {
163+
return sanitizedClassName;
164+
} else if (isRegularClassWithoutPackage(classNameWithLambdas)) {
165+
return "";
166+
} else {
167+
return null;
168+
}
169+
}
170+
171+
private boolean hasPackageStructure(String className) {
172+
return className.lastIndexOf('.') > 0;
173+
}
174+
175+
private boolean isRegularClassWithoutPackage(String className) {
176+
return !className.startsWith("[");
177+
}
178+
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+
197+
private boolean shouldMarkAsSystemFrame(StackTraceElement element, String className) {
198+
return element.isNativeMethod() || className.isEmpty();
199+
}
200+
201+
private @Nullable Integer extractLineNumber(StackTraceElement element) {
202+
return element.getLineNumber() != 0 ? element.getLineNumber() : null;
203+
}
204+
205+
private long resolveThreadId(int eventThreadId) {
206+
return jfr.threads.get(eventThreadId) != null
207+
? jfr.javaThreads.get(eventThreadId)
208+
: eventThreadId;
209+
}
210+
}
169211
}

0 commit comments

Comments
 (0)