{"version":3,"file":"hubextensions.js","sources":["../../../../src/profiling/hubextensions.ts"],"sourcesContent":["/* eslint-disable complexity */\nimport { spanToJSON } from '@sentry/core';\nimport type { Transaction } from '@sentry/types';\nimport { logger, timestampInSeconds, uuid4 } from '@sentry/utils';\n\nimport { DEBUG_BUILD } from '../debug-build';\nimport { WINDOW } from '../helpers';\nimport type { JSSelfProfile } from './jsSelfProfiling';\nimport {\n MAX_PROFILE_DURATION_MS,\n addProfileToGlobalCache,\n isAutomatedPageLoadTransaction,\n shouldProfileTransaction,\n startJSSelfProfile,\n} from './utils';\n\n/**\n * Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported -\n * if that happens we want to avoid throwing an error from profiling code.\n * see https://github.com/getsentry/sentry-javascript/issues/4731.\n *\n * @experimental\n */\nexport function onProfilingStartRouteTransaction(transaction: Transaction | undefined): Transaction | undefined {\n if (!transaction) {\n if (DEBUG_BUILD) {\n logger.log('[Profiling] Transaction is undefined, skipping profiling');\n }\n return transaction;\n }\n\n if (shouldProfileTransaction(transaction)) {\n return startProfileForTransaction(transaction);\n }\n\n return transaction;\n}\n\n/**\n * Wraps startTransaction and stopTransaction with profiling related logic.\n * startProfileForTransaction is called after the call to startTransaction in order to avoid our own code from\n * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.\n */\nexport function startProfileForTransaction(transaction: Transaction): Transaction {\n // Start the profiler and get the profiler instance.\n let startTimestamp: number | undefined;\n if (isAutomatedPageLoadTransaction(transaction)) {\n startTimestamp = timestampInSeconds() * 1000;\n }\n\n const profiler = startJSSelfProfile();\n\n // We failed to construct the profiler, fallback to original transaction.\n // No need to log anything as this has already been logged in startProfile.\n if (!profiler) {\n return transaction;\n }\n\n if (DEBUG_BUILD) {\n logger.log(`[Profiling] started profiling transaction: ${spanToJSON(transaction).description}`);\n }\n\n // We create \"unique\" transaction names to avoid concurrent transactions with same names\n // from being ignored by the profiler. From here on, only this transaction name should be used when\n // calling the profiler methods. Note: we log the original name to the user to avoid confusion.\n const profileId = uuid4();\n\n // A couple of important things to note here:\n // `CpuProfilerBindings.stopProfiling` will be scheduled to run in 30seconds in order to exceed max profile duration.\n // Whichever of the two (transaction.finish/timeout) is first to run, the profiling will be stopped and the gathered profile\n // will be processed when the original transaction is finished. Since onProfileHandler can be invoked multiple times in the\n // event of an error or user mistake (calling transaction.finish multiple times), it is important that the behavior of onProfileHandler\n // is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler.\n // After the original finish method is called, the event will be reported through the integration and delegated to transport.\n const processedProfile: JSSelfProfile | null = null;\n\n /**\n * Idempotent handler for profile stop\n */\n async function onProfileHandler(): Promise {\n // Check if the profile exists and return it the behavior has to be idempotent as users may call transaction.finish multiple times.\n if (!transaction) {\n return null;\n }\n // Satisfy the type checker, but profiler will always be defined here.\n if (!profiler) {\n return null;\n }\n if (processedProfile) {\n if (DEBUG_BUILD) {\n logger.log('[Profiling] profile for:', spanToJSON(transaction).description, 'already exists, returning early');\n }\n return null;\n }\n\n return profiler\n .stop()\n .then((profile: JSSelfProfile): null => {\n if (maxDurationTimeoutID) {\n WINDOW.clearTimeout(maxDurationTimeoutID);\n maxDurationTimeoutID = undefined;\n }\n\n if (DEBUG_BUILD) {\n logger.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(transaction).description}`);\n }\n\n // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.\n if (!profile) {\n if (DEBUG_BUILD) {\n logger.log(\n `[Profiling] profiler returned null profile for: ${spanToJSON(transaction).description}`,\n 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started',\n );\n }\n return null;\n }\n\n addProfileToGlobalCache(profileId, profile);\n return null;\n })\n .catch(error => {\n if (DEBUG_BUILD) {\n logger.log('[Profiling] error while stopping profiler:', error);\n }\n return null;\n });\n }\n\n // Enqueue a timeout to prevent profiles from running over max duration.\n let maxDurationTimeoutID: number | undefined = WINDOW.setTimeout(() => {\n if (DEBUG_BUILD) {\n logger.log(\n '[Profiling] max profile duration elapsed, stopping profiling for:',\n spanToJSON(transaction).description,\n );\n }\n // If the timeout exceeds, we want to stop profiling, but not finish the transaction\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n onProfileHandler();\n }, MAX_PROFILE_DURATION_MS);\n\n // We need to reference the original end call to avoid creating an infinite loop\n const originalEnd = transaction.end.bind(transaction);\n\n /**\n * Wraps startTransaction and stopTransaction with profiling related logic.\n * startProfiling is called after the call to startTransaction in order to avoid our own code from\n * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.\n */\n function profilingWrappedTransactionEnd(): Transaction {\n if (!transaction) {\n return originalEnd();\n }\n // onProfileHandler should always return the same profile even if this is called multiple times.\n // Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.\n void onProfileHandler().then(\n () => {\n // TODO: Can we rewrite this to use attributes?\n // eslint-disable-next-line deprecation/deprecation\n transaction.setContext('profile', { profile_id: profileId, start_timestamp: startTimestamp });\n originalEnd();\n },\n () => {\n // If onProfileHandler fails, we still want to call the original finish method.\n originalEnd();\n },\n );\n\n return transaction;\n }\n\n transaction.end = profilingWrappedTransactionEnd;\n return transaction;\n}\n"],"names":[],"mappings":"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"}