@@ -17,7 +17,7 @@ import { CpuProfilerBindings } from './cpu_profiler';
1717import { DEBUG_BUILD } from './debug-build' ;
1818import { NODE_MAJOR , NODE_VERSION } from './nodeVersion' ;
1919import { MAX_PROFILE_DURATION_MS , maybeProfileSpan , stopSpanProfile } from './spanProfileUtils' ;
20- import type { RawChunkCpuProfile , RawThreadCpuProfile } from './types' ;
20+ import type { RawThreadCpuProfile } from './types' ;
2121import { ProfileFormat } from './types' ;
2222import { PROFILER_THREAD_NAME } from './utils' ;
2323
@@ -161,7 +161,7 @@ interface ChunkData {
161161}
162162
163163class ContinuousProfiler {
164- private _profilerId = uuid4 ( ) ;
164+ private _profilerId : string | undefined ;
165165 private _client : NodeClient | undefined = undefined ;
166166 private _chunkData : ChunkData | undefined = undefined ;
167167
@@ -172,15 +172,48 @@ class ContinuousProfiler {
172172 */
173173 public initialize ( client : NodeClient ) : void {
174174 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 ) ) ;
175180 }
176181
177182 /**
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.
181184 * @returns void
182185 */
183186 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 {
184217 if ( ! this . _client ) {
185218 // The client is not attached to the profiler if the user has not enabled continuous profiling.
186219 // In this case, calling start() and stop() is a noop action.The reason this exists is because
@@ -193,20 +226,16 @@ class ContinuousProfiler {
193226 logger . log (
194227 `[Profiling] Chunk with chunk_id ${ this . _chunkData . id } is still running, current chunk will be stopped a new chunk will be started.` ,
195228 ) ;
196- this . stop ( ) ;
229+ this . _chunkStop ( ) ;
197230 }
198231
199- const traceId =
200- getCurrentScope ( ) . getPropagationContext ( ) . traceId || getIsolationScope ( ) . getPropagationContext ( ) . traceId ;
201- this . _initializeChunk ( traceId ) ;
202- this . _startChunkProfiling ( this . _chunkData ! ) ;
232+ this . _startChunkProfiling ( ) ;
203233 }
204234
205235 /**
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
208237 */
209- public stop ( ) : void {
238+ private _chunkStop ( ) : void {
210239 if ( this . _chunkData ?. timer ) {
211240 global . clearTimeout ( this . _chunkData . timer ) ;
212241 this . _chunkData . timer = undefined ;
@@ -223,12 +252,17 @@ class ContinuousProfiler {
223252 return ;
224253 }
225254
226- const profile = this . _stopChunkProfiling ( this . _chunkData ) ;
255+ const profile = CpuProfilerBindings . stopProfiling ( this . _chunkData . id , ProfileFormat . CHUNK ) ;
227256
228257 if ( ! profile ) {
229258 DEBUG_BUILD && logger . log ( `[Profiling] _chunkiledStartTraceID to collect profile for: ${ this . _chunkData . id } ` ) ;
230259 return ;
231260 }
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+ }
232266 if ( profile ) {
233267 DEBUG_BUILD && logger . log ( `[Profiling] Sending profile chunk ${ this . _chunkData . id } .` ) ;
234268 }
@@ -248,7 +282,7 @@ class ContinuousProfiler {
248282
249283 if ( ! chunk ) {
250284 DEBUG_BUILD && logger . log ( `[Profiling] Failed to create profile chunk for: ${ this . _chunkData . id } ` ) ;
251- this . _reset ( ) ;
285+ this . _resetChunkData ( ) ;
252286 return ;
253287 }
254288
@@ -257,7 +291,7 @@ class ContinuousProfiler {
257291 // the format may negatively impact the performance of the application. To avoid
258292 // blocking for too long, enqueue the next chunk start inside the next macrotask.
259293 // clear current chunk
260- this . _reset ( ) ;
294+ this . _resetChunkData ( ) ;
261295 }
262296
263297 /**
@@ -287,29 +321,23 @@ class ContinuousProfiler {
287321 } ) ;
288322 }
289323
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-
299324 /**
300325 * Starts the profiler and registers the flush timer for a given chunk.
301326 * @param chunk
302327 */
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+
305333 CpuProfilerBindings . startProfiling ( chunk . id ) ;
306334 DEBUG_BUILD && logger . log ( `[Profiling] starting profiling chunk: ${ chunk . id } ` ) ;
307335
308336 chunk . timer = global . setTimeout ( ( ) => {
309337 DEBUG_BUILD && logger . log ( `[Profiling] Stopping profiling chunk: ${ chunk . id } ` ) ;
310- this . stop ( ) ;
338+ this . _chunkStop ( ) ;
311339 DEBUG_BUILD && logger . log ( '[Profiling] Starting new profiling chunk.' ) ;
312- setImmediate ( this . start . bind ( this ) ) ;
340+ setImmediate ( this . _chunkStart . bind ( this ) ) ;
313341 } , CHUNK_INTERVAL_MS ) ;
314342
315343 // Unref timeout so it doesn't keep the process alive.
@@ -323,40 +351,51 @@ class ContinuousProfiler {
323351 private _setupSpanChunkInstrumentation ( ) : void {
324352 if ( ! this . _client ) {
325353 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+ ) ;
327357 return ;
328358 }
329359
360+ this . _profilerId = uuid4 ( ) ;
330361 getGlobalScope ( ) . setContext ( 'profile' , {
331362 profiler_id : this . _profilerId ,
332363 } ) ;
364+ }
333365
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 ) ;
335372 }
336373
337374 /**
338375 * Clear profiling information from global context when a profile is not running.
339376 */
340377 private _teardownSpanChunkInstrumentation ( ) : void {
378+ this . _profilerId = undefined ;
341379 const globalScope = getGlobalScope ( ) ;
342380 globalScope . setContext ( 'profile' , { } ) ;
343381 }
344382
345383 /**
346384 * Initializes new profile chunk metadata
347385 */
348- private _initializeChunk ( traceId : string ) : void {
386+ private _initializeChunk ( traceId : string ) : ChunkData {
349387 this . _chunkData = {
350388 id : uuid4 ( ) ,
351389 startTraceID : traceId ,
352390 timer : undefined ,
353391 } ;
392+ return this . _chunkData ;
354393 }
355394
356395 /**
357396 * Assigns thread_id and thread name context to a profiled event.
358397 */
359- private _assignThreadIdContext ( event : Event ) : any {
398+ private _assignThreadIdContext ( event : Event ) : void {
360399 if ( ! event ?. [ 'contexts' ] ?. [ 'profile' ] ) {
361400 return ;
362401 }
@@ -380,7 +419,7 @@ class ContinuousProfiler {
380419 /**
381420 * Resets the current chunk state.
382421 */
383- private _reset ( ) : void {
422+ private _resetChunkData ( ) : void {
384423 this . _chunkData = undefined ;
385424 }
386425}
0 commit comments