Skip to content

Commit 4250471

Browse files
committed
rework impl, unit tests
1 parent d1d7556 commit 4250471

File tree

2 files changed

+133
-156
lines changed

2 files changed

+133
-156
lines changed

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

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { SpanAttributes } from '@sentry/core';
21
import {
32
browserPerformanceTimeOrigin,
43
getActiveSpan,
@@ -9,13 +8,12 @@ import {
98
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
109
spanToJSON,
1110
startSpan,
12-
timestampInSeconds,
1311
} from '@sentry/core';
1412
import { addPerformanceInstrumentationHandler } from './instrument';
1513
import { getBrowserPerformanceAPI, msToSec } from './utils';
1614

1715
// ElementTiming interface based on the W3C spec
18-
interface PerformanceElementTiming extends PerformanceEntry {
16+
export interface PerformanceElementTiming extends PerformanceEntry {
1917
renderTime: number;
2018
loadTime: number;
2119
intersectionRect: DOMRectReadOnly;
@@ -57,71 +55,50 @@ export const _onElementTiming = ({ entries }: { entries: PerformanceEntry[] }):
5755
}
5856

5957
entries.forEach(entry => {
60-
const elementEntry = entry as PerformanceElementTiming;
58+
const { naturalWidth, naturalHeight, url, identifier, name, renderTime, loadTime, startTime, id, element } =
59+
entry as PerformanceElementTiming;
6160

62-
// Skip entries without identifier (elementtiming attribute)
63-
if (!elementEntry.identifier) {
61+
// Skip:
62+
// - entries without identifier (elementtiming attribute)
63+
// - entries without startTime (e.g. 3rd party Image nodes w/o Timing-Allow-Origin header returned instantly from cache)
64+
if (!identifier || !startTime) {
6465
return;
6566
}
6667

67-
// `name` contains the type of the element paint. Can be `'image-paint'` or `'text-paint'`.
68-
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming#instance_properties
69-
const paintType = elementEntry.name as 'image-paint' | 'text-paint' | undefined;
70-
71-
const renderTime = elementEntry.renderTime;
72-
const loadTime = elementEntry.loadTime;
73-
74-
// starting the span at:
75-
// - `loadTime` if available (should be available for all "image-paint" entries, 0 otherwise)
76-
// - `renderTime` if available (available for all entries, except 3rd party images, but these should be covered by `loadTime`, 0 otherwise)
77-
// - `timestampInSeconds()` as a safeguard
78-
// see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming/renderTime#cross-origin_image_render_time
79-
const [spanStartTime, spanStartTimeSource] = loadTime
80-
? [msToSec(timeOrigin + loadTime), 'load-time']
81-
: renderTime
82-
? [msToSec(timeOrigin + renderTime), 'render-time']
83-
: [timestampInSeconds(), 'entry-emission'];
84-
85-
const duration =
86-
paintType === 'image-paint'
87-
? // for image paints, we can acually get a duration because image-paint entries also have a `loadTime`
88-
// and `renderTime`. `loadTime` is the time when the image finished loading and `renderTime` is the
89-
// time when the image finished rendering.
90-
msToSec(Math.max(0, (renderTime ?? 0) - (loadTime ?? 0)))
91-
: // for `'text-paint'` entries, we can't get a duration because the `loadTime` is always zero.
92-
0;
93-
94-
const attributes: SpanAttributes = {
95-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.elementtiming',
96-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.elementtiming',
97-
// name must be user-entered, so we can assume low cardinality
98-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
99-
// recording the source of the span start time, as it varies depending on available data
100-
'sentry.span_start_time_source': spanStartTimeSource,
101-
'sentry.transaction_name': transactionName,
102-
'ui.element.id': elementEntry.id,
103-
'ui.element.type': elementEntry.element?.tagName?.toLowerCase() || 'unknown',
104-
'ui.element.dimensions':
105-
elementEntry.naturalWidth && elementEntry.naturalHeight
106-
? `${elementEntry.naturalWidth}x${elementEntry.naturalHeight}`
107-
: undefined,
108-
'ui.element.render_time': renderTime,
109-
'ui.element.load_time': loadTime,
110-
// `url` is `0`(number) for text paints (hence we fall back to undefined)
111-
'ui.element.url': elementEntry.url || undefined,
112-
'ui.element.identifier': elementEntry.identifier,
113-
'ui.element.paint_type': paintType,
114-
};
68+
// Span durations
69+
// Case 1: Text nodes: point-in-time spans at `renderTime`
70+
// Case 2: Image nodes: spans from `loadTime` to `renderTime` (i.e. "effective render time")
71+
// Case 3: 3rd party Image nodes w/o Timing-Allow-Origin header: point-in-time spans at `loadTime`
72+
// Case 4: Both times are 0 is already covered by the `startTime` check above
73+
const relativeStartTime = loadTime > 0 ? loadTime : renderTime;
74+
const relativeEndTime = renderTime > 0 ? renderTime : loadTime;
11575

11676
startSpan(
11777
{
118-
name: `element[${elementEntry.identifier}]`,
119-
attributes,
120-
startTime: spanStartTime,
78+
name: `element[${identifier}]`,
79+
attributes: {
80+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.elementtiming',
81+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.elementtiming',
82+
// name must be user-entered, so we can assume low cardinality
83+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
84+
'sentry.transaction_name': transactionName,
85+
'ui.element.id': id,
86+
'ui.element.type': element?.tagName?.toLowerCase() || 'unknown',
87+
'ui.element.width': naturalWidth,
88+
'ui.element.height': naturalHeight,
89+
'ui.element.render_time': renderTime,
90+
'ui.element.load_time': loadTime,
91+
// `url` is `0`(number) for text paints (hence we fall back to undefined)
92+
'ui.element.url': url || undefined,
93+
'ui.element.identifier': identifier,
94+
// `name` contains the type of the element paint. Can be `'image-paint'` or `'text-paint'`.
95+
'ui.element.paint_type': name,
96+
},
97+
startTime: msToSec(timeOrigin + relativeStartTime),
12198
onlyIfParent: true,
12299
},
123100
span => {
124-
span.end(spanStartTime + duration);
101+
span.end(msToSec(timeOrigin + relativeEndTime));
125102
},
126103
);
127104
});

0 commit comments

Comments
 (0)