8
8
import io .sentry .asyncprofiler .vendor .asyncprofiler .jfr .JfrReader ;
9
9
import io .sentry .asyncprofiler .vendor .asyncprofiler .jfr .StackTrace ;
10
10
import 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 ;
11
13
import io .sentry .protocol .SentryStackFrame ;
12
14
import io .sentry .protocol .profiling .SentryProfile ;
13
15
import io .sentry .protocol .profiling .SentrySample ;
14
16
import io .sentry .protocol .profiling .SentryThreadMetadata ;
15
17
import java .io .IOException ;
16
18
import java .nio .file .Path ;
17
19
import java .util .ArrayList ;
20
+ import java .util .HashMap ;
18
21
import java .util .List ;
22
+ import java .util .Map ;
19
23
import org .jetbrains .annotations .NotNull ;
20
24
import org .jetbrains .annotations .Nullable ;
21
25
22
26
public 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 ;
24
28
25
29
private final @ NotNull SentryProfile sentryProfile = new SentryProfile ();
26
30
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 <>();
27
33
28
34
public JfrAsyncProfilerToSentryProfileConverter (
29
35
JfrReader jfr , Arguments args , @ NotNull SentryStackTraceFactory stackTraceFactory ) {
@@ -36,12 +42,18 @@ protected void convertChunk() {
36
42
collector .forEach (new ProfileEventVisitor (sentryProfile , stackTraceFactory , jfr , args ));
37
43
}
38
44
45
+ @ Override
46
+ protected EventCollector createCollector (Arguments args ) {
47
+ return new NonAggregatingEventCollector ();
48
+ }
49
+
39
50
public static @ NotNull SentryProfile convertFromFileStatic (@ NotNull Path jfrFilePath )
40
51
throws IOException {
41
52
JfrAsyncProfilerToSentryProfileConverter converter ;
42
53
try (JfrReader jfrReader = new JfrReader (jfrFilePath .toString ())) {
43
54
Arguments args = new Arguments ();
44
55
args .cpu = false ;
56
+ args .wall = true ;
45
57
args .alloc = false ;
46
58
args .threads = true ;
47
59
args .lines = true ;
@@ -56,11 +68,12 @@ protected void convertChunk() {
56
68
return converter .sentryProfile ;
57
69
}
58
70
59
- private class ProfileEventVisitor extends AggregatedEventVisitor {
71
+ private class ProfileEventVisitor implements EventCollector . Visitor {
60
72
private final @ NotNull SentryProfile sentryProfile ;
61
73
private final @ NotNull SentryStackTraceFactory stackTraceFactory ;
62
74
private final @ NotNull JfrReader jfr ;
63
75
private final @ NotNull Arguments args ;
76
+ private final double ticksPerNanosecond ;
64
77
65
78
public ProfileEventVisitor (
66
79
@ NotNull SentryProfile sentryProfile ,
@@ -71,10 +84,11 @@ public ProfileEventVisitor(
71
84
this .stackTraceFactory = stackTraceFactory ;
72
85
this .jfr = jfr ;
73
86
this .args = args ;
87
+ ticksPerNanosecond = jfr .ticksPerSec / NANOS_PER_SECOND ;
74
88
}
75
89
76
90
@ Override
77
- public void visit (Event event , long value ) {
91
+ public void visit (Event event , long samples , long value ) {
78
92
StackTrace stackTrace = jfr .stackTraces .get (event .stackTraceId );
79
93
long threadId = resolveThreadId (event .tid );
80
94
@@ -83,12 +97,16 @@ public void visit(Event event, long value) {
83
97
processThreadMetadata (event , threadId );
84
98
}
85
99
86
- createSample (event , threadId );
87
-
88
- buildStackTraceAndFrames (stackTrace );
100
+ processSampleWithStack (event , threadId , stackTrace );
89
101
}
90
102
}
91
103
104
+ private long resolveThreadId (int eventThreadId ) {
105
+ return jfr .threads .get (eventThreadId ) != null
106
+ ? jfr .javaThreads .get (eventThreadId )
107
+ : eventThreadId ;
108
+ }
109
+
92
110
private void processThreadMetadata (Event event , long threadId ) {
93
111
final String threadName = getPlainThreadName (event .tid );
94
112
sentryProfile
@@ -103,28 +121,73 @@ private void processThreadMetadata(Event event, long threadId) {
103
121
});
104
122
}
105
123
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 <>();
109
159
110
160
long [] methods = stackTrace .methods ;
111
161
byte [] types = stackTrace .types ;
112
162
int [] locations = stackTrace .locations ;
113
163
114
164
for (int i = 0 ; i < methods .length ; i ++) {
115
165
StackTraceElement element = getStackTraceElement (methods [i ], types [i ], locations [i ]);
116
- if (element .isNativeMethod ()) {
166
+ if (element .isNativeMethod () || isNativeFrame ( types [ i ]) ) {
117
167
continue ;
118
168
}
119
169
120
170
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
+ }
122
175
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 ;
125
185
}
126
186
127
- sentryProfile .getStacks ().add (stack );
187
+ int newIndex = sentryProfile .getFrames ().size ();
188
+ sentryProfile .getFrames ().add (frame );
189
+ frameDeduplicationMap .put (frame , newIndex );
190
+ return newIndex ;
128
191
}
129
192
130
193
private SentryStackFrame createStackFrame (StackTraceElement element ) {
@@ -176,36 +239,12 @@ private boolean isRegularClassWithoutPackage(String className) {
176
239
return !className .startsWith ("[" );
177
240
}
178
241
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
242
private boolean shouldMarkAsSystemFrame (StackTraceElement element , String className ) {
198
243
return element .isNativeMethod () || className .isEmpty ();
199
244
}
200
245
201
246
private @ Nullable Integer extractLineNumber (StackTraceElement element ) {
202
247
return element .getLineNumber () != 0 ? element .getLineNumber () : null ;
203
248
}
204
-
205
- private long resolveThreadId (int eventThreadId ) {
206
- return jfr .threads .get (eventThreadId ) != null
207
- ? jfr .javaThreads .get (eventThreadId )
208
- : eventThreadId ;
209
- }
210
249
}
211
250
}
0 commit comments