@sentry/profiling-node 10.53.1 → 10.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,622 +4,385 @@ import { isMainThread, threadId } from 'worker_threads';
4
4
  import * as os from 'os';
5
5
  import { env, versions } from 'process';
6
6
 
7
- /**
8
- * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
9
- *
10
- * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
11
- */
12
7
  const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
13
8
 
14
- const NODE_VERSION = parseSemver(process.versions.node) ;
9
+ const NODE_VERSION = parseSemver(process.versions.node);
15
10
  const NODE_MAJOR = NODE_VERSION.major;
16
11
 
17
- // We require the file because if we import it, it will be included in the bundle.
18
- // I guess tsc does not check file contents when it's imported.
19
12
  const PROFILER_THREAD_ID_STRING = String(threadId);
20
- const PROFILER_THREAD_NAME = isMainThread ? 'main' : 'worker';
21
- const FORMAT_VERSION = '1';
22
- const CONTINUOUS_FORMAT_VERSION = '2';
23
-
24
- // Machine properties (eval only once)
13
+ const PROFILER_THREAD_NAME = isMainThread ? "main" : "worker";
14
+ const FORMAT_VERSION = "1";
15
+ const CONTINUOUS_FORMAT_VERSION = "2";
25
16
  const PLATFORM = os.platform();
26
17
  const RELEASE = os.release();
27
18
  const VERSION = os.version();
28
19
  const TYPE = os.type();
29
20
  const MODEL = os.machine();
30
21
  const ARCH = os.arch();
31
-
32
- /**
33
- * Checks if the profile is a raw profile or a profile enriched with thread information.
34
- * @param {ThreadCpuProfile | RawThreadCpuProfile} profile
35
- * @returns {boolean}
36
- */
37
- function isRawThreadCpuProfile(
38
- profile,
39
- ) {
40
- return !('thread_metadata' in profile);
22
+ function isRawThreadCpuProfile(profile) {
23
+ return !("thread_metadata" in profile);
41
24
  }
42
-
43
- /**
44
- * Enriches the profile with threadId of the current thread.
45
- * This is done in node as we seem to not be able to get the info from C native code.
46
- *
47
- * @param {ThreadCpuProfile | RawThreadCpuProfile} profile
48
- * @returns {ThreadCpuProfile}
49
- */
50
- function enrichWithThreadInformation(
51
- profile,
52
- ) {
25
+ function enrichWithThreadInformation(profile) {
53
26
  if (!isRawThreadCpuProfile(profile)) {
54
27
  return profile;
55
28
  }
56
-
57
29
  return {
58
30
  samples: profile.samples,
59
31
  frames: profile.frames,
60
32
  stacks: profile.stacks,
61
33
  thread_metadata: {
62
34
  [PROFILER_THREAD_ID_STRING]: {
63
- name: PROFILER_THREAD_NAME,
64
- },
65
- },
66
- } ;
35
+ name: PROFILER_THREAD_NAME
36
+ }
37
+ }
38
+ };
67
39
  }
68
-
69
- /**
70
- * Creates a profiling envelope item, if the profile does not pass validation, returns null.
71
- * @param {RawThreadCpuProfile}
72
- * @param {Event}
73
- * @returns {Profile | null}
74
- */
75
40
  function createProfilingEvent(client, profile, event) {
76
41
  if (!isValidProfile(profile)) {
77
42
  return null;
78
43
  }
79
-
80
44
  return createProfilePayload(client, profile, {
81
- release: event.release ?? '',
82
- environment: event.environment ?? '',
83
- event_id: event.event_id ?? '',
84
- transaction: event.transaction ?? '',
85
- start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
86
- trace_id: event.contexts?.trace?.trace_id ?? '',
87
- profile_id: profile.profile_id,
45
+ release: event.release ?? "",
46
+ environment: event.environment ?? "",
47
+ event_id: event.event_id ?? "",
48
+ transaction: event.transaction ?? "",
49
+ start_timestamp: event.start_timestamp ? event.start_timestamp * 1e3 : Date.now(),
50
+ trace_id: event.contexts?.trace?.trace_id ?? "",
51
+ profile_id: profile.profile_id
88
52
  });
89
53
  }
90
-
91
- /**
92
- * Create a profile
93
- * @param {RawThreadCpuProfile} cpuProfile
94
- * @param {options}
95
- * @returns {Profile}
96
- */
97
- function createProfilePayload(
98
- client,
99
- cpuProfile,
100
- {
101
- release,
102
- environment,
103
- event_id,
104
- transaction,
105
- start_timestamp,
106
- trace_id,
107
- profile_id,
108
- }
109
-
110
- ,
111
- ) {
112
- // Log a warning if the profile has an invalid traceId (should be uuidv4).
113
- // All profiles and transactions are rejected if this is the case and we want to
114
- // warn users that this is happening if they enable debug flag
54
+ function createProfilePayload(client, cpuProfile, {
55
+ release,
56
+ environment,
57
+ event_id,
58
+ transaction,
59
+ start_timestamp,
60
+ trace_id,
61
+ profile_id
62
+ }) {
115
63
  if (trace_id?.length !== 32) {
116
64
  DEBUG_BUILD && debug.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
117
65
  }
118
-
119
66
  const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
120
-
121
67
  const profile = {
122
68
  event_id: profile_id,
123
69
  timestamp: new Date(start_timestamp).toISOString(),
124
- platform: 'node',
70
+ platform: "node",
125
71
  version: FORMAT_VERSION,
126
- release: release,
127
- environment: environment,
72
+ release,
73
+ environment,
128
74
  measurements: cpuProfile.measurements,
129
75
  runtime: {
130
- name: 'node',
131
- version: versions.node || '',
76
+ name: "node",
77
+ version: versions.node || ""
132
78
  },
133
79
  os: {
134
80
  name: PLATFORM,
135
81
  version: RELEASE,
136
- build_number: VERSION,
82
+ build_number: VERSION
137
83
  },
138
84
  device: {
139
- locale: env['LC_ALL'] || env['LC_MESSAGES'] || env['LANG'] || env['LANGUAGE'] || '',
85
+ locale: env["LC_ALL"] || env["LC_MESSAGES"] || env["LANG"] || env["LANGUAGE"] || "",
140
86
  model: MODEL,
141
87
  manufacturer: TYPE,
142
88
  architecture: ARCH,
143
- is_emulator: false,
89
+ is_emulator: false
144
90
  },
145
91
  debug_meta: {
146
- images: applyDebugMetadata(client, cpuProfile.resources),
92
+ images: applyDebugMetadata(client, cpuProfile.resources)
147
93
  },
148
- profile: enrichedThreadProfile ,
94
+ profile: enrichedThreadProfile,
149
95
  transaction: {
150
96
  name: transaction,
151
97
  id: event_id,
152
- trace_id: trace_id || '',
153
- active_thread_id: PROFILER_THREAD_ID_STRING,
154
- },
98
+ trace_id: trace_id || "",
99
+ active_thread_id: PROFILER_THREAD_ID_STRING
100
+ }
155
101
  };
156
-
157
102
  return profile;
158
103
  }
159
-
160
- /**
161
- * Create a profile chunk from raw thread profile
162
- * @param {RawThreadCpuProfile} cpuProfile
163
- * @param {options}
164
- * @returns {Profile}
165
- */
166
- function createProfileChunkPayload(
167
- client,
168
- cpuProfile,
169
- {
170
- release,
171
- environment,
172
- trace_id,
173
- profiler_id,
174
- chunk_id,
175
- sdk,
176
- }
177
-
178
- ,
179
- ) {
180
- // Log a warning if the profile has an invalid traceId (should be uuidv4).
181
- // All profiles and transactions are rejected if this is the case and we want to
182
- // warn users that this is happening if they enable debug flag
104
+ function createProfileChunkPayload(client, cpuProfile, {
105
+ release,
106
+ environment,
107
+ trace_id,
108
+ profiler_id,
109
+ chunk_id,
110
+ sdk
111
+ }) {
183
112
  if (trace_id?.length !== 32) {
184
113
  DEBUG_BUILD && debug.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
185
114
  }
186
-
187
115
  const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
188
-
189
116
  const profile = {
190
- chunk_id: chunk_id,
117
+ chunk_id,
191
118
  client_sdk: {
192
- name: sdk?.name ?? 'sentry.javascript.node',
193
- version: sdk?.version ?? '0.0.0',
119
+ name: sdk?.name ?? "sentry.javascript.node",
120
+ version: sdk?.version ?? "0.0.0"
194
121
  },
195
- profiler_id: profiler_id,
196
- platform: 'node',
122
+ profiler_id,
123
+ platform: "node",
197
124
  version: CONTINUOUS_FORMAT_VERSION,
198
- release: release,
199
- environment: environment,
125
+ release,
126
+ environment,
200
127
  measurements: cpuProfile.measurements,
201
128
  debug_meta: {
202
- images: applyDebugMetadata(client, cpuProfile.resources),
129
+ images: applyDebugMetadata(client, cpuProfile.resources)
203
130
  },
204
- profile: enrichedThreadProfile ,
131
+ profile: enrichedThreadProfile
205
132
  };
206
-
207
133
  return profile;
208
134
  }
209
-
210
- /**
211
- * Creates a profiling chunk envelope item, if the profile does not pass validation, returns null.
212
- */
213
- function createProfilingChunkEvent(
214
- client,
215
- options,
216
- profile,
217
- sdk,
218
- identifiers,
219
- ) {
135
+ function createProfilingChunkEvent(client, options, profile, sdk, identifiers) {
220
136
  if (!isValidProfileChunk(profile)) {
221
137
  return null;
222
138
  }
223
-
224
139
  return createProfileChunkPayload(client, profile, {
225
- release: options.release ?? '',
226
- environment: options.environment ?? '',
227
- trace_id: identifiers.trace_id ?? '',
140
+ release: options.release ?? "",
141
+ environment: options.environment ?? "",
142
+ trace_id: identifiers.trace_id ?? "",
228
143
  chunk_id: identifiers.chunk_id,
229
144
  profiler_id: identifiers.profiler_id,
230
- sdk,
145
+ sdk
231
146
  });
232
147
  }
233
-
234
- /**
235
- * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
236
- * @param {unknown} rate
237
- * @returns {boolean}
238
- */
239
148
  function isValidSampleRate(rate) {
240
- // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
241
- if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) {
242
- DEBUG_BUILD &&
243
- debug.warn(
244
- `[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
245
- rate,
246
- )} of type ${JSON.stringify(typeof rate)}.`,
247
- );
149
+ if (typeof rate !== "number" && typeof rate !== "boolean" || typeof rate === "number" && isNaN(rate)) {
150
+ DEBUG_BUILD && debug.warn(
151
+ `[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
152
+ rate
153
+ )} of type ${JSON.stringify(typeof rate)}.`
154
+ );
248
155
  return false;
249
156
  }
250
-
251
- // Boolean sample rates are always valid
252
157
  if (rate === true || rate === false) {
253
158
  return true;
254
159
  }
255
-
256
- // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
257
160
  if (rate < 0 || rate > 1) {
258
161
  DEBUG_BUILD && debug.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`);
259
162
  return false;
260
163
  }
261
164
  return true;
262
165
  }
263
-
264
- /**
265
- * Checks if the profile is valid and can be sent to Sentry.
266
- * @param {RawThreadCpuProfile} profile
267
- * @returns {boolean}
268
- */
269
166
  function isValidProfile(profile) {
270
167
  if (profile.samples.length <= 1) {
271
- DEBUG_BUILD &&
272
- // Log a warning if the profile has less than 2 samples so users can know why
273
- // they are not seeing any profiling data and we cant avoid the back and forth
274
- // of asking them to provide us with a dump of the profile data.
275
- debug.log('[Profiling] Discarding profile because it contains less than 2 samples');
168
+ DEBUG_BUILD && // Log a warning if the profile has less than 2 samples so users can know why
169
+ // they are not seeing any profiling data and we cant avoid the back and forth
170
+ // of asking them to provide us with a dump of the profile data.
171
+ debug.log("[Profiling] Discarding profile because it contains less than 2 samples");
276
172
  return false;
277
173
  }
278
-
279
174
  if (!profile.profile_id) {
280
175
  return false;
281
176
  }
282
-
283
177
  return true;
284
178
  }
285
-
286
- /**
287
- * Checks if the profile chunk is valid and can be sent to Sentry.
288
- * @param profile
289
- * @returns
290
- */
291
179
  function isValidProfileChunk(profile) {
292
180
  if (profile.samples.length <= 1) {
293
- DEBUG_BUILD &&
294
- // Log a warning if the profile has less than 2 samples so users can know why
295
- // they are not seeing any profiling data and we cant avoid the back and forth
296
- // of asking them to provide us with a dump of the profile data.
297
- debug.log('[Profiling] Discarding profile chunk because it contains less than 2 samples');
181
+ DEBUG_BUILD && // Log a warning if the profile has less than 2 samples so users can know why
182
+ // they are not seeing any profiling data and we cant avoid the back and forth
183
+ // of asking them to provide us with a dump of the profile data.
184
+ debug.log("[Profiling] Discarding profile chunk because it contains less than 2 samples");
298
185
  return false;
299
186
  }
300
-
301
187
  return true;
302
188
  }
303
-
304
- /**
305
- * Adds items to envelope if they are not already present - mutates the envelope.
306
- * @param {Envelope} envelope
307
- * @param {Profile[]} profiles
308
- * @returns {Envelope}
309
- */
310
189
  function addProfilesToEnvelope(envelope, profiles) {
311
190
  if (!profiles.length) {
312
191
  return envelope;
313
192
  }
314
-
315
193
  for (const profile of profiles) {
316
- // @ts-expect-error untyped envelope
317
- envelope[1].push([{ type: 'profile' }, profile]);
194
+ envelope[1].push([{ type: "profile" }, profile]);
318
195
  }
319
196
  return envelope;
320
197
  }
321
-
322
- /**
323
- * Finds transactions with profile_id context in the envelope
324
- * @param {Envelope} envelope
325
- * @returns {Event[]}
326
- */
327
198
  function findProfiledTransactionsFromEnvelope(envelope) {
328
199
  const events = [];
329
-
330
200
  forEachEnvelopeItem(envelope, (item, type) => {
331
- if (type !== 'transaction') {
201
+ if (type !== "transaction") {
332
202
  return;
333
203
  }
334
-
335
- // First item is the type, so we can skip it, everything else is an event
336
204
  for (let j = 1; j < item.length; j++) {
337
- const event = item[j] ;
338
-
205
+ const event = item[j];
339
206
  if (!event) {
340
- // Shouldn't happen, but lets be safe
341
207
  continue;
342
208
  }
343
-
344
209
  const profile_id = event.contexts?.profile?.profile_id;
345
-
346
210
  if (event && profile_id) {
347
211
  events.push(event);
348
212
  }
349
213
  }
350
214
  });
351
-
352
215
  return events;
353
216
  }
354
-
355
- /**
356
- * Creates event envelope headers for a profile chunk. This is separate from createEventEnvelopeHeaders util
357
- * as the profile chunk does not conform to the sentry event type
358
- */
359
- function createEventEnvelopeHeaders(
360
- sdkInfo,
361
- tunnel,
362
- dsn,
363
- ) {
217
+ function createEventEnvelopeHeaders(sdkInfo, tunnel, dsn) {
364
218
  return {
365
219
  event_id: uuid4(),
366
- sent_at: new Date().toISOString(),
367
- ...(sdkInfo && { sdk: sdkInfo }),
368
- ...(!!tunnel && dsn && { dsn: dsnToString(dsn) }),
220
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
221
+ ...sdkInfo && { sdk: sdkInfo },
222
+ ...!!tunnel && dsn && { dsn: dsnToString(dsn) }
369
223
  };
370
224
  }
371
-
372
- /**
373
- * Creates a standalone profile_chunk envelope.
374
- */
375
- function makeProfileChunkEnvelope(
376
- platform,
377
- chunk,
378
- sdkInfo,
379
- tunnel,
380
- dsn,
381
- ) {
225
+ function makeProfileChunkEnvelope(platform, chunk, sdkInfo, tunnel, dsn) {
382
226
  const profileChunkHeader = {
383
- type: 'profile_chunk',
384
- platform,
227
+ type: "profile_chunk",
228
+ platform
385
229
  };
386
-
387
230
  return createEnvelope(createEventEnvelopeHeaders(sdkInfo, tunnel, dsn), [
388
- [profileChunkHeader, chunk],
231
+ [profileChunkHeader, chunk]
389
232
  ]);
390
233
  }
391
-
392
- /**
393
- * Cross reference profile collected resources with debug_ids and return a list of debug images.
394
- * @param {string[]} resource_paths
395
- * @returns {DebugImage[]}
396
- */
397
234
  function applyDebugMetadata(client, resource_paths) {
398
235
  const options = client.getOptions();
399
-
400
236
  if (!options?.stackParser) {
401
237
  return [];
402
238
  }
403
-
404
239
  return getDebugImagesForResources(options.stackParser, resource_paths);
405
240
  }
406
241
 
407
- const MAX_PROFILE_DURATION_MS = 30 * 1000;
408
-
409
- /**
410
- * Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the
411
- * profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well
412
- */
413
- function maybeProfileSpan(
414
- client,
415
- span,
416
- customSamplingContext,
417
- ) {
418
- // Profiling is not supported in worker threads as the native CPU profiler's
419
- // sampling thread can race with V8's GC across isolates, causing heap corruption.
242
+ const MAX_PROFILE_DURATION_MS = 30 * 1e3;
243
+ function maybeProfileSpan(client, span, customSamplingContext) {
420
244
  if (!isMainThread) {
421
- DEBUG_BUILD && debug.log('[Profiling] Skipping span profiling in worker thread.');
245
+ DEBUG_BUILD && debug.log("[Profiling] Skipping span profiling in worker thread.");
422
246
  return;
423
247
  }
424
-
425
- // profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform
426
- // the actual multiplication to get the final rate, but we discard the profile if the span was sampled,
427
- // so anything after this block from here is based on the span sampling.
428
248
  if (!spanIsSampled(span)) {
429
249
  return;
430
250
  }
431
-
432
- // Client and options are required for profiling
433
251
  if (!client) {
434
- DEBUG_BUILD && debug.log('[Profiling] Profiling disabled, no client found.');
252
+ DEBUG_BUILD && debug.log("[Profiling] Profiling disabled, no client found.");
435
253
  return;
436
254
  }
437
-
438
255
  const options = client.getOptions();
439
256
  if (!options) {
440
- DEBUG_BUILD && debug.log('[Profiling] Profiling disabled, no options found.');
257
+ DEBUG_BUILD && debug.log("[Profiling] Profiling disabled, no options found.");
441
258
  return;
442
259
  }
443
-
444
260
  const profilesSampler = options.profilesSampler;
445
261
  let profilesSampleRate = options.profilesSampleRate;
446
-
447
- // Prefer sampler to sample rate if both are provided.
448
- if (typeof profilesSampler === 'function') {
449
- const { description: spanName = '<unknown>', data } = spanToJSON(span);
450
- // We bail out early if that is not the case
262
+ if (typeof profilesSampler === "function") {
263
+ const { description: spanName = "<unknown>", data } = spanToJSON(span);
451
264
  const parentSampled = true;
452
-
453
265
  profilesSampleRate = profilesSampler({
454
266
  name: spanName,
455
267
  attributes: data,
456
268
  parentSampled,
457
- ...customSamplingContext,
269
+ ...customSamplingContext
458
270
  });
459
271
  }
460
-
461
- // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
462
- // only valid values are booleans or numbers between 0 and 1.)
463
272
  if (!isValidSampleRate(profilesSampleRate)) {
464
- DEBUG_BUILD && debug.warn('[Profiling] Discarding profile because of invalid sample rate.');
273
+ DEBUG_BUILD && debug.warn("[Profiling] Discarding profile because of invalid sample rate.");
465
274
  return;
466
275
  }
467
-
468
- // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped
469
276
  if (!profilesSampleRate) {
470
- DEBUG_BUILD &&
471
- debug.log(
472
- `[Profiling] Discarding profile because ${
473
- typeof profilesSampler === 'function'
474
- ? 'profileSampler returned 0 or false'
475
- : 'a negative sampling decision was inherited or profileSampleRate is set to 0'
476
- }`,
477
- );
277
+ DEBUG_BUILD && debug.log(
278
+ `[Profiling] Discarding profile because ${typeof profilesSampler === "function" ? "profileSampler returned 0 or false" : "a negative sampling decision was inherited or profileSampleRate is set to 0"}`
279
+ );
478
280
  return;
479
281
  }
480
-
481
- // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
482
- // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
483
282
  const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate;
484
- // Check if we should sample this profile
485
283
  if (!sampled) {
486
- DEBUG_BUILD &&
487
- debug.log(
488
- `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number(
489
- profilesSampleRate,
490
- )})`,
491
- );
284
+ DEBUG_BUILD && debug.log(
285
+ `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number(
286
+ profilesSampleRate
287
+ )})`
288
+ );
492
289
  return;
493
290
  }
494
-
495
291
  const profile_id = uuid4();
496
292
  CpuProfilerBindings.startProfiling(profile_id);
497
293
  DEBUG_BUILD && debug.log(`[Profiling] started profiling transaction: ${spanToJSON(span).description}`);
498
-
499
- // set transaction context - do this regardless if profiling fails down the line
500
- // so that we can still see the profile_id in the transaction context
501
294
  return profile_id;
502
295
  }
503
-
504
- /**
505
- * Stops the profiler for profile_id and returns the profile
506
- * @param transaction
507
- * @param profile_id
508
- * @returns
509
- */
510
296
  function stopSpanProfile(span, profile_id) {
511
- // Should not happen, but satisfy the type checker and be safe regardless.
512
297
  if (!profile_id) {
513
298
  return null;
514
299
  }
515
-
516
300
  const profile = CpuProfilerBindings.stopProfiling(profile_id, 0);
517
301
  DEBUG_BUILD && debug.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(span).description}`);
