({ metric })
| 94 | * exported only for testing |
| 95 | */ |
| 96 | export const _onInp: InstrumentationHandlerCallback = ({ metric }) => { |
| 97 | if (metric.value == undefined) { |
| 98 | return; |
| 99 | } |
| 100 | |
| 101 | const duration = msToSec(metric.value); |
| 102 | |
| 103 | // We received occasional reports of hour-long INP values. |
| 104 | // Therefore, we add a sanity check to avoid creating spans for |
| 105 | // unrealistically long INP durations. |
| 106 | if (duration > MAX_PLAUSIBLE_INP_DURATION) { |
| 107 | return; |
| 108 | } |
| 109 | |
| 110 | const entry = metric.entries.find(entry => entry.duration === metric.value && INP_ENTRY_MAP[entry.name]); |
| 111 | |
| 112 | if (!entry) { |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | const { interactionId } = entry; |
| 117 | const interactionType = INP_ENTRY_MAP[entry.name]; |
| 118 | |
| 119 | /** Build the INP span, create an envelope from the span, and then send the envelope */ |
| 120 | const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime); |
| 121 | const activeSpan = getActiveSpan(); |
| 122 | const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; |
| 123 | |
| 124 | // We first try to lookup the interaction context from our INTERACTIONS_SPAN_MAP, |
| 125 | // where we cache the route and element name per interactionId |
| 126 | const cachedInteractionContext = interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : undefined; |
| 127 | |
| 128 | const spanToUse = cachedInteractionContext?.span || rootSpan; |
| 129 | |
| 130 | // Else, we try to use the active span. |
| 131 | // Finally, we fall back to look at the transactionName on the scope |
| 132 | const routeName = spanToUse ? spanToJSON(spanToUse).description : getCurrentScope().getScopeData().transactionName; |
| 133 | |
| 134 | const name = cachedInteractionContext?.elementName || htmlTreeAsString(entry.target); |
| 135 | const attributes: SpanAttributes = { |
| 136 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser.inp', |
| 137 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `ui.interaction.${interactionType}`, |
| 138 | [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration, |
| 139 | }; |
| 140 | |
| 141 | const span = startStandaloneWebVitalSpan({ |
| 142 | name, |
| 143 | transaction: routeName, |
| 144 | attributes, |
| 145 | startTime, |
| 146 | }); |
| 147 | |
| 148 | if (span) { |
| 149 | span.addEvent('inp', { |
| 150 | [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: 'millisecond', |
| 151 | [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: metric.value, |
| 152 | }); |
| 153 |
no test coverage detected