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