UNPKG

@sentry/react-native

Version:
210 lines 9.78 kB
import { fill, getActiveSpan, getSpanDescendants, logger, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, SPAN_STATUS_OK, spanToJSON, startInactiveSpan } from '@sentry/core'; import * as React from 'react'; import { isTurboModuleEnabled } from '../utils/environment'; import { SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY, SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY } from './origin'; import { getRNSentryOnDrawReporter, nativeComponentExists } from './timetodisplaynative'; import { setSpanDurationAsMeasurement } from './utils'; let nativeComponentMissingLogged = false; /** * Flags of active spans with manual initial display. */ export const manualInitialDisplaySpans = new WeakMap(); /** * Flag full display called before initial display for an active span. */ const fullDisplayBeforeInitialDisplay = new WeakMap(); /** * Component to measure time to initial display. * * The initial display is recorded when the component prop `record` is true. * * <TimeToInitialDisplay record /> */ export function TimeToInitialDisplay(props) { const activeSpan = getActiveSpan(); if (activeSpan) { manualInitialDisplaySpans.set(activeSpan, true); startTimeToInitialDisplaySpan(); } return React.createElement(TimeToDisplay, { initialDisplay: props.record }, props.children); } /** * Component to measure time to full display. * * The initial display is recorded when the component prop `record` is true. * * <TimeToInitialDisplay record /> */ export function TimeToFullDisplay(props) { startTimeToFullDisplaySpan(); return React.createElement(TimeToDisplay, { fullDisplay: props.record }, props.children); } function TimeToDisplay(props) { const RNSentryOnDrawReporter = getRNSentryOnDrawReporter(); const isNewArchitecture = isTurboModuleEnabled(); if (__DEV__ && (isNewArchitecture || (!nativeComponentExists && !nativeComponentMissingLogged))) { nativeComponentMissingLogged = true; // Using setTimeout with a delay of 0 milliseconds to defer execution and avoid printing the React stack trace. setTimeout(() => { logger.warn('TimeToInitialDisplay and TimeToFullDisplay are not supported on the web, Expo Go and New Architecture. Run native build or report an issue at https://github.com/getsentry/sentry-react-native'); }, 0); } const onDraw = (event) => onDrawNextFrame(event); return (React.createElement(React.Fragment, null, React.createElement(RNSentryOnDrawReporter, { onDrawNextFrame: onDraw, initialDisplay: props.initialDisplay, fullDisplay: props.fullDisplay }), props.children)); } /** * Starts a new span for the initial display. * * Returns current span if already exists in the currently active span. */ export function startTimeToInitialDisplaySpan(options) { const activeSpan = getActiveSpan(); if (!activeSpan) { logger.warn(`[TimeToDisplay] No active span found to attach ui.load.initial_display to.`); return undefined; } const existingSpan = getSpanDescendants(activeSpan).find((span) => spanToJSON(span).op === 'ui.load.initial_display'); if (existingSpan) { logger.debug(`[TimeToDisplay] Found existing ui.load.initial_display span.`); return existingSpan; } const initialDisplaySpan = startInactiveSpan(Object.assign({ op: 'ui.load.initial_display', name: 'Time To Initial Display', startTime: spanToJSON(activeSpan).start_timestamp }, options)); if (!initialDisplaySpan) { return undefined; } if (options === null || options === void 0 ? void 0 : options.isAutoInstrumented) { initialDisplaySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY); } else { manualInitialDisplaySpans.set(activeSpan, true); initialDisplaySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY); } return initialDisplaySpan; } /** * Starts a new span for the full display. * * Returns current span if already exists in the currently active span. */ export function startTimeToFullDisplaySpan(options = { timeoutMs: 30000, }) { const activeSpan = getActiveSpan(); if (!activeSpan) { logger.warn(`[TimeToDisplay] No active span found to attach ui.load.full_display to.`); return undefined; } const descendantSpans = getSpanDescendants(activeSpan); const initialDisplaySpan = descendantSpans.find((span) => spanToJSON(span).op === 'ui.load.initial_display'); if (!initialDisplaySpan) { logger.warn(`[TimeToDisplay] No initial display span found to attach ui.load.full_display to.`); return undefined; } const existingSpan = descendantSpans.find((span) => spanToJSON(span).op === 'ui.load.full_display'); if (existingSpan) { logger.debug(`[TimeToDisplay] Found existing ui.load.full_display span.`); return existingSpan; } const fullDisplaySpan = startInactiveSpan(Object.assign({ op: 'ui.load.full_display', name: 'Time To Full Display', startTime: spanToJSON(initialDisplaySpan).start_timestamp }, options)); if (!fullDisplaySpan) { return undefined; } const timeout = setTimeout(() => { if (spanToJSON(fullDisplaySpan).timestamp) { return; } fullDisplaySpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }); fullDisplaySpan.end(spanToJSON(initialDisplaySpan).timestamp); setSpanDurationAsMeasurement('time_to_full_display', fullDisplaySpan); logger.warn(`[TimeToDisplay] Full display span deadline_exceeded.`); }, options.timeoutMs); fill(fullDisplaySpan, 'end', (originalEnd) => (endTimestamp) => { clearTimeout(timeout); originalEnd.call(fullDisplaySpan, endTimestamp); }); if (options === null || options === void 0 ? void 0 : options.isAutoInstrumented) { fullDisplaySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY); } else { fullDisplaySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY); } return fullDisplaySpan; } function onDrawNextFrame(event) { logger.debug(`[TimeToDisplay] onDrawNextFrame: ${JSON.stringify(event.nativeEvent)}`); if (event.nativeEvent.type === 'fullDisplay') { return updateFullDisplaySpan(event.nativeEvent.newFrameTimestampInSeconds); } if (event.nativeEvent.type === 'initialDisplay') { return updateInitialDisplaySpan(event.nativeEvent.newFrameTimestampInSeconds); } } function updateInitialDisplaySpan(frameTimestampSeconds) { const span = startTimeToInitialDisplaySpan(); if (!span) { logger.warn(`[TimeToDisplay] No span found or created, possibly performance is disabled.`); return; } const activeSpan = getActiveSpan(); if (!activeSpan) { logger.warn(`[TimeToDisplay] No active span found to attach ui.load.initial_display to.`); return; } if (spanToJSON(span).parent_span_id !== spanToJSON(activeSpan).span_id) { logger.warn(`[TimeToDisplay] Initial display span is not a child of current active span.`); return; } if (spanToJSON(span).timestamp) { logger.warn(`[TimeToDisplay] ${spanToJSON(span).description} span already ended.`); return; } span.end(frameTimestampSeconds); span.setStatus({ code: SPAN_STATUS_OK }); logger.debug(`[TimeToDisplay] ${spanToJSON(span).description} span updated with end timestamp.`); if (fullDisplayBeforeInitialDisplay.has(activeSpan)) { fullDisplayBeforeInitialDisplay.delete(activeSpan); logger.debug(`[TimeToDisplay] Updating full display with initial display (${span.spanContext().spanId}) end.`); updateFullDisplaySpan(frameTimestampSeconds, span); } setSpanDurationAsMeasurement('time_to_initial_display', span); } function updateFullDisplaySpan(frameTimestampSeconds, passedInitialDisplaySpan) { const activeSpan = getActiveSpan(); if (!activeSpan) { logger.warn(`[TimeToDisplay] No active span found to update ui.load.full_display in.`); return; } const existingInitialDisplaySpan = passedInitialDisplaySpan || getSpanDescendants(activeSpan).find((span) => spanToJSON(span).op === 'ui.load.initial_display'); const initialDisplayEndTimestamp = existingInitialDisplaySpan && spanToJSON(existingInitialDisplaySpan).timestamp; if (!initialDisplayEndTimestamp) { fullDisplayBeforeInitialDisplay.set(activeSpan, true); logger.warn(`[TimeToDisplay] Full display called before initial display for active span (${activeSpan.spanContext().spanId}).`); return; } const span = startTimeToFullDisplaySpan({ isAutoInstrumented: true, }); if (!span) { logger.warn(`[TimeToDisplay] No TimeToFullDisplay span found or created, possibly performance is disabled.`); return; } const spanJSON = spanToJSON(span); if (spanJSON.timestamp) { logger.warn(`[TimeToDisplay] ${spanJSON.description} (${spanJSON.span_id}) span already ended.`); return; } if (initialDisplayEndTimestamp > frameTimestampSeconds) { logger.warn(`[TimeToDisplay] Using initial display end. Full display end frame timestamp is before initial display end.`); span.end(initialDisplayEndTimestamp); } else { span.end(frameTimestampSeconds); } span.setStatus({ code: SPAN_STATUS_OK }); logger.debug(`[TimeToDisplay] ${spanJSON.description} (${spanJSON.span_id}) span updated with end timestamp.`); setSpanDurationAsMeasurement('time_to_full_display', span); } //# sourceMappingURL=timetodisplay.js.map