mirror of
https://github.com/rxliuli/apps.apple.com.git
synced 2025-11-10 01:40:32 +00:00
init commit
This commit is contained in:
240
shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js
generated
vendored
Normal file
240
shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
import { getCurrentHub } from '@sentry/core';
|
||||
import { logger, uuid4 } from '@sentry/utils';
|
||||
import { WINDOW } from '../helpers.js';
|
||||
import { isValidSampleRate, addProfileToMap } from './utils.js';
|
||||
|
||||
/* eslint-disable complexity */
|
||||
|
||||
const MAX_PROFILE_DURATION_MS = 30000;
|
||||
// Keep a flag value to avoid re-initializing the profiler constructor. If it fails
|
||||
// once, it will always fail and this allows us to early return.
|
||||
let PROFILING_CONSTRUCTOR_FAILED = false;
|
||||
|
||||
/**
|
||||
* Check if profiler constructor is available.
|
||||
* @param maybeProfiler
|
||||
*/
|
||||
function isJSProfilerSupported(maybeProfiler) {
|
||||
return typeof maybeProfiler === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported -
|
||||
* if that happens we want to avoid throwing an error from profiling code.
|
||||
* see https://github.com/getsentry/sentry-javascript/issues/4731.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
function onProfilingStartRouteTransaction(transaction) {
|
||||
if (!transaction) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log('[Profiling] Transaction is undefined, skipping profiling');
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
return wrapTransactionWithProfiling(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps startTransaction and stopTransaction with profiling related logic.
|
||||
* startProfiling is called after the call to startTransaction in order to avoid our own code from
|
||||
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
|
||||
*/
|
||||
function wrapTransactionWithProfiling(transaction) {
|
||||
// Feature support check first
|
||||
const JSProfilerConstructor = WINDOW.Profiler;
|
||||
|
||||
if (!isJSProfilerSupported(JSProfilerConstructor)) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(
|
||||
'[Profiling] Profiling is not supported by this browser, Profiler interface missing on window object.',
|
||||
);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// If constructor failed once, it will always fail, so we can early return.
|
||||
if (PROFILING_CONSTRUCTOR_FAILED) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log('[Profiling] Profiling has been disabled for the duration of the current user session.');
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
const client = getCurrentHub().getClient();
|
||||
const options = client && client.getOptions();
|
||||
if (!options) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Profiling] Profiling disabled, no options found.');
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// @ts-ignore profilesSampleRate is not part of the browser options yet
|
||||
const profilesSampleRate = options.profilesSampleRate;
|
||||
|
||||
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
|
||||
// only valid values are booleans or numbers between 0 and 1.)
|
||||
if (!isValidSampleRate(profilesSampleRate)) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('[Profiling] Discarding profile because of invalid sample rate.');
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped
|
||||
if (!profilesSampleRate) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
||||
logger.log(
|
||||
'[Profiling] Discarding profile because a negative sampling decision was inherited or profileSampleRate is set to 0',
|
||||
);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
|
||||
// a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
|
||||
const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate;
|
||||
// Check if we should sample this profile
|
||||
if (!sampled) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
||||
logger.log(
|
||||
`[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number(
|
||||
profilesSampleRate,
|
||||
)})`,
|
||||
);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// From initial testing, it seems that the minimum value for sampleInterval is 10ms.
|
||||
const samplingIntervalMS = 10;
|
||||
// Start the profiler
|
||||
const maxSamples = Math.floor(MAX_PROFILE_DURATION_MS / samplingIntervalMS);
|
||||
let profiler;
|
||||
|
||||
// Attempt to initialize the profiler constructor, if it fails, we disable profiling for the current user session.
|
||||
// This is likely due to a missing 'Document-Policy': 'js-profiling' header. We do not want to throw an error if this happens
|
||||
// as we risk breaking the user's application, so just disable profiling and log an error.
|
||||
try {
|
||||
profiler = new JSProfilerConstructor({ sampleInterval: samplingIntervalMS, maxBufferSize: maxSamples });
|
||||
} catch (e) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(
|
||||
"[Profiling] Failed to initialize the Profiling constructor, this is likely due to a missing 'Document-Policy': 'js-profiling' header.",
|
||||
);
|
||||
logger.log('[Profiling] Disabling profiling for current user session.');
|
||||
}
|
||||
PROFILING_CONSTRUCTOR_FAILED = true;
|
||||
}
|
||||
|
||||
// We failed to construct the profiler, fallback to original transaction - there is no need to log
|
||||
// anything as we already did that in the try/catch block.
|
||||
if (!profiler) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(`[Profiling] started profiling transaction: ${transaction.name || transaction.description}`);
|
||||
}
|
||||
|
||||
// We create "unique" transaction names to avoid concurrent transactions with same names
|
||||
// from being ignored by the profiler. From here on, only this transaction name should be used when
|
||||
// calling the profiler methods. Note: we log the original name to the user to avoid confusion.
|
||||
const profileId = uuid4();
|
||||
|
||||
/**
|
||||
* Idempotent handler for profile stop
|
||||
*/
|
||||
async function onProfileHandler() {
|
||||
// Check if the profile exists and return it the behavior has to be idempotent as users may call transaction.finish multiple times.
|
||||
if (!transaction) {
|
||||
return null;
|
||||
}
|
||||
// Satisfy the type checker, but profiler will always be defined here.
|
||||
if (!profiler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is temporary - we will use the collected span data to evaluate
|
||||
// if deferring txn.finish until profiler resolves is a viable approach.
|
||||
const stopProfilerSpan = transaction.startChild({ description: 'profiler.stop', op: 'profiler' });
|
||||
|
||||
return profiler
|
||||
.stop()
|
||||
.then((p) => {
|
||||
stopProfilerSpan.finish();
|
||||
|
||||
if (maxDurationTimeoutID) {
|
||||
WINDOW.clearTimeout(maxDurationTimeoutID);
|
||||
maxDurationTimeoutID = undefined;
|
||||
}
|
||||
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name || transaction.description}`);
|
||||
}
|
||||
|
||||
// In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
|
||||
if (!p) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(
|
||||
`[Profiling] profiler returned null profile for: ${transaction.name || transaction.description}`,
|
||||
'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
addProfileToMap(profileId, p);
|
||||
return null;
|
||||
})
|
||||
.catch(error => {
|
||||
stopProfilerSpan.finish();
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log('[Profiling] error while stopping profiler:', error);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Enqueue a timeout to prevent profiles from running over max duration.
|
||||
let maxDurationTimeoutID = WINDOW.setTimeout(() => {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(
|
||||
'[Profiling] max profile duration elapsed, stopping profiling for:',
|
||||
transaction.name || transaction.description,
|
||||
);
|
||||
}
|
||||
// If the timeout exceeds, we want to stop profiling, but not finish the transaction
|
||||
void onProfileHandler();
|
||||
}, MAX_PROFILE_DURATION_MS);
|
||||
|
||||
// We need to reference the original finish call to avoid creating an infinite loop
|
||||
const originalFinish = transaction.finish.bind(transaction);
|
||||
|
||||
/**
|
||||
* Wraps startTransaction and stopTransaction with profiling related logic.
|
||||
* startProfiling is called after the call to startTransaction in order to avoid our own code from
|
||||
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
|
||||
*/
|
||||
function profilingWrappedTransactionFinish() {
|
||||
if (!transaction) {
|
||||
return originalFinish();
|
||||
}
|
||||
// onProfileHandler should always return the same profile even if this is called multiple times.
|
||||
// Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.
|
||||
void onProfileHandler().then(
|
||||
() => {
|
||||
transaction.setContext('profile', { profile_id: profileId });
|
||||
originalFinish();
|
||||
},
|
||||
() => {
|
||||
// If onProfileHandler fails, we still want to call the original finish method.
|
||||
originalFinish();
|
||||
},
|
||||
);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
transaction.finish = profilingWrappedTransactionFinish;
|
||||
return transaction;
|
||||
}
|
||||
|
||||
export { MAX_PROFILE_DURATION_MS, onProfilingStartRouteTransaction, wrapTransactionWithProfiling };
|
||||
//# sourceMappingURL=hubextensions.js.map
|
||||
81
shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js
generated
vendored
Normal file
81
shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { logger } from '@sentry/utils';
|
||||
import { wrapTransactionWithProfiling } from './hubextensions.js';
|
||||
import { PROFILE_MAP, findProfiledTransactionsFromEnvelope, createProfilingEvent, addProfilesToEnvelope } from './utils.js';
|
||||
|
||||
/**
|
||||
* Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"]
|
||||
* This exists because we do not want to await async profiler.stop calls as transaction.finish is called
|
||||
* in a synchronous context. Instead, we handle sending the profile async from the promise callback and
|
||||
* rely on being able to pull the event from the cache when we need to construct the envelope. This makes the
|
||||
* integration less reliable as we might be dropping profiles when the cache is full.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
class BrowserProfilingIntegration {constructor() { BrowserProfilingIntegration.prototype.__init.call(this);BrowserProfilingIntegration.prototype.__init2.call(this); }
|
||||
__init() {this.name = 'BrowserProfilingIntegration';}
|
||||
__init2() {this.getCurrentHub = undefined;}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
||||
this.getCurrentHub = getCurrentHub;
|
||||
const client = this.getCurrentHub().getClient() ;
|
||||
|
||||
if (client && typeof client.on === 'function') {
|
||||
client.on('startTransaction', (transaction) => {
|
||||
wrapTransactionWithProfiling(transaction);
|
||||
});
|
||||
|
||||
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 context = profiledTransaction && profiledTransaction.contexts;
|
||||
const profile_id = context && context['profile'] && (context['profile']['profile_id'] );
|
||||
|
||||
if (!profile_id) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
||||
logger.log('[Profiling] cannot find profile for a transaction without a profile context');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
||||
if (context && context['profile']) {
|
||||
delete context.profile;
|
||||
}
|
||||
|
||||
const profile = PROFILE_MAP.get(profile_id);
|
||||
if (!profile) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
PROFILE_MAP.delete(profile_id);
|
||||
const profileEvent = createProfilingEvent(profile_id, profile, profiledTransaction );
|
||||
|
||||
if (profileEvent) {
|
||||
profilesToAddToEnvelope.push(profileEvent);
|
||||
}
|
||||
}
|
||||
|
||||
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
||||
});
|
||||
} else {
|
||||
logger.warn('[Profiling] Client does not support hooks, profiling will be disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { BrowserProfilingIntegration };
|
||||
//# sourceMappingURL=integration.js.map
|
||||
438
shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js
generated
vendored
Normal file
438
shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js
generated
vendored
Normal file
@@ -0,0 +1,438 @@
|
||||
import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core';
|
||||
import { forEachEnvelopeItem, logger, uuid4, GLOBAL_OBJ } from '@sentry/utils';
|
||||
import { WINDOW } from '../helpers.js';
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
const MS_TO_NS = 1e6;
|
||||
// Use 0 as main thread id which is identical to threadId in node:worker_threads
|
||||
// where main logs 0 and workers seem to log in increments of 1
|
||||
const THREAD_ID_STRING = String(0);
|
||||
const THREAD_NAME = 'main';
|
||||
|
||||
// Machine properties (eval only once)
|
||||
let OS_PLATFORM = '';
|
||||
let OS_PLATFORM_VERSION = '';
|
||||
let OS_ARCH = '';
|
||||
let OS_BROWSER = (WINDOW.navigator && WINDOW.navigator.userAgent) || '';
|
||||
let OS_MODEL = '';
|
||||
const OS_LOCALE =
|
||||
(WINDOW.navigator && WINDOW.navigator.language) ||
|
||||
(WINDOW.navigator && WINDOW.navigator.languages && WINDOW.navigator.languages[0]) ||
|
||||
'';
|
||||
|
||||
function isUserAgentData(data) {
|
||||
return typeof data === 'object' && data !== null && 'getHighEntropyValues' in data;
|
||||
}
|
||||
|
||||
// @ts-ignore userAgentData is not part of the navigator interface yet
|
||||
const userAgentData = WINDOW.navigator && WINDOW.navigator.userAgentData;
|
||||
|
||||
if (isUserAgentData(userAgentData)) {
|
||||
userAgentData
|
||||
.getHighEntropyValues(['architecture', 'model', 'platform', 'platformVersion', 'fullVersionList'])
|
||||
.then((ua) => {
|
||||
OS_PLATFORM = ua.platform || '';
|
||||
OS_ARCH = ua.architecture || '';
|
||||
OS_MODEL = ua.model || '';
|
||||
OS_PLATFORM_VERSION = ua.platformVersion || '';
|
||||
|
||||
if (ua.fullVersionList && ua.fullVersionList.length > 0) {
|
||||
const firstUa = ua.fullVersionList[ua.fullVersionList.length - 1];
|
||||
OS_BROWSER = `${firstUa.brand} ${firstUa.version}`;
|
||||
}
|
||||
})
|
||||
.catch(e => void e);
|
||||
}
|
||||
|
||||
function isProcessedJSSelfProfile(profile) {
|
||||
return !('thread_metadata' in profile);
|
||||
}
|
||||
|
||||
// Enriches the profile with threadId of the current thread.
|
||||
// This is done in node as we seem to not be able to get the info from C native code.
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function enrichWithThreadInformation(profile) {
|
||||
if (!isProcessedJSSelfProfile(profile)) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
return convertJSSelfProfileToSampledFormat(profile);
|
||||
}
|
||||
|
||||
// Profile is marked as optional because it is deleted from the metadata
|
||||
// by the integration before the event is processed by other integrations.
|
||||
|
||||
function getTraceId(event) {
|
||||
const traceId = event && event.contexts && event.contexts['trace'] && event.contexts['trace']['trace_id'];
|
||||
// Log a warning if the profile has an invalid traceId (should be uuidv4).
|
||||
// All profiles and transactions are rejected if this is the case and we want to
|
||||
// warn users that this is happening if they enable debug flag
|
||||
if (typeof traceId === 'string' && traceId.length !== 32) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log(`[Profiling] Invalid traceId: ${traceId} on profiled event`);
|
||||
}
|
||||
}
|
||||
if (typeof traceId !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return traceId;
|
||||
}
|
||||
/**
|
||||
* Creates a profiling event envelope from a Sentry event. If profile does not pass
|
||||
* validation, returns null.
|
||||
* @param event
|
||||
* @param dsn
|
||||
* @param metadata
|
||||
* @param tunnel
|
||||
* @returns {EventEnvelope | null}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a profiling event envelope from a Sentry event.
|
||||
*/
|
||||
function createProfilePayload(
|
||||
event,
|
||||
processedProfile,
|
||||
profile_id,
|
||||
) {
|
||||
if (event.type !== 'transaction') {
|
||||
// createProfilingEventEnvelope should only be called for transactions,
|
||||
// we type guard this behavior with isProfiledTransactionEvent.
|
||||
throw new TypeError('Profiling events may only be attached to transactions, this should never occur.');
|
||||
}
|
||||
|
||||
if (processedProfile === undefined || processedProfile === null) {
|
||||
throw new TypeError(
|
||||
`Cannot construct profiling event envelope without a valid profile. Got ${processedProfile} instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
const traceId = getTraceId(event);
|
||||
const enrichedThreadProfile = enrichWithThreadInformation(processedProfile);
|
||||
const transactionStartMs = typeof event.start_timestamp === 'number' ? event.start_timestamp * 1000 : Date.now();
|
||||
const transactionEndMs = typeof event.timestamp === 'number' ? event.timestamp * 1000 : Date.now();
|
||||
|
||||
const profile = {
|
||||
event_id: profile_id,
|
||||
timestamp: new Date(transactionStartMs).toISOString(),
|
||||
platform: 'javascript',
|
||||
version: '1',
|
||||
release: event.release || '',
|
||||
environment: event.environment || DEFAULT_ENVIRONMENT,
|
||||
runtime: {
|
||||
name: 'javascript',
|
||||
version: WINDOW.navigator.userAgent,
|
||||
},
|
||||
os: {
|
||||
name: OS_PLATFORM,
|
||||
version: OS_PLATFORM_VERSION,
|
||||
build_number: OS_BROWSER,
|
||||
},
|
||||
device: {
|
||||
locale: OS_LOCALE,
|
||||
model: OS_MODEL,
|
||||
manufacturer: OS_BROWSER,
|
||||
architecture: OS_ARCH,
|
||||
is_emulator: false,
|
||||
},
|
||||
debug_meta: {
|
||||
images: applyDebugMetadata(processedProfile.resources),
|
||||
},
|
||||
profile: enrichedThreadProfile,
|
||||
transactions: [
|
||||
{
|
||||
name: event.transaction || '',
|
||||
id: event.event_id || uuid4(),
|
||||
trace_id: traceId,
|
||||
active_thread_id: THREAD_ID_STRING,
|
||||
relative_start_ns: '0',
|
||||
relative_end_ns: ((transactionEndMs - transactionStartMs) * 1e6).toFixed(0),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JSSelfProfile to a our sampled format.
|
||||
* Does not currently perform stack indexing.
|
||||
*/
|
||||
function convertJSSelfProfileToSampledFormat(input) {
|
||||
let EMPTY_STACK_ID = undefined;
|
||||
let STACK_ID = 0;
|
||||
|
||||
// Initialize the profile that we will fill with data
|
||||
const profile = {
|
||||
samples: [],
|
||||
stacks: [],
|
||||
frames: [],
|
||||
thread_metadata: {
|
||||
[THREAD_ID_STRING]: { name: THREAD_NAME },
|
||||
},
|
||||
};
|
||||
|
||||
if (!input.samples.length) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
// We assert samples.length > 0 above and timestamp should always be present
|
||||
const start = input.samples[0].timestamp;
|
||||
|
||||
for (let i = 0; i < input.samples.length; i++) {
|
||||
const jsSample = input.samples[i];
|
||||
|
||||
// If sample has no stack, add an empty sample
|
||||
if (jsSample.stackId === undefined) {
|
||||
if (EMPTY_STACK_ID === undefined) {
|
||||
EMPTY_STACK_ID = STACK_ID;
|
||||
profile.stacks[EMPTY_STACK_ID] = [];
|
||||
STACK_ID++;
|
||||
}
|
||||
|
||||
profile['samples'][i] = {
|
||||
// convert ms timestamp to ns
|
||||
elapsed_since_start_ns: ((jsSample.timestamp - start) * MS_TO_NS).toFixed(0),
|
||||
stack_id: EMPTY_STACK_ID,
|
||||
thread_id: THREAD_ID_STRING,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
let stackTop = input.stacks[jsSample.stackId];
|
||||
|
||||
// Functions in top->down order (root is last)
|
||||
// We follow the stackTop.parentId trail and collect each visited frameId
|
||||
const stack = [];
|
||||
|
||||
while (stackTop) {
|
||||
stack.push(stackTop.frameId);
|
||||
|
||||
const frame = input.frames[stackTop.frameId];
|
||||
|
||||
// If our frame has not been indexed yet, index it
|
||||
if (profile.frames[stackTop.frameId] === undefined) {
|
||||
profile.frames[stackTop.frameId] = {
|
||||
function: frame.name,
|
||||
file: frame.resourceId ? input.resources[frame.resourceId] : undefined,
|
||||
line: frame.line,
|
||||
column: frame.column,
|
||||
};
|
||||
}
|
||||
|
||||
stackTop = stackTop.parentId === undefined ? undefined : input.stacks[stackTop.parentId];
|
||||
}
|
||||
|
||||
const sample = {
|
||||
// convert ms timestamp to ns
|
||||
elapsed_since_start_ns: ((jsSample.timestamp - start) * MS_TO_NS).toFixed(0),
|
||||
stack_id: STACK_ID,
|
||||
thread_id: THREAD_ID_STRING,
|
||||
};
|
||||
|
||||
profile['stacks'][STACK_ID] = stack;
|
||||
profile['samples'][i] = sample;
|
||||
STACK_ID++;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to envelope if they are not already present - mutates the envelope.
|
||||
* @param envelope
|
||||
*/
|
||||
function addProfilesToEnvelope(envelope, profiles) {
|
||||
if (!profiles.length) {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
for (const profile of profiles) {
|
||||
// @ts-ignore untyped envelope
|
||||
envelope[1].push([{ type: 'profile' }, profile]);
|
||||
}
|
||||
return envelope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds transactions with profile_id context in the envelope
|
||||
* @param envelope
|
||||
* @returns
|
||||
*/
|
||||
function findProfiledTransactionsFromEnvelope(envelope) {
|
||||
const events = [];
|
||||
|
||||
forEachEnvelopeItem(envelope, (item, type) => {
|
||||
if (type !== 'transaction') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let j = 1; j < item.length; j++) {
|
||||
const event = item[j] ;
|
||||
|
||||
if (event && event.contexts && event.contexts['profile'] && event.contexts['profile']['profile_id']) {
|
||||
events.push(item[j] );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
const debugIdStackParserCache = new WeakMap();
|
||||
/**
|
||||
* Applies debug meta data to an event from a list of paths to resources (sourcemaps)
|
||||
*/
|
||||
function applyDebugMetadata(resource_paths) {
|
||||
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
|
||||
|
||||
if (!debugIdMap) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hub = getCurrentHub();
|
||||
if (!hub) {
|
||||
return [];
|
||||
}
|
||||
const client = hub.getClient();
|
||||
if (!client) {
|
||||
return [];
|
||||
}
|
||||
const options = client.getOptions();
|
||||
if (!options) {
|
||||
return [];
|
||||
}
|
||||
const stackParser = options.stackParser;
|
||||
if (!stackParser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let debugIdStackFramesCache;
|
||||
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
|
||||
if (cachedDebugIdStackFrameCache) {
|
||||
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
|
||||
} else {
|
||||
debugIdStackFramesCache = new Map();
|
||||
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
|
||||
}
|
||||
|
||||
// Build a map of filename -> debug_id
|
||||
const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => {
|
||||
let parsedStack;
|
||||
|
||||
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
|
||||
if (cachedParsedStack) {
|
||||
parsedStack = cachedParsedStack;
|
||||
} else {
|
||||
parsedStack = stackParser(debugIdStackTrace);
|
||||
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
|
||||
}
|
||||
|
||||
for (let i = parsedStack.length - 1; i >= 0; i--) {
|
||||
const stackFrame = parsedStack[i];
|
||||
const file = stackFrame && stackFrame.filename;
|
||||
|
||||
if (stackFrame && file) {
|
||||
acc[file] = debugIdMap[debugIdStackTrace] ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const images = [];
|
||||
for (const path of resource_paths) {
|
||||
if (path && filenameDebugIdMap[path]) {
|
||||
images.push({
|
||||
type: 'sourcemap',
|
||||
code_file: path,
|
||||
debug_id: filenameDebugIdMap[path] ,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
|
||||
*/
|
||||
function isValidSampleRate(rate) {
|
||||
// we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
|
||||
if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
||||
logger.warn(
|
||||
`[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
|
||||
rate,
|
||||
)} of type ${JSON.stringify(typeof rate)}.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Boolean sample rates are always valid
|
||||
if (rate === true || rate === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
|
||||
if (rate < 0 || rate > 1) {
|
||||
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
|
||||
logger.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isValidProfile(profile) {
|
||||
if (profile.samples.length < 2) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
// Log a warning if the profile has less than 2 samples so users can know why
|
||||
// they are not seeing any profiling data and we cant avoid the back and forth
|
||||
// of asking them to provide us with a dump of the profile data.
|
||||
logger.log('[Profiling] Discarding profile because it contains less than 2 samples');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!profile.frames.length) {
|
||||
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
||||
logger.log('[Profiling] Discarding profile because it contains no frames');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a profiling envelope item, if the profile does not pass validation, returns null.
|
||||
* @param event
|
||||
* @returns {Profile | null}
|
||||
*/
|
||||
function createProfilingEvent(profile_id, profile, event) {
|
||||
if (!isValidProfile(profile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createProfilePayload(event, profile, profile_id);
|
||||
}
|
||||
|
||||
const PROFILE_MAP = new Map();
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function addProfileToMap(profile_id, profile) {
|
||||
PROFILE_MAP.set(profile_id, profile);
|
||||
|
||||
if (PROFILE_MAP.size > 30) {
|
||||
const last = PROFILE_MAP.keys().next().value;
|
||||
PROFILE_MAP.delete(last);
|
||||
}
|
||||
}
|
||||
|
||||
export { PROFILE_MAP, addProfileToMap, addProfilesToEnvelope, applyDebugMetadata, convertJSSelfProfileToSampledFormat, createProfilePayload, createProfilingEvent, enrichWithThreadInformation, findProfiledTransactionsFromEnvelope, isValidSampleRate };
|
||||
//# sourceMappingURL=utils.js.map
|
||||
Reference in New Issue
Block a user