@@ -17,7 +17,7 @@ import { CpuProfilerBindings } from './cpu_profiler';
17
17
import { DEBUG_BUILD } from './debug-build' ;
18
18
import { NODE_MAJOR , NODE_VERSION } from './nodeVersion' ;
19
19
import { MAX_PROFILE_DURATION_MS , maybeProfileSpan , stopSpanProfile } from './spanProfileUtils' ;
20
- import type { RawChunkCpuProfile , RawThreadCpuProfile } from './types' ;
20
+ import type { RawThreadCpuProfile } from './types' ;
21
21
import { ProfileFormat } from './types' ;
22
22
import { PROFILER_THREAD_NAME } from './utils' ;
23
23
@@ -161,7 +161,7 @@ interface ChunkData {
161
161
}
162
162
163
163
class ContinuousProfiler {
164
- private _profilerId = uuid4 ( ) ;
164
+ private _profilerId : string | undefined ;
165
165
private _client : NodeClient | undefined = undefined ;
166
166
private _chunkData : ChunkData | undefined = undefined ;
167
167
@@ -172,15 +172,48 @@ class ContinuousProfiler {
172
172
*/
173
173
public initialize ( client : NodeClient ) : void {
174
174
this . _client = client ;
175
+
176
+ // Attaches a listener to beforeSend which will add the threadId data to the event being sent.
177
+ // This adds a constant overhead to all events being sent which could be improved to only attach
178
+ // and detach the listener during a profiler session
179
+ this . _client . on ( 'beforeSendEvent' , this . _onBeforeSendThreadContextAssignment . bind ( this ) ) ;
175
180
}
176
181
177
182
/**
178
- * Recursively schedules chunk profiling to start and stop at a set interval.
179
- * Once the user calls stop(), the current chunk will be stopped and flushed to Sentry and no new chunks will
180
- * will be started. To restart continuous mode after calling stop(), the user must call start() again.
183
+ * Initializes a new profilerId session and schedules chunk profiling.
181
184
* @returns void
182
185
*/
183
186
public start ( ) : void {
187
+ if ( ! this . _client ) {
188
+ DEBUG_BUILD && logger . log ( '[Profiling] Failed to start, sentry client was never attached to the profiler.' ) ;
189
+ return ;
190
+ }
191
+
192
+ // Flush any existing chunks before starting a new one.
193
+ this . _chunkStop ( ) ;
194
+
195
+ // Restart the profiler session
196
+ this . _setupSpanChunkInstrumentation ( ) ;
197
+ this . _chunkStart ( ) ;
198
+ }
199
+
200
+ /**
201
+ * Stops the current chunk and flushes the profile to Sentry.
202
+ * @returns void
203
+ */
204
+ public stop ( ) : void {
205
+ if ( ! this . _client ) {
206
+ DEBUG_BUILD && logger . log ( '[Profiling] Failed to stop, sentry client was never attached to the profiler.' ) ;
207
+ return ;
208
+ }
209
+ this . _chunkStop ( ) ;
210
+ this . _teardownSpanChunkInstrumentation ( ) ;
211
+ }
212
+
213
+ /**
214
+ * Stop profiler and initializes profiling of the next chunk
215
+ */
216
+ private _chunkStart ( ) : void {
184
217
if ( ! this . _client ) {
185
218
// The client is not attached to the profiler if the user has not enabled continuous profiling.
186
219
// In this case, calling start() and stop() is a noop action.The reason this exists is because
@@ -193,20 +226,16 @@ class ContinuousProfiler {
193
226
logger . log (
194
227
`[Profiling] Chunk with chunk_id ${ this . _chunkData . id } is still running, current chunk will be stopped a new chunk will be started.` ,
195
228
) ;
196
- this . stop ( ) ;
229
+ this . _chunkStop ( ) ;
197
230
}
198
231
199
- const traceId =
200
- getCurrentScope ( ) . getPropagationContext ( ) . traceId || getIsolationScope ( ) . getPropagationContext ( ) . traceId ;
201
- this . _initializeChunk ( traceId ) ;
202
- this . _startChunkProfiling ( this . _chunkData ! ) ;
232
+ this . _startChunkProfiling ( ) ;
203
233
}
204
234
205
235
/**
206
- * Stops the current chunk and flushes the profile to Sentry.
207
- * @returns void
236
+ * Stops profiling of the current chunks and flushes the profile to Sentry
208
237
*/
209
- public stop ( ) : void {
238
+ private _chunkStop ( ) : void {
210
239
if ( this . _chunkData ?. timer ) {
211
240
global . clearTimeout ( this . _chunkData . timer ) ;
212
241
this . _chunkData . timer = undefined ;
@@ -223,12 +252,17 @@ class ContinuousProfiler {
223
252
return ;
224
253
}
225
254
226
- const profile = this . _stopChunkProfiling ( this . _chunkData ) ;
255
+ const profile = CpuProfilerBindings . stopProfiling ( this . _chunkData . id , ProfileFormat . CHUNK ) ;
227
256
228
257
if ( ! profile ) {
229
258
DEBUG_BUILD && logger . log ( `[Profiling] _chunkiledStartTraceID to collect profile for: ${ this . _chunkData . id } ` ) ;
230
259
return ;
231
260
}
261
+ if ( ! this . _profilerId ) {
262
+ DEBUG_BUILD &&
263
+ logger . log ( '[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK' ) ;
264
+ return ;
265
+ }
232
266
if ( profile ) {
233
267
DEBUG_BUILD && logger . log ( `[Profiling] Sending profile chunk ${ this . _chunkData . id } .` ) ;
234
268
}
@@ -248,7 +282,7 @@ class ContinuousProfiler {
248
282
249
283
if ( ! chunk ) {
250
284
DEBUG_BUILD && logger . log ( `[Profiling] Failed to create profile chunk for: ${ this . _chunkData . id } ` ) ;
251
- this . _reset ( ) ;
285
+ this . _resetChunkData ( ) ;
252
286
return ;
253
287
}
254
288
@@ -257,7 +291,7 @@ class ContinuousProfiler {
257
291
// the format may negatively impact the performance of the application. To avoid
258
292
// blocking for too long, enqueue the next chunk start inside the next macrotask.
259
293
// clear current chunk
260
- this . _reset ( ) ;
294
+ this . _resetChunkData ( ) ;
261
295
}
262
296
263
297
/**
@@ -287,29 +321,23 @@ class ContinuousProfiler {
287
321
} ) ;
288
322
}
289
323
290
- /**
291
- * Stops the profile and clears chunk instrumentation from global scope
292
- * @returns void
293
- */
294
- private _stopChunkProfiling ( chunk : ChunkData ) : RawChunkCpuProfile | null {
295
- this . _teardownSpanChunkInstrumentation ( ) ;
296
- return CpuProfilerBindings . stopProfiling ( chunk . id , ProfileFormat . CHUNK ) ;
297
- }
298
-
299
324
/**
300
325
* Starts the profiler and registers the flush timer for a given chunk.
301
326
* @param chunk
302
327
*/
303
- private _startChunkProfiling ( chunk : ChunkData ) : void {
304
- this . _setupSpanChunkInstrumentation ( ) ;
328
+ private _startChunkProfiling ( ) : void {
329
+ const traceId =
330
+ getCurrentScope ( ) . getPropagationContext ( ) . traceId || getIsolationScope ( ) . getPropagationContext ( ) . traceId ;
331
+ const chunk = this . _initializeChunk ( traceId ) ;
332
+
305
333
CpuProfilerBindings . startProfiling ( chunk . id ) ;
306
334
DEBUG_BUILD && logger . log ( `[Profiling] starting profiling chunk: ${ chunk . id } ` ) ;
307
335
308
336
chunk . timer = global . setTimeout ( ( ) => {
309
337
DEBUG_BUILD && logger . log ( `[Profiling] Stopping profiling chunk: ${ chunk . id } ` ) ;
310
- this . stop ( ) ;
338
+ this . _chunkStop ( ) ;
311
339
DEBUG_BUILD && logger . log ( '[Profiling] Starting new profiling chunk.' ) ;
312
- setImmediate ( this . start . bind ( this ) ) ;
340
+ setImmediate ( this . _chunkStart . bind ( this ) ) ;
313
341
} , CHUNK_INTERVAL_MS ) ;
314
342
315
343
// Unref timeout so it doesn't keep the process alive.
@@ -323,40 +351,51 @@ class ContinuousProfiler {
323
351
private _setupSpanChunkInstrumentation ( ) : void {
324
352
if ( ! this . _client ) {
325
353
DEBUG_BUILD &&
326
- logger . log ( '[Profiling] Failed to collect profile, sentry client was never attached to the profiler.' ) ;
354
+ logger . log (
355
+ '[Profiling] Failed to initialize span profiling, sentry client was never attached to the profiler.' ,
356
+ ) ;
327
357
return ;
328
358
}
329
359
360
+ this . _profilerId = uuid4 ( ) ;
330
361
getGlobalScope ( ) . setContext ( 'profile' , {
331
362
profiler_id : this . _profilerId ,
332
363
} ) ;
364
+ }
333
365
334
- this . _client . on ( 'beforeSendEvent' , e => this . _assignThreadIdContext ( e ) ) ;
366
+ /**
367
+ * Assigns thread_id and thread name context to a profiled event if there is an active profiler session
368
+ */
369
+ private _onBeforeSendThreadContextAssignment ( event : Event ) : void {
370
+ if ( ! this . _client || ! this . _profilerId ) return ;
371
+ this . _assignThreadIdContext ( event ) ;
335
372
}
336
373
337
374
/**
338
375
* Clear profiling information from global context when a profile is not running.
339
376
*/
340
377
private _teardownSpanChunkInstrumentation ( ) : void {
378
+ this . _profilerId = undefined ;
341
379
const globalScope = getGlobalScope ( ) ;
342
380
globalScope . setContext ( 'profile' , { } ) ;
343
381
}
344
382
345
383
/**
346
384
* Initializes new profile chunk metadata
347
385
*/
348
- private _initializeChunk ( traceId : string ) : void {
386
+ private _initializeChunk ( traceId : string ) : ChunkData {
349
387
this . _chunkData = {
350
388
id : uuid4 ( ) ,
351
389
startTraceID : traceId ,
352
390
timer : undefined ,
353
391
} ;
392
+ return this . _chunkData ;
354
393
}
355
394
356
395
/**
357
396
* Assigns thread_id and thread name context to a profiled event.
358
397
*/
359
- private _assignThreadIdContext ( event : Event ) : any {
398
+ private _assignThreadIdContext ( event : Event ) : void {
360
399
if ( ! event ?. [ 'contexts' ] ?. [ 'profile' ] ) {
361
400
return ;
362
401
}
@@ -380,7 +419,7 @@ class ContinuousProfiler {
380
419
/**
381
420
* Resets the current chunk state.
382
421
*/
383
- private _reset ( ) : void {
422
+ private _resetChunkData ( ) : void {
384
423
this . _chunkData = undefined ;
385
424
}
386
425
}
0 commit comments