UNPKG

@sentry/react-native

Version:
241 lines 13.8 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { debug } from '@sentry/core'; import { NATIVE } from '../../wrapper'; import { UI_LOAD_FULL_DISPLAY, UI_LOAD_INITIAL_DISPLAY } from '../ops'; import { SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY, SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY } from '../origin'; import { getReactNavigationIntegration } from '../reactnavigation'; import { SEMANTIC_ATTRIBUTE_ROUTE_HAS_BEEN_SEEN } from '../semanticAttributes'; import { SPAN_THREAD_NAME, SPAN_THREAD_NAME_JAVASCRIPT } from '../span'; import { _popImperativeTtfdTimestamp } from '../timetodisplay'; import { clearSpan as clearTimeToDisplayCoordinatorSpan } from '../timeToDisplayCoordinator'; import { getTimeToInitialDisplayFallback } from '../timeToDisplayFallback'; import { createSpanJSON } from '../utils'; export const INTEGRATION_NAME = 'TimeToDisplay'; const TIME_TO_DISPLAY_TIMEOUT_MS = 30000; const isDeadlineExceeded = (durationMs) => durationMs > TIME_TO_DISPLAY_TIMEOUT_MS; export const timeToDisplayIntegration = () => { let enableTimeToInitialDisplayForPreloadedRoutes = false; return { name: INTEGRATION_NAME, afterAllSetup(client) { var _a, _b; enableTimeToInitialDisplayForPreloadedRoutes = (_b = (_a = getReactNavigationIntegration(client)) === null || _a === void 0 ? void 0 : _a.options.enableTimeToInitialDisplayForPreloadedRoutes) !== null && _b !== void 0 ? _b : false; }, // oxlint-disable-next-line eslint(complexity) processEvent: (event) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d, _e; if (event.type !== 'transaction') { // TimeToDisplay data is only relevant for transactions return event; } const rootSpanId = (_b = (_a = event.contexts) === null || _a === void 0 ? void 0 : _a.trace) === null || _b === void 0 ? void 0 : _b.span_id; if (!rootSpanId) { debug.warn(`[${INTEGRATION_NAME}] No root span id found in transaction.`); return event; } // `trace_id` is a required field of a transaction's trace context and is // always present here alongside `rootSpanId`. Read it (instead of early // returning) so the TTID/TTFD spans inherit the transaction's trace and // nest correctly, and so we never skip the coordinator cleanup at the end // of `processEvent`. If it were ever absent, `createSpanJSON` keeps its // existing behavior of generating one. const traceId = (_d = (_c = event.contexts) === null || _c === void 0 ? void 0 : _c.trace) === null || _d === void 0 ? void 0 : _d.trace_id; const transactionStartTimestampSeconds = event.start_timestamp; if (!transactionStartTimestampSeconds) { // This should never happen debug.warn(`[${INTEGRATION_NAME}] No transaction start timestamp found in transaction.`); return event; } event.spans = event.spans || []; event.measurements = event.measurements || {}; const ttidSpan = yield addTimeToInitialDisplay({ event, rootSpanId, traceId, transactionStartTimestampSeconds, enableTimeToInitialDisplayForPreloadedRoutes, }); const ttfdSpan = yield addTimeToFullDisplay({ event, rootSpanId, traceId, transactionStartTimestampSeconds, ttidSpan, }); const ttidDurationMs = (ttidSpan === null || ttidSpan === void 0 ? void 0 : ttidSpan.start_timestamp) && (ttidSpan === null || ttidSpan === void 0 ? void 0 : ttidSpan.timestamp) ? (ttidSpan.timestamp - ttidSpan.start_timestamp) * 1000 : undefined; const ttidDeadlineExceeded = ttidDurationMs !== undefined && isDeadlineExceeded(ttidDurationMs); if (ttidDurationMs !== undefined && !ttidDeadlineExceeded) { event.measurements['time_to_initial_display'] = { value: ttidDurationMs, unit: 'millisecond', }; } const ttfdDurationMs = (ttfdSpan === null || ttfdSpan === void 0 ? void 0 : ttfdSpan.start_timestamp) && (ttfdSpan === null || ttfdSpan === void 0 ? void 0 : ttfdSpan.timestamp) ? (ttfdSpan.timestamp - ttfdSpan.start_timestamp) * 1000 : undefined; const ttfdDeadlineExceeded = ttfdDurationMs !== undefined && isDeadlineExceeded(ttfdDurationMs); if (ttfdDurationMs !== undefined) { if (ttfdDeadlineExceeded) { if (event.measurements['time_to_initial_display']) { event.measurements['time_to_full_display'] = event.measurements['time_to_initial_display']; } } else { event.measurements['time_to_full_display'] = { value: ttfdDurationMs, unit: 'millisecond', }; } } const newTransactionEndTimestampSeconds = Math.max(!ttidDeadlineExceeded && (ttidSpan === null || ttidSpan === void 0 ? void 0 : ttidSpan.timestamp) ? ttidSpan.timestamp : -1, !ttfdDeadlineExceeded && (ttfdSpan === null || ttfdSpan === void 0 ? void 0 : ttfdSpan.timestamp) ? ttfdSpan.timestamp : -1, (_e = event.timestamp) !== null && _e !== void 0 ? _e : -1); if (newTransactionEndTimestampSeconds !== -1) { event.timestamp = newTransactionEndTimestampSeconds; } clearTimeToDisplayCoordinatorSpan(rootSpanId); return event; }), }; }; function addTimeToInitialDisplay(_a) { return __awaiter(this, arguments, void 0, function* ({ event, rootSpanId, traceId, transactionStartTimestampSeconds, enableTimeToInitialDisplayForPreloadedRoutes, }) { var _b; const ttidEndTimestampSeconds = yield NATIVE.popTimeToDisplayFor(`ttid-${rootSpanId}`); event.spans = event.spans || []; let ttidSpan = (_b = event.spans) === null || _b === void 0 ? void 0 : _b.find(span => span.op === UI_LOAD_INITIAL_DISPLAY); if (ttidSpan && (ttidSpan.status === undefined || ttidSpan.status === 'ok') && !ttidEndTimestampSeconds) { debug.log(`[${INTEGRATION_NAME}] Ttid span already exists and is ok.`, ttidSpan); return ttidSpan; } if (!ttidEndTimestampSeconds) { debug.log(`[${INTEGRATION_NAME}] No manual ttid end timestamp found for span ${rootSpanId}.`); return addAutomaticTimeToInitialDisplay({ event, rootSpanId, traceId, transactionStartTimestampSeconds, enableTimeToInitialDisplayForPreloadedRoutes, }); } const manualDurationMs = (ttidEndTimestampSeconds - transactionStartTimestampSeconds) * 1000; const manualStatus = isDeadlineExceeded(manualDurationMs) ? 'deadline_exceeded' : 'ok'; if (ttidSpan) { ttidSpan.status = manualStatus; ttidSpan.timestamp = ttidEndTimestampSeconds; debug.log(`[${INTEGRATION_NAME}] Updated existing ttid span.`, ttidSpan); return ttidSpan; } ttidSpan = createSpanJSON({ status: manualStatus, op: UI_LOAD_INITIAL_DISPLAY, description: 'Time To Initial Display', start_timestamp: transactionStartTimestampSeconds, timestamp: ttidEndTimestampSeconds, origin: SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY, parent_span_id: rootSpanId, trace_id: traceId, data: { [SPAN_THREAD_NAME]: SPAN_THREAD_NAME_JAVASCRIPT, }, }); debug.log(`[${INTEGRATION_NAME}] Added ttid span to transaction.`, ttidSpan); event.spans.push(ttidSpan); return ttidSpan; }); } function addAutomaticTimeToInitialDisplay(_a) { return __awaiter(this, arguments, void 0, function* ({ event, rootSpanId, traceId, transactionStartTimestampSeconds, enableTimeToInitialDisplayForPreloadedRoutes, }) { var _b, _c, _d, _e, _f, _g; const ttidNativeTimestampSeconds = yield NATIVE.popTimeToDisplayFor(`ttid-navigation-${rootSpanId}`); const ttidFallbackTimestampSeconds = yield getTimeToInitialDisplayFallback(rootSpanId); const hasBeenSeen = (_d = (_c = (_b = event.contexts) === null || _b === void 0 ? void 0 : _b.trace) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d[SEMANTIC_ATTRIBUTE_ROUTE_HAS_BEEN_SEEN]; if (hasBeenSeen && !enableTimeToInitialDisplayForPreloadedRoutes) { debug.log(`[${INTEGRATION_NAME}] Route has been seen and time to initial display is disabled for preloaded routes.`); return undefined; } const ttidTimestampSeconds = ttidNativeTimestampSeconds !== null && ttidNativeTimestampSeconds !== void 0 ? ttidNativeTimestampSeconds : ttidFallbackTimestampSeconds; if (!ttidTimestampSeconds) { debug.log(`[${INTEGRATION_NAME}] No automatic ttid end timestamp found for span ${rootSpanId}.`); return undefined; } const viewNames = (_f = (_e = event.contexts) === null || _e === void 0 ? void 0 : _e.app) === null || _f === void 0 ? void 0 : _f.view_names; const screenName = Array.isArray(viewNames) ? viewNames[0] : viewNames; const durationMs = (ttidTimestampSeconds - transactionStartTimestampSeconds) * 1000; const ttidSpan = createSpanJSON({ status: isDeadlineExceeded(durationMs) ? 'deadline_exceeded' : 'ok', op: UI_LOAD_INITIAL_DISPLAY, description: screenName ? `${screenName} initial display` : 'Time To Initial Display', start_timestamp: transactionStartTimestampSeconds, timestamp: ttidTimestampSeconds, origin: SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY, parent_span_id: rootSpanId, trace_id: traceId, data: { [SPAN_THREAD_NAME]: SPAN_THREAD_NAME_JAVASCRIPT, }, }); event.spans = (_g = event.spans) !== null && _g !== void 0 ? _g : []; event.spans.push(ttidSpan); return ttidSpan; }); } function addTimeToFullDisplay(_a) { return __awaiter(this, arguments, void 0, function* ({ event, rootSpanId, traceId, transactionStartTimestampSeconds, ttidSpan, }) { var _b; const nativeTtfdTimestamp = yield NATIVE.popTimeToDisplayFor(`ttfd-${rootSpanId}`); const imperativeTtfdTimestamp = _popImperativeTtfdTimestamp(rootSpanId); const ttfdEndTimestampSeconds = nativeTtfdTimestamp !== null && nativeTtfdTimestamp !== void 0 ? nativeTtfdTimestamp : imperativeTtfdTimestamp; if (!ttidSpan) { return undefined; } event.spans = event.spans || []; let ttfdSpan = (_b = event.spans) === null || _b === void 0 ? void 0 : _b.find(span => span.op === UI_LOAD_FULL_DISPLAY); if (ttfdSpan && (ttfdSpan.status === undefined || ttfdSpan.status === 'ok') && !ttfdEndTimestampSeconds) { debug.log(`[${INTEGRATION_NAME}] Ttfd span already exists and is ok.`, ttfdSpan); return ttfdSpan; } if (!ttfdEndTimestampSeconds) { return undefined; } let ttfdAdjustedEndTimestampSeconds = ttfdEndTimestampSeconds; const ttfdIsBeforeTtid = ttidSpan.timestamp && ttfdEndTimestampSeconds < ttidSpan.timestamp; if (ttfdIsBeforeTtid && ttidSpan.timestamp) { ttfdAdjustedEndTimestampSeconds = ttidSpan.timestamp; } const durationMs = (ttfdAdjustedEndTimestampSeconds - transactionStartTimestampSeconds) * 1000; const ttfdStatus = isDeadlineExceeded(durationMs) ? 'deadline_exceeded' : 'ok'; if (ttfdSpan) { ttfdSpan.status = ttfdStatus; ttfdSpan.timestamp = ttfdAdjustedEndTimestampSeconds; debug.log(`[${INTEGRATION_NAME}] Updated existing ttfd span.`, ttfdSpan); return ttfdSpan; } ttfdSpan = createSpanJSON({ status: ttfdStatus, op: UI_LOAD_FULL_DISPLAY, description: 'Time To Full Display', start_timestamp: transactionStartTimestampSeconds, timestamp: ttfdAdjustedEndTimestampSeconds, origin: SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY, parent_span_id: rootSpanId, trace_id: traceId, data: { [SPAN_THREAD_NAME]: SPAN_THREAD_NAME_JAVASCRIPT, }, }); debug.log(`[${INTEGRATION_NAME}] Added ttfd span to transaction.`, ttfdSpan); event.spans.push(ttfdSpan); return ttfdSpan; }); } //# sourceMappingURL=timeToDisplayIntegration.js.map