core(load-fast-4-pwa): use computed artifacts (#4981)

This commit is contained in:
Patrick Hulce 2018-04-18 14:00:13 -07:00 коммит произвёл GitHub
Родитель fff50f2bfe
Коммит 9771117d08
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 73 добавлений и 180 удалений

Просмотреть файл

@ -11,17 +11,14 @@
* Afterwards, we report if the TTFI is less than 10 seconds.
*/
const isDeepEqual = require('lodash.isequal');
const Audit = require('./audit');
const URL = require('../lib/url-shim');
const targetLatencyMs = require('../config/constants').throttling.mobile3G.rttMs;
const Sentry = require('../lib/sentry');
const mobile3GThrottling = require('../config/constants').throttling.mobile3G;
const Util = require('../report/v2/renderer/util.js');
// Maximum TTFI to be considered "fast" for PWA baseline checklist
// Maximum TTI to be considered "fast" for PWA baseline checklist
// https://developers.google.com/web/progressive-web-apps/checklist
const MAXIMUM_TTFI = 10 * 1000;
const WHITELISTED_STATUS_CODES = [307];
const MAXIMUM_TTI = 10 * 1000;
class LoadFastEnough4Pwa extends Audit {
/**
@ -32,116 +29,47 @@ class LoadFastEnough4Pwa extends Audit {
name: 'load-fast-enough-for-pwa',
description: 'Page load is fast enough on 3G',
failureDescription: 'Page load is not fast enough on 3G',
helpText: 'A fast page load over a 3G network ensures a good mobile user experience. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).',
helpText:
'A fast page load over a 3G network ensures a good mobile user experience. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).',
requiredArtifacts: ['traces', 'devtoolsLogs'],
};
}
/**
* @param {!Artifacts} artifacts
* @return {!AuditResult}
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {LH.AuditResult}
*/
static audit(artifacts) {
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
return artifacts.requestNetworkRecords(devtoolsLogs).then(networkRecords => {
const firstRequestLatenciesByOrigin = new Map();
networkRecords.forEach(record => {
// Ignore requests that don't have valid origin, timing data, came from the cache, were
// redirected by Chrome without going to the network, or are not finished.
const fromCache = record._fromDiskCache || record._fromMemoryCache;
const origin = URL.getOrigin(record._url);
if (!origin || !record._timing || fromCache ||
WHITELISTED_STATUS_CODES.includes(record.statusCode) || !record.finished) {
return;
}
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
// Disregard requests with an invalid start time, (H2 request start times are sometimes less
// than issue time and even negative which throws off timing)
if (record._startTime < record._issueTime) {
return;
}
// If throttling was default devtools or lantern 3G throttling, then reuse the given settings
// Otherwise, we'll force the usage of lantern 3G.
const settingOverrides = {throttlingMethod: 'simulate', throttling: mobile3GThrottling};
const settings =
context.settings.throttlingMethod !== 'provided' &&
isDeepEqual(context.settings.throttling, mobile3GThrottling)
? context.settings
: Object.assign({}, context.settings, settingOverrides);
// Use DevTools' definition of Waiting latency: https://github.com/ChromeDevTools/devtools-frontend/blob/66595b8a73a9c873ea7714205b828866630e9e82/front_end/network/RequestTimingView.js#L164
const latency = record._timing.receiveHeadersEnd - record._timing.sendEnd;
const latencyInfo = {
url: record._url,
startTime: record._startTime,
origin,
latency,
};
const metricComputationData = {trace, devtoolsLog, settings};
const tti = await artifacts.requestConsistentlyInteractive(metricComputationData);
// Only examine the first request per origin to reduce noisiness from cases like H2 push
// where individual request latency may not apply.
const existing = firstRequestLatenciesByOrigin.get(origin);
if (!existing || latencyInfo.startTime < existing.startTime) {
firstRequestLatenciesByOrigin.set(origin, latencyInfo);
}
});
const score = Number(tti.timing < MAXIMUM_TTI);
let firstRequestLatencies = Array.from(firstRequestLatenciesByOrigin.values());
const latency3gMin = targetLatencyMs - 10;
const areLatenciesAll3G = firstRequestLatencies.every(val => val.latency > latency3gMin);
firstRequestLatencies = firstRequestLatencies.map(item => ({
url: item.url,
latency: Util.formatNumber(item.latency, 0.01),
}));
let debugString;
if (!score) {
// eslint-disable-next-line max-len
debugString = `First Interactive was ${Util.formatMilliseconds(tti.timing)}. More details in the "Performance" section.`;
}
const trace = artifacts.traces[Audit.DEFAULT_PASS];
return artifacts.requestFirstInteractive(trace).then(firstInteractive => {
const timeToFirstInteractive = firstInteractive.timeInMs;
const isFast = timeToFirstInteractive < MAXIMUM_TTFI;
const extendedInfo = {
value: {areLatenciesAll3G, firstRequestLatencies, isFast, timeToFirstInteractive},
};
const details = Audit.makeTableDetails([
{key: 'url', itemType: 'url', text: 'URL'},
{key: 'latency', itemType: 'text', text: 'Latency (ms)'},
], firstRequestLatencies);
if (!isFast) {
return {
rawValue: false,
// eslint-disable-next-line max-len
debugString: `First Interactive was at ${Util.formatMilliseconds(timeToFirstInteractive)}. More details in the "Performance" section.`,
extendedInfo,
};
}
if (!areLatenciesAll3G) {
const sentryContext = Sentry.getContext();
const hadThrottlingEnabled = sentryContext && sentryContext.extra &&
sentryContext.extra.networkThrottling;
if (hadThrottlingEnabled) {
// Track these instances in Sentry since there should be no requests that are fast when
// throttling is enabled, and it's likely a throttling bug we should look into.
const violatingLatency = firstRequestLatencies
.find(item => Number(item.latency) < latency3gMin);
Sentry.captureMessage('Network request latencies were not realistic', {
tags: {audit: this.meta.name},
extra: {violatingLatency},
level: 'warning',
});
}
return {
rawValue: true,
// eslint-disable-next-line max-len
debugString: `First Interactive was found at ${Util.formatMilliseconds(timeToFirstInteractive)}; however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`,
extendedInfo,
details,
};
}
return {
rawValue: true,
extendedInfo,
};
});
});
return {
score,
debugString,
rawValue: tti.timing,
};
}
}

Просмотреть файл

@ -48,6 +48,10 @@ class ComputedMetric extends ComputedArtifact {
*/
async compute_(data, artifacts) {
const {trace, devtoolsLog, settings} = data;
if (!trace || !devtoolsLog || !settings) {
throw new Error('Did not provide necessary metric computation data');
}
const augmentedData = Object.assign({
networkRecords: await artifacts.requestNetworkRecords(devtoolsLog),
traceOfTab: await artifacts.requestTraceOfTab(trace),

Просмотреть файл

@ -6,22 +6,24 @@
'use strict';
const FastPWAAudit = require('../../audits/load-fast-enough-for-pwa');
const Runner = require('../../runner.js');
const Audit = require('../../audits/audit.js');
const mobile3GThrottling = require('../../config/constants').throttling.mobile3G;
const assert = require('assert');
function generateArtifacts(firstInteractiveValue, networkRecords = []) {
const trace = require('../fixtures/traces/progressive-app-m60.json');
const devtoolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
function generateArtifacts(ttiValue) {
return {
devtoolsLogs: {
[Audit.DEFAULT_PASS]: [],
},
requestNetworkRecords: () => {
return Promise.resolve(networkRecords);
},
traces: {
[Audit.DEFAULT_PASS]: {traceEvents: []},
},
requestFirstInteractive: () => Promise.resolve({
timeInMs: firstInteractiveValue,
requestConsistentlyInteractive: () => Promise.resolve({
timing: ttiValue,
}),
};
}
@ -29,65 +31,41 @@ function generateArtifacts(firstInteractiveValue, networkRecords = []) {
/* eslint-env mocha */
describe('PWA: load-fast-enough-for-pwa audit', () => {
it('returns boolean based on TTI value', () => {
return FastPWAAudit.audit(generateArtifacts(5000)).then(result => {
assert.equal(result.rawValue, true, 'fixture trace is not passing audit');
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
return FastPWAAudit.audit(generateArtifacts(5000), {settings}).then(result => {
assert.equal(result.score, true, 'fixture trace is not passing audit');
assert.equal(result.rawValue, 5000);
});
});
it('fails a bad TTI value', () => {
return FastPWAAudit.audit(generateArtifacts(15000)).then(result => {
assert.equal(result.rawValue, false, 'not failing a long TTI value');
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
return FastPWAAudit.audit(generateArtifacts(15000), {settings}).then(result => {
assert.equal(result.score, false, 'not failing a long TTI value');
assert.equal(result.rawValue, 15000);
assert.ok(result.debugString);
});
});
it('warns on a good TTI value with no throttling', () => {
// latencies are very short
const mockNetworkRecords = [
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, finished: true, _url: 'https://google.com/'},
{_timing: {sendEnd: 0, receiveHeadersEnd: 75}, finished: true, _url: 'https://google.com/a'},
{ },
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, finished: true, _url: 'https://google.com/b'},
];
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
assert.equal(result.rawValue, true);
assert.ok(result.debugString.includes('network request latencies'));
assert.ok(result.details, 'contains details when latencies were not realistic');
});
it('respects the observed result when throttling is preset', async () => {
const artifacts = Object.assign({
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
}, Runner.instantiateComputedArtifacts());
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
const result = await FastPWAAudit.audit(artifacts, {settings});
assert.equal(Math.round(result.rawValue), 1582);
});
it('ignores resources coming from cache', () => {
const mockNetworkRecords = [
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, _fromDiskCache: true},
];
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
assert.equal(result.rawValue, true);
assert.strictEqual(result.debugString, undefined);
});
});
it('overrides with simulated result when throttling is modified', async () => {
const artifacts = Object.assign({
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
}, Runner.instantiateComputedArtifacts());
it('passes a good TTI value and WITH throttling', () => {
// latencies are very long
const urlA = 'https://google.com';
const urlB = 'https://example.com';
const urlC = 'https://example-c.com';
const mockNetworkRecords = [
{_timing: {sendEnd: 0, receiveHeadersEnd: 250}, finished: true, _url: urlA, _startTime: 0},
{_timing: {sendEnd: 0, receiveHeadersEnd: 250}, finished: true, _url: urlB},
// ignored for not having timing
{ },
// ignored for not being the first of the origin
{_timing: {sendEnd: 0, receiveHeadersEnd: 100}, finished: true, _url: urlA, _startTime: 100},
// ignored for being redirected internally
{_timing: {sendEnd: 0, receiveHeadersEnd: 100}, finished: true, _url: urlC, _startTime: 0,
statusCode: 307},
// ignored for not finishing
{_timing: {sendEnd: 0, receiveHeadersEnd: -1}, finished: false},
];
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
assert.equal(result.rawValue, true);
assert.strictEqual(result.debugString, undefined);
assert.ok(!result.details, 'does not contain details when latencies are realistic');
});
const settings = {throttlingMethod: 'provided', throttling: {rttMs: 40, throughput: 100000}};
const result = await FastPWAAudit.audit(artifacts, {settings});
assert.equal(Math.round(result.rawValue), 5308);
});
});

Просмотреть файл

@ -105,24 +105,7 @@
"load-fast-enough-for-pwa": {
"score": 1,
"displayValue": "",
"rawValue": true,
"extendedInfo": {
"value": {
"areLatenciesAll3G": true,
"firstRequestLatencies": [
{
"url": "http://localhost:10200/dobetterweb/dbw_tester.html",
"latency": "570.56"
},
{
"url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js",
"latency": "564.12"
}
],
"isFast": true,
"timeToFirstInteractive": 4927.278
}
},
"rawValue": 4927.278,
"scoreDisplayMode": "binary",
"name": "load-fast-enough-for-pwa",
"description": "Page load is fast enough on 3G",
@ -5403,6 +5386,6 @@
}
},
"timing": {
"total": 877
"total": 788
}
}