-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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(replay): Share performance instrumentation with tracing #9296
Conversation
size-limit report 📦
|
Replay SDK metrics 🚀
develop |
Revision | LCP | CLS | CPU | JS heap avg | JS heap max | netTx | netRx | netCount | netTime |
---|---|---|---|---|---|---|---|---|---|
a410b04 | -9.12 ms | +0.01 ms | +23.52 pp | +3.43 MB | +8.42 MB | +50.19 kB | +41 B | +1 | +87.46 ms |
9218eaa | -1.59 ms | -0.00 ms | +25.02 pp | +3.47 MB | +8.06 MB | +11.02 kB | +41 B | +1 | +69.00 ms |
42a60b6 | -6.72 ms | +0.02 ms | +24.97 pp | +3.43 MB | +8.45 MB | +50.09 kB | +41 B | +1 | +80.65 ms |
54babf0 | -9.97 ms | -0.02 ms | +25.74 pp | +3.42 MB | +8.29 MB | +38.49 kB | +41 B | +1 | +98.01 ms |
046f868 | +1.85 ms | +0.00 ms | +23.91 pp | +3.44 MB | +8.43 MB | +50.08 kB | +41 B | +1 | +75.77 ms |
16594de | -7.14 ms | +0.00 ms | +23.37 pp | +3.46 MB | +7.72 MB | +11.05 kB | +41 B | +1 | +63.46 ms |
d7de20e | -10.79 ms | -0.00 ms | +25.71 pp | +3.45 MB | +7.9 MB | +11.06 kB | +41 B | +1 | +67.65 ms |
d7de20e | +5.92 ms | +0.01 ms | +24.47 pp | +3.41 MB | +8.4 MB | +50.26 kB | +41 B | +1 | +66.02 ms |
7236510 | -2.12 ms | +0.01 ms | +21.49 pp | +3.41 MB | +8.57 MB | +50.25 kB | +41 B | +1 | +67.97 ms |
Previous results on branch: fn/web-vitals
fn/web-vitals
Revision | LCP | CLS | CPU | JS heap avg | JS heap max | netTx | netRx | netCount | netTime |
---|---|---|---|---|---|---|---|---|---|
921b8df | -18.88 ms | +0.02 ms | +26.26 pp | +3.47 MB | +7.89 MB | +39.18 kB | +41 B | +1 | +54.46 ms |
a410b04 | -9.12 ms | +0.01 ms | +23.52 pp | +3.43 MB | +8.42 MB | +50.19 kB | +41 B | +1 | +87.46 ms |
9218eaa | -1.59 ms | -0.00 ms | +25.02 pp | +3.47 MB | +8.06 MB | +11.02 kB | +41 B | +1 | +69.00 ms |
42a60b6 | -6.72 ms | +0.02 ms | +24.97 pp | +3.43 MB | +8.45 MB | +50.09 kB | +41 B | +1 | +80.65 ms |
54babf0 | -9.97 ms | -0.02 ms | +25.74 pp | +3.42 MB | +8.29 MB | +38.49 kB | +41 B | +1 | +98.01 ms |
046f868 | +1.85 ms | +0.00 ms | +23.91 pp | +3.44 MB | +8.43 MB | +50.08 kB | +41 B | +1 | +75.77 ms |
16594de | -7.14 ms | +0.00 ms | +23.37 pp | +3.46 MB | +7.72 MB | +11.05 kB | +41 B | +1 | +63.46 ms |
d7de20e | -10.79 ms | -0.00 ms | +25.71 pp | +3.45 MB | +7.9 MB | +11.06 kB | +41 B | +1 | +67.65 ms |
d7de20e | +5.92 ms | +0.01 ms | +24.47 pp | +3.41 MB | +8.4 MB | +50.26 kB | +41 B | +1 | +66.02 ms |
7236510 | -2.12 ms | +0.01 ms | +21.49 pp | +3.41 MB | +8.57 MB | +50.25 kB | +41 B | +1 | +67.97 ms |
Last updated: Tue, 24 Oct 2023 08:28:35 GMT
return performanceObserver; | ||
clearCallbacks.push( | ||
addPerformanceInstrumentationHandler('lcp', ({ metric }) => { | ||
replay.lcpMetric = metric; |
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.
Why not push this into performanceEvents?
What would this look like if we wanted to add other metrics (e.g. CLS)?
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.
because it is not an performance event, but already the formed metric. Instead of this we could also have a performanceMetrics
array eventually where we could also push other metrics like CLS in, that would work the same way. Currently we only transform the metric when we send it, but we could eventually refactor this to transform in here and already push the transformed event to a separate array.
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.
I changed this a bit to make this more generic - now we keep performanceEntries
and replayPerformanceEntries
, which hopefully makes this a bit clearer.
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.
👍
@@ -103,25 +95,20 @@ export function startTrackingInteractions(): void { | |||
const duration = msToSec(entry.duration); | |||
|
|||
transaction.startChild({ | |||
description: htmlTreeAsString(entry.target), | |||
description: htmlTreeAsString((entry as PerformanceEventTiming).target), |
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.
l: Would it make sense to type the different performance entry types with a union to avoid these casts?
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.
I am currently struggling with this quite a bit because non-browser apps complain that these types are in here, as they do not know that.
WIP trying to make this work 😬
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.
I ended up typing this specific case more specifically so this should work now, hopefully, without a type cast.
entries.forEach(entry => { | ||
if (isPerformanceResourceTiming(entry) && entry.name.endsWith(url)) { | ||
const spanData = resourceTimingEntryToSpanData(entry); | ||
spanData.forEach(data => span.setData(...data)); | ||
observer.disconnect(); | ||
// In the next tick, clean this handler up | ||
setTimeout(cleanup); |
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.
l: Can we add a comment why we do this in the next tick?
7c66a4e
to
1c7b475
Compare
const instrumented: { [key in InstrumentHandlerType]?: boolean } = {}; | ||
|
||
/** Instruments given API */ | ||
function instrument(type: InstrumentHandlerType): void { |
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.
I'd like to see instrument broken down into individual exported functions instead of using the switch statement (reduces bundle size), but I'm fine with keeping it for now.
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.
Hmm, I wonder if it would really reduce bundle size, as we'd still need to do instrumented['cls']
etc. in each of the functions? But I can give it a try!
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.
OK, I split this up, which is probably nicer anyhow. I was able to keep this in as reusable pieces as possible, making this as small as can be (and I think it shaved off a few bytes even).
This streamlines web-vital & performance observer handling, by exposing a new
addPerformanceInstrumentationHandler
method from@sentry-internal/tracing
.This works similar to the instrumentation in utils, where the first time you add instrumentation for a given type, it will add a performance observer. And any further calls will just add more callbacks. This way, we avoid having multiple of the same performance observers.
Furthermore, this also aligns the handling of LCP capturing for replay. We used to do this separately, now we use the same data as for performance.
Finally, while doing this I noticed that a whole bunch of performance observer stuff we used to capture in Replay, was actually discarded 😬 so no need to capture these anymore at all. (We can always add it back later, if needed)
Some integration tests needed slight adjustments for this, probably due to minor timing semantics. But I think all the changes are good/"correct".
I also got rid of the event deduplication in replay 🤔 All tests are passing, so should we be good - or is there another thing we need to test there? This would supersede #8836.
Closes #9246