Skip to content
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

feat: Report LCP metric on pageload transactions #2624

Merged
merged 11 commits into from
Jun 8, 2020
Merged

Conversation

rhcarvalho
Copy link
Contributor

@rhcarvalho rhcarvalho commented May 28, 2020

Largest Contentful Paint (LCP) is an user-centric metric for measuring
perceived load speed. It marks the point in the page load timeline when
the page's main content has likely loaded.

Reference: https://web.dev/lcp/#measure-lcp-in-javascript

image

@rhcarvalho rhcarvalho marked this pull request as draft May 28, 2020 13:08
@getsentry-bot
Copy link
Contributor

getsentry-bot commented May 28, 2020

Messages
📖

@sentry/browser bundle gzip'ed minified size: (ES5: 17.0732 kB) (ES6: 16.1367 kB)

📖 ✅ TSLint passed

Generated by 🚫 dangerJS against c98d924

packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
Comment on lines +575 to +521
// FIXME: depending on the 'op' directly is brittle.
if (transactionSpan.op === 'pageload') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see an obvious quick fix for this brittleness.

The problem:

  • Any transaction could be called pageload, and that should not mean it should report the LCP;
  • The "pageload" transaction could be called something else, and we'd miss the LCP;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add additional things into the data property and check for that but tbh I think for now this is enough.

forceLCP();
if (lcp) {
// Set the last observed LCP score.
transactionSpan.setData('_sentry_extra_metrics', JSON.stringify({ lcp }));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sending the LCP as a data entry for now, can be changed.

image

// Set the last observed LCP score.
transactionSpan.setData('_sentry_extra_metrics', JSON.stringify({ lcp }));
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the reference implementation sends the LCP when the page visibility changes to hidden. That happens when a user switches tabs, navigates away, etc.

Instead here, as per Daniel's suggestion, we send the current known LCP at the time of sending the "idle transaction".
Local exploratory tests suggest that it is easy to miss the proper LCP entry (either get nothing or get an earlier element).

We may discuss this behavior and decide whether we're happy with it for starters.

Two alternative ways forward are delaying the submission of the pageload transaction (wait for page visibility hidden event) or sending a separate transaction for LCP (in which case we would benefit from a being able to send a trimmed down payload, e.g. without breadcrumbs and other non-essential or redundant data).


Snippet from the reference implementation

image

@rhcarvalho rhcarvalho marked this pull request as ready for review May 30, 2020 00:57
@rhcarvalho rhcarvalho requested a review from HazAT May 30, 2020 00:57
Copy link
Member

@HazAT HazAT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ Changelog

I left a comment that this should move into the class, we otherwise also miss the option of adding an option to toggle this in the future.

packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
@rhcarvalho
Copy link
Contributor Author

  • Changelog

Added.

Would be nice to automate this when generating the release. Doing this in every PR requires extra manual work and is an obvious spot for merge conflicts when multiple PRs are in-flight.

For the Go SDK I've documented a partial automation for the CHANGELOG generation:

https://github.com/getsentry/sentry-go/blob/master/CONTRIBUTING.md#release

$ git log --no-merges --format=%s $(git describe --abbrev=0).. | sed 's/^/- /'

Comment on lines 683 to 692
Tracing._lcp = {
// @ts-ignore
elementId: entry.id,
// @ts-ignore
elementSize: entry.size,
largestContentfulPaint: entry.startTime,
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry.id and entry.size are not always available.
We may or may not want more details from the LCP entry.
I've put those here as examples of what is possible.

https://developer.mozilla.org/en-US/docs/Web/API/LargestContentfulPaint

@HazAT @kamilogorek @dashed feel free to comment on this.

Also note that we're only sending a number like 63ms instead of a UNIX timestamp (like in spans).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think entry.startTime would be relative to performance.timeOrigin.

Is it sufficient to infer the start timestamp relative to the transaction's start timestamp?

This is assuming that the transaction's start timestamp uses performance.timeOrigin in some fashion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dashed yes, yes, yes :)

Since the LCP metric is defined in terms of a page load, when looking at aggregation reporting we should probably look at it in isolation from transaction start timestamps.

For overlaying into a single pageload transaction view (a.k.a. vertical line), it should be okay-ish to consider it an offset from the transaction start timestamp.

Tracing._forceLCP();
if (Tracing._lcp) {
// Set the last observed LCP score.
transactionSpan.setData('_sentry_extra_metrics', { lcp: Tracing._lcp });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HazAT @kamilogorek @dashed PTAL

The idea here is to have a "namespace" where we can add other browser metrics later.

Using Transaction.data for now, but we can brainstorm how we want to do this going forward.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhcarvalho I'm open to have a meeting to talk more about this "namespace" to store these kinds of metrics.

packages/core/src/baseclient.ts Outdated Show resolved Hide resolved
@rhcarvalho rhcarvalho requested a review from HazAT June 4, 2020 18:21
packages/core/src/baseclient.ts Outdated Show resolved Hide resolved
Copy link
Member

@HazAT HazAT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind, let's not normalize contexts for now.

Copy link
Member

@dashed dashed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍

@rhcarvalho
Copy link
Contributor Author

Note that this without a fix for #2646 requires manual change of normalizeDepth in Sentry.init, otherwise the page load transaction will look like

{"contexts":{"trace":{"data":{"_sentry_web_vitals":"[Object]"}...

@rhcarvalho
Copy link
Contributor Author

Rebased on top of latest master and fixed conflict in CHANGELOG.md.

@rhcarvalho rhcarvalho marked this pull request as ready for review June 8, 2020 11:44
CHANGELOG.md Outdated Show resolved Hide resolved
Largest Contentful Paint (LCP) is an user-centric metric for measuring
perceived load speed. It marks the point in the page load timeline when
the page's main content has likely loaded.

Reference: https://web.dev/lcp/#measure-lcp-in-javascript
This allows us to optionally disable LCP based on config later.
rhcarvalho and others added 9 commits June 8, 2020 14:10
This allows for proper serialization of objects as values in
context.trace.data.
This reverts commit 19cdf22.

This is too aggressive, would increase the side of breadcrumbs
All of the context types documented in [1] are flat objects.

The new contexts.trace is the only key under contexts that may have
nested objects under contexts.trace.data. That value holds the data for
the Transaction Span and matches spans[].data for invidual Spans. At the
moment one of the data fields is heavily limited, while the other is
ignored by normalization.

[1]: https://develop.sentry.dev/sdk/event-payloads/contexts/

This is the simplest to implement fix to #2646, thus what I'm doing here
perhaps temporarily.

Fixes #2646
Tracing._lcp can be undefined if no LCP is ever reported.
The idea is to align with the name used in web-vitals (LCP in uppercase,
not lcp) and to constrain the custom container in data to metrics
related to Web Vitals.

Do not double encode as JSON, assume that normalization is solved
elsewhere.
Co-authored-by: Daniel Griesser <daniel.griesser.86@gmail.com>
@rhcarvalho
Copy link
Contributor Author

Rebased on top of latest master and fixed conflict in CHANGELOG.md, again.

@rhcarvalho
Copy link
Contributor Author

Going to ignore the test runs, @kamilogorek will do a release next so we can save some time re-running CI.

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

Successfully merging this pull request may close these issues.

None yet

5 participants