@sentry/profiling-node 10.70.0 → 11.0.0-alpha.1

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.
@@ -1,8 +1,6 @@
1
- import { parseSemver, forEachEnvelopeItem, createEnvelope, debug, dsnToString, uuid4, getDebugImagesForResources, spanIsSampled, spanToJSON, LRUMap, defineIntegration, consoleSandbox, getRootSpan, getIsolationScope, getCurrentScope, getGlobalScope } from '@sentry/core';
1
+ import { parseSemver, createEnvelope, debug, dsnToString, uuid4, getDebugImagesForResources, defineIntegration, consoleSandbox, getCurrentScope, getGlobalScope } from '@sentry/core';
2
2
  import { CpuProfilerBindings, ProfileFormat } from '@sentry/node-cpu-profiler';
3
3
  import { isMainThread, threadId } from 'worker_threads';
4
- import * as os from 'os';
5
- import { env, versions } from 'process';
6
4
 
7
5
  const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
8
6
 
@@ -11,14 +9,7 @@ const NODE_MAJOR = NODE_VERSION.major;
11
9
 
12
10
  const PROFILER_THREAD_ID_STRING = String(threadId);
13
11
  const PROFILER_THREAD_NAME = isMainThread ? "main" : "worker";
14
- const FORMAT_VERSION = "1";
15
12
  const CONTINUOUS_FORMAT_VERSION = "2";
16
- const PLATFORM = os.platform();
17
- const RELEASE = os.release();
18
- const VERSION = os.version();
19
- const TYPE = os.type();
20
- const MODEL = os.machine();
21
- const ARCH = os.arch();
22
13
  function isRawThreadCpuProfile(profile) {
23
14
  return !("thread_metadata" in profile);
24
15
  }
@@ -37,70 +28,6 @@ function enrichWithThreadInformation(profile) {
37
28
  }
38
29
  };
39
30
  }
40
- function createProfilingEvent(client, profile, event) {
41
- if (!isValidProfile(profile)) {
42
- return null;
43
- }
44
- return createProfilePayload(client, profile, {
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
52
- });
53
- }
54
- function createProfilePayload(client, cpuProfile, {
55
- release,
56
- environment,
57
- event_id,
58
- transaction,
59
- start_timestamp,
60
- trace_id,
61
- profile_id
62
- }) {
63
- if (trace_id?.length !== 32) {
64
- DEBUG_BUILD && debug.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
65
- }
66
- const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
67
- const profile = {
68
- event_id: profile_id,
69
- timestamp: new Date(start_timestamp).toISOString(),
70
- platform: "node",
71
- version: FORMAT_VERSION,
72
- release,
73
- environment,
74
- measurements: cpuProfile.measurements,
75
- runtime: {
76
- name: "node",
77
- version: versions.node || ""
78
- },
79
- os: {
80
- name: PLATFORM,
81
- version: RELEASE,
82
- build_number: VERSION
83
- },
84
- device: {
85
- locale: env["LC_ALL"] || env["LC_MESSAGES"] || env["LANG"] || env["LANGUAGE"] || "",
86
- model: MODEL,
87
- manufacturer: TYPE,
88
- architecture: ARCH,
89
- is_emulator: false
90
- },
91
- debug_meta: {
92
- images: applyDebugMetadata(client, cpuProfile.resources)
93
- },
94
- profile: enrichedThreadProfile,
95
- transaction: {
96
- name: transaction,
97
- id: event_id,
98
- trace_id: trace_id || "",
99
- active_thread_id: PROFILER_THREAD_ID_STRING
100
- }
101
- };
102
- return profile;
103
- }
104
31
  function createProfileChunkPayload(client, cpuProfile, {
105
32
  release,
106
33
  environment,
@@ -145,37 +72,6 @@ function createProfilingChunkEvent(client, options, profile, sdk, identifiers) {
145
72
  sdk
146
73
  });
147
74
  }
148
- function isValidSampleRate(rate) {
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
- );
155
- return false;
156
- }
157
- if (rate === true || rate === false) {
158
- return true;
159
- }
160
- if (rate < 0 || rate > 1) {
161
- DEBUG_BUILD && debug.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`);
162
- return false;
163
- }
164
- return true;
165
- }
166
- function isValidProfile(profile) {
167
- if (profile.samples.length <= 1) {
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");
172
- return false;
173
- }
174
- if (!profile.profile_id) {
175
- return false;
176
- }
177
- return true;
178
- }
179
75
  function isValidProfileChunk(profile) {
180
76
  if (profile.samples.length <= 1) {
181
77
  DEBUG_BUILD && // Log a warning if the profile has less than 2 samples so users can know why
@@ -186,34 +82,6 @@ function isValidProfileChunk(profile) {
186
82
  }
187
83
  return true;
188
84
  }
189
- function addProfilesToEnvelope(envelope, profiles) {
190
- if (!profiles.length) {
191
- return envelope;
192
- }
193
- for (const profile of profiles) {
194
- envelope[1].push([{ type: "profile" }, profile]);
195
- }
196
- return envelope;
197
- }
198
- function findProfiledTransactionsFromEnvelope(envelope) {
199
- const events = [];
200
- forEachEnvelopeItem(envelope, (item, type) => {
201
- if (type !== "transaction") {
202
- return;
203
- }
204
- for (let j = 1; j < item.length; j++) {
205
- const event = item[j];
206
- if (!event) {
207
- continue;
208
- }
209
- const profile_id = event.contexts?.profile?.profile_id;
210
- if (event && profile_id) {
211
- events.push(event);
212
- }
213
- }
214
- });
215
- return events;
216
- }
217
85
  function createEventEnvelopeHeaders(sdkInfo, tunnel, dsn) {
218
86
  return {
219
87
  event_id: uuid4(),
@@ -239,94 +107,11 @@ function applyDebugMetadata(client, resource_paths) {
239
107
  return getDebugImagesForResources(options.stackParser, resource_paths);
240
108
  }
241
109
 
242
- const MAX_PROFILE_DURATION_MS = 30 * 1e3;
243
- function maybeProfileSpan(client, span, customSamplingContext) {
244
- if (!isMainThread) {
245
- DEBUG_BUILD && debug.log("[Profiling] Skipping span profiling in worker thread.");
246
- return;
247
- }
248
- if (!spanIsSampled(span)) {
249
- return;
250
- }
251
- if (!client) {
252
- DEBUG_BUILD && debug.log("[Profiling] Profiling disabled, no client found.");
253
- return;
254
- }
255
- const options = client.getOptions();
256
- if (!options) {
257
- DEBUG_BUILD && debug.log("[Profiling] Profiling disabled, no options found.");
258
- return;
259
- }
260
- const profilesSampler = options.profilesSampler;
261
- let profilesSampleRate = options.profilesSampleRate;
262
- if (typeof profilesSampler === "function") {
263
- const { description: spanName = "<unknown>", data } = spanToJSON(span);
264
- const parentSampled = true;
265
- profilesSampleRate = profilesSampler({
266
- name: spanName,
267
- attributes: data,
268
- parentSampled,
269
- ...customSamplingContext
270
- });
271
- }
272
- if (!isValidSampleRate(profilesSampleRate)) {
273
- DEBUG_BUILD && debug.warn("[Profiling] Discarding profile because of invalid sample rate.");
274
- return;
275
- }
276
- if (!profilesSampleRate) {
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
- );
280
- return;
281
- }
282
- const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate;
283
- if (!sampled) {
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
- );
289
- return;
290
- }
291
- const profile_id = uuid4();
292
- CpuProfilerBindings.startProfiling(profile_id);
293
- DEBUG_BUILD && debug.log(`[Profiling] started profiling transaction: ${spanToJSON(span).description}`);
294
- return profile_id;
295
- }
296
- function stopSpanProfile(span, profile_id) {
297
- if (!profile_id) {
298
- return null;
299
- }
300
- const profile = CpuProfilerBindings.stopProfiling(profile_id, 0);
301
- DEBUG_BUILD && debug.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(span).description}`);
302
- if (!profile) {
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
- );
307
- return null;
308
- }
309
- profile.profile_id = profile_id;
310
- return profile;
311
- }
312
-
313
110
  const CHUNK_INTERVAL_MS = 1e3 * 60;
