-
Notifications
You must be signed in to change notification settings - Fork 9.5k
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
core(tracehouse): sort trace events by nesting order #9230
Changes from 5 commits
4852262
575aa1d
5db102d
79a53fe
30b5eb7
28876ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,10 +62,110 @@ class TraceProcessor { | |
} | ||
|
||
/** | ||
* This method sorts a group of trace events that have the same timestamp. We want to... | ||
* | ||
* 1. Put E events first, we finish off our existing events before we start new ones. | ||
* 2. Order B/X events by their duration, we want parents to start before child events. | ||
* 3. If we don't have any of this to go on, just use the position in the original array (stable sort). | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it worth calling out for future readers that this is expected to usually get There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah good call, it's a good reminder we'll all need for the next poor sap to touch trace events 👍 😆 |
||
* @param {number[]} tsGroupIndices | ||
* @param {number[]} timestampSortedIndices | ||
* @param {number} indexOfTsGroupIndicesStart | ||
* @param {LH.TraceEvent[]} traceEvents | ||
* @return {number[]} | ||
*/ | ||
static _sortTimestampEventGroup( | ||
tsGroupIndices, | ||
timestampSortedIndices, | ||
indexOfTsGroupIndicesStart, | ||
traceEvents | ||
) { | ||
/* | ||
* We have two different sets of indices going on here. | ||
|
||
* 1. There's the index for an element of `traceEvents`, referred to here as an `ArrayIndex`. | ||
* `timestampSortedIndices` is an array of `ArrayIndex` elements. | ||
* 2. There's the index for an element of `timestampSortedIndices`, referred to here as a `TsIndex`. | ||
* A `TsIndex` is therefore an index to an element which is itself an index. | ||
* | ||
* These two helper functions help resolve this layer of indirection. | ||
* Our final return value is an array of `ArrayIndex` in their final sort order. | ||
*/ | ||
/** @param {number} i */ | ||
const lookupArrayIndexByTsIndex = i => timestampSortedIndices[i]; | ||
/** @param {number} i */ | ||
const lookupEventByTsIndex = i => traceEvents[lookupArrayIndexByTsIndex(i)]; | ||
|
||
/** @type {Array<number>} */ | ||
const eEventIndices = []; | ||
/** @type {Array<number>} */ | ||
const bxEventIndices = []; | ||
/** @type {Array<number>} */ | ||
const otherEventIndices = []; | ||
|
||
for (const tsIndex of tsGroupIndices) { | ||
// See comment above for the distinction between `tsIndex` and `arrayIndex`. | ||
const arrayIndex = lookupArrayIndexByTsIndex(tsIndex); | ||
const event = lookupEventByTsIndex(tsIndex); | ||
if (event.ph === 'E') eEventIndices.push(arrayIndex); | ||
else if (event.ph === 'X' || event.ph === 'B') bxEventIndices.push(arrayIndex); | ||
else otherEventIndices.push(arrayIndex); | ||
} | ||
|
||
/** @type {Map<number, number>} */ | ||
const effectiveDuration = new Map(); | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (const index of bxEventIndices) { | ||
brendankenny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const event = traceEvents[index]; | ||
if (event.ph === 'X') { | ||
effectiveDuration.set(index, event.dur); | ||
} else { | ||
// Find the next available 'E' event *after* the current group of events that matches our name, pid, and tid. | ||
let duration = Number.MAX_SAFE_INTEGER; | ||
// To find the next "available" 'E' event, we need to account for nested events of the same name. | ||
let additionalNestedEventsWithSameName = 0; | ||
const startIndex = indexOfTsGroupIndicesStart + tsGroupIndices.length; | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
brendankenny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (let j = startIndex; j < timestampSortedIndices.length; j++) { | ||
const potentialMatchingEvent = lookupEventByTsIndex(j); | ||
const eventMatches = potentialMatchingEvent.name === event.name && | ||
potentialMatchingEvent.pid === event.pid && | ||
potentialMatchingEvent.tid === event.tid; | ||
|
||
// The event doesn't match, just skip it. | ||
if (!eventMatches) continue; | ||
|
||
if (potentialMatchingEvent.ph === 'E' && additionalNestedEventsWithSameName === 0) { | ||
// It's the next available 'E' event for us, so set the duration and break the loop. | ||
duration = potentialMatchingEvent.ts - event.ts; | ||
break; | ||
} else if (potentialMatchingEvent.ph === 'E') { | ||
// It's an 'E' event but for a nested event. Decrement our counter and move on. | ||
additionalNestedEventsWithSameName--; | ||
} else if (potentialMatchingEvent.ph === 'B') { | ||
// It's a nested 'B' event. Increment our counter and move on. | ||
additionalNestedEventsWithSameName++; | ||
} | ||
} | ||
|
||
effectiveDuration.set(index, duration); | ||
} | ||
} | ||
|
||
bxEventIndices.sort((indexA, indexB) => ((effectiveDuration.get(indexB) || 0) - | ||
(effectiveDuration.get(indexA) || 0) || (indexA - indexB))); | ||
|
||
otherEventIndices.sort((indexA, indexB) => indexA - indexB); | ||
|
||
return [...eEventIndices, ...bxEventIndices, ...otherEventIndices]; | ||
} | ||
|
||
/** | ||
* Sorts and filters trace events by timestamp and respecting the nesting structure inherent to | ||
* parent/child event relationships. | ||
* | ||
* @param {LH.TraceEvent[]} traceEvents | ||
* @param {(e: LH.TraceEvent) => boolean} filter | ||
*/ | ||
static _filteredStableSort(traceEvents, filter) { | ||
static filteredTraceSort(traceEvents, filter) { | ||
// create an array of the indices that we want to keep | ||
const indices = []; | ||
for (let srcIndex = 0; srcIndex < traceEvents.length; srcIndex++) { | ||
|
@@ -74,11 +174,33 @@ class TraceProcessor { | |
} | ||
} | ||
|
||
// sort by ts, if there's no ts difference sort by index | ||
indices.sort((indexA, indexB) => { | ||
const result = traceEvents[indexA].ts - traceEvents[indexB].ts; | ||
return result ? result : indexA - indexB; | ||
}); | ||
// Sort by ascending timestamp first. | ||
indices.sort((indexA, indexB) => traceEvents[indexA].ts - traceEvents[indexB].ts); | ||
|
||
// Now we find groups with equal timestamps and order them by their nesting structure. | ||
for (let i = 0; i < indices.length - 1; i++) { | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const ts = traceEvents[indices[i]].ts; | ||
const tsGroupIndices = [i]; | ||
for (let j = i + 1; j < indices.length; j++) { | ||
if (traceEvents[indices[j]].ts !== ts) break; | ||
tsGroupIndices.push(j); | ||
} | ||
|
||
// We didn't find any other events with the same timestamp, just keep going. | ||
if (tsGroupIndices.length === 1) continue; | ||
|
||
// Sort the group by other criteria and replace our index array with it. | ||
const finalIndexOrder = TraceProcessor._sortTimestampEventGroup( | ||
tsGroupIndices, | ||
indices, | ||
i, | ||
traceEvents | ||
); | ||
indices.splice(i, finalIndexOrder.length, ...finalIndexOrder); | ||
// We just sorted this set of identical timestamps, so skip over the rest of the group. | ||
// -1 because we already have i++. | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
i += tsGroupIndices.length - 1; | ||
} | ||
|
||
// create a new array using the target indices from previous sort step | ||
const sorted = []; | ||
|
@@ -352,7 +474,7 @@ class TraceProcessor { | |
static computeTraceOfTab(trace) { | ||
// Parse the trace for our key events and sort them by timestamp. Note: sort | ||
// *must* be stable to keep events correctly nested. | ||
const keyEvents = this._filteredStableSort(trace.traceEvents, e => { | ||
const keyEvents = this.filteredTraceSort(trace.traceEvents, e => { | ||
return e.cat.includes('blink.user_timing') || | ||
e.cat.includes('loading') || | ||
e.cat.includes('devtools.timeline') || | ||
|
@@ -406,7 +528,7 @@ class TraceProcessor { | |
// subset all trace events to just our tab's process (incl threads other than main) | ||
// stable-sort events to keep them correctly nested. | ||
const processEvents = TraceProcessor | ||
._filteredStableSort(trace.traceEvents, e => e.pid === mainFrameIds.pid); | ||
.filteredTraceSort(trace.traceEvents, e => e.pid === mainFrameIds.pid); | ||
|
||
const mainThreadEvents = processEvents | ||
.filter(e => e.tid === mainFrameIds.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.
can a B/E pair ever have the same
ts
? If so, this would misorder 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.
If so, there's no other way for us to know at this stage either because we now know the order they came in doesn't really mean anything. We'll have to handle this on consumption side like we do with the other problematic cases then.
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, but in that case wouldn't just ordering them B then E fix it? The hard part would be knowing how to connect the two
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 kinda gets at the fact that there are still lots of edge cases where we have to change our consumption. this won't particularly matter with the PR I'm about to open so I have no strong feelings 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.
so I instrumented this code a bit and ran it over a good set of gnarly traces and never found any instance where this happened if we differentiate by event
name
andpid
.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.
Actually,
lighthouse-core/test/fixtures/traces/tracingstarted-after-navstart.json
does fail at this, but unclear if it's from us editing it :)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 conflict should be resolved by #9491 + this existing work, so I would vote to punt, it's already captured in a test in #9491 👍