UNPKG

@sentry/react-native

Version:
179 lines 7.37 kB
import { debug } from '@sentry/core'; import { MAX_PROFILE_DURATION_MS } from './constants'; import { DEFAULT_BUNDLE_NAME } from './hermes'; const PLACEHOLDER_THREAD_ID_STRING = '0'; const MS_TO_NS = 1e6; const MAX_PROFILE_DURATION_NS = MAX_PROFILE_DURATION_MS * MS_TO_NS; const UNKNOWN_STACK_ID = -1; const JS_THREAD_NAME = 'JavaScriptThread'; const JS_THREAD_PRIORITY = 1; /** * Converts a Hermes profile to a Sentry profile. * * Maps Hermes samples to Sentry samples. * Maps Hermes stack frames to Sentry frames. * Hermes stack frame is an object representing a function call in the stack * with a link to its parent stack frame. Root of the represented stack tree * is main function call in Hermes that is [root] stack frame. * * @returns Sentry profile or null if no samples are found. */ export function convertToSentryProfile(hermesProfile) { if (hermesProfile.samples.length === 0) { debug.warn('[Profiling] No samples found in profile.'); return null; } const { samples, hermesStacks, jsThreads } = mapSamples(hermesProfile.samples); const { frames, hermesStackFrameIdToSentryFrameIdMap } = mapFrames(hermesProfile.stackFrames); const { stacks, hermesStackToSentryStackMap } = mapStacks(hermesStacks, hermesProfile.stackFrames, hermesStackFrameIdToSentryFrameIdMap); for (const sample of samples) { const sentryStackId = hermesStackToSentryStackMap.get(sample.stack_id); if (sentryStackId === undefined) { debug.error(`[Profiling] Hermes Stack ID ${sample.stack_id} not found when mapping to Sentry Stack ID.`); sample.stack_id = UNKNOWN_STACK_ID; } else { sample.stack_id = sentryStackId; } } const thread_metadata = {}; for (const jsThreadId of jsThreads) { thread_metadata[jsThreadId] = { name: JS_THREAD_NAME, priority: JS_THREAD_PRIORITY, }; } const active_thread_id = Object.keys(thread_metadata)[0] || PLACEHOLDER_THREAD_ID_STRING; return { samples, frames, stacks, thread_metadata, active_thread_id, }; } /** * Maps Hermes samples to Sentry samples. * Calculates the elapsed time since the first sample based on the absolute timestamps of the Hermes samples. * Hermes stack frame IDs represent the last (leaf, furthest from the main func) frame of the call stack. * @returns the mapped Sentry samples, the set of Hermes stack frame IDs, and the set of JS thread IDs */ export function mapSamples(hermesSamples, maxElapsedSinceStartNs = MAX_PROFILE_DURATION_NS) { const samples = []; const jsThreads = new Set(); const hermesStacks = new Set(); const firstSample = hermesSamples[0]; if (!firstSample) { debug.warn('[Profiling] No samples found in profile.'); return { samples, hermesStacks, jsThreads, }; } const start = Number(firstSample.ts); for (const hermesSample of hermesSamples) { jsThreads.add(hermesSample.tid); hermesStacks.add(hermesSample.sf); const elapsed_since_start_ns = (Number(hermesSample.ts) - start) * 1e3; if (elapsed_since_start_ns >= maxElapsedSinceStartNs) { debug.warn(`[Profiling] Sample has elapsed time since start ${elapsed_since_start_ns}ns ` + `greater than the max elapsed time ${maxElapsedSinceStartNs}ns.`); break; } samples.push({ stack_id: hermesSample.sf, thread_id: hermesSample.tid, elapsed_since_start_ns: elapsed_since_start_ns.toFixed(0), }); } return { samples, hermesStacks, jsThreads, }; } /** * Maps Hermes StackFrames tree represented as an JS object to a Sentry frames array. * Converts line and columns strings to numbers. * @returns the mapped Sentry frames */ function mapFrames(hermesStackFrames) { const frames = []; const hermesStackFrameIdToSentryFrameIdMap = new Map(); for (const key in hermesStackFrames) { // asc order based on the key is not guaranteed if (!Object.prototype.hasOwnProperty.call(hermesStackFrames, key)) { continue; } const hermesStackFrame = hermesStackFrames[key]; if (hermesStackFrame) { hermesStackFrameIdToSentryFrameIdMap.set(Number(key), frames.length); frames.push(parseHermesJSStackFrame(hermesStackFrame)); } } return { frames, hermesStackFrameIdToSentryFrameIdMap, }; } /** * Maps Hermes stack frame IDs to Sentry stack arrays. * Hermes stack frame IDs represent the last (leaf, furthest from the main func) frame of the call stack. * @returns the mapped Sentry stacks and a map from Hermes stack IDs to Sentry stack IDs (indices in the stacks array) */ function mapStacks(hermesStacks, hermesStackFrames, hermesStackFrameIdToSentryFrameIdMap) { var _a; const hermesStackToSentryStackMap = new Map(); const stacks = []; for (const hermesStackFunctionFrameId of hermesStacks) { const stackId = stacks.length; hermesStackToSentryStackMap.set(hermesStackFunctionFrameId, stackId); const stack = []; let currentHermesFrameId = hermesStackFunctionFrameId; while (currentHermesFrameId !== undefined) { const sentryFrameId = hermesStackFrameIdToSentryFrameIdMap.get(currentHermesFrameId); sentryFrameId !== undefined && stack.push(sentryFrameId); currentHermesFrameId = (_a = hermesStackFrames[currentHermesFrameId]) === null || _a === void 0 ? void 0 : _a.parent; } stacks.push(stack); } return { stacks, hermesStackToSentryStackMap, }; } /** * Parses Hermes StackFrame to Sentry StackFrame. * For native frames only function name is returned, for Hermes bytecode the line and column are calculated. */ export function parseHermesJSStackFrame(frame) { if (frame.category !== 'JavaScript') { // Native if (frame.name === '[root]') { return { function: frame.name, in_app: false }; } return { function: frame.name }; } if (frame.funcVirtAddr !== undefined && frame.offset !== undefined) { // Hermes Bytecode return { function: frame.name, abs_path: DEFAULT_BUNDLE_NAME, // https://github.com/krystofwoldrich/metro/blob/417e6f276ff9422af6039fc4d1bce41fcf7d9f46/packages/metro-symbolicate/src/Symbolication.js#L298-L301 // Hermes lineno is hardcoded 1, currently only one bundle symbolication is supported by metro-symbolicate and thus by us. lineno: 1, // Hermes colno is 0-based, while Sentry is 1-based colno: Number(frame.funcVirtAddr) + Number(frame.offset) + 1, }; } // JavaScript const indexOfLeftParenthesis = frame.name.indexOf('('); return { function: indexOfLeftParenthesis !== -1 ? frame.name.substring(0, indexOfLeftParenthesis) || undefined : frame.name, abs_path: DEFAULT_BUNDLE_NAME, lineno: frame.line !== undefined ? Number(frame.line) : undefined, colno: frame.column !== undefined ? Number(frame.column) : undefined, }; } //# sourceMappingURL=convertHermesProfile.js.map