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

core(preload): use lantern to compute savings #5062

Merged
merged 9 commits into from
May 3, 2018

Conversation

patrickhulce
Copy link
Collaborator

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 :)

// initialize the necessary data containers
this._ignoreObserved = options.ignoreObserved;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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"

Copy link
Member

Choose a reason for hiding this comment

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

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?

@patrickhulce
Copy link
Collaborator Author

friendly ping! :)

// initialize the necessary data containers
this._ignoreObserved = options.ignoreObserved;
Copy link
Member

Choose a reason for hiding this comment

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

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?

// 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();

this._connectionsByOrigin.set(origin, connections);
}
}

/**
Copy link
Member

Choose a reason for hiding this comment

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

can we add a few lines to describe what's happening in here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

const needsWarmConnection = !options.ignoreObserved &&
this._connectionReusedByRequestId.get(record.requestId);

let connectionToUse = availableWarmConnection || availableColdConnection;
Copy link
Member

Choose a reason for hiding this comment

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

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();
Copy link
Member

Choose a reason for hiding this comment

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

how would we end up with >6? our network records indicate we have >6 even though that should be impossible?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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);
Copy link
Member

Choose a reason for hiding this comment

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

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);
Copy link
Member

Choose a reason for hiding this comment

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

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done 👍

@@ -212,7 +223,7 @@ class Simulator {

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));
Copy link
Member

Choose a reason for hiding this comment

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

how is this known not-null at this point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

commented

@@ -255,7 +266,7 @@ class Simulator {
if (node.type !== Node.TYPES.NETWORK) throw new Error('Unsupported');

const record = /** @type {NetworkNode} */ (node).record;
const connection = /** @type {TcpConnection} */ (this._connectionPool.acquire(record));
const connection = /** @type {TcpConnection} */ (this._acquireConnection(record));
Copy link
Member

Choose a reason for hiding this comment

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

also never null?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

commented

@@ -82,15 +82,27 @@ module.exports = class ConnectionPool {
throw new Error(`Could not find a connection for origin: ${origin}`);
}

// 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());
Copy link
Member

Choose a reason for hiding this comment

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

pull out into a file level constant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

// get all critical requests 2 + mainResourceIndex levels deep
const mainResourceIndex = mainResource.redirects ? mainResource.redirects.length : 0;

const criticalRequests = UsesRelPreloadAudit._flattenRequests(critChains,
3 + mainResourceIndex, 2 + mainResourceIndex);
criticalRequests.forEach(request => {
const networkRecord = request;
const urls = new Set();
Copy link
Member

Choose a reason for hiding this comment

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

I think you'll need an explicit /** @type {Set<string>} */ or it'll be a Set<any>

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@@ -281,10 +292,13 @@ class Simulator {
/**
* Estimates the time taken to process all of the graph's nodes.
* @param {Node} graph
* @param {{flexibleOrdering?: boolean}=} options
Copy link
Member

Choose a reason for hiding this comment

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

definitely need an explanation of flexibleOrdering added to the docstring above :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done 👍

node.addDependency(mainDocumentNode);
}

const simulationAfterChanges = simulator.simulate(modifiedGraph, {flexibleOrdering: true});
Copy link
Member

Choose a reason for hiding this comment

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

comment why flexibleOrdering should be here would be 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

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)),
Copy link
Member

Choose a reason for hiding this comment

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

seems like it gets most of the way to an opportunity but then stops here? Is measuring impact on TTI or whatever problematic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah fair, I was mostly retaining parity with the old way, but we can do max(TTI, load) like the others 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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 = {
Copy link
Member

Choose a reason for hiding this comment

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

is this reused for the other tests?

Copy link
Collaborator Author

@patrickhulce patrickhulce May 2, 2018

Choose a reason for hiding this comment

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

nope, just scoped for the mockArtifacts to be able to return it. I added an afterEach that clears it to make it more explciit

Copy link
Member

Choose a reason for hiding this comment

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

wait, but how are the others running with an undefined simulator? (obvs I'm too lazy to debug here :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the other tests are asserting that no savings are found, so a simulator isn't necessary

Copy link
Member

Choose a reason for hiding this comment

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

oh, lol

let mockGraph;
let mockSimulator;

const mockArtifacts = (networkRecords, mockChain, mainResource = defaultMainResource) => {
Copy link
Member

Choose a reason for hiding this comment

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

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 👍 👍

Copy link
Collaborator Author

@patrickhulce patrickhulce May 2, 2018

Choose a reason for hiding this comment

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

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?

Copy link
Member

Choose a reason for hiding this comment

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

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/

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sure

Copy link
Member

Choose a reason for hiding this comment

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

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah that sounds good

@patrickhulce
Copy link
Collaborator Author

FYI @brendankenny I rebased against your changes and fixed the remaining type check items

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

best weak smoke test there ever was :):)

LGTM!

@patrickhulce patrickhulce merged commit 9cc23a3 into master May 3, 2018
@patrickhulce patrickhulce deleted the lantern_preload_opp branch May 3, 2018 00:22
kdzwinel pushed a commit to kdzwinel/lighthouse that referenced this pull request Aug 16, 2018
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.

4 participants