@sentry/profiling-node 8.7.0 → 8.9.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.
- package/bindings/cpu_profiler.cc +174 -73
- package/lib/cjs/index.js +479 -154
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/index.js +478 -154
- package/lib/esm/index.js.map +1 -1
- package/lib/sentry_cpu_profiler-darwin-arm64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-127.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-93.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-127.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-93.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-127.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-93.node +0 -0
- package/lib/types/profiling-node/src/cpu_profiler.d.ts +16 -0
- package/lib/types/profiling-node/src/cpu_profiler.d.ts.map +1 -0
- package/lib/types/profiling-node/src/debug-build.d.ts.map +1 -0
- package/lib/types/profiling-node/src/index.d.ts.map +1 -0
- package/lib/types/profiling-node/src/integration.d.ts +57 -0
- package/lib/types/profiling-node/src/integration.d.ts.map +1 -0
- package/lib/types/profiling-node/src/nodeVersion.d.ts.map +1 -0
- package/lib/types/{spanProfileUtils.d.ts → profiling-node/src/spanProfileUtils.d.ts} +2 -2
- package/lib/types/profiling-node/src/spanProfileUtils.d.ts.map +1 -0
- package/lib/types/profiling-node/src/types.d.ts +60 -0
- package/lib/types/profiling-node/src/types.d.ts.map +1 -0
- package/lib/types/{utils.d.ts → profiling-node/src/utils.d.ts} +30 -4
- package/lib/types/profiling-node/src/utils.d.ts.map +1 -0
- package/lib/types/types/src/debugMeta.d.ts +28 -0
- package/lib/types/types/src/debugMeta.d.ts.map +1 -0
- package/lib/types/types/src/measurement.d.ts +24 -0
- package/lib/types/types/src/measurement.d.ts.map +1 -0
- package/lib/types/types/src/profiling.d.ts +126 -0
- package/lib/types/types/src/profiling.d.ts.map +1 -0
- package/lib/types-ts3.8/profiling-node/src/cpu_profiler.d.ts +16 -0
- package/lib/types-ts3.8/profiling-node/src/integration.d.ts +57 -0
- package/lib/types-ts3.8/{spanProfileUtils.d.ts → profiling-node/src/spanProfileUtils.d.ts} +2 -2
- package/lib/types-ts3.8/profiling-node/src/types.d.ts +60 -0
- package/lib/types-ts3.8/{utils.d.ts → profiling-node/src/utils.d.ts} +30 -4
- package/lib/types-ts3.8/types/src/debugMeta.d.ts +28 -0
- package/lib/types-ts3.8/types/src/measurement.d.ts +24 -0
- package/lib/types-ts3.8/types/src/profiling.d.ts +126 -0
- package/package.json +5 -5
- package/lib/types/cpu_profiler.d.ts +0 -10
- package/lib/types/cpu_profiler.d.ts.map +0 -1
- package/lib/types/debug-build.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/integration.d.ts +0 -14
- package/lib/types/integration.d.ts.map +0 -1
- package/lib/types/nodeVersion.d.ts.map +0 -1
- package/lib/types/spanProfileUtils.d.ts.map +0 -1
- package/lib/types/types.d.ts +0 -100
- package/lib/types/types.d.ts.map +0 -1
- package/lib/types/utils.d.ts.map +0 -1
- package/lib/types-ts3.8/cpu_profiler.d.ts +0 -10
- package/lib/types-ts3.8/integration.d.ts +0 -14
- package/lib/types-ts3.8/types.d.ts +0 -100
- /package/lib/types/{debug-build.d.ts → profiling-node/src/debug-build.d.ts} +0 -0
- /package/lib/types/{index.d.ts → profiling-node/src/index.d.ts} +0 -0
- /package/lib/types/{nodeVersion.d.ts → profiling-node/src/nodeVersion.d.ts} +0 -0
- /package/lib/types-ts3.8/{debug-build.d.ts → profiling-node/src/debug-build.d.ts} +0 -0
- /package/lib/types-ts3.8/{index.d.ts → profiling-node/src/index.d.ts} +0 -0
- /package/lib/types-ts3.8/{nodeVersion.d.ts → profiling-node/src/nodeVersion.d.ts} +0 -0
package/lib/cjs/index.js
CHANGED
|
@@ -8,12 +8,15 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
8
8
|
|
|
9
9
|
const core = require('@sentry/core');
|
|
10
10
|
const utils = require('@sentry/utils');
|
|
11
|
-
const
|
|
11
|
+
const node_os = require('node:os');
|
|
12
12
|
const node_path = require('node:path');
|
|
13
13
|
const node_process = require('node:process');
|
|
14
14
|
const node_worker_threads = require('node:worker_threads');
|
|
15
15
|
const detectLibc = require('detect-libc');
|
|
16
16
|
const nodeAbi = require('node-abi');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const process$1 = require('process');
|
|
19
|
+
const worker_threads = require('worker_threads');
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* 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.
|
|
@@ -22,12 +25,9 @@ const nodeAbi = require('node-abi');
|
|
|
22
25
|
*/
|
|
23
26
|
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
24
27
|
|
|
25
|
-
const NODE_VERSION = utils.parseSemver(process.versions.node) ;
|
|
26
|
-
const NODE_MAJOR = NODE_VERSION.major;
|
|
27
|
-
|
|
28
28
|
const stdlib = detectLibc.familySync();
|
|
29
|
-
const platform = process.env['BUILD_PLATFORM'] ||
|
|
30
|
-
const arch = process.env['BUILD_ARCH'] ||
|
|
29
|
+
const platform = process.env['BUILD_PLATFORM'] || node_os.platform();
|
|
30
|
+
const arch = process.env['BUILD_ARCH'] || node_os.arch();
|
|
31
31
|
const abi = nodeAbi.getAbi(node_process.versions.node, 'node');
|
|
32
32
|
const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-');
|
|
33
33
|
|
|
@@ -167,31 +167,49 @@ function importCppBindingsModule() {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
const PrivateCpuProfilerBindings = importCppBindingsModule();
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
|
|
171
|
+
class Bindings {
|
|
172
|
+
startProfiling(name) {
|
|
172
173
|
if (!PrivateCpuProfilerBindings) {
|
|
173
174
|
DEBUG_BUILD && utils.logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.');
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
return PrivateCpuProfilerBindings.startProfiling(name);
|
|
178
|
-
}
|
|
179
|
-
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
stopProfiling(
|
|
182
|
+
name,
|
|
183
|
+
format,
|
|
184
|
+
) {
|
|
180
185
|
if (!PrivateCpuProfilerBindings) {
|
|
181
186
|
DEBUG_BUILD &&
|
|
182
187
|
utils.logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.');
|
|
183
188
|
return null;
|
|
184
189
|
}
|
|
185
|
-
return PrivateCpuProfilerBindings.stopProfiling(name, node_worker_threads.threadId, !!utils.GLOBAL_OBJ._sentryDebugIds);
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
return PrivateCpuProfilerBindings.stopProfiling(
|
|
192
|
+
name,
|
|
193
|
+
format ,
|
|
194
|
+
node_worker_threads.threadId,
|
|
195
|
+
!!utils.GLOBAL_OBJ._sentryDebugIds,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const CpuProfilerBindings = new Bindings();
|
|
201
|
+
|
|
202
|
+
const NODE_VERSION = utils.parseSemver(process.versions.node) ;
|
|
203
|
+
const NODE_MAJOR = NODE_VERSION.major;
|
|
204
|
+
|
|
205
|
+
function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* eslint-disable max-lines */
|
|
206
|
+
|
|
190
207
|
// We require the file because if we import it, it will be included in the bundle.
|
|
191
208
|
// I guess tsc does not check file contents when it's imported.
|
|
192
|
-
const THREAD_ID_STRING = String(
|
|
193
|
-
const THREAD_NAME =
|
|
209
|
+
const THREAD_ID_STRING = String(worker_threads.threadId);
|
|
210
|
+
const THREAD_NAME = worker_threads.isMainThread ? 'main' : 'worker';
|
|
194
211
|
const FORMAT_VERSION = '1';
|
|
212
|
+
const CONTINUOUS_FORMAT_VERSION = '2';
|
|
195
213
|
|
|
196
214
|
// Os machine was backported to 16.18, but this was not reflected in the types
|
|
197
215
|
// @ts-expect-error ignore missing
|
|
@@ -210,7 +228,9 @@ const ARCH = os.arch();
|
|
|
210
228
|
* @param {ThreadCpuProfile | RawThreadCpuProfile} profile
|
|
211
229
|
* @returns {boolean}
|
|
212
230
|
*/
|
|
213
|
-
function isRawThreadCpuProfile(
|
|
231
|
+
function isRawThreadCpuProfile(
|
|
232
|
+
profile,
|
|
233
|
+
) {
|
|
214
234
|
return !('thread_metadata' in profile);
|
|
215
235
|
}
|
|
216
236
|
|
|
@@ -221,7 +241,9 @@ function isRawThreadCpuProfile(profile) {
|
|
|
221
241
|
* @param {ThreadCpuProfile | RawThreadCpuProfile} profile
|
|
222
242
|
* @returns {ThreadCpuProfile}
|
|
223
243
|
*/
|
|
224
|
-
function enrichWithThreadInformation(
|
|
244
|
+
function enrichWithThreadInformation(
|
|
245
|
+
profile,
|
|
246
|
+
) {
|
|
225
247
|
if (!isRawThreadCpuProfile(profile)) {
|
|
226
248
|
return profile;
|
|
227
249
|
}
|
|
@@ -235,7 +257,7 @@ function enrichWithThreadInformation(profile) {
|
|
|
235
257
|
name: THREAD_NAME,
|
|
236
258
|
},
|
|
237
259
|
},
|
|
238
|
-
};
|
|
260
|
+
} ;
|
|
239
261
|
}
|
|
240
262
|
|
|
241
263
|
/**
|
|
@@ -266,7 +288,6 @@ function createProfilingEvent(client, profile, event) {
|
|
|
266
288
|
* @param {options}
|
|
267
289
|
* @returns {Profile}
|
|
268
290
|
*/
|
|
269
|
-
|
|
270
291
|
function createProfilePayload(
|
|
271
292
|
client,
|
|
272
293
|
cpuProfile,
|
|
@@ -301,7 +322,7 @@ function createProfilePayload(
|
|
|
301
322
|
measurements: cpuProfile.measurements,
|
|
302
323
|
runtime: {
|
|
303
324
|
name: 'node',
|
|
304
|
-
version:
|
|
325
|
+
version: process$1.versions.node || '',
|
|
305
326
|
},
|
|
306
327
|
os: {
|
|
307
328
|
name: PLATFORM,
|
|
@@ -309,7 +330,7 @@ function createProfilePayload(
|
|
|
309
330
|
build_number: VERSION,
|
|
310
331
|
},
|
|
311
332
|
device: {
|
|
312
|
-
locale:
|
|
333
|
+
locale: process$1.env['LC_ALL'] || process$1.env['LC_MESSAGES'] || process$1.env['LANG'] || process$1.env['LANGUAGE'] || '',
|
|
313
334
|
model: MODEL,
|
|
314
335
|
manufacturer: TYPE,
|
|
315
336
|
architecture: ARCH,
|
|
@@ -318,7 +339,7 @@ function createProfilePayload(
|
|
|
318
339
|
debug_meta: {
|
|
319
340
|
images: applyDebugMetadata(client, cpuProfile.resources),
|
|
320
341
|
},
|
|
321
|
-
profile: enrichedThreadProfile,
|
|
342
|
+
profile: enrichedThreadProfile ,
|
|
322
343
|
transaction: {
|
|
323
344
|
name: transaction,
|
|
324
345
|
id: event_id,
|
|
@@ -330,6 +351,77 @@ function createProfilePayload(
|
|
|
330
351
|
return profile;
|
|
331
352
|
}
|
|
332
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Create a profile chunk from raw thread profile
|
|
356
|
+
* @param {RawThreadCpuProfile} cpuProfile
|
|
357
|
+
* @param {options}
|
|
358
|
+
* @returns {Profile}
|
|
359
|
+
*/
|
|
360
|
+
function createProfileChunkPayload(
|
|
361
|
+
client,
|
|
362
|
+
cpuProfile,
|
|
363
|
+
{
|
|
364
|
+
release,
|
|
365
|
+
environment,
|
|
366
|
+
start_timestamp,
|
|
367
|
+
trace_id,
|
|
368
|
+
profiler_id,
|
|
369
|
+
chunk_id,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
,
|
|
373
|
+
) {
|
|
374
|
+
// Log a warning if the profile has an invalid traceId (should be uuidv4).
|
|
375
|
+
// All profiles and transactions are rejected if this is the case and we want to
|
|
376
|
+
// warn users that this is happening if they enable debug flag
|
|
377
|
+
if (trace_id && trace_id.length !== 32) {
|
|
378
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
|
|
382
|
+
|
|
383
|
+
const profile = {
|
|
384
|
+
chunk_id: chunk_id,
|
|
385
|
+
profiler_id: profiler_id,
|
|
386
|
+
timestamp: new Date(start_timestamp).toISOString(),
|
|
387
|
+
platform: 'node',
|
|
388
|
+
version: CONTINUOUS_FORMAT_VERSION,
|
|
389
|
+
release: release,
|
|
390
|
+
environment: environment,
|
|
391
|
+
measurements: cpuProfile.measurements,
|
|
392
|
+
debug_meta: {
|
|
393
|
+
images: applyDebugMetadata(client, cpuProfile.resources),
|
|
394
|
+
},
|
|
395
|
+
profile: enrichedThreadProfile ,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return profile;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Creates a profiling chunk envelope item, if the profile does not pass validation, returns null.
|
|
403
|
+
*/
|
|
404
|
+
function createProfilingChunkEvent(
|
|
405
|
+
start_timestamp,
|
|
406
|
+
client,
|
|
407
|
+
options,
|
|
408
|
+
profile,
|
|
409
|
+
identifiers,
|
|
410
|
+
) {
|
|
411
|
+
if (!isValidProfileChunk(profile)) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return createProfileChunkPayload(client, profile, {
|
|
416
|
+
release: _nullishCoalesce(options.release, () => ( '')),
|
|
417
|
+
environment: _nullishCoalesce(options.environment, () => ( '')),
|
|
418
|
+
start_timestamp: start_timestamp,
|
|
419
|
+
trace_id: _nullishCoalesce(identifiers.trace_id, () => ( '')),
|
|
420
|
+
chunk_id: identifiers.chunk_id,
|
|
421
|
+
profiler_id: identifiers.profiler_id,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
333
425
|
/**
|
|
334
426
|
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
|
|
335
427
|
* @param {unknown} rate
|
|
@@ -382,6 +474,24 @@ function isValidProfile(profile) {
|
|
|
382
474
|
return true;
|
|
383
475
|
}
|
|
384
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Checks if the profile chunk is valid and can be sent to Sentry.
|
|
479
|
+
* @param profile
|
|
480
|
+
* @returns
|
|
481
|
+
*/
|
|
482
|
+
function isValidProfileChunk(profile) {
|
|
483
|
+
if (profile.samples.length <= 1) {
|
|
484
|
+
DEBUG_BUILD &&
|
|
485
|
+
// Log a warning if the profile has less than 2 samples so users can know why
|
|
486
|
+
// they are not seeing any profiling data and we cant avoid the back and forth
|
|
487
|
+
// of asking them to provide us with a dump of the profile data.
|
|
488
|
+
utils.logger.log('[Profiling] Discarding profile chunk because it contains less than 2 samples');
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
385
495
|
/**
|
|
386
496
|
* Adds items to envelope if they are not already present - mutates the envelope.
|
|
387
497
|
* @param {Envelope} envelope
|
|
@@ -434,6 +544,41 @@ function findProfiledTransactionsFromEnvelope(envelope) {
|
|
|
434
544
|
return events;
|
|
435
545
|
}
|
|
436
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Creates event envelope headers for a profile chunk. This is separate from createEventEnvelopeHeaders util
|
|
549
|
+
* as the profile chunk does not conform to the sentry event type
|
|
550
|
+
*/
|
|
551
|
+
function createEventEnvelopeHeaders(
|
|
552
|
+
sdkInfo,
|
|
553
|
+
tunnel,
|
|
554
|
+
dsn,
|
|
555
|
+
) {
|
|
556
|
+
return {
|
|
557
|
+
event_id: utils.uuid4(),
|
|
558
|
+
sent_at: new Date().toISOString(),
|
|
559
|
+
...(sdkInfo && { sdk: sdkInfo }),
|
|
560
|
+
...(!!tunnel && dsn && { dsn: utils.dsnToString(dsn) }),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Creates a standalone profile_chunk envelope.
|
|
566
|
+
*/
|
|
567
|
+
function makeProfileChunkEnvelope(
|
|
568
|
+
chunk,
|
|
569
|
+
sdkInfo,
|
|
570
|
+
tunnel,
|
|
571
|
+
dsn,
|
|
572
|
+
) {
|
|
573
|
+
const profileChunkHeader = {
|
|
574
|
+
type: 'profile_chunk',
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
return utils.createEnvelope(createEventEnvelopeHeaders(sdkInfo, tunnel, dsn), [
|
|
578
|
+
[profileChunkHeader, chunk],
|
|
579
|
+
]);
|
|
580
|
+
}
|
|
581
|
+
|
|
437
582
|
const debugIdStackParserCache = new WeakMap();
|
|
438
583
|
|
|
439
584
|
/**
|
|
@@ -601,16 +746,13 @@ function maybeProfileSpan(
|
|
|
601
746
|
* @param profile_id
|
|
602
747
|
* @returns
|
|
603
748
|
*/
|
|
604
|
-
function stopSpanProfile(
|
|
605
|
-
span,
|
|
606
|
-
profile_id,
|
|
607
|
-
) {
|
|
749
|
+
function stopSpanProfile(span, profile_id) {
|
|
608
750
|
// Should not happen, but satisfy the type checker and be safe regardless.
|
|
609
751
|
if (!profile_id) {
|
|
610
752
|
return null;
|
|
611
753
|
}
|
|
612
754
|
|
|
613
|
-
const profile = CpuProfilerBindings.stopProfiling(profile_id);
|
|
755
|
+
const profile = CpuProfilerBindings.stopProfiling(profile_id, 0);
|
|
614
756
|
|
|
615
757
|
DEBUG_BUILD && utils.logger.log(`[Profiling] stopped profiling of transaction: ${core.spanToJSON(span).description}`);
|
|
616
758
|
|
|
@@ -629,16 +771,296 @@ function stopSpanProfile(
|
|
|
629
771
|
return profile;
|
|
630
772
|
}
|
|
631
773
|
|
|
632
|
-
|
|
633
|
-
const
|
|
774
|
+
var ProfileFormat;(function (ProfileFormat) {
|
|
775
|
+
const THREAD = 0; ProfileFormat[ProfileFormat["THREAD"] = THREAD] = "THREAD";
|
|
776
|
+
const CHUNK = 1; ProfileFormat[ProfileFormat["CHUNK"] = CHUNK] = "CHUNK";
|
|
777
|
+
})(ProfileFormat || (ProfileFormat = {}));
|
|
778
|
+
|
|
779
|
+
const CHUNK_INTERVAL_MS = 5000;
|
|
780
|
+
const PROFILE_MAP = new utils.LRUMap(50);
|
|
634
781
|
const PROFILE_TIMEOUTS = {};
|
|
635
782
|
|
|
636
|
-
function addToProfileQueue(profile) {
|
|
637
|
-
|
|
783
|
+
function addToProfileQueue(profile_id, profile) {
|
|
784
|
+
PROFILE_MAP.set(profile_id, profile);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function takeFromProfileQueue(profile_id) {
|
|
788
|
+
const profile = PROFILE_MAP.get(profile_id);
|
|
789
|
+
PROFILE_MAP.remove(profile_id);
|
|
790
|
+
return profile;
|
|
791
|
+
}
|
|
638
792
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
793
|
+
/**
|
|
794
|
+
* Instruments the client to automatically invoke the profiler on span start and stop events.
|
|
795
|
+
* @param client
|
|
796
|
+
*/
|
|
797
|
+
function setupAutomatedSpanProfiling(client) {
|
|
798
|
+
const spanToProfileIdMap = new WeakMap();
|
|
799
|
+
|
|
800
|
+
client.on('spanStart', span => {
|
|
801
|
+
if (span !== core.getRootSpan(span)) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const profile_id = maybeProfileSpan(client, span);
|
|
806
|
+
|
|
807
|
+
if (profile_id) {
|
|
808
|
+
const options = client.getOptions();
|
|
809
|
+
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
810
|
+
// currently exceed the default timeout set by the SDKs.
|
|
811
|
+
const maxProfileDurationMs =
|
|
812
|
+
(options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS;
|
|
813
|
+
|
|
814
|
+
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
815
|
+
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
816
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
817
|
+
delete PROFILE_TIMEOUTS[profile_id];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
821
|
+
const timeout = global.setTimeout(() => {
|
|
822
|
+
DEBUG_BUILD &&
|
|
823
|
+
utils.logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', core.spanToJSON(span).description);
|
|
824
|
+
|
|
825
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
826
|
+
if (profile) {
|
|
827
|
+
addToProfileQueue(profile_id, profile);
|
|
828
|
+
}
|
|
829
|
+
}, maxProfileDurationMs);
|
|
830
|
+
|
|
831
|
+
// Unref timeout so it doesn't keep the process alive.
|
|
832
|
+
timeout.unref();
|
|
833
|
+
|
|
834
|
+
core.getCurrentScope().setContext('profile', { profile_id });
|
|
835
|
+
spanToProfileIdMap.set(span, profile_id);
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
client.on('spanEnd', span => {
|
|
840
|
+
const profile_id = spanToProfileIdMap.get(span);
|
|
841
|
+
|
|
842
|
+
if (profile_id) {
|
|
843
|
+
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
844
|
+
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
845
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
846
|
+
delete PROFILE_TIMEOUTS[profile_id];
|
|
847
|
+
}
|
|
848
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
849
|
+
|
|
850
|
+
if (profile) {
|
|
851
|
+
addToProfileQueue(profile_id, profile);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
client.on('beforeEnvelope', (envelope) => {
|
|
857
|
+
// if not profiles are in queue, there is nothing to add to the envelope.
|
|
858
|
+
if (!PROFILE_MAP.size) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
|
|
863
|
+
if (!profiledTransactionEvents.length) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const profilesToAddToEnvelope = [];
|
|
868
|
+
|
|
869
|
+
for (const profiledTransaction of profiledTransactionEvents) {
|
|
870
|
+
const profileContext = _optionalChain([profiledTransaction, 'access', _ => _.contexts, 'optionalAccess', _2 => _2['profile']]);
|
|
871
|
+
const profile_id = _optionalChain([profileContext, 'optionalAccess', _3 => _3['profile_id']]);
|
|
872
|
+
|
|
873
|
+
if (!profile_id) {
|
|
874
|
+
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
|
878
|
+
if (profileContext) {
|
|
879
|
+
_optionalChainDelete([profiledTransaction, 'access', _4 => _4.contexts, 'optionalAccess', _5 => delete _5['profile']]);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const cpuProfile = takeFromProfileQueue(profile_id);
|
|
883
|
+
if (!cpuProfile) {
|
|
884
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const profile = createProfilingEvent(client, cpuProfile, profiledTransaction);
|
|
889
|
+
if (!profile) return;
|
|
890
|
+
|
|
891
|
+
profilesToAddToEnvelope.push(profile);
|
|
892
|
+
|
|
893
|
+
// @ts-expect-error profile does not inherit from Event
|
|
894
|
+
client.emit('preprocessEvent', profile, {
|
|
895
|
+
event_id: profiledTransaction.event_id,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this);ContinuousProfiler.prototype.__init3.call(this); }
|
|
904
|
+
__init() {this._profilerId = utils.uuid4();}
|
|
905
|
+
__init2() {this._client = undefined;}
|
|
906
|
+
__init3() {this._chunkData = undefined;}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler
|
|
910
|
+
* methods called before the profiler is initialized will result in a noop action with debug logs.
|
|
911
|
+
* @param client
|
|
912
|
+
*/
|
|
913
|
+
initialize(client) {
|
|
914
|
+
this._client = client;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Recursively schedules chunk profiling to start and stop at a set interval.
|
|
919
|
+
* Once the user calls stop(), the current chunk will be stopped and flushed to Sentry and no new chunks will
|
|
920
|
+
* will be started. To restart continuous mode after calling stop(), the user must call start() again.
|
|
921
|
+
* @returns void
|
|
922
|
+
*/
|
|
923
|
+
start() {
|
|
924
|
+
if (!this._client) {
|
|
925
|
+
// The client is not attached to the profiler if the user has not enabled continuous profiling.
|
|
926
|
+
// In this case, calling start() and stop() is a noop action.The reason this exists is because
|
|
927
|
+
// it makes the types easier to work with and avoids users having to do null checks.
|
|
928
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] Profiler was never attached to the client.');
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (this._chunkData) {
|
|
932
|
+
DEBUG_BUILD &&
|
|
933
|
+
utils.logger.log(
|
|
934
|
+
`[Profiling] Chunk with chunk_id ${this._chunkData.id} is still running, current chunk will be stopped a new chunk will be started.`,
|
|
935
|
+
);
|
|
936
|
+
this.stop();
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const traceId =
|
|
940
|
+
core.getCurrentScope().getPropagationContext().traceId || core.getIsolationScope().getPropagationContext().traceId;
|
|
941
|
+
this._initializeChunk(traceId);
|
|
942
|
+
this._startChunkProfiling(this._chunkData);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Stops the current chunk and flushes the profile to Sentry.
|
|
947
|
+
* @returns void
|
|
948
|
+
*/
|
|
949
|
+
stop() {
|
|
950
|
+
if (_optionalChain([this, 'access', _6 => _6._chunkData, 'optionalAccess', _7 => _7.timer])) {
|
|
951
|
+
global.clearTimeout(this._chunkData.timer);
|
|
952
|
+
this._chunkData.timer = undefined;
|
|
953
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Stopping profiling chunk: ${this._chunkData.id}`);
|
|
954
|
+
}
|
|
955
|
+
if (!this._client) {
|
|
956
|
+
DEBUG_BUILD &&
|
|
957
|
+
utils.logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (!_optionalChain([this, 'access', _8 => _8._chunkData, 'optionalAccess', _9 => _9.id])) {
|
|
961
|
+
DEBUG_BUILD &&
|
|
962
|
+
utils.logger.log(`[Profiling] Failed to collect profile for: ${_optionalChain([this, 'access', _10 => _10._chunkData, 'optionalAccess', _11 => _11.id])}, the chunk_id is missing.`);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const profile = CpuProfilerBindings.stopProfiling(this._chunkData.id, ProfileFormat.CHUNK);
|
|
966
|
+
if (!profile || !this._chunkData.startTimestampMS) {
|
|
967
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] _chunkiledStartTraceID to collect profile for: ${this._chunkData.id}`);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (profile) {
|
|
971
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Sending profile chunk ${this._chunkData.id}.`);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Profile chunk ${this._chunkData.id} sent to Sentry.`);
|
|
975
|
+
const chunk = createProfilingChunkEvent(
|
|
976
|
+
this._chunkData.startTimestampMS,
|
|
977
|
+
this._client,
|
|
978
|
+
this._client.getOptions(),
|
|
979
|
+
profile,
|
|
980
|
+
{
|
|
981
|
+
chunk_id: this._chunkData.id,
|
|
982
|
+
trace_id: this._chunkData.startTraceID,
|
|
983
|
+
profiler_id: this._profilerId,
|
|
984
|
+
},
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
if (!chunk) {
|
|
988
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Failed to create profile chunk for: ${this._chunkData.id}`);
|
|
989
|
+
this._reset();
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
this._flush(chunk);
|
|
994
|
+
// Depending on the profile and stack sizes, stopping the profile and converting
|
|
995
|
+
// the format may negatively impact the performance of the application. To avoid
|
|
996
|
+
// blocking for too long, enqueue the next chunk start inside the next macrotask.
|
|
997
|
+
// clear current chunk
|
|
998
|
+
this._reset();
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Flushes the profile chunk to Sentry.
|
|
1003
|
+
* @param chunk
|
|
1004
|
+
*/
|
|
1005
|
+
_flush(chunk) {
|
|
1006
|
+
if (!this._client) {
|
|
1007
|
+
DEBUG_BUILD &&
|
|
1008
|
+
utils.logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const transport = this._client.getTransport();
|
|
1013
|
+
if (!transport) {
|
|
1014
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] No transport available to send profile chunk.');
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const dsn = this._client.getDsn();
|
|
1019
|
+
const metadata = this._client.getSdkMetadata();
|
|
1020
|
+
const tunnel = this._client.getOptions().tunnel;
|
|
1021
|
+
|
|
1022
|
+
const envelope = makeProfileChunkEnvelope(chunk, _optionalChain([metadata, 'optionalAccess', _12 => _12.sdk]), tunnel, dsn);
|
|
1023
|
+
transport.send(envelope).then(null, reason => {
|
|
1024
|
+
DEBUG_BUILD && utils.logger.error('Error while sending profile chunk envelope:', reason);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Starts the profiler and registers the flush timer for a given chunk.
|
|
1030
|
+
* @param chunk
|
|
1031
|
+
*/
|
|
1032
|
+
_startChunkProfiling(chunk) {
|
|
1033
|
+
CpuProfilerBindings.startProfiling(chunk.id);
|
|
1034
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] starting profiling chunk: ${chunk.id}`);
|
|
1035
|
+
|
|
1036
|
+
chunk.timer = global.setTimeout(() => {
|
|
1037
|
+
DEBUG_BUILD && utils.logger.log(`[Profiling] Stopping profiling chunk: ${chunk.id}`);
|
|
1038
|
+
this.stop();
|
|
1039
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] Starting new profiling chunk.');
|
|
1040
|
+
setImmediate(this.start.bind(this));
|
|
1041
|
+
}, CHUNK_INTERVAL_MS);
|
|
1042
|
+
|
|
1043
|
+
// Unref timeout so it doesn't keep the process alive.
|
|
1044
|
+
chunk.timer.unref();
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Initializes new profile chunk metadata
|
|
1049
|
+
*/
|
|
1050
|
+
_initializeChunk(traceId) {
|
|
1051
|
+
this._chunkData = {
|
|
1052
|
+
id: utils.uuid4(),
|
|
1053
|
+
startTraceID: traceId,
|
|
1054
|
+
startTimestampMS: utils.timestampInSeconds(),
|
|
1055
|
+
timer: undefined,
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Resets the current chunk state.
|
|
1061
|
+
*/
|
|
1062
|
+
_reset() {
|
|
1063
|
+
this._chunkData = undefined;
|
|
642
1064
|
}
|
|
643
1065
|
}
|
|
644
1066
|
|
|
@@ -655,129 +1077,32 @@ const _nodeProfilingIntegration = (() => {
|
|
|
655
1077
|
|
|
656
1078
|
return {
|
|
657
1079
|
name: 'ProfilingIntegration',
|
|
1080
|
+
_profiler: new ContinuousProfiler(),
|
|
658
1081
|
setup(client) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// currently exceed the default timeout set by the SDKs.
|
|
672
|
-
const maxProfileDurationMs =
|
|
673
|
-
(options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS;
|
|
674
|
-
|
|
675
|
-
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
676
|
-
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
677
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
678
|
-
delete PROFILE_TIMEOUTS[profile_id];
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
682
|
-
PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => {
|
|
683
|
-
DEBUG_BUILD &&
|
|
684
|
-
utils.logger.log(
|
|
685
|
-
'[Profiling] max profile duration elapsed, stopping profiling for:',
|
|
686
|
-
core.spanToJSON(span).description,
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
const profile = stopSpanProfile(span, profile_id);
|
|
690
|
-
if (profile) {
|
|
691
|
-
addToProfileQueue(profile);
|
|
692
|
-
}
|
|
693
|
-
}, maxProfileDurationMs);
|
|
694
|
-
|
|
695
|
-
core.getCurrentScope().setContext('profile', { profile_id });
|
|
696
|
-
|
|
697
|
-
spanToProfileIdMap.set(span, profile_id);
|
|
1082
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] Profiling integration setup.');
|
|
1083
|
+
const options = client.getOptions();
|
|
1084
|
+
|
|
1085
|
+
const mode =
|
|
1086
|
+
(options.profilesSampleRate === undefined || options.profilesSampleRate === 0) && !options.profilesSampler
|
|
1087
|
+
? 'continuous'
|
|
1088
|
+
: 'span';
|
|
1089
|
+
switch (mode) {
|
|
1090
|
+
case 'continuous': {
|
|
1091
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] Continuous profiler mode enabled.');
|
|
1092
|
+
this._profiler.initialize(client);
|
|
1093
|
+
break;
|
|
698
1094
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
706
|
-
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
707
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
708
|
-
delete PROFILE_TIMEOUTS[profile_id];
|
|
709
|
-
}
|
|
710
|
-
const profile = stopSpanProfile(span, profile_id);
|
|
711
|
-
|
|
712
|
-
if (profile) {
|
|
713
|
-
addToProfileQueue(profile);
|
|
714
|
-
}
|
|
1095
|
+
// Default to span profiling when no mode profiler mode is set
|
|
1096
|
+
case 'span':
|
|
1097
|
+
case undefined: {
|
|
1098
|
+
DEBUG_BUILD && utils.logger.log('[Profiling] Span profiler mode enabled.');
|
|
1099
|
+
setupAutomatedSpanProfiling(client);
|
|
1100
|
+
break;
|
|
715
1101
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
client.on('beforeEnvelope', (envelope) => {
|
|
719
|
-
// if not profiles are in queue, there is nothing to add to the envelope.
|
|
720
|
-
if (!PROFILE_QUEUE.length) {
|
|
721
|
-
return;
|
|
1102
|
+
default: {
|
|
1103
|
+
DEBUG_BUILD && utils.logger.warn(`[Profiling] Unknown profiler mode: ${mode}, profiler was not initialized`);
|
|
722
1104
|
}
|
|
723
|
-
|
|
724
|
-
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
|
|
725
|
-
if (!profiledTransactionEvents.length) {
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
const profilesToAddToEnvelope = [];
|
|
730
|
-
|
|
731
|
-
for (const profiledTransaction of profiledTransactionEvents) {
|
|
732
|
-
const profileContext = _optionalChain([profiledTransaction, 'access', _ => _.contexts, 'optionalAccess', _2 => _2['profile']]);
|
|
733
|
-
const profile_id = _optionalChain([profileContext, 'optionalAccess', _3 => _3['profile_id']]);
|
|
734
|
-
|
|
735
|
-
if (!profile_id) {
|
|
736
|
-
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
|
740
|
-
if (profileContext) {
|
|
741
|
-
_optionalChainDelete([profiledTransaction, 'access', _4 => _4.contexts, 'optionalAccess', _5 => delete _5['profile']]);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// We need to find both a profile and a transaction event for the same profile_id.
|
|
745
|
-
const profileIndex = PROFILE_QUEUE.findIndex(p => p.profile_id === profile_id);
|
|
746
|
-
if (profileIndex === -1) {
|
|
747
|
-
DEBUG_BUILD && utils.logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const cpuProfile = PROFILE_QUEUE[profileIndex];
|
|
752
|
-
if (!cpuProfile) {
|
|
753
|
-
DEBUG_BUILD && utils.logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
754
|
-
continue;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Remove the profile from the queue.
|
|
758
|
-
PROFILE_QUEUE.splice(profileIndex, 1);
|
|
759
|
-
const profile = createProfilingEvent(client, cpuProfile, profiledTransaction);
|
|
760
|
-
|
|
761
|
-
if (client.emit && profile) {
|
|
762
|
-
const integrations =
|
|
763
|
-
client['_integrations'] && client['_integrations'] !== null && !Array.isArray(client['_integrations'])
|
|
764
|
-
? Object.keys(client['_integrations'])
|
|
765
|
-
: undefined;
|
|
766
|
-
|
|
767
|
-
// @ts-expect-error bad overload due to unknown event
|
|
768
|
-
client.emit('preprocessEvent', profile, {
|
|
769
|
-
event_id: profiledTransaction.event_id,
|
|
770
|
-
integrations,
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
if (profile) {
|
|
775
|
-
profilesToAddToEnvelope.push(profile);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
780
|
-
});
|
|
1105
|
+
}
|
|
781
1106
|
},
|
|
782
1107
|
};
|
|
783
1108
|
}) ;
|