core(metrics): add lantern speed index (#4695)
This commit is contained in:
Родитель
93ce1dc0ee
Коммит
e2fc6dddf7
|
@ -40,6 +40,7 @@ class PredictivePerf extends Audit {
|
|||
const fmp = await artifacts.requestLanternFirstMeaningfulPaint({trace, devtoolsLog});
|
||||
const ttci = await artifacts.requestLanternConsistentlyInteractive({trace, devtoolsLog});
|
||||
const ttfcpui = await artifacts.requestLanternFirstCPUIdle({trace, devtoolsLog});
|
||||
const si = await artifacts.requestLanternSpeedIndex({trace, devtoolsLog});
|
||||
|
||||
const values = {
|
||||
roughEstimateOfFCP: fcp.timing,
|
||||
|
@ -57,6 +58,10 @@ class PredictivePerf extends Audit {
|
|||
roughEstimateOfTTFCPUI: ttfcpui.timing,
|
||||
optimisticTTFCPUI: ttfcpui.optimisticEstimate.timeInMs,
|
||||
pessimisticTTFCPUI: ttfcpui.pessimisticEstimate.timeInMs,
|
||||
|
||||
roughEstimateOfSI: si.timing,
|
||||
optimisticSI: si.optimisticEstimate.timeInMs,
|
||||
pessimisticSI: si.pessimisticEstimate.timeInMs,
|
||||
};
|
||||
|
||||
const score = Audit.computeLogNormalScore(
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* @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 MetricArtifact = require('./lantern-metric');
|
||||
const Node = require('../../../lib/dependency-graph/node');
|
||||
const CPUNode = require('../../../lib/dependency-graph/cpu-node'); // eslint-disable-line no-unused-vars
|
||||
|
||||
class SpeedIndex extends MetricArtifact {
|
||||
get name() {
|
||||
return 'LanternSpeedIndex';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
||||
*/
|
||||
get COEFFICIENTS() {
|
||||
return {
|
||||
intercept: 200,
|
||||
optimistic: 1.16,
|
||||
pessimistic: 0.57,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} dependencyGraph
|
||||
* @return {Node}
|
||||
*/
|
||||
getOptimisticGraph(dependencyGraph) {
|
||||
return dependencyGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} dependencyGraph
|
||||
* @return {Node}
|
||||
*/
|
||||
getPessimisticGraph(dependencyGraph) {
|
||||
return dependencyGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
||||
* @param {Object} extras
|
||||
* @return {LH.Gatherer.Simulation.Result}
|
||||
*/
|
||||
getEstimateFromSimulation(simulationResult, extras) {
|
||||
const fcpTimeInMs = extras.fcpResult.timing;
|
||||
const estimate = extras.optimistic
|
||||
? extras.speedline.perceptualSpeedIndex
|
||||
: SpeedIndex.computeLayoutBasedSpeedIndex(simulationResult.nodeTiming, fcpTimeInMs);
|
||||
return {
|
||||
timeInMs: estimate,
|
||||
nodeTiming: simulationResult.nodeTiming,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LH.Artifacts.MetricComputationData} data
|
||||
* @param {Object} artifacts
|
||||
* @return {Promise<LH.Artifacts.LanternMetric>}
|
||||
*/
|
||||
async compute_(data, artifacts) {
|
||||
const speedline = await artifacts.requestSpeedline(data.trace);
|
||||
const fcpResult = await artifacts.requestLanternFirstContentfulPaint(data, artifacts);
|
||||
const metricResult = await this.computeMetricWithGraphs(data, artifacts, {
|
||||
speedline,
|
||||
fcpResult,
|
||||
});
|
||||
metricResult.timing = Math.max(metricResult.timing, fcpResult.timing);
|
||||
return metricResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Approximate speed index using layout events from the simulated node timings.
|
||||
* The layout-based speed index is the weighted average of the endTime of CPU nodes that contained
|
||||
* a 'Layout' task. log(duration) is used as the weight to stand for "significance" to the page.
|
||||
*
|
||||
* If no layout events can be found or the endTime of a CPU task is too early, FCP is used instead.
|
||||
*
|
||||
* This approach was determined after evaluating the accuracy/complexity tradeoff of many
|
||||
* different methods. Read more in the evaluation doc.
|
||||
*
|
||||
* @see https://docs.google.com/document/d/1qJWXwxoyVLVadezIp_Tgdk867G3tDNkkVRvUJSH3K1E/edit#
|
||||
* @param {Map<Node, LH.Gatherer.Simulation.NodeTiming>} nodeTiming
|
||||
* @param {number} fcpTimeInMs
|
||||
* @return {number}
|
||||
*/
|
||||
static computeLayoutBasedSpeedIndex(nodeTiming, fcpTimeInMs) {
|
||||
/** @type {Array<{time: number, weight: number}>} */
|
||||
const layoutWeights = [];
|
||||
for (const [node, timing] of nodeTiming.entries()) {
|
||||
if (node.type !== Node.TYPES.CPU) continue;
|
||||
if (!timing.startTime || !timing.endTime) continue;
|
||||
|
||||
const cpuNode = /** @type {CPUNode} */ (node);
|
||||
if (cpuNode.childEvents.some(x => x.name === 'Layout')) {
|
||||
const timingWeight = Math.max(Math.log2(timing.endTime - timing.startTime), 0);
|
||||
layoutWeights.push({time: timing.endTime, weight: timingWeight});
|
||||
}
|
||||
}
|
||||
|
||||
if (!layoutWeights.length) {
|
||||
return fcpTimeInMs;
|
||||
}
|
||||
|
||||
const totalWeightedTime = layoutWeights
|
||||
.map(evt => evt.weight * Math.max(evt.time, fcpTimeInMs))
|
||||
.reduce((a, b) => a + b, 0);
|
||||
const totalWeight = layoutWeights.map(evt => evt.weight).reduce((a, b) => a + b, 0);
|
||||
return totalWeightedTime / totalWeight;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpeedIndex;
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @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 Runner = require('../../../../runner');
|
||||
const assert = require('assert');
|
||||
|
||||
const trace = require('../../../fixtures/traces/progressive-app-m60.json');
|
||||
const devtoolsLog = require('../../../fixtures/traces/progressive-app-m60.devtools.log.json');
|
||||
|
||||
/* eslint-env mocha */
|
||||
describe('Metrics: Lantern Speed Index', () => {
|
||||
it('should compute predicted value', async () => {
|
||||
const artifacts = Runner.instantiateComputedArtifacts();
|
||||
const result = await artifacts.requestLanternSpeedIndex({trace, devtoolsLog});
|
||||
|
||||
assert.equal(Math.round(result.timing), 2069);
|
||||
assert.equal(Math.round(result.optimisticEstimate.timeInMs), 609);
|
||||
assert.equal(Math.round(result.pessimisticEstimate.timeInMs), 2038);
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче