Conversation

Lms24

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 a PerformanceObserver and extract spans from newly emitted ET entries.

Important:

  • We'll by default emit ET spans. Users can opt out by setting enableElementTiming: false in browserTracingIntegration
  • We for now only emit an ET span, if there is an active parent span. Happy to adjust this but considering the limitations from below I'm not sure if we actually want all spans after the pageload span. For now, we'll also emit spans on other transactions that pageload (most prominently 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:

  • ET is only emitted for text and image nodes.
  • For image nodes, we get the loadTime which is the relative timestamp to the browser's timeOrigin, when the image finished loading. For text nodes, loadTime is always 0, since nothing needs to be loaded.
  • For all nodes, we get renderTime which is the relative timestamp to the browser's timeOrigin, when the node finished rendering (i.e. was painted by the browser).
  • In any case, we do not get start times for rendering or loading. Consequently, the span duration is
    • renderTime - loadTime for image nodes
    • 0 for text nodes
  • The span start time is:
    • timeOrigin + loadTime for image nodes
    • timeOrigin + renderTime for text nodes

In 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 or p)
  • element.size - width x height of the element
  • element.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 the elementtiming=identifier HTML attribute
  • element.paint-type - the node paint type (image-paint or text-paint)

also some additional sentry-sepcific attributes:

  • route - the route name, either from the active root span (if available) or from the scope's transactionName
  • sentry.span-start-time-source - the data point we used as the span start time

More than happy to adjust any of this logic or attribute names, based on review feedback :)

closes #13675
also ref #7292

@Lms24Lms24 force-pushed the lms/feat-browser-elementtiming branch from 7680005 to 5379c13 Compare June 16, 2025 11:25
@github-actionsGitHub Actions

size-limit report 📦

PathSize% ChangeChange
@sentry/browser23.99 kB--
@sentry/browser - with treeshaking flags23.76 kB--
@sentry/browser (incl. Tracing)39.36 kB+1.47%+569 B 🔺
@sentry/browser (incl. Tracing, Replay)77.45 kB+0.71%+540 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.53 kB+0.76%+532 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)82.22 kB+0.66%+539 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)94.32 kB+0.62%+574 B 🔺
@sentry/browser (incl. Feedback)40.73 kB--
@sentry/browser (incl. sendFeedback)28.7 kB--
@sentry/browser (incl. FeedbackAsync)33.59 kB--
@sentry/react25.76 kB--
@sentry/react (incl. Tracing)41.34 kB+1.38%+561 B 🔺
@sentry/vue28.36 kB--
@sentry/vue (incl. Tracing)41.17 kB+1.27%+515 B 🔺
@sentry/svelte24.01 kB--
CDN Bundle25.48 kB--
CDN Bundle (incl. Tracing)39.43 kB+1.21%+469 B 🔺
CDN Bundle (incl. Tracing, Replay)75.27 kB+0.62%+458 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)80.69 kB+0.58%+458 B 🔺
CDN Bundle - uncompressed74.48 kB--
CDN Bundle (incl. Tracing) - uncompressed116.25 kB+0.83%+954 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed230.31 kB+0.42%+954 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed243.13 kB+0.4%+954 B 🔺
@sentry/nextjs (client)42.97 kB+1.26%+534 B 🔺
@sentry/sveltekit (client)39.8 kB+1.32%+518 B 🔺
@sentry/node150.76 kB--
@sentry/node - without tracing98.51 kB-0.01%-2 B 🔽
@sentry/aws-serverless124.27 kB-0.01%-1 B 🔽

View base workflow run

@Lms24Lms24 self-assigned this Jun 16, 2025
@Lms24Lms24 marked this pull request as ready for review June 16, 2025 14:43
// - `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
Copy link
Member

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.

Comment on lines 81 to 83
const activeSpan = getActiveSpan();
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
const route = rootSpan ? spanToJSON(rootSpan).description : getCurrentScope().getScopeData().transactionName;
Copy link
Member

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

Sign up for free to join this conversation on . Already have an account? Sign in to comment
None yet
None yet

Successfully merging this pull request may close these issues.

Support Element Timing API in Browser SDK