518
-
519
- // In case of an overlapping span, stopProfiling may return null and silently ignore the overlapping profile.
520
302
  if (!profile) {
521
- DEBUG_BUILD &&
522
- debug.log(
523
- `[Profiling] profiler returned null profile for: ${spanToJSON(span).description}`,
524
- 'this may indicate an overlapping span or a call to stopProfiling with a profile title that was never started',
525
- );
303
+ DEBUG_BUILD && debug.log(
304
+ `[Profiling] profiler returned null profile for: ${spanToJSON(span).description}`,
305
+ "this may indicate an overlapping span or a call to stopProfiling with a profile title that was never started"
306
+ );
526
307
  return null;
527
308
  }
528
-
529
- // Assign profile_id to the profile
530
309
  profile.profile_id = profile_id;
531
310
  return profile;
532
311
  }
533
312
 
534
- const CHUNK_INTERVAL_MS = 1000 * 60;
313
+ const CHUNK_INTERVAL_MS = 1e3 * 60;
535
314
  const PROFILE_MAP = new LRUMap(50);
536
315
  const PROFILE_TIMEOUTS = {};
537
-
538
316
  function addToProfileQueue(profile_id, profile) {
539
317
  PROFILE_MAP.set(profile_id, profile);
540
318
  }
541
-
542
319
  function takeFromProfileQueue(profile_id) {
543
320
  const profile = PROFILE_MAP.get(profile_id);
544
321
  PROFILE_MAP.remove(profile_id);
545
322
  return profile;
546
323
  }
547
-
548
- class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this);ContinuousProfiler.prototype.__init3.call(this);ContinuousProfiler.prototype.__init4.call(this);ContinuousProfiler.prototype.__init5.call(this);ContinuousProfiler.prototype.__init6.call(this);ContinuousProfiler.prototype.__init7.call(this); }
549
-
550
- __init() {this._client = undefined;}
551
- __init2() {this._chunkData = undefined;}
552
- __init3() {this._mode = undefined;}
553
- __init4() {this._legacyProfilerMode = undefined;}
554
- __init5() {this._profileLifecycle = undefined;}
555
- __init6() {this._sampled = undefined;}
556
- __init7() {this._sessionSamplingRate = undefined;}
324
+ class ContinuousProfiler {
325
+ constructor() {
326
+ this._client = void 0;
327
+ this._chunkData = void 0;
328
+ this._mode = void 0;
329
+ this._legacyProfilerMode = void 0;
330
+ this._profileLifecycle = void 0;
331
+ this._sampled = void 0;
332
+ this._sessionSamplingRate = void 0;
333
+ }
557
334
  /**
558
335
  * Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler
559
336
  * methods called before the profiler is initialized will result in a noop action with debug logs.
560
337
  * @param client
561
338
  */
562
- initialize(client) {
339
+ initialize(client) {
563
340
  if (!isMainThread) {
564
- DEBUG_BUILD &&
565
- debug.warn(
566
- '[Profiling] nodeProfilingIntegration() does not support worker threads — profiling will be disabled for this thread.',
567
- );
341
+ DEBUG_BUILD && debug.warn(
342
+ "[Profiling] nodeProfilingIntegration() does not support worker threads \u2014 profiling will be disabled for this thread."
343
+ );
568
344
  return;
569
345
  }
570
-
571
346
  this._client = client;
572
347
  const options = client.getOptions();
573
-
574
348
  this._mode = getProfilingMode(options);
575
349
  this._sessionSamplingRate = Math.random();
576
350
  this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0);
577
- this._profileLifecycle = options.profileLifecycle ?? 'manual';
578
-
351
+ this._profileLifecycle = options.profileLifecycle ?? "manual";
579
352
  switch (this._mode) {
580
- case 'legacy': {
581
- this._legacyProfilerMode =
582
- 'profilesSampleRate' in options || 'profilesSampler' in options ? 'span' : 'continuous';
583
-
353
+ case "legacy": {
354
+ this._legacyProfilerMode = "profilesSampleRate" in options || "profilesSampler" in options ? "span" : "continuous";
584
355
  DEBUG_BUILD && debug.log(`[Profiling] Profiling mode is ${this._legacyProfilerMode}.`);
585
-
586
356
  switch (this._legacyProfilerMode) {
587
- case 'span': {
357
+ case "span": {
588
358
  this._setupAutomaticSpanProfiling();
589
359
  break;
590
360
  }
591
- case 'continuous': {
592
- // Continous mode requires manual calls to profiler.start() and profiler.stop()
361
+ case "continuous": {
593
362
  break;
594
363
  }
595
364
  default: {
596
- DEBUG_BUILD &&
597
- debug.warn(
598
- `[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`,
599
- );
365
+ DEBUG_BUILD && debug.warn(
366
+ `[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`
367
+ );
600
368
  break;
601
369
  }
602
370
  }
603
371
  break;
604
372
  }
605
-
606
- case 'current': {
373
+ case "current": {
607
374
  this._setupSpanChunkInstrumentation();
608
-
609
375
  DEBUG_BUILD && debug.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`);
610
-
611
376
  switch (this._profileLifecycle) {
612
- case 'trace': {
377
+ case "trace": {
613
378
  this._startTraceLifecycleProfiling();
614
379
  break;
615
380
  }
616
- case 'manual': {
617
- // Manual mode requires manual calls to profiler.startProfiler() and profiler.stopProfiler()
381
+ case "manual": {
618
382
  break;
619
383
  }
620
384
  default: {
621
- DEBUG_BUILD &&
622
- debug.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`);
385
+ DEBUG_BUILD && debug.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`);
623
386
  break;
624
387
  }
625
388
  }
@@ -630,350 +393,258 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
630
393
  break;
631
394
  }
632
395
  }
633
-
634
- // Attaches a listener to beforeSend which will add the threadId data to the event being sent.
635
- // This adds a constant overhead to all events being sent which could be improved to only attach
636
- // and detach the listener during a profiler session
637
- this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this));
396
+ this._client.on("beforeSendEvent", this._onBeforeSendThreadContextAssignment.bind(this));
638
397
  }
639
-
640
398
  /**
641
399
  * Initializes a new profilerId session and schedules chunk profiling.
642
400
  * @returns void
643
401
  */
644
- start() {
645
- if (this._mode === 'current') {
402
+ start() {
403
+ if (this._mode === "current") {
646
404
  this._startProfiler();
647
405
  return;
648
406
  }
649
-
650
407
  if (!this._client) {
651
- DEBUG_BUILD && debug.log('[Profiling] Failed to start, sentry client was never attached to the profiler.');
408
+ DEBUG_BUILD && debug.log("[Profiling] Failed to start, sentry client was never attached to the profiler.");
652
409
  return;
653
410
  }
654
-
655
- if (this._mode !== 'legacy') {
656
- DEBUG_BUILD && debug.log('[Profiling] Continuous profiling is not supported in the current mode.');
411
+ if (this._mode !== "legacy") {
412
+ DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
657
413
  return;
658
414
  }
659
-
660
- if (this._legacyProfilerMode === 'span') {
661
- DEBUG_BUILD && debug.log('[Profiling] Calls to profiler.start() are not supported in span profiling mode.');
415
+ if (this._legacyProfilerMode === "span") {
416
+ DEBUG_BUILD && debug.log("[Profiling] Calls to profiler.start() are not supported in span profiling mode.");
662
417
  return;
663
418
  }
664
-
665
- // Flush any existing chunks before starting a new one.
666
-
667
419
  this._stopChunkProfiling();
668
-
669
- // Restart the profiler session
670
420
  this._setupSpanChunkInstrumentation();
671
421
  this._restartChunkProfiling();
672
422
  }
673
-
674
423
  /**
675
424
  * Stops the current chunk and flushes the profile to Sentry.
676
425
  * @returns void
677
426
  */
678
- stop() {
679
- if (this._mode === 'current') {
427
+ stop() {
428
+ if (this._mode === "current") {
680
429
  this._stopProfiler();
681
430
  return;
682
431
  }
683
-
684
432
  if (!this._client) {
685
- DEBUG_BUILD && debug.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.');
433
+ DEBUG_BUILD && debug.log("[Profiling] Failed to stop, sentry client was never attached to the profiler.");
686
434
  return;
687
435
  }
688
-
689
- if (this._mode !== 'legacy') {
690
- DEBUG_BUILD && debug.log('[Profiling] Continuous profiling is not supported in the current mode.');
436
+ if (this._mode !== "legacy") {
437
+ DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
691
438
  return;
692
439
  }
693
-
694
- if (this._legacyProfilerMode === 'span') {
695
- DEBUG_BUILD && debug.log('[Profiling] Calls to profiler.stop() are not supported in span profiling mode.');
440
+ if (this._legacyProfilerMode === "span") {
441
+ DEBUG_BUILD && debug.log("[Profiling] Calls to profiler.stop() are not supported in span profiling mode.");
696
442
  return;
697
443
  }
698
-
699
444
  this._stopChunkProfiling();
700
445
  this._teardownSpanChunkInstrumentation();
701
446
  }
702
-
703
- _startProfiler() {
704
- if (this._mode !== 'current') {
705
- DEBUG_BUILD && debug.log('[Profiling] Continuous profiling is not supported in the current mode.');
447
+ _startProfiler() {
448
+ if (this._mode !== "current") {
449
+ DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
706
450
  return;
707
451
  }
708
-
709
- if (this._chunkData !== undefined) {
710
- DEBUG_BUILD && debug.log('[Profiling] Profile session already running, no-op.');
452
+ if (this._chunkData !== void 0) {
453
+ DEBUG_BUILD && debug.log("[Profiling] Profile session already running, no-op.");
711
454
  return;
712
455
  }
713
-
714
- if (this._mode === 'current') {
456
+ if (this._mode === "current") {
715
457
  if (!this._sampled) {
716
- DEBUG_BUILD && debug.log('[Profiling] Profile session not sampled, no-op.');
458
+ DEBUG_BUILD && debug.log("[Profiling] Profile session not sampled, no-op.");
717
459
  return;
718
460
  }
719
461
  }
720
-
721
- if (this._profileLifecycle === 'trace') {
722
- DEBUG_BUILD &&
723
- debug.log(
724
- '[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.',
725
- );
462
+ if (this._profileLifecycle === "trace") {
463
+ DEBUG_BUILD && debug.log(
464
+ "[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored."
465
+ );
726
466
  return;
727
467
  }
728
-
729
468
  this._startChunkProfiling();
730
469
  }
731
-
732
- _stopProfiler() {
733
- if (this._mode !== 'current') {
734
- DEBUG_BUILD && debug.log('[Profiling] Continuous profiling is not supported in the current mode.');
470
+ _stopProfiler() {
471
+ if (this._mode !== "current") {
472
+ DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
735
473
  return;
736
474
  }
737
-
738
- if (this._profileLifecycle === 'trace') {
739
- DEBUG_BUILD &&
740
- debug.log(
741
- '[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.',
742
- );
475
+ if (this._profileLifecycle === "trace") {
476
+ DEBUG_BUILD && debug.log(
477
+ "[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored."
478
+ );
743
479
  return;
744
480
  }
745
-
746
481
  if (!this._chunkData) {
747
- DEBUG_BUILD && debug.log('[Profiling] No profile session running, no-op.');
482
+ DEBUG_BUILD && debug.log("[Profiling] No profile session running, no-op.");
748
483
  return;
749
484
  }
750
-
751
485
  this._stopChunkProfiling();
752
486
  }
753
-
754
487
  /**
755
488
  * Starts trace lifecycle profiling. Profiling will remain active as long as there is an active span.
756
489
  */
757
- _startTraceLifecycleProfiling() {
490
+ _startTraceLifecycleProfiling() {
758
491
  if (!this._client) {
759
- DEBUG_BUILD &&
760
- debug.log(
761
- '[Profiling] Failed to start trace lifecycle profiling, sentry client was never attached to the profiler.',
762
- );
492
+ DEBUG_BUILD && debug.log(
493
+ "[Profiling] Failed to start trace lifecycle profiling, sentry client was never attached to the profiler."
494
+ );
763
495
  return;
764
496
  }
765
-
766
497
  let activeSpanCounter = 0;
767
- this._client.on('spanStart', _span => {
498
+ this._client.on("spanStart", (_span) => {
768
499
  if (activeSpanCounter === 0) {
769
500
  this._startChunkProfiling();
770
501
  }
771
502
  activeSpanCounter++;
772
503
  });
773
-
774
- this._client.on('spanEnd', _span => {
504
+ this._client.on("spanEnd", (_span) => {
775
505
  if (activeSpanCounter === 1) {
776
506
  this._stopChunkProfiling();
777
507
  }
778
508
  activeSpanCounter--;
779
509
  });
780
510
  }
781
-
782
- _setupAutomaticSpanProfiling() {
511
+ _setupAutomaticSpanProfiling() {
783
512
  if (!this._client) {
784
- DEBUG_BUILD &&
785
- debug.log(
786
- '[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler.',
787
- );
513
+ DEBUG_BUILD && debug.log(
514
+ "[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler."
515
+ );
788
516
  return;
789
517
  }
790
-
791
- const spanToProfileIdMap = new WeakMap();
792
-
793
- this._client.on('spanStart', span => {
518
+ const spanToProfileIdMap = /* @__PURE__ */ new WeakMap();
519
+ this._client.on("spanStart", (span) => {
794
520
  if (span !== getRootSpan(span)) {
795
521
  return;
796
522
  }
797
-
798
523
  const profile_id = maybeProfileSpan(this._client, span);
799
-
800
524
  if (profile_id) {
801
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
802
525
  const options = this._client.getOptions();
803
- // Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
804
- // currently exceed the default timeout set by the SDKs.
805
526
  const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS;
806
-
807
527
  if (PROFILE_TIMEOUTS[profile_id]) {
808
528
  global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
809
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
810
529
  delete PROFILE_TIMEOUTS[profile_id];
811
530
  }
812
-
813
- // Enqueue a timeout to prevent profiles from running over max duration.
814
531
  const timeout = global.setTimeout(() => {
815
- DEBUG_BUILD &&
816
- debug.log(
817
- '[Profiling] max profile duration elapsed, stopping profiling for:',
818
- spanToJSON(span).description,
819
- );
820
-
532
+ DEBUG_BUILD && debug.log(
533
+ "[Profiling] max profile duration elapsed, stopping profiling for:",
534
+ spanToJSON(span).description
535
+ );
821
536
  const profile = stopSpanProfile(span, profile_id);
822
537
  if (profile) {
823
538
  addToProfileQueue(profile_id, profile);
824
539
  }
825
540
  }, maxProfileDurationMs);
826
-
827
- // Unref timeout so it doesn't keep the process alive.
828
541
  timeout.unref();
829
-
830
- getIsolationScope().setContext('profile', { profile_id });
542
+ getIsolationScope().setContext("profile", { profile_id });
831
543
  spanToProfileIdMap.set(span, profile_id);
832
544
  }
833
545
  });
834
-
835
- this._client.on('spanEnd', span => {
546
+ this._client.on("spanEnd", (span) => {
836
547
  const profile_id = spanToProfileIdMap.get(span);
837
-
838
548
  if (profile_id) {
839
549
  if (PROFILE_TIMEOUTS[profile_id]) {
840
550
  global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
841
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
842
551
  delete PROFILE_TIMEOUTS[profile_id];
843
552
  }
844
553
  const profile = stopSpanProfile(span, profile_id);
845
-
846
554
  if (profile) {
847
555
  addToProfileQueue(profile_id, profile);
848
556
  }
849
557
  }
850
558
  });
851
-
852
- this._client.on('beforeEnvelope', (envelope) => {
853
- // if not profiles are in queue, there is nothing to add to the envelope.
559
+ this._client.on("beforeEnvelope", (envelope) => {
854
560
  if (!PROFILE_MAP.size) {
855
561
  return;
856
562
  }
857
-
858
563
  const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
859
564
  if (!profiledTransactionEvents.length) {
860
565
  return;
861
566
  }
862
-
863
567
  const profilesToAddToEnvelope = [];
864
-
865
568
  for (const profiledTransaction of profiledTransactionEvents) {
866
569
  const profileContext = profiledTransaction.contexts?.profile;
867
570
  const profile_id = profileContext?.profile_id;
868
-
869
571
  if (!profile_id) {
870
- throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
572
+ throw new TypeError("[Profiling] cannot find profile for a transaction without a profile context");
871
573
  }
872
-
873
- // Remove the profile from the transaction context before sending, relay will take care of the rest.
874
574
  if (profileContext) {
875
575
  delete profiledTransaction.contexts?.profile;
876
576
  }
877
-
878
577
  const cpuProfile = takeFromProfileQueue(profile_id);
879
578
  if (!cpuProfile) {
880
579
  DEBUG_BUILD && debug.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
881
580
  continue;
882
581
  }
883
-
884
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
885
582
  const profile = createProfilingEvent(this._client, cpuProfile, profiledTransaction);
886
583
  if (!profile) return;
887
-
888
584
  profilesToAddToEnvelope.push(profile);
889
-
890
- // @ts-expect-error profile does not inherit from Event
891
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
892
- this._client.emit('preprocessEvent', profile, {
893
- event_id: profiledTransaction.event_id,
585
+ this._client.emit("preprocessEvent", profile, {
586
+ event_id: profiledTransaction.event_id
894
587
  });
895
-
896
- // @ts-expect-error profile does not inherit from Event
897
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
898
- this._client.emit('postprocessEvent', profile, {
899
- event_id: profiledTransaction.event_id,
588
+ this._client.emit("postprocessEvent", profile, {
589
+ event_id: profiledTransaction.event_id
900
590
  });
901
591
  }
902
-
903
592
  addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
904
593
  });
905
594
  }
906
-
907
595
  /**
908
596
  * Stop profiler and initializes profiling of the next chunk
909
597
  */
910
- _restartChunkProfiling() {
598
+ _restartChunkProfiling() {
911
599
  if (!this._client) {
912
- // The client is not attached to the profiler if the user has not enabled continuous profiling.
913
- // In this case, calling start() and stop() is a noop action.The reason this exists is because
914
- // it makes the types easier to work with and avoids users having to do null checks.
915
- DEBUG_BUILD && debug.log('[Profiling] Profiler was never attached to the client.');
600
+ DEBUG_BUILD && debug.log("[Profiling] Profiler was never attached to the client.");
916
601
  return;
917
602
  }
918
-
919
603
  if (this._chunkData) {
920
- DEBUG_BUILD &&
921
- debug.log(
922
- `[Profiling] Chunk with chunk_id ${this._chunkData.id} is still running, current chunk will be stopped a new chunk will be started.`,
923
- );
604
+ DEBUG_BUILD && debug.log(
605
+ `[Profiling] Chunk with chunk_id ${this._chunkData.id} is still running, current chunk will be stopped a new chunk will be started.`
606
+ );
924
607
  this._stopChunkProfiling();
925
608
  }
926
-
927
609
  this._startChunkProfiling();
928
610
  }
929
-
930
611
  /**
931
612
  * Stops profiling of the current chunks and flushes the profile to Sentry
932
613
  */
933
- _stopChunkProfiling() {
614
+ _stopChunkProfiling() {
934
615
  if (!this._chunkData) {
935
- DEBUG_BUILD && debug.log('[Profiling] No chunk data found, no-op.');
616
+ DEBUG_BUILD && debug.log("[Profiling] No chunk data found, no-op.");
936
617
  return;
937
618
  }
938
-
939
619
  if (this._chunkData?.timer) {
940
620
  global.clearTimeout(this._chunkData.timer);
941
- this._chunkData.timer = undefined;
621
+ this._chunkData.timer = void 0;
942
622
  DEBUG_BUILD && debug.log(`[Profiling] Stopping profiling chunk: ${this._chunkData.id}`);
943
623
  }
944
-
945
624
  if (!this._client) {
946
- DEBUG_BUILD &&
947
- debug.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
625
+ DEBUG_BUILD && debug.log("[Profiling] Failed to collect profile, sentry client was never attached to the profiler.");
948
626
  this._resetChunkData();
949
627
  return;
950
628
  }
951
-
952
629
  if (!this._chunkData?.id) {
953
- DEBUG_BUILD &&
954
- debug.log(`[Profiling] Failed to collect profile for: ${this._chunkData?.id}, the chunk_id is missing.`);
630
+ DEBUG_BUILD && debug.log(`[Profiling] Failed to collect profile for: ${this._chunkData?.id}, the chunk_id is missing.`);
955
631
  this._resetChunkData();
956
632
  return;
957
633
  }
958
-
959
634
  const profile = CpuProfilerBindings.stopProfiling(this._chunkData.id, ProfileFormat.CHUNK);
960
-
961
635
  if (!profile) {
962
636
  DEBUG_BUILD && debug.log(`[Profiling] Failed to collect profile for: ${this._chunkData.id}`);
963
637
  this._resetChunkData();
964
638
  return;
965
639
  }
966
-
967
640
  if (!this._profilerId) {
968
- DEBUG_BUILD &&
969
- debug.log('[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK');
641
+ DEBUG_BUILD && debug.log("[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK");
970
642
  this._resetChunkData();
971
643
  return;
972
644
  }
973
645
  if (profile) {
974
646
  DEBUG_BUILD && debug.log(`[Profiling] Sending profile chunk ${this._chunkData.id}.`);
975
647
  }
976
-
977
648
  DEBUG_BUILD && debug.log(`[Profiling] Profile chunk ${this._chunkData.id} sent to Sentry.`);
978
649
  const chunk = createProfilingChunkEvent(
979
650
  this._client,
@@ -983,207 +654,156 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
983
654
  {
984
655
  chunk_id: this._chunkData.id,
985
656
  trace_id: this._chunkData.startTraceID,
986
- profiler_id: this._profilerId,
987
- },
657
+ profiler_id: this._profilerId
658
+ }
988
659
  );
989
-
990
660
  if (!chunk) {
991
661
  DEBUG_BUILD && debug.log(`[Profiling] Failed to create profile chunk for: ${this._chunkData.id}`);
992
662
  this._resetChunkData();
993
663
  return;
994
664
  }
995
-
996
665
  this._flush(chunk);
997
- // Depending on the profile and stack sizes, stopping the profile and converting
998
- // the format may negatively impact the performance of the application. To avoid
999
- // blocking for too long, enqueue the next chunk start inside the next macrotask.
1000
- // clear current chunk
1001
666
  this._resetChunkData();
1002
667
  }
1003
-
1004
668
  /**
1005
669
  * Flushes the profile chunk to Sentry.
1006
670
  * @param chunk
1007
671
  */
1008
- _flush(chunk) {
672
+ _flush(chunk) {
1009
673
  if (!this._client) {
1010
- DEBUG_BUILD &&
1011
- debug.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
674
+ DEBUG_BUILD && debug.log("[Profiling] Failed to collect profile, sentry client was never attached to the profiler.");
1012
675
  return;
1013
676
  }
1014
-
1015
677
  const transport = this._client.getTransport();
1016
678
  if (!transport) {
1017
- DEBUG_BUILD && debug.log('[Profiling] No transport available to send profile chunk.');
679
+ DEBUG_BUILD && debug.log("[Profiling] No transport available to send profile chunk.");
1018
680
  return;
1019
681
  }
1020
-
1021
682
  const dsn = this._client.getDsn();
1022
683
  const metadata = this._client.getSdkMetadata();
1023
684
  const tunnel = this._client.getOptions().tunnel;
1024
-
1025
- const envelope = makeProfileChunkEnvelope('node', chunk, metadata?.sdk, tunnel, dsn);
1026
- transport.send(envelope).then(null, reason => {
1027
- DEBUG_BUILD && debug.error('Error while sending profile chunk envelope:', reason);
685
+ const envelope = makeProfileChunkEnvelope("node", chunk, metadata?.sdk, tunnel, dsn);
686
+ transport.send(envelope).then(null, (reason) => {
687
+ DEBUG_BUILD && debug.error("Error while sending profile chunk envelope:", reason);
1028
688
  });
1029
689
  }
1030
-
1031
690
  /**
1032
691
  * Starts the profiler and registers the flush timer for a given chunk.
1033
692
  * @param chunk
1034
693
  */
1035
- _startChunkProfiling() {
694
+ _startChunkProfiling() {
1036
695
  if (this._chunkData) {
1037
- DEBUG_BUILD && debug.log('[Profiling] Chunk is already running, no-op.');
696
+ DEBUG_BUILD && debug.log("[Profiling] Chunk is already running, no-op.");
1038
697
  return;
1039
698
  }
1040
-
1041
- const traceId =
1042
- getCurrentScope().getPropagationContext().traceId || getIsolationScope().getPropagationContext().traceId;
699
+ const traceId = getCurrentScope().getPropagationContext().traceId || getIsolationScope().getPropagationContext().traceId;
1043
700
  const chunk = this._initializeChunk(traceId);
1044
-
1045
701
  CpuProfilerBindings.startProfiling(chunk.id);
1046
702
  DEBUG_BUILD && debug.log(`[Profiling] starting profiling chunk: ${chunk.id}`);
1047
-
1048
703
  chunk.timer = global.setTimeout(() => {
1049
704
  DEBUG_BUILD && debug.log(`[Profiling] Stopping profiling chunk: ${chunk.id}`);
1050
705
  this._stopChunkProfiling();
1051
- DEBUG_BUILD && debug.log('[Profiling] Starting new profiling chunk.');
706
+ DEBUG_BUILD && debug.log("[Profiling] Starting new profiling chunk.");
1052
707
  setImmediate(this._restartChunkProfiling.bind(this));
1053
708
  }, CHUNK_INTERVAL_MS);
1054
-
1055
- // Unref timeout so it doesn't keep the process alive.
1056
709
  chunk.timer.unref();
1057
710
  }
1058
-
1059
711
  /**
1060
712
  * Attaches profiling information to spans that were started
1061
713
  * during a profiling session.
1062
714
  */
1063
- _setupSpanChunkInstrumentation() {
715
+ _setupSpanChunkInstrumentation() {
1064
716
  if (!this._client) {
1065
- DEBUG_BUILD &&
1066
- debug.log('[Profiling] Failed to initialize span profiling, sentry client was never attached to the profiler.');
717
+ DEBUG_BUILD && debug.log("[Profiling] Failed to initialize span profiling, sentry client was never attached to the profiler.");
1067
718
  return;
1068
719
  }
1069
-
1070
720
  this._profilerId = uuid4();
1071
- getGlobalScope().setContext('profile', {
1072
- profiler_id: this._profilerId,
721
+ getGlobalScope().setContext("profile", {
722
+ profiler_id: this._profilerId
1073
723
  });
1074
724
  }
1075
-
1076
725
  /**
1077
726
  * Assigns thread_id and thread name context to a profiled event if there is an active profiler session
1078
727
  */
1079
- _onBeforeSendThreadContextAssignment(event) {
728
+ _onBeforeSendThreadContextAssignment(event) {
1080
729
  if (!this._client || !this._profilerId) return;
1081
730
  this._assignThreadIdContext(event);
1082
731
  }
1083
-
1084
732
  /**
1085
733
  * Clear profiling information from global context when a profile is not running.
1086
734
  */
1087
- _teardownSpanChunkInstrumentation() {
1088
- this._profilerId = undefined;
735
+ _teardownSpanChunkInstrumentation() {
736
+ this._profilerId = void 0;
1089
737
  const globalScope = getGlobalScope();
1090
- globalScope.setContext('profile', {});
738
+ globalScope.setContext("profile", {});
1091
739
  }
1092
-
1093
740
  /**
1094
741
  * Initializes new profile chunk metadata
1095
742
  */
1096
- _initializeChunk(traceId) {
743
+ _initializeChunk(traceId) {
1097
744
  this._chunkData = {
1098
745
  id: uuid4(),
1099
746
  startTraceID: traceId,
1100
- timer: undefined,
747
+ timer: void 0
1101
748
  };
1102
749
  return this._chunkData;
1103
750
  }
1104
-
1105
751
  /**
1106
752
  * Assigns thread_id and thread name context to a profiled event.
1107
753
  */
1108
- _assignThreadIdContext(event) {
754
+ _assignThreadIdContext(event) {
1109
755
  if (!event?.contexts?.profile) {
1110
756
  return;
1111
757
  }
1112
-
1113
758
  if (!event.contexts) {
1114
759
  return;
1115
760
  }
1116
-
1117
- // @ts-expect-error the trace fallback value is wrong, though it should never happen
1118
- // and in case it does, we dont want to override whatever was passed initially.
1119
761
  event.contexts.trace = {
1120
- ...(event.contexts?.trace ?? {}),
762
+ ...event.contexts?.trace ?? {},
1121
763
  data: {
1122
- ...(event.contexts?.trace?.data ?? {}),
1123
- ['thread.id']: PROFILER_THREAD_ID_STRING,
1124
- ['thread.name']: PROFILER_THREAD_NAME,
1125
- },
764
+ ...event.contexts?.trace?.data ?? {},
765
+ ["thread.id"]: PROFILER_THREAD_ID_STRING,
766
+ ["thread.name"]: PROFILER_THREAD_NAME
767
+ }
1126
768
  };
1127
769
  }
1128
-
1129
770
  /**
1130
771
  * Resets the current chunk state.
1131
772
  */
1132
- _resetChunkData() {
1133
- this._chunkData = undefined;
773
+ _resetChunkData() {
774
+ this._chunkData = void 0;
1134
775
  }
1135
776
  }
1136
-
1137
- /** Exported only for tests. */
1138
777
  const _nodeProfilingIntegration = (() => {
1139
778
  if (![16, 18, 20, 22, 24, 26].includes(NODE_MAJOR)) {
1140
779
  consoleSandbox(() => {
1141
- // eslint-disable-next-line no-console
1142
780
  console.warn(
1143
781
  `[Sentry Profiling] You are using a Node.js version that does not have prebuilt binaries (${NODE_MAJOR}).`,
1144
- 'The @sentry/profiling-node package only has prebuilt support for the following LTS versions of Node.js: 16, 18, 20, 22, 24.',
1145
- 'To use the @sentry/profiling-node package with this version of Node.js, you will need to compile the native addon from source.',
1146
- 'See: https://github.com/getsentry/sentry-javascript/tree/develop/packages/profiling-node#building-the-package-from-source',
782
+ "The @sentry/profiling-node package only has prebuilt support for the following LTS versions of Node.js: 16, 18, 20, 22, 24.",
783
+ "To use the @sentry/profiling-node package with this version of Node.js, you will need to compile the native addon from source.",
784
+ "See: https://github.com/getsentry/sentry-javascript/tree/develop/packages/profiling-node#building-the-package-from-source"
1147
785
  );
1148
786
  });
1149
787
  }
1150
-
1151
788
  return {
1152
- name: 'ProfilingIntegration',
789
+ name: "ProfilingIntegration",
1153
790
  _profiler: new ContinuousProfiler(),
1154
791
  setup(client) {
1155
- DEBUG_BUILD && debug.log('[Profiling] Profiling integration setup.');
792
+ DEBUG_BUILD && debug.log("[Profiling] Profiling integration setup.");
1156
793
  this._profiler.initialize(client);
1157
794
  return;
1158
- },
795
+ }
1159
796
  };
1160
- }) ;
1161
-
1162
- /**
1163
- * Determines the profiling mode based on the options.
1164
- * @param options
1165
- * @returns 'legacy' if the options are using the legacy profiling API, 'current' if the options are using the current profiling API
1166
- */
797
+ });
1167
798
  function getProfilingMode(options) {
1168
- // Legacy mode takes precedence over current mode
1169
- if ('profilesSampleRate' in options || 'profilesSampler' in options) {
1170
- return 'legacy';
799
+ if ("profilesSampleRate" in options || "profilesSampler" in options) {
800
+ return "legacy";
1171
801
  }
1172
-
1173
- if ('profileSessionSampleRate' in options || 'profileLifecycle' in options) {
1174
- return 'current';
802
+ if ("profileSessionSampleRate" in options || "profileLifecycle" in options) {
803
+ return "current";
1175
804
  }
1176
-
1177
- // If neither are set, we are in the legacy continuous profiling mode
1178
- return 'legacy';
805
+ return "legacy";
1179
806
  }
1180
-
1181
- /**
1182
- * We need this integration in order to send data to Sentry. We hook into the event processor
1183
- * and inspect each event to see if it is a transaction event and if that transaction event
1184
- * contains a profile on it's metadata. If that is the case, we create a profiling event envelope
1185
- * and delete the profile from the transaction metadata.
1186
- */
1187
807
  const nodeProfilingIntegration = defineIntegration(_nodeProfilingIntegration);
1188
808
 
1189
809
  export { nodeProfilingIntegration };