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[apm]: Use Performance API #2474

Merged
merged 9 commits into from
Mar 9, 2020
Merged

feat[apm]: Use Performance API #2474

merged 9 commits into from
Mar 9, 2020

Conversation

HazAT
Copy link
Member

@HazAT HazAT commented Mar 6, 2020

If available we use https://developer.mozilla.org/en-US/docs/Web/API/Performance API to get more insight into page load. This is especially helpful for the initial pageLoad since we get a lot of additional info there.

In addition to new spans, we also use the timings of the performance API to update our measurements.

Before:
CleanShot 2020-03-06 at 13 04 56@2x

After:
CleanShot 2020-03-06 at 13 02 28@2x

Internal note:

We need to document that if users mark their entry script with data-entry="true" like:
<script src="..." data-entry="true"></script>
We are able to measure the script evaluation.

ref: https://github.com/getsentry/sentry/pull/17501/files#diff-cd6de428a381ccfe274524ae3f0e9ea1L41

@HazAT HazAT requested a review from kamilogorek as a code owner March 6, 2020 12:08
@HazAT HazAT self-assigned this Mar 6, 2020
@getsentry-bot
Copy link
Contributor

getsentry-bot commented Mar 6, 2020

Messages
📖

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

📖 ✅ TSLint passed

Generated by 🚫 dangerJS against 3b34841

Copy link
Contributor

@rhcarvalho rhcarvalho left a comment

Choose a reason for hiding this comment

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

Kamil recently added some fallback code to support Node 6 and whatever else don't have the performance API, we may want to integrate with that -- even if it means explicitly disabling some functionality depending on the availability of the API.

@HazAT
Copy link
Member Author

HazAT commented Mar 6, 2020

This integration only works in browsers so it's not relevant I think.

// We go through all scripts on the page and look for 'data-entry'
// We remember the name and measure the time between this script finished loading and
// our mark 'sentry-tracing-init'
if (document.scripts[i].getAttribute('data-entry') === 'true') {
Copy link
Member

Choose a reason for hiding this comment

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

Does the user need to manually indicate the data-entry attribute?

Copy link
Member Author

@HazAT HazAT Mar 6, 2020

Choose a reason for hiding this comment

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

Yes, otherwise it's hard to determine which is the "entry script" of your application.
If that data-entry is not there, it's just missing the script.evaluation span.
Also added an internal note in the description of the PR so we don't forget to document this if we think it makes sense.

Copy link
Contributor

@rhcarvalho rhcarvalho left a comment

Choose a reason for hiding this comment

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

Let's unit test the span update logic to avoid breaking it when we touch this code in the future?

performance.clearMarks();
performance.clearMeasures();
performance.clearResourceTimings();
Tracing._performanceCursor = Math.max(performance.getEntries().length - 1, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the intent of this _performanceCursor?

Copy link
Member

Choose a reason for hiding this comment

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

@rhcarvalho From what I understand, it's to skip over certain entries that were created after clearing the performance entries.

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, since not all of the performance entries can be cleared with clear*() we need to remember which entries we already considered. And we call clear in the first place since browsers have an upper limit so this can overflow. TBH I am not sure if clearing this is a good practice but we'll see.

packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
Tracing._performanceCursor = Math.max(performance.getEntries().length - 1, 0);
}

// tslint:enable: no-unsafe-any
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary here?!

*/
private static _addOffsetToSpan(measureName: string, span: SpanClass): void {
if (global.performance) {
const name = `#sentry-${measureName}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the # in front of sentry?

Copy link
Member

Choose a reason for hiding this comment

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

to make it "private" :D

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a global namespace (within the perf entries), #sentry-foo is no more private than sentry-foo, is it?

Copy link
Member

Choose a reason for hiding this comment

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

Nope. Likely nobody should use this prefix.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to use a "special char" in order if we ever need to do any string matching. No other reason than my personal style to prefix it with # ^^

if (global.performance) {
const name = `#sentry-${measureName}`;
performance.measure(name);
const measure = performance.getEntriesByName(name).pop();
Copy link
Contributor

Choose a reason for hiding this comment

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

There can be more than one entry with the same name!

In this very page here, open the developer console and run:

> performance.getEntriesByName("https://github.com/getsentry/sentry-javascript/pull/2474/review_comment/create")
(8) [PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming, PerformanceResourceTiming]

Copy link
Member Author

Choose a reason for hiding this comment

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

That's true, but since this mark /measure is only so short, it only lives to calculate the offset.
Therefore I think this can never return more than one.

packages/apm/src/integrations/tracing.ts Outdated Show resolved Hide resolved
Comment on lines +544 to +546
private static _msToSec(time: number): number {
return time / 1000;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd imagine we must already have this somewhere else? Though I prefer repeating it here than adding a dependency on a leftpad :P

@dashed
Copy link
Member

dashed commented Mar 7, 2020

@rhcarvalho It looks like the code paths with these changes will only be run in browsers. For node.js, there are specialized performance instrumentation: https://nodejs.org/api/perf_hooks.html#perf_hooks_performanceentry_entrytype

I imagine that could be supported later on.

* Also, we update our timings since we consider the timings in this API to be more correct than our manual
* measurements.
*
* @param transactionSpan The transaction span
Copy link
Contributor

@kamilogorek kamilogorek Mar 9, 2020

Choose a reason for hiding this comment

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

revolver ocelot

— revolver ocelot

) {
navigationOffset = transactionSpan.data.offset;
}
// tslint:disable-next-line: completed-docs
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can just disable it in a whole file instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem seems to be TS lint here, even if I write docs it still complains.

op: 'browser',
});
// tslint:disable: no-unsafe-any
span.startTimestamp = parent.startTimestamp + Tracing._msToSec(entry[`${event}Start`] as number);
Copy link
Contributor

@kamilogorek kamilogorek Mar 9, 2020

Choose a reason for hiding this comment

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

It can be written as

span.timestamp = span.startTimestamp = parent.startTimestamp + Tracing._msToSec(entry[`${event}Start`] as number);

Copy link
Member Author

@HazAT HazAT Mar 9, 2020

Choose a reason for hiding this comment

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

Nope,

Tracing._msToSec(entry[`${event}Start`]
Tracing._msToSec(entry[`${event}End`]

Copy link
Contributor

@kamilogorek kamilogorek left a comment

Choose a reason for hiding this comment

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

.

@HazAT HazAT merged commit 0bc7a4d into master Mar 9, 2020
@HazAT HazAT deleted the apm-performance-api branch March 9, 2020 10:45
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.

6 participants