88import io .sentry .asyncprofiler .vendor .asyncprofiler .jfr .JfrReader ;
99import io .sentry .asyncprofiler .vendor .asyncprofiler .jfr .StackTrace ;
1010import 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 ;
1113import io .sentry .protocol .SentryStackFrame ;
1214import io .sentry .protocol .profiling .SentryProfile ;
1315import io .sentry .protocol .profiling .SentrySample ;
1416import io .sentry .protocol .profiling .SentryThreadMetadata ;
1517import java .io .IOException ;
1618import java .nio .file .Path ;
1719import java .util .ArrayList ;
20+ import java .util .HashMap ;
1821import java .util .List ;
22+ import java .util .Map ;
1923import org .jetbrains .annotations .NotNull ;
2024import org .jetbrains .annotations .Nullable ;
2125
2226public 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}
0 commit comments