core(preload): use lantern to compute savings#5062
Conversation
| simulate(graph, options) { | ||
| options = Object.assign({ignoreObserved: false}, options); | ||
| // initialize the necessary data containers | ||
| this._ignoreObserved = options.ignoreObserved; |
There was a problem hiding this comment.
I'm really not sure what to call this thing, basically it's "don't try and stick just to how the page loaded in the trace, feel free to load it as efficiently as you think possible"
There was a problem hiding this comment.
yeah i don't love this term. :) as you know i don't love reusing it in different places in diff contexts.
is reconstructTCPConnectionPooling accurate?
|
friendly ping! :) |
| simulate(graph, options) { | ||
| options = Object.assign({ignoreObserved: false}, options); | ||
| // initialize the necessary data containers | ||
| this._ignoreObserved = options.ignoreObserved; |
There was a problem hiding this comment.
yeah i don't love this term. :) as you know i don't love reusing it in different places in diff contexts.
is reconstructTCPConnectionPooling accurate?
| } | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
can we add a few lines to describe what's happening in here?
| const needsWarmConnection = !options.ignoreObserved && | ||
| this._connectionReusedByRequestId.get(record.requestId); | ||
|
|
||
| let connectionToUse = availableWarmConnection || availableColdConnection; |
There was a problem hiding this comment.
still not sure exactly what logic you're trying to apply here, but it does look odd there's no else case in all 4 lines here.
| // Make sure each origin has 6 connections available | ||
| // https://cs.chromium.org/chromium/src/net/socket/client_socket_pool_manager.cc?type=cs&q="int+g_max_sockets_per_group" | ||
| while (connections.length < 6) connections.push(connections[0].clone()); | ||
| while (connections.length > 6) connections.pop(); |
There was a problem hiding this comment.
how would we end up with >6? our network records indicate we have >6 even though that should be impossible?
There was a problem hiding this comment.
If Chrome killed connections and needed to reopen them, we can kill this line though
| this._connectionsByRecord.set(record, connection); | ||
| return connection; | ||
| const needsColdConnection = !options.ignoreObserved && | ||
| !this._connectionReusedByRequestId.get(record.requestId); |
There was a problem hiding this comment.
meta point: it's really funky to be mixing this "connection reused" state that refers to observed network activity with what's "available" and "needed" for the simulation. I wish these were clearer
| */ | ||
| clone() { | ||
| // @ts-ignore | ||
| return Object.assign(new TcpConnection(), this); |
There was a problem hiding this comment.
I'd probably personally prefer the long way of setting these, but if you prefer this way,
return Object.assign(new TcpConnection(this._rtt, this._throughput), this);
should at least let you avoid the // @ts-ignore (TcpConnection & this should collapse just to TcpConnection externally)
| const record = /** @type {NetworkNode} */ (node).record; | ||
| const timingData = this._nodeTimings.get(node); | ||
| const connection = /** @type {TcpConnection} */ (this._connectionPool.acquire(record)); | ||
| const connection = /** @type {TcpConnection} */ (this._acquireConnection(record)); |
There was a problem hiding this comment.
how is this known not-null at this point?
|
|
||
| const record = /** @type {NetworkNode} */ (node).record; | ||
| const connection = /** @type {TcpConnection} */ (this._connectionPool.acquire(record)); | ||
| const connection = /** @type {TcpConnection} */ (this._acquireConnection(record)); |
|
|
||
| // Make sure each origin has 6 connections available | ||
| // https://cs.chromium.org/chromium/src/net/socket/client_socket_pool_manager.cc?type=cs&q="int+g_max_sockets_per_group" | ||
| while (connections.length < 6) connections.push(connections[0].clone()); |
There was a problem hiding this comment.
pull out into a file level constant?
| 3 + mainResourceIndex, 2 + mainResourceIndex); | ||
| criticalRequests.forEach(request => { | ||
| const networkRecord = request; | ||
| const urls = new Set(); |
There was a problem hiding this comment.
I think you'll need an explicit /** @type {Set<string>} */ or it'll be a Set<any>
| /** | ||
| * Estimates the time taken to process all of the graph's nodes. | ||
| * @param {Node} graph | ||
| * @param {{flexibleOrdering?: boolean}=} options |
There was a problem hiding this comment.
definitely need an explanation of flexibleOrdering added to the docstring above :)
| node.addDependency(mainDocumentNode); | ||
| } | ||
|
|
||
| const simulationAfterChanges = simulator.simulate(modifiedGraph, {flexibleOrdering: true}); |
There was a problem hiding this comment.
comment why flexibleOrdering should be here would be 👍
| return { | ||
| // Preload won't necessarily impact the deepest chain/overall time | ||
| // We'll use the maximum endTime improvement for now | ||
| wastedMs: Math.max(...results.map(item => item.wastedMs)), |
There was a problem hiding this comment.
seems like it gets most of the way to an opportunity but then stops here? Is measuring impact on TTI or whatever problematic?
There was a problem hiding this comment.
Yeah fair, I was mostly retaining parity with the old way, but we can do max(TTI, load) like the others 👍
There was a problem hiding this comment.
having had the hour to think about it, I've reconvinced myself that this is a bad idea and we should keep it to max(...wastedMs), our selection of preloaded items is fairly narrow and in a busy page it will rarely impact the longest chain
given our super ambiguous no-longer-just-on-a-particular-metric approach, it seems OK for now
| scriptAddedNode.addDependency(scriptNode); | ||
|
|
||
| mockGraph = rootNode; | ||
| mockSimulator = { |
There was a problem hiding this comment.
is this reused for the other tests?
There was a problem hiding this comment.
nope, just scoped for the mockArtifacts to be able to return it. I added an afterEach that clears it to make it more explciit
There was a problem hiding this comment.
wait, but how are the others running with an undefined simulator? (obvs I'm too lazy to debug here :)
There was a problem hiding this comment.
the other tests are asserting that no savings are found, so a simulator isn't necessary
| let mockGraph; | ||
| let mockSimulator; | ||
|
|
||
| const mockArtifacts = (networkRecords, mockChain, mainResource = defaultMainResource) => { |
There was a problem hiding this comment.
the audit has some non-trivial uses of the graph and simulator, which means we're doing a lot of testing of the mocks :) Some tests using the full real thing would be 👍 👍
There was a problem hiding this comment.
totally agreed, but it requires a trace and devtoolslog of a site that would benefit from preloading which we don't have checked in yet and can be quite large.
my proposal:
- we rely on the smoke test + unit tests for now
- for cases like this where we need to exercise complicated lantern paths with real traces we use my patent-pending post-I/O approach of storing ~100 traces/devtoolslogs on GCP that can run on-demand/on commits that touch lantern files to assert that opportunities and metrics look decent
WDYT?
There was a problem hiding this comment.
it wouldn't give good coverage, but even a simple devtoolslog/trace with little or no savings would be a start until the GCP smoke tests. there's lighthouse-core/tests/fixtures/artifacts/perflog/ and of course lighthouse-core/test/results/artifacts/
There was a problem hiding this comment.
we should maybe make a tool to take real artifacts and strip out a bunch of stuff to make nice artifact fixtures and maybe only a few hundred KB at most. e.g. for things like this that are mostly networking we can get rid of a lot of trace events (enough that it was the same page load but with a lot less busy CPU)
(we have a lot of old traces for testing, but without paired devtools logs they're becoming increasingly useless for tests)
There was a problem hiding this comment.
yeah that sounds good
|
FYI @brendankenny I rebased against your changes and fixed the remaining type check items |
brendankenny
left a comment
There was a problem hiding this comment.
best weak smoke test there ever was :):)
LGTM!
this ended up turning into quite the behemoth, I was tempted to break up the lantern/connection-pool test changes, but it's kinda hard to grok without the motivating example.
most of the lines are tests, so that's the good news :)