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 .EventCollector ;
1112import io .sentry .protocol .SentryStackFrame ;
1213import io .sentry .protocol .profiling .SentryProfile ;
1314import io .sentry .protocol .profiling .SentrySample ;
1415import io .sentry .protocol .profiling .SentryThreadMetadata ;
1516import java .io .IOException ;
1617import java .nio .file .Path ;
1718import java .util .ArrayList ;
19+ import java .util .HashMap ;
1820import java .util .List ;
21+ import java .util .Map ;
1922import org .jetbrains .annotations .NotNull ;
2023import org .jetbrains .annotations .Nullable ;
2124
2225public 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}
0 commit comments