Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion lighthouse-cli/test/smokehouse/perf/expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = [
'first-meaningful-paint': {
score: '>=0.90',
},
'first-interactive': {
'first-cpu-idle': {
score: '>=0.90',
},
'consistently-interactive': {
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-cli/test/smokehouse/tricky-ttci/expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = [
initialUrl: 'http://localhost:10200/tricky-ttci.html',
url: 'http://localhost:10200/tricky-ttci.html',
audits: {
'first-interactive': {
'first-cpu-idle': {
score: '<75',
rawValue: '>9000',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class UnusedBytes extends Audit {
.requestNetworkRecords(devtoolsLog)
.then(networkRecords =>
Promise.all([
this.audit_(artifacts, networkRecords),
this.audit_(artifacts, networkRecords, context),
artifacts.requestPageDependencyGraph({trace, devtoolsLog}),
artifacts.requestLoadSimulator(simulatorOptions),
])
Expand Down
7 changes: 5 additions & 2 deletions lighthouse-core/audits/byte-efficiency/offscreen-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ class OffscreenImages extends ByteEfficiencyAudit {
* @param {!Artifacts} artifacts
* @return {!Audit.HeadingsResult}
*/
static audit_(artifacts) {
static audit_(artifacts, networkRecords, context) {
const images = artifacts.ImageUsage;
const viewportDimensions = artifacts.ViewportDimensions;
const trace = artifacts.traces[ByteEfficiencyAudit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS];

let debugString;
const resultsMap = images.reduce((results, image) => {
Expand All @@ -112,7 +113,9 @@ class OffscreenImages extends ByteEfficiencyAudit {
return results;
}, new Map());

return artifacts.requestFirstInteractive(trace).then(firstInteractive => {
// TODO(phulce): move this to always use lantern
const settings = context.settings;
return artifacts.requestFirstCPUIdle({trace, devtoolsLog, settings}).then(firstInteractive => {
const ttiTimestamp = firstInteractive.timestamp / 1000000;
const results = Array.from(resultsMap.values()).filter(item => {
const isWasteful =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
const Audit = require('./audit');
const Util = require('../report/v2/renderer/util.js');

class FirstInteractiveMetric extends Audit {
class FirstCPUIdle extends Audit {
/**
* @return {!AuditMeta}
*/
static get meta() {
return {
name: 'first-interactive',
description: 'First Interactive (beta)',
helpText: 'First Interactive marks the time at which the page is ' +
'minimally interactive. ' +
name: 'first-cpu-idle',
description: 'First CPU Idle',
helpText: 'First CPU Idle marks the first time at which the page\'s main thread is ' +
'quiet enough to handle input. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-interactive).',
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
requiredArtifacts: ['traces'],
Expand All @@ -39,28 +39,26 @@ class FirstInteractiveMetric extends Audit {
* Identify the time the page is "first interactive"
* @see https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit#
*
* @param {!Artifacts} artifacts
* @param {Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {!Promise<!AuditResult>}
* @return {Promise<AuditResult>}
*/
static audit(artifacts, context) {
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
return artifacts.requestFirstInteractive(trace)
.then(firstInteractive => {
return {
score: Audit.computeLogNormalScore(
firstInteractive.timeInMs,
context.options.scorePODR,
context.options.scoreMedian
),
rawValue: firstInteractive.timeInMs,
displayValue: Util.formatMilliseconds(firstInteractive.timeInMs),
extendedInfo: {
value: firstInteractive,
},
};
});
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const metricComputationData = {trace, devtoolsLog, settings: context.settings};
const metricResult = await artifacts.requestFirstCPUIdle(metricComputationData);

return {
score: Audit.computeLogNormalScore(
metricResult.timing,
context.options.scorePODR,
context.options.scoreMedian
),
rawValue: metricResult.timing,
displayValue: Util.formatMilliseconds(metricResult.timing),
};
}
}

module.exports = FirstInteractiveMetric;
module.exports = FirstCPUIdle;
137 changes: 27 additions & 110 deletions lighthouse-core/audits/load-fast-enough-for-pwa.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -32,116 +29,36 @@ 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;
}

// 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;
}

// 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,
};

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

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),
}));

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},
};
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const settingOverrides = {throttlingMethod: 'simulate', throttling: mobile3GThrottling};
const settings =
context.settings.throttlingMethod !== 'provided' &&
isDeepEqual(context.settings.throttling, mobile3GThrottling)
? context.settings
: Object.assign({}, context.settings, settingOverrides);
const metricComputationData = {trace, devtoolsLog, settings};
const tti = await artifacts.requestConsistentlyInteractive(metricComputationData);

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: Number(tti.timing <= MAXIMUM_TTI),
rawValue: tti.timing,
// eslint-disable-next-line max-len
debugString: `First Interactive was ${Util.formatMilliseconds(tti.timing)}. More details in the "Performance" section.`,
};
}
}

Expand Down
8 changes: 7 additions & 1 deletion lighthouse-core/audits/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ class Metrics extends Audit {
const traceOfTab = await artifacts.requestTraceOfTab(trace);
const firstContentfulPaint = await artifacts.requestFirstContentfulPaint(metricComputationData);
const firstMeaningfulPaint = await artifacts.requestFirstMeaningfulPaint(metricComputationData);
const firstCPUIdle = await artifacts.requestFirstCPUIdle(metricComputationData);
const timeToInteractive = await artifacts.requestConsistentlyInteractive(metricComputationData);
const metrics = [];

// Include the simulated/observed performance metrics
const metricsMap = {firstContentfulPaint, firstMeaningfulPaint, timeToInteractive};
const metricsMap = {
firstContentfulPaint,
firstMeaningfulPaint,
firstCPUIdle,
timeToInteractive,
};
for (const [metricName, values] of Object.entries(metricsMap)) {
metrics.push(Object.assign({metricName}, values));
}
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/closure/typedefs/ComputedArtifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ ComputedArtifacts.prototype.requestSpeedline;
ComputedArtifacts.prototype.requestTraceOfTab;

/** @type {function(!Trace): !Promise<{timeInMs: number, timestamp: number}>} */
ComputedArtifacts.prototype.requestFirstInteractive;
ComputedArtifacts.prototype.requestFirstCPUIdle;

/** @type {function(!DevtoolsLog): !Promise<WebInspector.NetworkRequest>} */
ComputedArtifacts.prototype.requestMainResource;
4 changes: 2 additions & 2 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ module.exports = {
'estimated-input-latency',
'errors-in-console',
'time-to-first-byte',
'first-interactive',
'first-cpu-idle',
'consistently-interactive',
'user-timings',
'critical-request-chains',
Expand Down Expand Up @@ -266,7 +266,7 @@ module.exports = {
audits: [
{id: 'first-contentful-paint', weight: 5, group: 'perf-metric'},
{id: 'first-meaningful-paint', weight: 3, group: 'perf-metric'},
{id: 'first-interactive', weight: 5, group: 'perf-metric'},
{id: 'first-cpu-idle', weight: 5, group: 'perf-metric'},
{id: 'consistently-interactive', weight: 5, group: 'perf-metric'},
{id: 'speed-index-metric', weight: 1, group: 'perf-metric'},
{id: 'estimated-input-latency', weight: 1, group: 'perf-metric'},
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/config/fast-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {

// disabled for now because their results are not meaningful/cannot be computed anymore
'first-meaningful-paint',
'first-interactive',
'first-cpu-idle',
'consistently-interactive',
'estimated-input-latency',
'speed-index-metric',
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/config/plots-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = {
'first-meaningful-paint',
'speed-index-metric',
'estimated-input-latency',
'first-interactive',
'first-cpu-idle',
'consistently-interactive',
],
},
Expand Down
Loading