-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
fix: normalize all times to navStart, remove traceviewer model #2347
Conversation
did a plots run on this and it brought TTCI failures down from 20% to 13% so that seems like a win, of the remaining 13%, 8% are timeouts and 5% are all for react-hn cases where there's a random long task that is impossible to predict from other signals (it's on a setinterval/settimeout or something) until we have protocol support for CPU quiet I'm comfortable opening this up for review :) |
what was the plots population size? Runs per site? can we move the navstart change in a separate PR? |
This reverts commit ea619df.
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
same 100 sites from before single run
ya reverted |
🏏 🏏 |
const events = tabTrace.processEvents.filter(e => e.ts !== 0); | ||
const lastEvent = events[events.length - 1]; | ||
const navStart = tabTrace.navigationStartEvt.ts; | ||
const traceStart = tabTrace.processEvents |
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.
should we move these two (traceStart/traceEnd) into tabTrace ? i'm okay either way.
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.
yeah good call
events: [event], | ||
}; | ||
|
||
if (currentEvt.end < lastEvt.end) { |
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.
can you add this kind of comment?
// note: processEvents is already sorted by event start
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.
done
if (currentEvt.end < lastEvt.end) { | ||
// The current event is completely inside the previous event, add to previous | ||
lastEvt.events.push(event); | ||
} else if (currentEvt.start < lastEvt.end) { |
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 is possible?
we're in a single process, though admittedly not a single thread. since we're only looking at these scheduleable tasks, i dont think they should overlap..
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.
yup, that's one of the reasons the event count was so far off from before
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.
you're saying you have traces where these do overlap? can we look at them?
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.
yeah the test fixtures do fixtures/traces/progressive-app.json
lastEvt.events.push(event); | ||
} else if (currentEvt.start < lastEvt.end) { | ||
// The current event overlaps with the previous event, extend the duration and add to previous | ||
lastEvt.events.push(event); |
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.
are these childEvents being used somewhere?
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.
no but seemed like a bad idea to throw them away
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.
kinda feel like we should throw them away since we have no use for them.
@@ -47,25 +47,6 @@ describe('TracingProcessor lib', () => { | |||
}); | |||
}); | |||
|
|||
it('doesn\'t throw when user_timing events have a colon', () => { |
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 remove?
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 we don't ask tracing processor to do anything anymore, I can bring back though I guess it doesn't hurt anything
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.
wait not nevermind it does hurt something there's no init function at all anymore :)
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.
ah true! :) can move this to user-timing-test.js
to make sure we can inject a trace with these colons and everything goes through fine?
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.
donezo
what's the expected difference between this and what trace viewer produces today? I'm seeing some pretty different numbers of events |
// The current event overlaps with the previous event, extend the duration and add to previous | ||
lastEvt.events.push(event); | ||
lastEvt.end = currentEvt.end; | ||
lastEvt.duration = currentEvt.end - currentEvt.start; |
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.
should this be lastEvt.duration = currentEvt.end - lastEvt.start;
(or currentEvt.end - currentEvt.start
) if extending duration by the end of currentEvt
(but keeping the same start
)?
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.
heh made the same mistake as in your or :) yeah lastEvt.end - lastEvt.start
or currentEvt.end - lastEvt.start
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.
added tests for this func :)
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.
heh made the same mistake as in your or
lol
how different is "pretty different"? many of the differences I saw originally in Paul's branch were eliminated by handling the overlapping events, there are likely more edge cases we're not handling but this fixes the ~7% of definitely wrong interpretations of the trace I was seeing. Hard to do more comparisons of what's "right" since no ground truth exists |
I called In the new implementation I got +35% number of top level events and some pretty different timings, though some of that may be due to it being difficult to tell which events match up and which are in the new 35% due to there being so many events. But there were also some missing from the new version, and they could make a significant difference in our metrics. e.g. here's all event durations over 100ms:
I can dig in more and look at the actual events behind these missing durations to see where they're going.
This was extracting all of the top level events in the trace (using |
@@ -98,7 +98,11 @@ class TraceOfTab extends ComputedArtifact { | |||
.filter(e => e.pid === startedInPageEvt.pid) | |||
.sort((event0, event1) => event0.ts - event1.ts); | |||
|
|||
const traceEnd = trace.traceEvents.reduce((max, evt) => { | |||
const earliestTraceEvt = trace.traceEvents.reduce((min, evt) => { |
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.
let's add this guy:
const mainThreadEvents = processEvents
.filter(e => e.tid === startedInPageEvt.tid);
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.
done
@@ -124,7 +128,9 @@ class TraceOfTab extends ComputedArtifact { | |||
timings, | |||
timestamps, | |||
processEvents, |
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.
mainThreadEvents,
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.
done
let lastEvt = {start: -Infinity, end: -Infinity}; | ||
const topLevelEvents = []; | ||
// note: processEvents is already sorted by event start | ||
for (const event of tabTrace.processEvents) { |
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.
let's use mainThreadEvents
here, since we don't want ProcessTaskFromWorkQueue
events from HTML parser thread, script streamer thread, service worker thread, etc.
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.
done
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.
alright! this is looking good
return Promise.all(pending).then(([tabTrace, model]) => { | ||
return EstimatedInputLatency.calculate(tabTrace, model, trace); | ||
return artifacts.requestTraceOfTab(trace).then(tabTrace => { | ||
return EstimatedInputLatency.calculate(tabTrace); |
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.
since it's static it would nice if this were able to be .then(EstimatedInputLatency.calculate)
, though that would require cleaning up our uses of this
in here
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.
done
@@ -191,7 +190,7 @@ class TTIMetric extends Audit { | |||
} | |||
|
|||
const times = {fmpTiming, visuallyReadyTiming, traceEndTiming}; | |||
const data = {tabTrace, model, trace}; | |||
const data = {tabTrace, trace}; |
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.
looks like only data.tabTrace
is being used above, not data.trace
, so can maybe drop data
in favor of just tabTrace
? (also need to update the jsdocs still referring to model
)
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.
or we can just kill the audit entirely :)
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.
haha, good point. I'm fine with leaving as is and deleting it next :)
@@ -38,7 +38,7 @@ class TraceOfTab extends ComputedArtifact { | |||
|
|||
/** | |||
* @param {{traceEvents: !Array}} trace | |||
* @return {!{processEvents: !Array<TraceEvent>, startedInPageEvt: TraceEvent, navigationStartEvt: TraceEvent, firstContentfulPaintEvt: TraceEvent, firstMeaningfulPaintEvt: TraceEvent}} | |||
* @return {!TraceOfTabArtifact} |
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.
TraceOfTabArtifact
is really out of date. Mind updating it? :D
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.
sure
@@ -107,7 +114,7 @@ class TraceOfTab extends ComputedArtifact { | |||
firstPaint, | |||
firstContentfulPaint, | |||
firstMeaningfulPaint, | |||
traceEnd, | |||
traceEnd: latestTraceEvt, |
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.
traceEnd
in metrics
but latestTraceEvt
in the top level. Should latestTraceEvt
be traceEndEvt
to match how the others pair up (timing name + Evt
)?
(and don't feel strongly about it, but traceStartEvt
may make more sense than earliestTraceEvt
. e.g. new code in tracing-processor already does traceEnd = tabTrace.latestTraceEvt.ts
)
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 had a very good explanation for why I named them this instead of traceEnd (that these were supposed to be just about the main thread, latestMainThreadEvt
earliestMainThreadEvt
and distinct from traceEnd
) but the rest of the code betrays me. In fact, there are a number of things in here that I strongly feel like I accidentally dropped a stash...
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.
yeah what do you think about using navStart and traceEnd timings instead of all this other nonsense entirely?
* @see https://docs.google.com/document/d/1b9slyaB9yho91YTOkAQfpCdULFkZM9LqsipcX3t7He8/preview | ||
* @param {!traceviewer.Model} model | ||
* @param {{traceEvents: !Array<!Object>}} trace | ||
* @see https://docs.google.com/document/d/18gvP-CBA2BiBpi3Rz1I1ISciKGhniTSZ9TY0XCnXS7E/preview |
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.
not sure how this snuck into the diff. This is the old doc (which has just a link to the currently linked doc)
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.
yeah probably a bad rebase from the earlier branch, I think paul's been on the hunt for this for a while ;) fixed
// Range of responsiveness we care about. Default to bounds of model. | ||
startTime = startTime === undefined ? model.bounds.min : startTime; | ||
endTime = endTime === undefined ? model.bounds.max : endTime; | ||
startTime = startTime === undefined ? (traceStart - navStart) / 1000 : startTime; |
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.
the - navStart
is somewhat surprising here. Maybe add a comment explaining that they need to be shifted too? Or could use tabTrace.timings.traceEnd
and tabTrace.timings.traceStart
(if it was added to trace-of-tab
) to get the already shifted bounds
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.
better question, should we just default these bounds to navstart and traceEnd now instead? negative start time doesn't really make a whole lot of sense and we take it to be the beginning of time everywhere we consume this data
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.
better question, should we just default these bounds to navstart and traceEnd now instead? negative start time doesn't really make a whole lot of sense and we take it to be the beginning of time everywhere we consume this data
ha, sorry I was too slow to respond :)
I think it would be better to still have getMainThreadTopLevelEvents
and getMainThreadTopLevelEventDurations
return all events, even ones before navStart.
Our current uses don't need anything before then, but we'll hopefully be doing more sophisticated trace analysis in the future (e.g. over multiple navigations, though we'll have several other complications to figure out before then :) and I think having them return all of the main thread/top level events in the trace will be what you would expect from them.
Having a note that the times are relative to navStart (so negative times are just before that) doesn't seem too confusing.
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 guess on the other hand we could say something like "use a
startTime
of-Infinity
to get all top level events", but that's pretty awkward - On the other other hand, getting everything after navStart is our typical use case right now, so it's kind of awkward that you have to specify 0 for that.
- On the original hand, though, it still makes sense to me that these would return all of the top level events in the main thread of the trace when no timing bounds are specified, so idk :)
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.
getting everything after navStart is our typical use case right now
though that's not completely true. It looks like all of our calls have to deal with events earlier than they care about anyways, so it doesn't seem harmful to default to all events, even before navStart
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 gotta disagree that it makes sense to return everything. The entire rest of the LH world completely ignores time before navstart and having this one spot that doesn't do that is what led to total failure on 10% of sites. Having them return everything would require every caller to filter out the negative events or provide a non-default parameter at which point why bother having a default at all?
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.
but they do all filter out negative events or provide their own startTime
.
why bother having a default at all?
start of trace/end of trace defaults are no defaults in a way, since it's pretty much just letting the trace you passed in define the bounds.
I guess it comes down to what these methods are for, just TTI-like things or any sort of processing where you need to extract the top level events.
idk, @paulirish, as a user, what would you expect from these methods?
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 think getRiskToResponsiveness makes sense to be aware of navstart, whereas getMainThreadTopLevelEventDurations shouldnt. that seems to be the state of things here, so i'm pretty good.
I would appreciate a comment being left about the shifting for navstart. (it seems like the jsdoc comment is incorrect 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.
OK, I'm good with what we have here. I'll file an issue after this PR with a proposal to separate concerns slightly but still make sure all our audits can easily do the right thing by default (don't pay attention to events before navStart). This works well for all our current audits, though.
start, | ||
end, | ||
duration: event.dur / 1000, | ||
events: [event], |
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.
since it'll be just the one event now, drop the array and just have event
? (or rawEvent
or whatever...also may not really be needed)
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.
just nuked it we don't use it I forgot to remove
@@ -37,7 +37,7 @@ describe('Performance: estimated-input-latency audit', () => { | |||
const artifacts = generateArtifactsWithTrace({traceEvents: pwaTrace}); | |||
return Audit.audit(artifacts).then(output => { | |||
assert.equal(output.debugString, undefined); | |||
assert.equal(output.rawValue, 16.4); | |||
assert.equal(output.rawValue, 16.2); |
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 and the TTI change, are they just from the navStart shift?
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.
yep, can confirm. the trace for this one now skips the first 15ms and the TTI one skips the first 5ms, adding back this delta yields the old results
@@ -43,7 +44,14 @@ describe('FirstInteractive computed artifact:', () => { | |||
|
|||
it('should compute firstInteractive', () => { | |||
return computedArtifacts.requestFirstInteractive(acceptableTrace).then(output => { | |||
assert.equal(Math.round(output.timeInMs), 1587); | |||
assert.equal(Math.round(output.timeInMs), 1582); |
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.
also just from navStart shift?
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.
yep
const durations = ret.durations; | ||
|
||
function getDurationFromIndex(index) { | ||
return Number(durations[index].toFixed(2)); | ||
} | ||
|
||
assert.equal(durations.filter(dur => isNaN(dur)).length, 0, 'NaN found'); | ||
assert.equal(durations.length, 652); | ||
assert.equal(durations.length, 651); |
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 the change? Seems like it shouldn't lose any events
const durations = ret.durations; | ||
|
||
function getDurationFromIndex(index) { | ||
return Number(durations[index].toFixed(2)); | ||
} | ||
|
||
assert.equal(durations.filter(dur => isNaN(dur)).length, 0, 'NaN found'); | ||
assert.equal(durations.length, 652); | ||
assert.equal(durations.length, 645); |
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.
can you confirm this one as well? Just the new navStart default?
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.
6 of them definitely the 7th is eluding me, trying to track it down
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.
ah found the 7th it's a result of !dur
they return an event that has no duration which we'll ignore
need a more recent rebase? travis is still complaining about javascriptDialog |
Ya I didn't rebase this one.
|
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.
sweet! Test failures are devtools compilation, so lets not let that get in the way of this big step forward
🎉 🎉 |
with this change, the compiled lighthouse-background.js just dropped by 562KB, around 25% |
brings us closer to closing #2235 (still there for stats we can remove separately)
Would like to do some more testing of how this impacts metrics, but the current state is pretty bad with ~10% of sites (just did 100 from alexa top 1000) failing to compute TTCI when it should have been computed (there was a 5s quiet of both network and CPU but due to the inconsistency between traceviewer model and network record timestamps the quiet periods didn't appear to line up in the audit)
NOTE: moving to first navStart is optional and can easily be reverted, I wanted to test this out since it still meets the goal of the tests of capturing the correct FMP it just reports longer times which in some cases is definitely the right thing to do and the failure cases are unknown(reverted)