Skip to content
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
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;
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* 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 TracingProcessor = require('../../lib/traces/tracing-processor');
const LHError = require('../../lib/errors');
const MetricArtifact = require('./metric');
const TracingProcessor = require('../../../lib/traces/tracing-processor');
const LHError = require('../../../lib/errors');

const LONG_TASK_THRESHOLD = 50;

Expand All @@ -23,15 +23,15 @@ const EXPONENTIATION_COEFFICIENT = -Math.log(3 - 1) / 15;
* @fileoverview This artifact identifies the time the page is "first interactive" as defined below
* @see https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit#
*
* First Interactive marks the first moment when a website is minimally interactive:
* First CPU Idle marks the first moment when a website is minimally interactive:
* > Enough (but maybe not all) UI components shown on the screen are interactive
* DISCLAIMER: This is assumed by virtue of the fact that the CPU is idle; actual event
* listeners are not examined. Server-side rendering and extreme network latency can trick this
* definition.
* > The page responds to user input in a reasonable time on average, but it’s ok if this
* response is not always immediate.
*
* First Interactive is defined as the first period after FMP of N-seconds that has no bad task
* First CPU Idle is defined as the first period after FMP of N-seconds that has no bad task
* clusters.
*
* > t = time in seconds since FMP
Expand All @@ -45,11 +45,11 @@ const EXPONENTIATION_COEFFICIENT = -Math.log(3 - 1) / 15;
* > Spans more than 250ms from the start of the earliest task in the cluster to the end of the
* latest task in the cluster.
*
* If this timestamp is earlier than DOMContentLoaded, use DOMContentLoaded as firstInteractive.
* If this timestamp is earlier than DOMContentLoaded, use DOMContentLoaded as firstCPUIdle.
*/
class FirstInteractive extends ComputedArtifact {
class FirstCPUIdle extends MetricArtifact {
get name() {
return 'FirstInteractive';
return 'FirstCPUIdle';
}

/**
Expand Down Expand Up @@ -126,7 +126,7 @@ class FirstInteractive extends ComputedArtifact {
static findQuietWindow(FMP, traceEnd, longTasks) {
// If we have an empty window at the very beginning, just return FMP early
if (longTasks.length === 0 ||
longTasks[0].start > FMP + FirstInteractive.getRequiredWindowSizeInMs(0)) {
longTasks[0].start > FMP + FirstCPUIdle.getRequiredWindowSizeInMs(0)) {
return FMP;
}

Expand All @@ -137,11 +137,11 @@ class FirstInteractive extends ComputedArtifact {
/** @param {TaskCluster} cluster */
const isBadCluster = cluster => isTooCloseToFMP(cluster) || isTooLong(cluster);

// FirstInteractive must start at the end of a long task, consider each long task and
// FirstCPUIdle must start at the end of a long task, consider each long task and
// examine the window that follows it.
for (let i = 0; i < longTasks.length; i++) {
const windowStart = longTasks[i].end;
const windowSize = FirstInteractive.getRequiredWindowSizeInMs(windowStart - FMP);
const windowSize = FirstCPUIdle.getRequiredWindowSizeInMs(windowStart - FMP);
const windowEnd = windowStart + windowSize;

// Check that we have a long enough trace
Expand All @@ -155,7 +155,7 @@ class FirstInteractive extends ComputedArtifact {
continue;
}

const taskClusters = FirstInteractive.getTaskClustersInWindow(longTasks, i + 1, windowEnd);
const taskClusters = FirstCPUIdle.getTaskClustersInWindow(longTasks, i + 1, windowEnd);
const hasBadTaskClusters = taskClusters.some(isBadCluster);

if (!hasBadTaskClusters) {
Expand All @@ -167,10 +167,11 @@ class FirstInteractive extends ComputedArtifact {
}

/**
* @param {LH.Artifacts.TraceOfTab} traceOfTab
* @return {{timeInMs: number, timestamp: number}}
* @param {LH.Artifacts.MetricComputationData} data
* @return {Promise<LH.Artifacts.Metric>}
*/
computeWithArtifacts(traceOfTab) {
computeObservedMetric(data) {
const {traceOfTab} = data;
const navStart = traceOfTab.timestamps.navigationStart;
const FMP = traceOfTab.timings.firstMeaningfulPaint;
const DCL = traceOfTab.timings.domContentLoaded;
Expand All @@ -186,23 +187,13 @@ class FirstInteractive extends ComputedArtifact {

const longTasksAfterFMP = TracingProcessor.getMainThreadTopLevelEvents(traceOfTab, FMP)
.filter(evt => evt.duration >= LONG_TASK_THRESHOLD);
const firstInteractive = FirstInteractive.findQuietWindow(FMP, traceEnd, longTasksAfterFMP);
const firstInteractive = FirstCPUIdle.findQuietWindow(FMP, traceEnd, longTasksAfterFMP);

const valueInMs = Math.max(firstInteractive, DCL);
return {
timeInMs: valueInMs,
timestamp: valueInMs * 1000 + navStart,
};
}

/**
* @param {LH.Trace} trace
* @param {LH.Artifacts} artifacts
* @return {Promise<{timeInMs: number, timestamp: number}>}
*/
compute_(trace, artifacts) {
return artifacts.requestTraceOfTab(trace).then(traceOfTab => {
return this.computeWithArtifacts(traceOfTab);
return Promise.resolve({
timing: valueInMs,
timestamp: valueInMs * 1000 + navStart,
});
}
}
Expand All @@ -212,4 +203,4 @@ class FirstInteractive extends ComputedArtifact {
* @typedef {{start: number, end: number, duration: number}} TaskCluster
*/

module.exports = FirstInteractive;
module.exports = FirstCPUIdle;
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ const Node = require('../../../lib/dependency-graph/node');
const CPUNode = require('../../../lib/dependency-graph/cpu-node'); // eslint-disable-line no-unused-vars
const NetworkNode = require('../../../lib/dependency-graph/network-node'); // eslint-disable-line no-unused-vars

const FirstInteractive = require('../first-interactive');
const FirstCPUIdle = require('./first-cpu-idle');
const LanternConsistentlyInteractive = require('./lantern-consistently-interactive');

class FirstCPUIdle extends LanternConsistentlyInteractive {
class LanternFirstCPUIdle extends LanternConsistentlyInteractive {
get name() {
return 'LanternFirstCPUIdle';
}

/**
* @param {LH.Gatherer.Simulation.Result} simulationResult
* @param {LH.Gatherer.Simulation.Result} simulation
* @param {Object} extras
* @return {LH.Gatherer.Simulation.Result}
*/
getEstimateFromSimulation(simulationResult, extras) {
getEstimateFromSimulation(simulation, extras) {
const fmpTimeInMs = extras.optimistic
? extras.fmpResult.optimisticEstimate.timeInMs
: extras.fmpResult.pessimisticEstimate.timeInMs;

return {
timeInMs: FirstCPUIdle.getFirstCPUIdleWindowStart(simulationResult.nodeTiming, fmpTimeInMs),
nodeTiming: simulationResult.nodeTiming,
timeInMs: LanternFirstCPUIdle.getFirstCPUIdleWindowStart(simulation.nodeTiming, fmpTimeInMs),
nodeTiming: simulation.nodeTiming,
};
}

Expand All @@ -48,8 +48,8 @@ class FirstCPUIdle extends LanternConsistentlyInteractive {
longTasks.push({start: timing.startTime, end: timing.endTime});
}

return FirstInteractive.findQuietWindow(fmpTimeInMs, Infinity, longTasks);
return FirstCPUIdle.findQuietWindow(fmpTimeInMs, Infinity, longTasks);
}
}

module.exports = FirstCPUIdle;
module.exports = LanternFirstCPUIdle;
12 changes: 3 additions & 9 deletions lighthouse-core/lib/traces/pwmetrics-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,10 @@ class Metrics {
},
},
{
name: 'First Interactive (vBeta)',
name: 'First CPU Idle',
id: 'ttfi',
getTs: auditResults => {
const ttfiExt = auditResults['first-interactive'].extendedInfo;
return safeGet(ttfiExt, 'value.timestamp');
},
getTiming: auditResults => {
const ttfiExt = auditResults['first-interactive'].extendedInfo;
return safeGet(ttfiExt, 'value.timeInMs');
},
getTs: findValueInMetricsAuditFn('firstCPUIdle', 'timestamp'),
getTiming: findValueInMetricsAuditFn('firstCPUIdle', 'timing'),
},
{
name: 'Time to Consistently Interactive (vBeta)',
Expand Down
Loading