summaryrefslogtreecommitdiff
path: root/shared/logger/node_modules/@sentry/browser/esm/profiling
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /shared/logger/node_modules/@sentry/browser/esm/profiling
init commit
Diffstat (limited to 'shared/logger/node_modules/@sentry/browser/esm/profiling')
-rw-r--r--shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js240
-rw-r--r--shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js81
-rw-r--r--shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js438
3 files changed, 759 insertions, 0 deletions
diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js
new file mode 100644
index 0000000..c1cd92d
--- /dev/null
+++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js
@@ -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
diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js
new file mode 100644
index 0000000..f09e4ed
--- /dev/null
+++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js
@@ -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
diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js
new file mode 100644
index 0000000..e8beae2
--- /dev/null
+++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js
@@ -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