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