-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(browser): Add ElementTiming instrumentation and spans #16589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
7680005
to
5379c13
Compare
size-limit report 📦
|
// - `renderTime` if available (available for all entries, except 3rd party images, but these should be covered by `loadTime`, 0 otherwise) | ||
// - `timestampInSeconds()` as a safeguard | ||
// see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming/renderTime#cross-origin_image_render_time | ||
const { spanStartTime, spanStartTimeSource } = loadTime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be a helper or return tuples, will help reduce bundle size of spanStartTime
and spanStartTimeSource
being un-minifiable.
const activeSpan = getActiveSpan(); | ||
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; | ||
const route = rootSpan ? spanToJSON(rootSpan).description : getCurrentScope().getScopeData().transactionName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can move these outside of the loop to avoid having to keep calling spanToJSON
This PR implements sending standalone LCP spans as an opt-in feature. Behaviour-wise, it's mostly aligned with our prior implementation of sending CLS standalone spans (#13056): - add an `_experiments.enableStandaloneLcpSpans` option and treat it as opt-in - keep collecting LCP values until users soft-navigate or the page is hidden - then, send the LCP span once - adds all `lcp.*` span attributes as well as the `lcp` measurement to the span (depending on if we merge #16589 or this first, we might need to readjust size limit) closes #13063 --------- Co-authored-by: s1gr1d <[email protected]> Co-authored-by: Sigrid Huemer <[email protected]>
# Conflicts: # .size-limit.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: ElementTiming Timing Inconsistencies
ElementTiming spans for image-paint
entries have inconsistent timing due to how loadTime
and renderTime
are used for span start time and duration calculation, especially when one of these values is 0 (e.g., for cross-origin images without CORS). This leads to:
- Zero-duration spans starting at
loadTime
whenrenderTime
is 0 butloadTime
is present. - Spans ending at
2 * renderTime
whenloadTime
is 0 butrenderTime
is present, as duration is incorrectly calculated asrenderTime
.
packages/browser-utils/src/metrics/elementTiming.ts#L71-L85
sentry-javascript/packages/browser-utils/src/metrics/elementTiming.ts
Lines 71 to 85 in f6b225c
// see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming/renderTime#cross-origin_image_render_time | |
const [spanStartTime, spanStartTimeSource] = loadTime | |
? [msToSec(loadTime), 'load-time'] | |
: renderTime | |
? [msToSec(renderTime), 'render-time'] | |
: [timestampInSeconds(), 'entry-emission']; | |
const duration = | |
paintType === 'image-paint' | |
? // for image paints, we can acually get a duration because image-paint entries also have a `loadTime` | |
// and `renderTime`. `loadTime` is the time when the image finished loading and `renderTime` is the | |
// time when the image finished rendering. | |
msToSec(Math.max(0, (renderTime ?? 0) - (loadTime ?? 0))) | |
: // for `'text-paint'` entries, we can't get a duration because the `loadTime` is always zero. | |
0; |
Was this report helpful? Give feedback by reacting with 👍 or 👎
This PR adds support for instrumenting and sending spans from
ElementTiming
API entries. Just like with web vitals and long tasks/animation frames, we register aPerformanceObserver
and extract spans from newly emitted ET entries.Important:
enableElementTiming: false
inbrowserTracingIntegration
navigation
spans as well). We could also go the route of only sending until the first navigation as with standalone CLS/LCP spans. Happy to accept any direction we wanna take this.Some noteworthy findings while working on this:
loadTime
which is the relative timestamp to the browser'stimeOrigin
, when the image finished loading. For text nodes,loadTime
is always0
, since nothing needs to be loaded.renderTime
which is the relative timestamp to the browser'stimeOrigin
, when the node finished rendering (i.e. was painted by the browser).renderTime - loadTime
for image nodes0
for text nodestimeOrigin + loadTime
for image nodestimeOrigin + renderTime
for text nodesIn addition to the raw span and conventional attributes, we also collect a bunch of ET-specific attributes:
element.type
- tag name of the element (e.g.img
orp
)element.size
- width x height of the elementelement.render-time
-entry.renderTime
element.load-time
-entry.loadTime
element.url
- url of the loaded image (undefined
for text nodes)element.identifier
- the identifier passed to theelementtiming=identifier
HTML attributeelement.paint-type
- the node paint type (image-paint
ortext-paint
)also some additional sentry-sepcific attributes:
route
- the route name, either from the active root span (if available) or from the scope'stransactionName
sentry.span-start-time-source
- the data point we used as the span start timeMore than happy to adjust any of this logic or attribute names, based on review feedback :)
closes #13675
also ref #7292