1515import java .io .IOException ;
1616import java .nio .file .Path ;
1717import java .util .ArrayList ;
18+ import java .util .HashMap ;
1819import java .util .List ;
20+ import java .util .Map ;
1921import org .jetbrains .annotations .NotNull ;
2022import org .jetbrains .annotations .Nullable ;
2123
2224public final class JfrAsyncProfilerToSentryProfileConverter extends JfrConverter {
23- private static final long NANOS_PER_SECOND = 1_000_000_000L ;
25+ private static final double NANOS_PER_SECOND = 1_000_000_000.0 ;
2426
2527 private final @ NotNull SentryProfile sentryProfile = new SentryProfile ();
2628 private final @ NotNull SentryStackTraceFactory stackTraceFactory ;
29+ private final @ NotNull Map <SentryStackFrame , Integer > frameDeduplicationMap = new HashMap <>();
2730
2831 public JfrAsyncProfilerToSentryProfileConverter (
2932 JfrReader jfr , Arguments args , @ NotNull SentryStackTraceFactory stackTraceFactory ) {
@@ -61,6 +64,7 @@ private class ProfileEventVisitor extends AggregatedEventVisitor {
6164 private final @ NotNull SentryStackTraceFactory stackTraceFactory ;
6265 private final @ NotNull JfrReader jfr ;
6366 private final @ NotNull Arguments args ;
67+ private final double ticksPerNanosecond ;
6468
6569 public ProfileEventVisitor (
6670 @ NotNull SentryProfile sentryProfile ,
@@ -71,6 +75,7 @@ public ProfileEventVisitor(
7175 this .stackTraceFactory = stackTraceFactory ;
7276 this .jfr = jfr ;
7377 this .args = args ;
78+ ticksPerNanosecond = jfr .ticksPerSec / NANOS_PER_SECOND ;
7479 }
7580
7681 @ Override
@@ -83,12 +88,16 @@ public void visit(Event event, long value) {
8388 processThreadMetadata (event , threadId );
8489 }
8590
86- createSample (event , threadId );
87-
88- buildStackTraceAndFrames (stackTrace );
91+ processSampleWithStack (event , threadId , stackTrace );
8992 }
9093 }
9194
95+ private long resolveThreadId (int eventThreadId ) {
96+ return jfr .threads .get (eventThreadId ) != null
97+ ? jfr .javaThreads .get (eventThreadId )
98+ : eventThreadId ;
99+ }
100+
92101 private void processThreadMetadata (Event event , long threadId ) {
93102 final String threadName = getPlainThreadName (event .tid );
94103 sentryProfile
@@ -103,28 +112,66 @@ private void processThreadMetadata(Event event, long threadId) {
103112 });
104113 }
105114
106- private void buildStackTraceAndFrames (StackTrace stackTrace ) {
107- List <Integer > stack = new ArrayList <>();
108- int currentFrame = sentryProfile .getFrames ().size ();
115+ private void processSampleWithStack (Event event , long threadId , StackTrace stackTrace ) {
116+ int stackIndex = addStackTrace (stackTrace );
117+
118+ SentrySample sample = new SentrySample ();
119+ sample .setTimestamp (calculateTimestamp (event ));
120+ sample .setThreadId (String .valueOf (threadId ));
121+ sample .setStackId (stackIndex );
122+
123+ sentryProfile .getSamples ().add (sample );
124+ }
125+
126+ private double calculateTimestamp (Event event ) {
127+ long nanosFromStart = (long ) ((event .time - jfr .chunkStartTicks ) / ticksPerNanosecond );
128+
129+ long timeNs = jfr .chunkStartNanos + nanosFromStart ;
130+
131+ return DateUtils .nanosToSeconds (timeNs );
132+ }
133+
134+ private int addStackTrace (StackTrace stackTrace ) {
135+ int stackIndex = sentryProfile .getStacks ().size ();
136+ List <Integer > callStack = createFramesAndCallStack (stackTrace );
137+ sentryProfile .getStacks ().add (callStack );
138+ return stackIndex ;
139+ }
140+
141+ private List <Integer > createFramesAndCallStack (StackTrace stackTrace ) {
142+ List <Integer > callStack = new ArrayList <>();
109143
110144 long [] methods = stackTrace .methods ;
111145 byte [] types = stackTrace .types ;
112146 int [] locations = stackTrace .locations ;
113147
114148 for (int i = 0 ; i < methods .length ; i ++) {
115149 StackTraceElement element = getStackTraceElement (methods [i ], types [i ], locations [i ]);
116- if (element .isNativeMethod ()) {
150+ if (element .isNativeMethod () || isNativeFrame ( types [ i ]) ) {
117151 continue ;
118152 }
119153
120154 SentryStackFrame frame = createStackFrame (element );
121- sentryProfile .getFrames ().add (frame );
155+ frame .setNative (isNativeFrame (types [i ]));
156+ int frameIndex = getOrAddFrame (frame );
157+ callStack .add (frameIndex );
158+ }
159+
160+ return callStack ;
161+ }
162+
163+ // Get existing frame index or add new frame and return its index
164+ private int getOrAddFrame (SentryStackFrame frame ) {
165+ Integer existingIndex = frameDeduplicationMap .get (frame );
122166
123- stack . add ( currentFrame );
124- currentFrame ++ ;
167+ if ( existingIndex != null ) {
168+ return existingIndex ;
125169 }
126170
127- sentryProfile .getStacks ().add (stack );
171+ int newIndex = sentryProfile .getFrames ().size ();
172+ sentryProfile .getFrames ().add (frame );
173+ frameDeduplicationMap .put (frame , newIndex );
174+ return newIndex ;
128175 }
129176
130177 private SentryStackFrame createStackFrame (StackTraceElement element ) {
@@ -176,36 +223,12 @@ private boolean isRegularClassWithoutPackage(String className) {
176223 return !className .startsWith ("[" );
177224 }
178225
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-
197226 private boolean shouldMarkAsSystemFrame (StackTraceElement element , String className ) {
198227 return element .isNativeMethod () || className .isEmpty ();
199228 }
200229
201230 private @ Nullable Integer extractLineNumber (StackTraceElement element ) {
202231 return element .getLineNumber () != 0 ? element .getLineNumber () : null ;
203232 }
204-
205- private long resolveThreadId (int eventThreadId ) {
206- return jfr .threads .get (eventThreadId ) != null
207- ? jfr .javaThreads .get (eventThreadId )
208- : eventThreadId ;
209- }
210233 }
211234}
0 commit comments