Skip to content

Commit 1ac7f15

Browse files
committed
feat(browser): Add timing and status atttributes to resource spans
1 parent 4745177 commit 1ac7f15

File tree

1 file changed

+72
-27
lines changed

1 file changed

+72
-27
lines changed

packages/browser-utils/src/metrics/browserMetrics.ts

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ export function _addResourceSpans(
637637
startTime: number,
638638
duration: number,
639639
timeOrigin: number,
640-
ignoreResourceSpans?: Array<string>,
640+
ignoredResourceSpanOps?: Array<string>,
641641
): void {
642642
// we already instrument based on fetch and xhr, so we don't need to
643643
// duplicate spans here.
@@ -646,31 +646,15 @@ export function _addResourceSpans(
646646
}
647647

648648
const op = entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other';
649-
if (ignoreResourceSpans?.includes(op)) {
649+
if (ignoredResourceSpanOps?.includes(op)) {
650650
return;
651651
}
652652

653-
const parsedUrl = parseUrl(resourceUrl);
654-
655653
const attributes: SpanAttributes = {
656654
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics',
657655
};
658-
setResourceEntrySizeData(attributes, entry, 'transferSize', 'http.response_transfer_size');
659-
setResourceEntrySizeData(attributes, entry, 'encodedBodySize', 'http.response_content_length');
660-
setResourceEntrySizeData(attributes, entry, 'decodedBodySize', 'http.decoded_response_content_length');
661-
662-
// `deliveryType` is experimental and does not exist everywhere
663-
const deliveryType = (entry as { deliveryType?: 'cache' | 'navigational-prefetch' | '' }).deliveryType;
664-
if (deliveryType != null) {
665-
attributes['http.response_delivery_type'] = deliveryType;
666-
}
667656

668-
// Types do not reflect this property yet
669-
const renderBlockingStatus = (entry as { renderBlockingStatus?: 'render-blocking' | 'non-render-blocking' })
670-
.renderBlockingStatus;
671-
if (renderBlockingStatus) {
672-
attributes['resource.render_blocking_status'] = renderBlockingStatus;
673-
}
657+
const parsedUrl = parseUrl(resourceUrl);
674658

675659
if (parsedUrl.protocol) {
676660
attributes['url.scheme'] = parsedUrl.protocol.split(':').pop(); // the protocol returned by parseUrl includes a :, but OTEL spec does not, so we remove it.
@@ -690,6 +674,49 @@ export function _addResourceSpans(
690674
attributes['network.protocol.version'] = version;
691675
}
692676

677+
setResourceRequestAttributes(entry, attributes, [
678+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
679+
['responseStatus', 'http.request.response_status'],
680+
681+
// Timing attributes (request/response lifecycle)
682+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming#timestamps
683+
['redirectStart', 'http.request.redirect_start'],
684+
['redirectEnd', 'http.request.redirect_end'],
685+
686+
['workerStart', 'http.request.worker_start'],
687+
688+
['fetchStart', 'http.request.fetch_start'],
689+
690+
['domainLookupStart', 'http.request.domain_lookup_start'],
691+
['domainLookupEnd', 'http.request.domain_lookup_end'],
692+
693+
['connectStart', 'http.request.connect_start'],
694+
['secureConnectionStart', 'http.request.secure_connection_start'],
695+
['connectEnd', 'http.request.connect_end'],
696+
697+
['requestStart', 'http.request.request_start'],
698+
['firstInterimResponseStart', 'http.response.first_interim_response_start'],
699+
['finalResponseHeadersStart', 'http.response.final_response_headers_start'],
700+
701+
// responseStart can also be interpreted as TTFB for resource requests: https://web.dev/articles/ttfb
702+
['responseStart', 'http.response.response_start'],
703+
['responseEnd', 'http.response.response_end'],
704+
705+
// Size attributes:
706+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/transferSize
707+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/encodedBodySize
708+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/decodedBodySize
709+
['transferSize', 'http.response_transfer_size'],
710+
['encodedBodySize', 'http.response_content_length'],
711+
['decodedBodySize', 'http.decoded_response_content_length'],
712+
713+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/renderBlockingStatus
714+
['renderBlockingStatus', 'resource.render_blocking_status'],
715+
716+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/deliveryType
717+
['deliveryType', 'http.response_delivery_type'],
718+
]);
719+
693720
const startTimestamp = timeOrigin + startTime;
694721
const endTimestamp = startTimestamp + duration;
695722

@@ -776,16 +803,34 @@ function _setWebVitalAttributes(span: Span, options: AddPerformanceEntriesOption
776803
}
777804
}
778805

779-
function setResourceEntrySizeData(
806+
type ExperimentalResourceTimingProperty =
807+
| 'firstInterimResponseStart'
808+
| 'finalResponseHeadersStart'
809+
| 'renderBlockingStatus'
810+
| 'deliveryType';
811+
812+
/**
813+
* Use this to set any attributes we can take directly form the PerformanceResourceTiming entry.
814+
*
815+
* This is just a mapping function for entry->attribute to keep bundle-size minimal.
816+
* Experimental properties are also accepted (see {@link ExperimentalResourceTimingProperty}).
817+
* Assumes that all entry properties might be undefined for browser-specific differences.
818+
* Only accepts string and number values for now and also sets 0-values.
819+
*/
820+
function setResourceRequestAttributes(
821+
entry: Partial<PerformanceResourceTiming> & Partial<Record<ExperimentalResourceTimingProperty, number>>,
780822
attributes: SpanAttributes,
781-
entry: PerformanceResourceTiming,
782-
key: keyof Pick<PerformanceResourceTiming, 'transferSize' | 'encodedBodySize' | 'decodedBodySize'>,
783-
dataKey: 'http.response_transfer_size' | 'http.response_content_length' | 'http.decoded_response_content_length',
823+
properties: [keyof PerformanceResourceTiming | ExperimentalResourceTimingProperty, string][],
784824
): void {
785-
const entryVal = entry[key];
786-
if (entryVal != null && entryVal < MAX_INT_AS_BYTES) {
787-
attributes[dataKey] = entryVal;
788-
}
825+
properties.forEach(([entryKey, attributeKey]) => {
826+
const entryVal = entry[entryKey];
827+
if (
828+
entryVal != null &&
829+
((typeof entryVal === 'number' && entryVal < MAX_INT_AS_BYTES) || typeof entryVal === 'string')
830+
) {
831+
attributes[attributeKey] = entryVal;
832+
}
833+
});
789834
}
790835

791836
/**

0 commit comments

Comments
 (0)