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(lantern): cleanup Simulator construction #4910

Merged
merged 3 commits into from
Apr 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

const Audit = require('../audit');
const ConsistentlyInteractive = require('../../gather/computed/metrics/lantern-consistently-interactive'); // eslint-disable-line max-len
const NetworkAnalysis = require('../../gather/computed/network-analysis');
const LoadSimulator = require('../../lib/dependency-graph/simulator/simulator.js');
const Simulator = require('../../lib/dependency-graph/simulator/simulator'); // eslint-disable-line no-unused-vars

const KB_IN_BYTES = 1024;

Expand Down Expand Up @@ -69,33 +68,43 @@ class UnusedBytes extends Audit {
}

/**
* @param {!Artifacts} artifacts
* @return {!Promise<!AuditResult>}
* @param {Artifacts} artifacts
* @param {LH.Audit.Context=} context
* @return {Promise<AuditResult>}
*/
static audit(artifacts) {
static audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const settings = context && context.settings || {};
const simulatorOptions = {
devtoolsLog,
throttlingMethod: settings.throttlingMethod,
throttling: settings.throttling,
};

return artifacts
.requestNetworkRecords(devtoolsLog)
.then(networkRecords =>
Promise.all([
this.audit_(artifacts, networkRecords),
artifacts.requestPageDependencyGraph({trace, devtoolsLog}),
artifacts.requestLoadSimulator(simulatorOptions),
])
)
.then(([result, graph]) => this.createAuditResult(result, graph));
.then(([result, graph, simulator]) => this.createAuditResult(result, graph, simulator));
}

