@sentry/profiling-node
Advanced tools
Comparing version
@@ -378,2 +378,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
function makeProfileChunkEnvelope( | ||
platform, | ||
chunk, | ||
@@ -386,2 +387,3 @@ sdkInfo, | ||
type: 'profile_chunk', | ||
platform, | ||
}; | ||
@@ -409,2 +411,4 @@ | ||
/* eslint-disable deprecation/deprecation */ | ||
const MAX_PROFILE_DURATION_MS = 30 * 1000; | ||
@@ -546,164 +550,351 @@ | ||
/** | ||
* Instruments the client to automatically invoke the profiler on span start and stop events. | ||
* @param client | ||
*/ | ||
function setupAutomatedSpanProfiling(client) { | ||
const spanToProfileIdMap = new WeakMap(); | ||
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this);ContinuousProfiler.prototype.__init3.call(this);ContinuousProfiler.prototype.__init4.call(this);ContinuousProfiler.prototype.__init5.call(this);ContinuousProfiler.prototype.__init6.call(this);ContinuousProfiler.prototype.__init7.call(this); } | ||
client.on('spanStart', span => { | ||
if (span !== core.getRootSpan(span)) { | ||
return; | ||
} | ||
__init() {this._client = undefined;} | ||
__init2() {this._chunkData = undefined;} | ||
__init3() {this._mode = undefined;} | ||
__init4() {this._legacyProfilerMode = undefined;} | ||
__init5() {this._profileLifecycle = undefined;} | ||
__init6() {this._sampled = undefined;} | ||
__init7() {this._sessionSamplingRate = undefined;} | ||
/** | ||
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler | ||
* methods called before the profiler is initialized will result in a noop action with debug logs. | ||
* @param client | ||
*/ | ||
initialize(client) { | ||
this._client = client; | ||
const options = client.getOptions(); | ||
const profile_id = maybeProfileSpan(client, span); | ||
this._mode = getProfilingMode(options); | ||
this._sessionSamplingRate = Math.random(); | ||
this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0); | ||
this._profileLifecycle = options.profileLifecycle ?? 'manual'; | ||
if (profile_id) { | ||
const options = client.getOptions(); | ||
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that | ||
// currently exceed the default timeout set by the SDKs. | ||
const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS; | ||
switch (this._mode) { | ||
case 'legacy': { | ||
this._legacyProfilerMode = | ||
'profilesSampleRate' in options || 'profilesSampler' in options ? 'span' : 'continuous'; | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Profiling mode is ${this._legacyProfilerMode}.`); | ||
switch (this._legacyProfilerMode) { | ||
case 'span': { | ||
this._setupAutomaticSpanProfiling(); | ||
break; | ||
} | ||
case 'continuous': { | ||
// Continous mode requires manual calls to profiler.start() and profiler.stop() | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && | ||
core.logger.warn( | ||
`[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`, | ||
); | ||
break; | ||
} | ||
} | ||
break; | ||
} | ||
// Enqueue a timeout to prevent profiles from running over max duration. | ||
const timeout = global.setTimeout(() => { | ||
DEBUG_BUILD && | ||
core.logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', core.spanToJSON(span).description); | ||
case 'current': { | ||
this._setupSpanChunkInstrumentation(); | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`); | ||
switch (this._profileLifecycle) { | ||
case 'trace': { | ||
this._startTraceLifecycleProfiling(); | ||
break; | ||
} | ||
case 'manual': { | ||
// Manual mode requires manual calls to profiler.startProfiler() and profiler.stopProfiler() | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && | ||
core.logger.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`); | ||
break; | ||
} | ||
} | ||
}, maxProfileDurationMs); | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && core.logger.warn(`[Profiling] Unknown profiler mode: ${this._mode}, profiler was not initialized`); | ||
break; | ||
} | ||
} | ||
// Unref timeout so it doesn't keep the process alive. | ||
timeout.unref(); | ||
// Attaches a listener to beforeSend which will add the threadId data to the event being sent. | ||
// This adds a constant overhead to all events being sent which could be improved to only attach | ||
// and detach the listener during a profiler session | ||
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this)); | ||
} | ||
core.getIsolationScope().setContext('profile', { profile_id }); | ||
spanToProfileIdMap.set(span, profile_id); | ||
/** | ||
* Initializes a new profilerId session and schedules chunk profiling. | ||
* @returns void | ||
*/ | ||
start() { | ||
if (this._mode === 'current') { | ||
this._startProfiler(); | ||
return; | ||
} | ||
}); | ||
client.on('spanEnd', span => { | ||
const profile_id = spanToProfileIdMap.get(span); | ||
if (!this._client) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Failed to start, sentry client was never attached to the profiler.'); | ||
return; | ||
} | ||
if (profile_id) { | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (this._mode !== 'legacy') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
if (this._legacyProfilerMode === 'span') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Calls to profiler.start() are not supported in span profiling mode.'); | ||
return; | ||
} | ||
}); | ||
client.on('beforeEnvelope', (envelope) => { | ||
// if not profiles are in queue, there is nothing to add to the envelope. | ||
if (!PROFILE_MAP.size) { | ||
// Flush any existing chunks before starting a new one. | ||
this._stopChunkProfiling(); | ||
// Restart the profiler session | ||
this._setupSpanChunkInstrumentation(); | ||
this._restartChunkProfiling(); | ||
} | ||
/** | ||
* Stops the current chunk and flushes the profile to Sentry. | ||
* @returns void | ||
*/ | ||
stop() { | ||
if (this._mode === 'current') { | ||
this._stopProfiler(); | ||
return; | ||
} | ||
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); | ||
if (!profiledTransactionEvents.length) { | ||
if (!this._client) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.'); | ||
return; | ||
} | ||
const profilesToAddToEnvelope = []; | ||
if (this._mode !== 'legacy') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
for (const profiledTransaction of profiledTransactionEvents) { | ||
const profileContext = profiledTransaction.contexts?.profile; | ||
const profile_id = profileContext?.profile_id; | ||
if (this._legacyProfilerMode === 'span') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Calls to profiler.stop() are not supported in span profiling mode.'); | ||
return; | ||
} | ||
if (!profile_id) { | ||
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); | ||
} | ||
this._stopChunkProfiling(); | ||
this._teardownSpanChunkInstrumentation(); | ||
} | ||
// Remove the profile from the transaction context before sending, relay will take care of the rest. | ||
if (profileContext) { | ||
delete profiledTransaction.contexts?.profile; | ||
} | ||
_startProfiler() { | ||
if (this._mode !== 'current') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
const cpuProfile = takeFromProfileQueue(profile_id); | ||
if (!cpuProfile) { | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); | ||
continue; | ||
if (this._chunkData !== undefined) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Profile session already running, no-op.'); | ||
return; | ||
} | ||
if (this._mode === 'current') { | ||
if (!this._sampled) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Profile session not sampled, no-op.'); | ||
return; | ||
} | ||
} | ||
const profile = createProfilingEvent(client, cpuProfile, profiledTransaction); | ||
if (!profile) return; | ||
if (this._profileLifecycle === 'trace') { | ||
DEBUG_BUILD && | ||
core.logger.log( | ||
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.', | ||
); | ||
return; | ||
} | ||
profilesToAddToEnvelope.push(profile); | ||
this._startChunkProfiling(); | ||
} | ||
// @ts-expect-error profile does not inherit from Event | ||
client.emit('preprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
_stopProfiler() { | ||
if (this._mode !== 'current') { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
// @ts-expect-error profile does not inherit from Event | ||
client.emit('postprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
if (this._profileLifecycle === 'trace') { | ||
DEBUG_BUILD && | ||
core.logger.log( | ||
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.', | ||
); | ||
return; | ||
} | ||
addProfilesToEnvelope(envelope, profilesToAddToEnvelope); | ||
}); | ||
} | ||
if (!this._chunkData) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] No profile session running, no-op.'); | ||
return; | ||
} | ||
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this); } | ||
__init() {this._client = undefined;} | ||
__init2() {this._chunkData = undefined;} | ||
/** | ||
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler | ||
* methods called before the profiler is initialized will result in a noop action with debug logs. | ||
* @param client | ||
*/ | ||
initialize(client) { | ||
this._client = client; | ||
// Attaches a listener to beforeSend which will add the threadId data to the event being sent. | ||
// This adds a constant overhead to all events being sent which could be improved to only attach | ||
// and detach the listener during a profiler session | ||
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this)); | ||
this._stopChunkProfiling(); | ||
} | ||
/** | ||
* Initializes a new profilerId session and schedules chunk profiling. | ||
* @returns void | ||
* Starts trace lifecycle profiling. Profiling will remain active as long as there is an active span. | ||
*/ | ||
start() { | ||
_startTraceLifecycleProfiling() { | ||
if (!this._client) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Failed to start, sentry client was never attached to the profiler.'); | ||
DEBUG_BUILD && | ||
core.logger.log( | ||
'[Profiling] Failed to start trace lifecycle profiling, sentry client was never attached to the profiler.', | ||
); | ||
return; | ||
} | ||
// Flush any existing chunks before starting a new one. | ||
this._chunkStop(); | ||
let activeSpanCounter = 0; | ||
this._client.on('spanStart', _span => { | ||
if (activeSpanCounter === 0) { | ||
this._startChunkProfiling(); | ||
} | ||
activeSpanCounter++; | ||
}); | ||
// Restart the profiler session | ||
this._setupSpanChunkInstrumentation(); | ||
this._chunkStart(); | ||
this._client.on('spanEnd', _span => { | ||
if (activeSpanCounter === 1) { | ||
this._stopChunkProfiling(); | ||
} | ||
activeSpanCounter--; | ||
}); | ||
} | ||
/** | ||
* Stops the current chunk and flushes the profile to Sentry. | ||
* @returns void | ||
*/ | ||
stop() { | ||
_setupAutomaticSpanProfiling() { | ||
if (!this._client) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.'); | ||
DEBUG_BUILD && | ||
core.logger.log( | ||
'[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler.', | ||
); | ||
return; | ||
} | ||
this._chunkStop(); | ||
this._teardownSpanChunkInstrumentation(); | ||
const spanToProfileIdMap = new WeakMap(); | ||
this._client.on('spanStart', span => { | ||
if (span !== core.getRootSpan(span)) { | ||
return; | ||
} | ||
const profile_id = maybeProfileSpan(this._client, span); | ||
if (profile_id) { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const options = this._client.getOptions(); | ||
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that | ||
// currently exceed the default timeout set by the SDKs. | ||
const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS; | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
// Enqueue a timeout to prevent profiles from running over max duration. | ||
const timeout = global.setTimeout(() => { | ||
DEBUG_BUILD && | ||
core.logger.log( | ||
'[Profiling] max profile duration elapsed, stopping profiling for:', | ||
core.spanToJSON(span).description, | ||
); | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
}, maxProfileDurationMs); | ||
// Unref timeout so it doesn't keep the process alive. | ||
timeout.unref(); | ||
core.getIsolationScope().setContext('profile', { profile_id }); | ||
spanToProfileIdMap.set(span, profile_id); | ||
} | ||
}); | ||
this._client.on('spanEnd', span => { | ||
const profile_id = spanToProfileIdMap.get(span); | ||
if (profile_id) { | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
} | ||
}); | ||
this._client.on('beforeEnvelope', (envelope) => { | ||
// if not profiles are in queue, there is nothing to add to the envelope. | ||
if (!PROFILE_MAP.size) { | ||
return; | ||
} | ||
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); | ||
if (!profiledTransactionEvents.length) { | ||
return; | ||
} | ||
const profilesToAddToEnvelope = []; | ||
for (const profiledTransaction of profiledTransactionEvents) { | ||
const profileContext = profiledTransaction.contexts?.profile; | ||
const profile_id = profileContext?.profile_id; | ||
if (!profile_id) { | ||
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); | ||
} | ||
// Remove the profile from the transaction context before sending, relay will take care of the rest. | ||
if (profileContext) { | ||
delete profiledTransaction.contexts?.profile; | ||
} | ||
const cpuProfile = takeFromProfileQueue(profile_id); | ||
if (!cpuProfile) { | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); | ||
continue; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const profile = createProfilingEvent(this._client, cpuProfile, profiledTransaction); | ||
if (!profile) return; | ||
profilesToAddToEnvelope.push(profile); | ||
// @ts-expect-error profile does not inherit from Event | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._client.emit('preprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
// @ts-expect-error profile does not inherit from Event | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._client.emit('postprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
} | ||
addProfilesToEnvelope(envelope, profilesToAddToEnvelope); | ||
}); | ||
} | ||
@@ -714,3 +905,3 @@ | ||
*/ | ||
_chunkStart() { | ||
_restartChunkProfiling() { | ||
if (!this._client) { | ||
@@ -723,2 +914,3 @@ // The client is not attached to the profiler if the user has not enabled continuous profiling. | ||
} | ||
if (this._chunkData) { | ||
@@ -729,3 +921,3 @@ DEBUG_BUILD && | ||
); | ||
this._chunkStop(); | ||
this._stopChunkProfiling(); | ||
} | ||
@@ -739,3 +931,8 @@ | ||
*/ | ||
_chunkStop() { | ||
_stopChunkProfiling() { | ||
if (!this._chunkData) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] No chunk data found, no-op.'); | ||
return; | ||
} | ||
if (this._chunkData?.timer) { | ||
@@ -746,10 +943,14 @@ global.clearTimeout(this._chunkData.timer); | ||
} | ||
if (!this._client) { | ||
DEBUG_BUILD && | ||
core.logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.'); | ||
this._resetChunkData(); | ||
return; | ||
} | ||
if (!this._chunkData?.id) { | ||
DEBUG_BUILD && | ||
core.logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData?.id}, the chunk_id is missing.`); | ||
this._resetChunkData(); | ||
return; | ||
@@ -761,8 +962,11 @@ } | ||
if (!profile) { | ||
DEBUG_BUILD && core.logger.log(`[Profiling] _chunkiledStartTraceID to collect profile for: ${this._chunkData.id}`); | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData.id}`); | ||
this._resetChunkData(); | ||
return; | ||
} | ||
if (!this._profilerId) { | ||
DEBUG_BUILD && | ||
core.logger.log('[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK'); | ||
this._resetChunkData(); | ||
return; | ||
@@ -822,3 +1026,3 @@ } | ||
const envelope = makeProfileChunkEnvelope(chunk, metadata?.sdk, tunnel, dsn); | ||
const envelope = makeProfileChunkEnvelope('node', chunk, metadata?.sdk, tunnel, dsn); | ||
transport.send(envelope).then(null, reason => { | ||
@@ -834,2 +1038,7 @@ DEBUG_BUILD && core.logger.error('Error while sending profile chunk envelope:', reason); | ||
_startChunkProfiling() { | ||
if (this._chunkData) { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Chunk is already running, no-op.'); | ||
return; | ||
} | ||
const traceId = | ||
@@ -844,5 +1053,5 @@ core.getCurrentScope().getPropagationContext().traceId || core.getIsolationScope().getPropagationContext().traceId; | ||
DEBUG_BUILD && core.logger.log(`[Profiling] Stopping profiling chunk: ${chunk.id}`); | ||
this._chunkStop(); | ||
this._stopChunkProfiling(); | ||
DEBUG_BUILD && core.logger.log('[Profiling] Starting new profiling chunk.'); | ||
setImmediate(this._chunkStart.bind(this)); | ||
setImmediate(this._restartChunkProfiling.bind(this)); | ||
}, CHUNK_INTERVAL_MS); | ||
@@ -953,28 +1162,4 @@ | ||
DEBUG_BUILD && core.logger.log('[Profiling] Profiling integration setup.'); | ||
const options = client.getOptions(); | ||
const mode = | ||
(options.profilesSampleRate === undefined || | ||
options.profilesSampleRate === null || | ||
options.profilesSampleRate === 0) && | ||
!options.profilesSampler | ||
? 'continuous' | ||
: 'span'; | ||
switch (mode) { | ||
case 'continuous': { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Continuous profiler mode enabled.'); | ||
this._profiler.initialize(client); | ||
break; | ||
} | ||
// Default to span profiling when no mode profiler mode is set | ||
case 'span': | ||
case undefined: { | ||
DEBUG_BUILD && core.logger.log('[Profiling] Span profiler mode enabled.'); | ||
setupAutomatedSpanProfiling(client); | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && core.logger.warn(`[Profiling] Unknown profiler mode: ${mode}, profiler was not initialized`); | ||
} | ||
} | ||
this._profiler.initialize(client); | ||
return; | ||
}, | ||
@@ -985,2 +1170,21 @@ }; | ||
/** | ||
* Determines the profiling mode based on the options. | ||
* @param options | ||
* @returns 'legacy' if the options are using the legacy profiling API, 'current' if the options are using the current profiling API | ||
*/ | ||
function getProfilingMode(options) { | ||
// Legacy mode takes precedence over current mode | ||
if ('profilesSampleRate' in options || 'profilesSampler' in options) { | ||
return 'legacy'; | ||
} | ||
if ('profileSessionSampleRate' in options || 'profileLifecycle' in options) { | ||
return 'current'; | ||
} | ||
// If neither are set, we are in the legacy continuous profiling mode | ||
return 'legacy'; | ||
} | ||
/** | ||
* We need this integration in order to send data to Sentry. We hook into the event processor | ||
@@ -987,0 +1191,0 @@ * and inspect each event to see if it is a transaction event and if that transaction event |
import { CpuProfilerBindings, ProfileFormat } from '@sentry-internal/node-cpu-profiler'; | ||
import { parseSemver, createEnvelope, forEachEnvelopeItem, logger, dsnToString, uuid4, getDebugImagesForResources, spanIsSampled, spanToJSON, LRUMap, defineIntegration, consoleSandbox, getCurrentScope, getIsolationScope, getGlobalScope, getRootSpan } from '@sentry/core'; | ||
import { parseSemver, forEachEnvelopeItem, createEnvelope, logger, dsnToString, uuid4, getDebugImagesForResources, spanIsSampled, spanToJSON, LRUMap, defineIntegration, consoleSandbox, getRootSpan, getIsolationScope, getCurrentScope, getGlobalScope } from '@sentry/core'; | ||
import * as os from 'os'; | ||
@@ -376,2 +376,3 @@ import { env, versions } from 'process'; | ||
function makeProfileChunkEnvelope( | ||
platform, | ||
chunk, | ||
@@ -384,2 +385,3 @@ sdkInfo, | ||
type: 'profile_chunk', | ||
platform, | ||
}; | ||
@@ -407,2 +409,4 @@ | ||
/* eslint-disable deprecation/deprecation */ | ||
const MAX_PROFILE_DURATION_MS = 30 * 1000; | ||
@@ -544,164 +548,351 @@ | ||
/** | ||
* Instruments the client to automatically invoke the profiler on span start and stop events. | ||
* @param client | ||
*/ | ||
function setupAutomatedSpanProfiling(client) { | ||
const spanToProfileIdMap = new WeakMap(); | ||
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this);ContinuousProfiler.prototype.__init3.call(this);ContinuousProfiler.prototype.__init4.call(this);ContinuousProfiler.prototype.__init5.call(this);ContinuousProfiler.prototype.__init6.call(this);ContinuousProfiler.prototype.__init7.call(this); } | ||
client.on('spanStart', span => { | ||
if (span !== getRootSpan(span)) { | ||
return; | ||
} | ||
__init() {this._client = undefined;} | ||
__init2() {this._chunkData = undefined;} | ||
__init3() {this._mode = undefined;} | ||
__init4() {this._legacyProfilerMode = undefined;} | ||
__init5() {this._profileLifecycle = undefined;} | ||
__init6() {this._sampled = undefined;} | ||
__init7() {this._sessionSamplingRate = undefined;} | ||
/** | ||
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler | ||
* methods called before the profiler is initialized will result in a noop action with debug logs. | ||
* @param client | ||
*/ | ||
initialize(client) { | ||
this._client = client; | ||
const options = client.getOptions(); | ||
const profile_id = maybeProfileSpan(client, span); | ||
this._mode = getProfilingMode(options); | ||
this._sessionSamplingRate = Math.random(); | ||
this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0); | ||
this._profileLifecycle = options.profileLifecycle ?? 'manual'; | ||
if (profile_id) { | ||
const options = client.getOptions(); | ||
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that | ||
// currently exceed the default timeout set by the SDKs. | ||
const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS; | ||
switch (this._mode) { | ||
case 'legacy': { | ||
this._legacyProfilerMode = | ||
'profilesSampleRate' in options || 'profilesSampler' in options ? 'span' : 'continuous'; | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
DEBUG_BUILD && logger.log(`[Profiling] Profiling mode is ${this._legacyProfilerMode}.`); | ||
switch (this._legacyProfilerMode) { | ||
case 'span': { | ||
this._setupAutomaticSpanProfiling(); | ||
break; | ||
} | ||
case 'continuous': { | ||
// Continous mode requires manual calls to profiler.start() and profiler.stop() | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && | ||
logger.warn( | ||
`[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`, | ||
); | ||
break; | ||
} | ||
} | ||
break; | ||
} | ||
// Enqueue a timeout to prevent profiles from running over max duration. | ||
const timeout = global.setTimeout(() => { | ||
DEBUG_BUILD && | ||
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(span).description); | ||
case 'current': { | ||
this._setupSpanChunkInstrumentation(); | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
DEBUG_BUILD && logger.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`); | ||
switch (this._profileLifecycle) { | ||
case 'trace': { | ||
this._startTraceLifecycleProfiling(); | ||
break; | ||
} | ||
case 'manual': { | ||
// Manual mode requires manual calls to profiler.startProfiler() and profiler.stopProfiler() | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && | ||
logger.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`); | ||
break; | ||
} | ||
} | ||
}, maxProfileDurationMs); | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && logger.warn(`[Profiling] Unknown profiler mode: ${this._mode}, profiler was not initialized`); | ||
break; | ||
} | ||
} | ||
// Unref timeout so it doesn't keep the process alive. | ||
timeout.unref(); | ||
// Attaches a listener to beforeSend which will add the threadId data to the event being sent. | ||
// This adds a constant overhead to all events being sent which could be improved to only attach | ||
// and detach the listener during a profiler session | ||
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this)); | ||
} | ||
getIsolationScope().setContext('profile', { profile_id }); | ||
spanToProfileIdMap.set(span, profile_id); | ||
/** | ||
* Initializes a new profilerId session and schedules chunk profiling. | ||
* @returns void | ||
*/ | ||
start() { | ||
if (this._mode === 'current') { | ||
this._startProfiler(); | ||
return; | ||
} | ||
}); | ||
client.on('spanEnd', span => { | ||
const profile_id = spanToProfileIdMap.get(span); | ||
if (!this._client) { | ||
DEBUG_BUILD && logger.log('[Profiling] Failed to start, sentry client was never attached to the profiler.'); | ||
return; | ||
} | ||
if (profile_id) { | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (this._mode !== 'legacy') { | ||
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
if (this._legacyProfilerMode === 'span') { | ||
DEBUG_BUILD && logger.log('[Profiling] Calls to profiler.start() are not supported in span profiling mode.'); | ||
return; | ||
} | ||
}); | ||
client.on('beforeEnvelope', (envelope) => { | ||
// if not profiles are in queue, there is nothing to add to the envelope. | ||
if (!PROFILE_MAP.size) { | ||
// Flush any existing chunks before starting a new one. | ||
this._stopChunkProfiling(); | ||
// Restart the profiler session | ||
this._setupSpanChunkInstrumentation(); | ||
this._restartChunkProfiling(); | ||
} | ||
/** | ||
* Stops the current chunk and flushes the profile to Sentry. | ||
* @returns void | ||
*/ | ||
stop() { | ||
if (this._mode === 'current') { | ||
this._stopProfiler(); | ||
return; | ||
} | ||
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); | ||
if (!profiledTransactionEvents.length) { | ||
if (!this._client) { | ||
DEBUG_BUILD && logger.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.'); | ||
return; | ||
} | ||
const profilesToAddToEnvelope = []; | ||
if (this._mode !== 'legacy') { | ||
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
for (const profiledTransaction of profiledTransactionEvents) { | ||
const profileContext = profiledTransaction.contexts?.profile; | ||
const profile_id = profileContext?.profile_id; | ||
if (this._legacyProfilerMode === 'span') { | ||
DEBUG_BUILD && logger.log('[Profiling] Calls to profiler.stop() are not supported in span profiling mode.'); | ||
return; | ||
} | ||
if (!profile_id) { | ||
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); | ||
} | ||
this._stopChunkProfiling(); | ||
this._teardownSpanChunkInstrumentation(); | ||
} | ||
// Remove the profile from the transaction context before sending, relay will take care of the rest. | ||
if (profileContext) { | ||
delete profiledTransaction.contexts?.profile; | ||
} | ||
_startProfiler() { | ||
if (this._mode !== 'current') { | ||
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
const cpuProfile = takeFromProfileQueue(profile_id); | ||
if (!cpuProfile) { | ||
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); | ||
continue; | ||
if (this._chunkData !== undefined) { | ||
DEBUG_BUILD && logger.log('[Profiling] Profile session already running, no-op.'); | ||
return; | ||
} | ||
if (this._mode === 'current') { | ||
if (!this._sampled) { | ||
DEBUG_BUILD && logger.log('[Profiling] Profile session not sampled, no-op.'); | ||
return; | ||
} | ||
} | ||
const profile = createProfilingEvent(client, cpuProfile, profiledTransaction); | ||
if (!profile) return; | ||
if (this._profileLifecycle === 'trace') { | ||
DEBUG_BUILD && | ||
logger.log( | ||
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.', | ||
); | ||
return; | ||
} | ||
profilesToAddToEnvelope.push(profile); | ||
this._startChunkProfiling(); | ||
} | ||
// @ts-expect-error profile does not inherit from Event | ||
client.emit('preprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
_stopProfiler() { | ||
if (this._mode !== 'current') { | ||
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.'); | ||
return; | ||
} | ||
// @ts-expect-error profile does not inherit from Event | ||
client.emit('postprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
if (this._profileLifecycle === 'trace') { | ||
DEBUG_BUILD && | ||
logger.log( | ||
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.', | ||
); | ||
return; | ||
} | ||
addProfilesToEnvelope(envelope, profilesToAddToEnvelope); | ||
}); | ||
} | ||
if (!this._chunkData) { | ||
DEBUG_BUILD && logger.log('[Profiling] No profile session running, no-op.'); | ||
return; | ||
} | ||
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this); } | ||
__init() {this._client = undefined;} | ||
__init2() {this._chunkData = undefined;} | ||
/** | ||
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler | ||
* methods called before the profiler is initialized will result in a noop action with debug logs. | ||
* @param client | ||
*/ | ||
initialize(client) { | ||
this._client = client; | ||
// Attaches a listener to beforeSend which will add the threadId data to the event being sent. | ||
// This adds a constant overhead to all events being sent which could be improved to only attach | ||
// and detach the listener during a profiler session | ||
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this)); | ||
this._stopChunkProfiling(); | ||
} | ||
/** | ||
* Initializes a new profilerId session and schedules chunk profiling. | ||
* @returns void | ||
* Starts trace lifecycle profiling. Profiling will remain active as long as there is an active span. | ||
*/ | ||
start() { | ||
_startTraceLifecycleProfiling() { | ||
if (!this._client) { | ||
DEBUG_BUILD && logger.log('[Profiling] Failed to start, sentry client was never attached to the profiler.'); | ||
DEBUG_BUILD && | ||
logger.log( | ||
'[Profiling] Failed to start trace lifecycle profiling, sentry client was never attached to the profiler.', | ||
); | ||
return; | ||
} | ||
// Flush any existing chunks before starting a new one. | ||
this._chunkStop(); | ||
let activeSpanCounter = 0; | ||
this._client.on('spanStart', _span => { | ||
if (activeSpanCounter === 0) { | ||
this._startChunkProfiling(); | ||
} | ||
activeSpanCounter++; | ||
}); | ||
// Restart the profiler session | ||
this._setupSpanChunkInstrumentation(); | ||
this._chunkStart(); | ||
this._client.on('spanEnd', _span => { | ||
if (activeSpanCounter === 1) { | ||
this._stopChunkProfiling(); | ||
} | ||
activeSpanCounter--; | ||
}); | ||
} | ||
/** | ||
* Stops the current chunk and flushes the profile to Sentry. | ||
* @returns void | ||
*/ | ||
stop() { | ||
_setupAutomaticSpanProfiling() { | ||
if (!this._client) { | ||
DEBUG_BUILD && logger.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.'); | ||
DEBUG_BUILD && | ||
logger.log( | ||
'[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler.', | ||
); | ||
return; | ||
} | ||
this._chunkStop(); | ||
this._teardownSpanChunkInstrumentation(); | ||
const spanToProfileIdMap = new WeakMap(); | ||
this._client.on('spanStart', span => { | ||
if (span !== getRootSpan(span)) { | ||
return; | ||
} | ||
const profile_id = maybeProfileSpan(this._client, span); | ||
if (profile_id) { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const options = this._client.getOptions(); | ||
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that | ||
// currently exceed the default timeout set by the SDKs. | ||
const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS; | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
// Enqueue a timeout to prevent profiles from running over max duration. | ||
const timeout = global.setTimeout(() => { | ||
DEBUG_BUILD && | ||
logger.log( | ||
'[Profiling] max profile duration elapsed, stopping profiling for:', | ||
spanToJSON(span).description, | ||
); | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
}, maxProfileDurationMs); | ||
// Unref timeout so it doesn't keep the process alive. | ||
timeout.unref(); | ||
getIsolationScope().setContext('profile', { profile_id }); | ||
spanToProfileIdMap.set(span, profile_id); | ||
} | ||
}); | ||
this._client.on('spanEnd', span => { | ||
const profile_id = spanToProfileIdMap.get(span); | ||
if (profile_id) { | ||
if (PROFILE_TIMEOUTS[profile_id]) { | ||
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete PROFILE_TIMEOUTS[profile_id]; | ||
} | ||
const profile = stopSpanProfile(span, profile_id); | ||
if (profile) { | ||
addToProfileQueue(profile_id, profile); | ||
} | ||
} | ||
}); | ||
this._client.on('beforeEnvelope', (envelope) => { | ||
// if not profiles are in queue, there is nothing to add to the envelope. | ||
if (!PROFILE_MAP.size) { | ||
return; | ||
} | ||
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); | ||
if (!profiledTransactionEvents.length) { | ||
return; | ||
} | ||
const profilesToAddToEnvelope = []; | ||
for (const profiledTransaction of profiledTransactionEvents) { | ||
const profileContext = profiledTransaction.contexts?.profile; | ||
const profile_id = profileContext?.profile_id; | ||
if (!profile_id) { | ||
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); | ||
} | ||
// Remove the profile from the transaction context before sending, relay will take care of the rest. | ||
if (profileContext) { | ||
delete profiledTransaction.contexts?.profile; | ||
} | ||
const cpuProfile = takeFromProfileQueue(profile_id); | ||
if (!cpuProfile) { | ||
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); | ||
continue; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const profile = createProfilingEvent(this._client, cpuProfile, profiledTransaction); | ||
if (!profile) return; | ||
profilesToAddToEnvelope.push(profile); | ||
// @ts-expect-error profile does not inherit from Event | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._client.emit('preprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
// @ts-expect-error profile does not inherit from Event | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._client.emit('postprocessEvent', profile, { | ||
event_id: profiledTransaction.event_id, | ||
}); | ||
} | ||
addProfilesToEnvelope(envelope, profilesToAddToEnvelope); | ||
}); | ||
} | ||
@@ -712,3 +903,3 @@ | ||
*/ | ||
_chunkStart() { | ||
_restartChunkProfiling() { | ||
if (!this._client) { | ||
@@ -721,2 +912,3 @@ // The client is not attached to the profiler if the user has not enabled continuous profiling. | ||
} | ||
if (this._chunkData) { | ||
@@ -727,3 +919,3 @@ DEBUG_BUILD && | ||
); | ||
this._chunkStop(); | ||
this._stopChunkProfiling(); | ||
} | ||
@@ -737,3 +929,8 @@ | ||
*/ | ||
_chunkStop() { | ||
_stopChunkProfiling() { | ||
if (!this._chunkData) { | ||
DEBUG_BUILD && logger.log('[Profiling] No chunk data found, no-op.'); | ||
return; | ||
} | ||
if (this._chunkData?.timer) { | ||
@@ -744,10 +941,14 @@ global.clearTimeout(this._chunkData.timer); | ||
} | ||
if (!this._client) { | ||
DEBUG_BUILD && | ||
logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.'); | ||
this._resetChunkData(); | ||
return; | ||
} | ||
if (!this._chunkData?.id) { | ||
DEBUG_BUILD && | ||
logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData?.id}, the chunk_id is missing.`); | ||
this._resetChunkData(); | ||
return; | ||
@@ -759,8 +960,11 @@ } | ||
if (!profile) { | ||
DEBUG_BUILD && logger.log(`[Profiling] _chunkiledStartTraceID to collect profile for: ${this._chunkData.id}`); | ||
DEBUG_BUILD && logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData.id}`); | ||
this._resetChunkData(); | ||
return; | ||
} | ||
if (!this._profilerId) { | ||
DEBUG_BUILD && | ||
logger.log('[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK'); | ||
this._resetChunkData(); | ||
return; | ||
@@ -820,3 +1024,3 @@ } | ||
const envelope = makeProfileChunkEnvelope(chunk, metadata?.sdk, tunnel, dsn); | ||
const envelope = makeProfileChunkEnvelope('node', chunk, metadata?.sdk, tunnel, dsn); | ||
transport.send(envelope).then(null, reason => { | ||
@@ -832,2 +1036,7 @@ DEBUG_BUILD && logger.error('Error while sending profile chunk envelope:', reason); | ||
_startChunkProfiling() { | ||
if (this._chunkData) { | ||
DEBUG_BUILD && logger.log('[Profiling] Chunk is already running, no-op.'); | ||
return; | ||
} | ||
const traceId = | ||
@@ -842,5 +1051,5 @@ getCurrentScope().getPropagationContext().traceId || getIsolationScope().getPropagationContext().traceId; | ||
DEBUG_BUILD && logger.log(`[Profiling] Stopping profiling chunk: ${chunk.id}`); | ||
this._chunkStop(); | ||
this._stopChunkProfiling(); | ||
DEBUG_BUILD && logger.log('[Profiling] Starting new profiling chunk.'); | ||
setImmediate(this._chunkStart.bind(this)); | ||
setImmediate(this._restartChunkProfiling.bind(this)); | ||
}, CHUNK_INTERVAL_MS); | ||
@@ -951,28 +1160,4 @@ | ||
DEBUG_BUILD && logger.log('[Profiling] Profiling integration setup.'); | ||
const options = client.getOptions(); | ||
const mode = | ||
(options.profilesSampleRate === undefined || | ||
options.profilesSampleRate === null || | ||
options.profilesSampleRate === 0) && | ||
!options.profilesSampler | ||
? 'continuous' | ||
: 'span'; | ||
switch (mode) { | ||
case 'continuous': { | ||
DEBUG_BUILD && logger.log('[Profiling] Continuous profiler mode enabled.'); | ||
this._profiler.initialize(client); | ||
break; | ||
} | ||
// Default to span profiling when no mode profiler mode is set | ||
case 'span': | ||
case undefined: { | ||
DEBUG_BUILD && logger.log('[Profiling] Span profiler mode enabled.'); | ||
setupAutomatedSpanProfiling(client); | ||
break; | ||
} | ||
default: { | ||
DEBUG_BUILD && logger.warn(`[Profiling] Unknown profiler mode: ${mode}, profiler was not initialized`); | ||
} | ||
} | ||
this._profiler.initialize(client); | ||
return; | ||
}, | ||
@@ -983,2 +1168,21 @@ }; | ||
/** | ||
* Determines the profiling mode based on the options. | ||
* @param options | ||
* @returns 'legacy' if the options are using the legacy profiling API, 'current' if the options are using the current profiling API | ||
*/ | ||
function getProfilingMode(options) { | ||
// Legacy mode takes precedence over current mode | ||
if ('profilesSampleRate' in options || 'profilesSampler' in options) { | ||
return 'legacy'; | ||
} | ||
if ('profileSessionSampleRate' in options || 'profileLifecycle' in options) { | ||
return 'current'; | ||
} | ||
// If neither are set, we are in the legacy continuous profiling mode | ||
return 'legacy'; | ||
} | ||
/** | ||
* We need this integration in order to send data to Sentry. We hook into the event processor | ||
@@ -985,0 +1189,0 @@ * and inspect each event to see if it is a transaction event and if that transaction event |
@@ -1,1 +0,1 @@ | ||
{"type":"module","version":"9.7.0","sideEffects":false} | ||
{"type":"module","version":"9.8.0","sideEffects":false} |
@@ -72,3 +72,3 @@ import { Client, ContinuousThreadCpuProfile, DebugImage, DsnComponents, Envelope, Event, EventEnvelopeHeaders, Profile, ProfileChunk, ProfileChunkEnvelope, SdkInfo, ThreadCpuProfile } from '@sentry/core'; | ||
*/ | ||
export declare function makeProfileChunkEnvelope(chunk: ProfileChunk, sdkInfo: SdkInfo | undefined, tunnel: string | undefined, dsn?: DsnComponents): ProfileChunkEnvelope; | ||
export declare function makeProfileChunkEnvelope(platform: 'node', chunk: ProfileChunk, sdkInfo: SdkInfo | undefined, tunnel: string | undefined, dsn?: DsnComponents): ProfileChunkEnvelope; | ||
/** | ||
@@ -75,0 +75,0 @@ * Cross reference profile collected resources with debug_ids and return a list of debug images. |
@@ -72,3 +72,3 @@ import type { Client, ContinuousThreadCpuProfile, DebugImage, DsnComponents, Envelope, Event, EventEnvelopeHeaders, Profile, ProfileChunk, ProfileChunkEnvelope, SdkInfo, ThreadCpuProfile } from '@sentry/core'; | ||
*/ | ||
export declare function makeProfileChunkEnvelope(chunk: ProfileChunk, sdkInfo: SdkInfo | undefined, tunnel: string | undefined, dsn?: DsnComponents): ProfileChunkEnvelope; | ||
export declare function makeProfileChunkEnvelope(platform: 'node', chunk: ProfileChunk, sdkInfo: SdkInfo | undefined, tunnel: string | undefined, dsn?: DsnComponents): ProfileChunkEnvelope; | ||
/** | ||
@@ -75,0 +75,0 @@ * Cross reference profile collected resources with debug_ids and return a list of debug images. |
{ | ||
"name": "@sentry/profiling-node", | ||
"version": "9.7.0", | ||
"version": "9.8.0", | ||
"description": "Official Sentry SDK for Node.js Profiling", | ||
@@ -66,4 +66,4 @@ "repository": "git://github.com/getsentry/sentry-javascript.git", | ||
"@sentry-internal/node-cpu-profiler": "^2.0.0", | ||
"@sentry/core": "9.7.0", | ||
"@sentry/node": "9.7.0" | ||
"@sentry/core": "9.8.0", | ||
"@sentry/node": "9.8.0" | ||
}, | ||
@@ -70,0 +70,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
243166
16.95%2444
16.05%237
-0.42%114
-0.87%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated