Skip to content

Commit afa79b6

Browse files
authored
ref(profiling): reinitialize profilerId on explicit stop calls (getsentry#13681)
Modifies the functionality of profiler start and stop calls to regenerate the profilerId
1 parent ee0b5b5 commit afa79b6

File tree

2 files changed

+135
-91
lines changed

2 files changed

+135
-91
lines changed

Diff for: packages/profiling-node/src/integration.ts

+73-34
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { CpuProfilerBindings } from './cpu_profiler';
1717
import { DEBUG_BUILD } from './debug-build';
1818
import { NODE_MAJOR, NODE_VERSION } from './nodeVersion';
1919
import { MAX_PROFILE_DURATION_MS, maybeProfileSpan, stopSpanProfile } from './spanProfileUtils';
20-
import type { RawChunkCpuProfile, RawThreadCpuProfile } from './types';
20+
import type { RawThreadCpuProfile } from './types';
2121
import { ProfileFormat } from './types';
2222
import { PROFILER_THREAD_NAME } from './utils';
2323

@@ -161,7 +161,7 @@ interface ChunkData {
161161
}
162162

163163
class 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

Comments
 (0)