/**
* Computes the estimated effect of all the byte savings on the last long task
* in the provided graph.
*
* @param {!Array<{url: string, wastedBytes: number}>} results The array of byte savings results per resource
* @param {!Node} graph
* @param {Array<{url: string, wastedBytes: number}>} results The array of byte savings results per resource
* @param {Node} graph
* @param {Simulator} simulator
* @return {number}
*/
static computeWasteWithTTIGraph(results, graph, simulator) {
const simulationBeforeChanges = simulator.simulate();
const simulationBeforeChanges = simulator.simulate(graph);
const resultsByUrl = new Map();
for (const result of results) {
resultsByUrl.set(result.url, result);
Expand All @@ -112,7 +121,7 @@ class UnusedBytes extends Audit {
node.record._transferSize = Math.max(original - wastedBytes, 0);
});

const simulationAfterChanges = simulator.simulate();
const simulationAfterChanges = simulator.simulate(graph);
// Restore the original transfer size after we've done our simulation
graph.traverse(node => {
if (node.type !== 'network') return;
Expand All @@ -131,20 +140,12 @@ class UnusedBytes extends Audit {
}

/**
* @param {!Audit.HeadingsResult} result
* @param {!Node} graph
* @return {!AuditResult}
* @param {Audit.HeadingsResult} result
* @param {Node} graph
* @param {Simulator} simulator
* @return {AuditResult}
*/
static createAuditResult(result, graph) {
const records = [];
graph.traverse(node => node.record && records.push(node.record));
const simulatorOptions = NetworkAnalysis.computeRTTAndServerResponseTime(records);
// TODO: use rtt/throughput from config.settings instead of defaults
delete simulatorOptions.rtt;
// TODO: calibrate multipliers, see https://github.com/GoogleChrome/lighthouse/issues/820
Object.assign(simulatorOptions, {cpuSlowdownMultiplier: 1, layoutTaskMultiplier: 1});
const simulator = new LoadSimulator(graph, simulatorOptions);

static createAuditResult(result, graph, simulator) {
const debugString = result.debugString;
const results = result.results.sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes);

Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const DEVTOOLS_RTT_ADJUSTMENT_FACTOR = 3.75;
const DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR = 0.9;

const throttling = {
DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
mobile3G: {
rttMs: 150,
throughputKbps: 1.6 * 1024,
Expand Down
66 changes: 66 additions & 0 deletions lighthouse-core/gather/computed/load-simulator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

const ComputedArtifact = require('./computed-artifact');
const constants = require('../../config/constants');
const Simulator = require('../../lib/dependency-graph/simulator/simulator');

class LoadSimulatorArtifact extends ComputedArtifact {
get name() {
return 'LoadSimulator';
}

/**
* @param {{devtoolsLog: Array, settings: LH.ConfigSettings|undefined}} data
* @param {!Artifacts} artifacts
* @return {!Promise}
*/
async compute_(data, artifacts) {
const {throttlingMethod, throttling} = data.settings || {};
const networkAnalysis = await artifacts.requestNetworkAnalysis(data.devtoolsLog);

const options = {
additionalRttByOrigin: networkAnalysis.additionalRttByOrigin,
serverResponseTimeByOrigin: networkAnalysis.serverResponseTimeByOrigin,
};

switch (throttlingMethod) {
case 'provided':
options.rtt = networkAnalysis.rtt;
options.throughput = networkAnalysis.throughput;
options.cpuSlowdownMultiplier = 1;
options.layoutTaskMultiplier = 1;
break;
case 'devtools':
if (throttling) {
options.rtt =
throttling.requestLatencyMs / constants.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR;
options.throughput =
throttling.downloadThroughputKbps * 1024 /
constants.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR;
}

options.cpuSlowdownMultiplier = 1;
options.layoutTaskMultiplier = 1;
break;
case 'simulate':
if (throttling) {
options.rtt = throttling.rttMs;
options.throughput = throttling.throughputKbps * 1024;
options.cpuSlowdownMultiplier = throttling.cpuSlowdownMultiplier;
}
break;
default:
// intentionally fallback to simulator defaults
break;
}

return new Simulator(options);
}
}

module.exports = LoadSimulatorArtifact;
16 changes: 6 additions & 10 deletions lighthouse-core/gather/computed/metrics/lantern-metric.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const ComputedArtifact = require('../computed-artifact');
const Node = require('../../../lib/dependency-graph/node');
const NetworkNode = require('../../../lib/dependency-graph/network-node'); // eslint-disable-line no-unused-vars
const Simulator = require('../../../lib/dependency-graph/simulator/simulator');
const Simulator = require('../../../lib/dependency-graph/simulator/simulator'); // eslint-disable-line no-unused-vars
const WebInspector = require('../../../lib/web-inspector');

class LanternMetricArtifact extends ComputedArtifact {
Expand Down Expand Up @@ -75,19 +75,15 @@ class LanternMetricArtifact extends ComputedArtifact {
const {trace, devtoolsLog} = data;
const graph = await artifacts.requestPageDependencyGraph({trace, devtoolsLog});
const traceOfTab = await artifacts.requestTraceOfTab(trace);
const networkAnalysis = await artifacts.requestNetworkAnalysis(devtoolsLog);
// TODO(phulce): passthrough settings once all metrics are converted to computed artifacts
/** @type {Simulator} */
const simulator = await artifacts.requestLoadSimulator({devtoolsLog});

const optimisticGraph = this.getOptimisticGraph(graph, traceOfTab);
const pessimisticGraph = this.getPessimisticGraph(graph, traceOfTab);

// TODO(phulce): use rtt and throughput from config.settings instead of defaults
const options = {
additionalRttByOrigin: networkAnalysis.additionalRttByOrigin,
serverResponseTimeByOrigin: networkAnalysis.serverResponseTimeByOrigin,
};

const optimisticSimulation = new Simulator(optimisticGraph, options).simulate();
const pessimisticSimulation = new Simulator(pessimisticGraph, options).simulate();
const optimisticSimulation = simulator.simulate(optimisticGraph);
const pessimisticSimulation = simulator.simulate(pessimisticGraph);

const optimisticEstimate = this.getEstimateFromSimulation(
optimisticSimulation,
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/gather/computed/network-analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class NetworkAnalysis extends ComputedArtifact {
const records = await artifacts.requestNetworkRecords(devtoolsLog);
const throughput = await artifacts.requestNetworkThroughput(devtoolsLog);
const rttAndServerResponseTime = NetworkAnalysis.computeRTTAndServerResponseTime(records);
rttAndServerResponseTime.throughput = throughput;
rttAndServerResponseTime.throughput = throughput * 8; // convert from KBps to Kbps
return rttAndServerResponseTime;
}
}
Expand Down
1 change: 1 addition & 0 deletions lighthouse-core/gather/computed/network-throughput.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class NetworkThroughput extends ComputedArtifact {
* @return {!Promise<!Object>}
*/
compute_(devtoolsLog, artifacts) {
// TODO(phulce): migrate this to network-analysis computed artifact
return artifacts.requestNetworkRecords(devtoolsLog)
.then(NetworkThroughput.getThroughput);
}
Expand Down
4 changes: 2 additions & 2 deletions lighthouse-core/gather/gather-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ class GatherRunner {
.then(_ => GatherRunner.disposeDriver(driver))
.then(_ => GatherRunner.collectArtifacts(gathererResults))
.then(artifacts => {
// Add tracing data to the artifacts object.
return Object.assign(artifacts, tracingData);
// Add tracing data and settings used to the artifacts object.
return Object.assign(artifacts, tracingData, {settings: options.settings});
})
// cleanup on error
.catch(err => {
Expand Down
22 changes: 11 additions & 11 deletions lighthouse-core/lib/dependency-graph/simulator/simulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ const NodeState = {

class Simulator {
/**
* @param {Node} graph
* @param {LH.Gatherer.Simulation.Options} [options]
*/
constructor(graph, options) {
this._graph = graph;
constructor(options) {
this._options = Object.assign(
{
rtt: mobile3G.rttMs,
Expand All @@ -46,13 +44,14 @@ class Simulator {

this._rtt = this._options.rtt;
this._throughput = this._options.throughput;
this._maximumConcurrentRequests = Math.min(
this._maximumConcurrentRequests = Math.max(Math.min(
TcpConnection.maximumSaturatedConnections(this._rtt, this._throughput),
this._options.maximumConcurrentRequests
);
), 1);
this._cpuSlowdownMultiplier = this._options.cpuSlowdownMultiplier;
this._layoutTaskMultiplier = this._cpuSlowdownMultiplier * this._options.layoutTaskMultiplier;

// Properties reset on every `.simulate` call but duplicated here for type checking
this._nodeTiming = new Map();
this._numberInProgressByType = new Map();
this._nodes = {};
Expand All @@ -61,12 +60,12 @@ class Simulator {
}

/**
*
* @param {Node} graph
*/
_initializeConnectionPool() {
_initializeConnectionPool(graph) {
/** @type {LH.WebInspector.NetworkRequest[]} */
const records = [];
this._graph.getRootNode().traverse(node => {
graph.getRootNode().traverse(node => {
if (node.type === Node.TYPES.NETWORK) {
records.push(/** @type {NetworkNode} */ (node).record);
}
Expand Down Expand Up @@ -281,18 +280,19 @@ class Simulator {

/**
* Estimates the time taken to process all of the graph's nodes.
* @param {Node} graph
* @return {LH.Gatherer.Simulation.Result}
*/
simulate() {
simulate(graph) {
// initialize the necessary data containers
this._initializeConnectionPool();
this._initializeConnectionPool(graph);
this._initializeAuxiliaryData();

const nodesNotReadyToStart = this._nodes[NodeState.NotReadyToStart];
const nodesReadyToStart = this._nodes[NodeState.ReadyToStart];
const nodesInProgress = this._nodes[NodeState.InProgress];

const rootNode = this._graph.getRootNode();
const rootNode = graph.getRootNode();
rootNode.traverse(node => nodesNotReadyToStart.add(node));

let totalElapsedTime = 0;
Expand Down
19 changes: 16 additions & 3 deletions lighthouse-core/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// @ts-nocheck
'use strict';

const isDeepEqual = require('lodash.isequal');
const Driver = require('./gather/driver.js');
const GatherRunner = require('./gather/gather-runner');
const ReportScoring = require('./scoring');
Expand Down Expand Up @@ -177,12 +178,23 @@ class Runner {
artifacts = Object.assign(Runner.instantiateComputedArtifacts(),
artifacts || opts.config.artifacts);

if (artifacts.settings) {
const overrides = {gatherMode: undefined, auditMode: undefined};
const normalizedGatherSettings = Object.assign({}, artifacts.settings, overrides);
const normalizedAuditSettings = Object.assign({}, opts.settings, overrides);

// TODO(phulce): allow change of throttling method to `simulate`
if (!isDeepEqual(normalizedGatherSettings, normalizedAuditSettings)) {
throw new Error('Cannot change settings between gathering and auditing');
}
}

// Run each audit sequentially
const auditResults = [];
let promise = Promise.resolve();
for (const auditDefn of opts.config.audits) {
promise = promise.then(_ => {
return Runner._runAudit(auditDefn, artifacts).then(ret => auditResults.push(ret));
return Runner._runAudit(auditDefn, artifacts, opts).then(ret => auditResults.push(ret));
Copy link
Member

Choose a reason for hiding this comment

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

re: conversation on settings === artifact settings, not sure of the best place, but rather than buried in the specific audits that use them (since all audits have access to settings) could be a few lines above here after artifacts is defined?

});
}
return promise.then(_ => {
Expand All @@ -204,10 +216,11 @@ class Runner {
* Otherwise returns error audit result.
* @param {!Audit} audit
* @param {!Artifacts} artifacts
* @param {{settings: LH.ConfigSettings}} opts
* @return {!Promise<!AuditResult>}
* @private
*/
static _runAudit(auditDefn, artifacts) {
static _runAudit(auditDefn, artifacts, opts) {
const audit = auditDefn.implementation;
const status = `Evaluating: ${audit.meta.description}`;

Expand Down Expand Up @@ -248,7 +261,7 @@ class Runner {
}
}
// all required artifacts are in good shape, so we proceed
return audit.audit(artifacts, {options: auditDefn.options || {}});
return audit.audit(artifacts, {options: auditDefn.options || {}, settings: opts.settings});
// Fill remaining audit result fields.
}).then(auditResult => Audit.generateAuditResult(audit, auditResult))
.catch(err => {
Expand Down
Loading