314
- const PROFILE_MAP = new LRUMap(50);
315
- const PROFILE_TIMEOUTS = {};
316
- function addToProfileQueue(profile_id, profile) {
317
- PROFILE_MAP.set(profile_id, profile);
318
- }
319
- function takeFromProfileQueue(profile_id) {
320
- const profile = PROFILE_MAP.get(profile_id);
321
- PROFILE_MAP.remove(profile_id);
322
- return profile;
323
- }
324
111
  class ContinuousProfiler {
325
112
  constructor() {
326
113
  this._client = void 0;
327
114
  this._chunkData = void 0;
328
- this._mode = void 0;
329
- this._legacyProfilerMode = void 0;
330
115
  this._profileLifecycle = void 0;
331
116
  this._sampled = void 0;
332
117
  this._sessionSamplingRate = void 0;
@@ -345,51 +130,21 @@ class ContinuousProfiler {
345
130
  }
346
131
  this._client = client;
347
132
  const options = client.getOptions();
348
- this._mode = getProfilingMode(options);
349
133
  this._sessionSamplingRate = Math.random();
350
134
  this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0);
351
135
  this._profileLifecycle = options.profileLifecycle ?? "manual";
352
- switch (this._mode) {
353
- case "legacy": {
354
- this._legacyProfilerMode = "profilesSampleRate" in options || "profilesSampler" in options ? "span" : "continuous";
355
- DEBUG_BUILD && debug.log(`[Profiling] Profiling mode is ${this._legacyProfilerMode}.`);
356
- switch (this._legacyProfilerMode) {
357
- case "span": {
358
- this._setupAutomaticSpanProfiling();
359
- break;
360
- }
361
- case "continuous": {
362
- break;
363
- }
364
- default: {
365
- DEBUG_BUILD && debug.warn(
366
- `[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`
367
- );
368
- break;
369
- }
370
- }
136
+ this._setupSpanChunkInstrumentation();
137
+ DEBUG_BUILD && debug.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`);
138
+ switch (this._profileLifecycle) {
139
+ case "trace": {
140
+ this._startTraceLifecycleProfiling();
371
141
  break;
372
142
  }
373
- case "current": {
374
- this._setupSpanChunkInstrumentation();
375
- DEBUG_BUILD && debug.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`);
376
- switch (this._profileLifecycle) {
377
- case "trace": {
378
- this._startTraceLifecycleProfiling();
379
- break;
380
- }
381
- case "manual": {
382
- break;
383
- }
384
- default: {
385
- DEBUG_BUILD && debug.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`);
386
- break;
387
- }
388
- }
143
+ case "manual": {
389
144
  break;
390
145
  }
391
146
  default: {
392
- DEBUG_BUILD && debug.warn(`[Profiling] Unknown profiler mode: ${this._mode}, profiler was not initialized`);
147
+ DEBUG_BUILD && debug.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`);
393
148
  break;
394
149
  }
395
150
  }
@@ -400,64 +155,26 @@ class ContinuousProfiler {
400
155
  * @returns void
401
156
  */
402
157
  start() {
403
- if (this._mode === "current") {
404
- this._startProfiler();
405
- return;
406
- }
407
158
  if (!this._client) {
408
- DEBUG_BUILD && debug.log("[Profiling] Failed to start, sentry client was never attached to the profiler.");
409
- return;
410
- }
411
- if (this._mode !== "legacy") {
412
- DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
413
- return;
414
- }
415
- if (this._legacyProfilerMode === "span") {
416
- DEBUG_BUILD && debug.log("[Profiling] Calls to profiler.start() are not supported in span profiling mode.");
159
+ DEBUG_BUILD && debug.log("[Profiling] Failed to start, Sentry client was never attached to the profiler.");
417
160
  return;
418
161
  }
419
- this._stopChunkProfiling();
420
- this._setupSpanChunkInstrumentation();
421
- this._restartChunkProfiling();
162
+ this._startProfiler();
422
163
  }
423
164
  /**
424
165
  * Stops the current chunk and flushes the profile to Sentry.
425
- * @returns void
426
166
  */
427
167
  stop() {
428
- if (this._mode === "current") {
429
- this._stopProfiler();
430
- return;
431
- }
432
- if (!this._client) {
433
- DEBUG_BUILD && debug.log("[Profiling] Failed to stop, sentry client was never attached to the profiler.");
434
- return;
435
- }
436
- if (this._mode !== "legacy") {
437
- DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
438
- return;
439
- }
440
- if (this._legacyProfilerMode === "span") {
441
- DEBUG_BUILD && debug.log("[Profiling] Calls to profiler.stop() are not supported in span profiling mode.");
442
- return;
443
- }
444
- this._stopChunkProfiling();
445
- this._teardownSpanChunkInstrumentation();
168
+ this._stopProfiler();
446
169
  }
447
170
  _startProfiler() {
448
- if (this._mode !== "current") {
449
- DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
450
- return;
451
- }
452
171
  if (this._chunkData !== void 0) {
453
172
  DEBUG_BUILD && debug.log("[Profiling] Profile session already running, no-op.");
454
173
  return;
455
174
  }
456
- if (this._mode === "current") {
457
- if (!this._sampled) {
458
- DEBUG_BUILD && debug.log("[Profiling] Profile session not sampled, no-op.");
459
- return;
460
- }
175
+ if (!this._sampled) {
176
+ DEBUG_BUILD && debug.log("[Profiling] Profile session not sampled, no-op.");
177
+ return;
461
178
  }
462
179
  if (this._profileLifecycle === "trace") {
463
180
  DEBUG_BUILD && debug.log(
@@ -468,10 +185,6 @@ class ContinuousProfiler {
468
185
  this._startChunkProfiling();
469
186
  }
470
187
  _stopProfiler() {
471
- if (this._mode !== "current") {
472
- DEBUG_BUILD && debug.log("[Profiling] Continuous profiling is not supported in the current mode.");
473
- return;
474
- }
475
188
  if (this._profileLifecycle === "trace") {
476
189
  DEBUG_BUILD && debug.log(
477
190
  "[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored."
@@ -512,90 +225,6 @@ class ContinuousProfiler {
512
225
  activeSpanCounter--;
513
226
  });
514
227
  }
515
- _setupAutomaticSpanProfiling() {
516
- if (!this._client) {
517
- DEBUG_BUILD && debug.log(
518
- "[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler."
519
- );
520
- return;
521
- }
522
- const spanToProfileIdMap = /* @__PURE__ */ new WeakMap();
523
- this._client.on("spanStart", (span) => {
524
- if (span !== getRootSpan(span)) {
525
- return;
526
- }
527
- const profile_id = maybeProfileSpan(this._client, span);
528
- if (profile_id) {
529
- const options = this._client.getOptions();
530
- const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS;
531
- if (PROFILE_TIMEOUTS[profile_id]) {
532
- global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
533
- delete PROFILE_TIMEOUTS[profile_id];
534
- }
535
- const timeout = global.setTimeout(() => {
536
- DEBUG_BUILD && debug.log(
537
- "[Profiling] max profile duration elapsed, stopping profiling for:",
538
- spanToJSON(span).description
539
- );
540
- const profile = stopSpanProfile(span, profile_id);
541
- if (profile) {
542
- addToProfileQueue(profile_id, profile);
543
- }
544
- }, maxProfileDurationMs);
545
- timeout.unref();
546
- getIsolationScope().setContext("profile", { profile_id });
547
- spanToProfileIdMap.set(span, profile_id);
548
- }
549
- });
550
- this._client.on("spanEnd", (span) => {
551
- const profile_id = spanToProfileIdMap.get(span);
552
- if (profile_id) {
553
- if (PROFILE_TIMEOUTS[profile_id]) {
554
- global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
555
- delete PROFILE_TIMEOUTS[profile_id];
556
- }
557
- const profile = stopSpanProfile(span, profile_id);
558
- if (profile) {
559
- addToProfileQueue(profile_id, profile);
560
- }
561
- }
562
- });
563
- this._client.on("beforeEnvelope", (envelope) => {
564
- if (!PROFILE_MAP.size) {
565
- return;
566
- }
567
- const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
568
- if (!profiledTransactionEvents.length) {
569
- return;
570
- }
571
- const profilesToAddToEnvelope = [];
572
- for (const profiledTransaction of profiledTransactionEvents) {
573
- const profileContext = profiledTransaction.contexts?.profile;
574
- const profile_id = profileContext?.profile_id;
575
- if (!profile_id) {
576
- throw new TypeError("[Profiling] cannot find profile for a transaction without a profile context");
577
- }
578
- if (profileContext) {
579
- delete profiledTransaction.contexts?.profile;
580
- }
581
- const cpuProfile = takeFromProfileQueue(profile_id);
582
- if (!cpuProfile) {
583
- DEBUG_BUILD && debug.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
584
- continue;
585
- }
586
- const profile = createProfilingEvent(this._client, cpuProfile, profiledTransaction);
587
- if (!profile) return;
588
- profilesToAddToEnvelope.push(profile);
589
- this._client.emit("preprocessEvent", profile, {
590
- event_id: profiledTransaction.event_id
591
- });
592
- this._client.emit("postprocessEvent", profile, {
593
- event_id: profiledTransaction.event_id
594
- });
595
- }
596
- addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
597
- });
598
- }
599
228
  /**
600
229
  * Stop profiler and initializes profiling of the next chunk
601
230
  */
@@ -700,7 +329,7 @@ class ContinuousProfiler {
700
329
  DEBUG_BUILD && debug.log("[Profiling] Chunk is already running, no-op.");
701
330
  return;
702
331
  }
703
- const traceId = getCurrentScope().getPropagationContext().traceId || getIsolationScope().getPropagationContext().traceId;
332
+ const traceId = getCurrentScope().getPropagationContext().traceId;
704
333
  const chunk = this._initializeChunk(traceId);
705
334
  CpuProfilerBindings.startProfiling(chunk.id);
706
335
  DEBUG_BUILD && debug.log(`[Profiling] starting profiling chunk: ${chunk.id}`);
@@ -799,15 +428,6 @@ const _nodeProfilingIntegration = (() => {
799
428
  }
800
429
  };
801
430
  });
802
- function getProfilingMode(options) {
803
- if ("profilesSampleRate" in options || "profilesSampler" in options) {
804
- return "legacy";
805
- }
806
- if ("profileSessionSampleRate" in options || "profileLifecycle" in options) {
807
- return "current";
808
- }
809
- return "legacy";
810
- }
811
431
  const nodeProfilingIntegration = defineIntegration(_nodeProfilingIntegration);
812
432
 
813
433
  export { nodeProfilingIntegration };