Skip to content

Commit

Permalink
core(metrics): support CLS for all frames (#11713)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine committed Nov 26, 2020
1 parent fc507b6 commit 5c5de46
Show file tree
Hide file tree
Showing 16 changed files with 604 additions and 305 deletions.
2 changes: 2 additions & 0 deletions lighthouse-core/audits/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const ComputedTimingSummary = require('../computed/metrics/timing-summary.js');
/** @type {Set<keyof LH.Artifacts.TimingSummary>} */
const DECIMAL_METRIC_KEYS = new Set([
'cumulativeLayoutShift',
'cumulativeLayoutShiftAllFrames',
'observedCumulativeLayoutShift',
'observedCumulativeLayoutShiftAllFrames',
]);

class Metrics extends Audit {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license Copyright 2020 The Lighthouse Authors. 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 makeComputedArtifact = require('../computed-artifact.js');

class CumulativeLayoutShiftAllFrames {
/**
* @param {LH.Trace} trace
* @return {Promise<{value: number}>}
*/
static async compute_(trace) {
const cumulativeShift = trace.traceEvents
.filter(e =>
e.name === 'LayoutShift' &&
e.args &&
e.args.data &&
e.args.data.score &&
!e.args.data.had_recent_input
)
.map(e => {
// @ts-expect-error Events without score are filtered out.
return /** @type {number} */ (e.args.data.score);
})
.reduce((sum, score) => sum + score, 0);
return {
value: cumulativeShift,
};
}
}

module.exports = makeComputedArtifact(CumulativeLayoutShiftAllFrames);
6 changes: 6 additions & 0 deletions lighthouse-core/computed/metrics/timing-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const LargestContentfulPaintAllFrames = require('./largest-contentful-paint-all-
const FirstCPUIdle = require('./first-cpu-idle.js');
const Interactive = require('./interactive.js');
const CumulativeLayoutShift = require('./cumulative-layout-shift.js');
const CumulativeLayoutShiftAllFrames = require('./cumulative-layout-shift-all-frames.js');
const SpeedIndex = require('./speed-index.js');
const EstimatedInputLatency = require('./estimated-input-latency.js');
const MaxPotentialFID = require('./max-potential-fid.js');
Expand Down Expand Up @@ -49,6 +50,7 @@ class TimingSummary {
const firstCPUIdle = await requestOrUndefined(FirstCPUIdle, metricComputationData);
const interactive = await requestOrUndefined(Interactive, metricComputationData);
const cumulativeLayoutShift = await requestOrUndefined(CumulativeLayoutShift, trace);
const cumulativeLayoutShiftAllFrames = await requestOrUndefined(CumulativeLayoutShiftAllFrames, trace); // eslint-disable-line max-len
const maxPotentialFID = await requestOrUndefined(MaxPotentialFID, metricComputationData);
const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData);
const estimatedInputLatency = await EstimatedInputLatency.request(metricComputationData, context); // eslint-disable-line max-len
Expand All @@ -57,6 +59,8 @@ class TimingSummary {
const cumulativeLayoutShiftValue = cumulativeLayoutShift &&
cumulativeLayoutShift.value !== null ?
cumulativeLayoutShift.value : undefined;
const cumulativeLayoutShiftAllFramesValue = cumulativeLayoutShiftAllFrames ?
cumulativeLayoutShiftAllFrames.value : undefined;

/** @type {LH.Artifacts.TimingSummary} */
const metrics = {
Expand All @@ -80,6 +84,7 @@ class TimingSummary {
totalBlockingTime: totalBlockingTime.timing,
maxPotentialFID: maxPotentialFID && maxPotentialFID.timing,
cumulativeLayoutShift: cumulativeLayoutShiftValue,
cumulativeLayoutShiftAllFrames: cumulativeLayoutShiftAllFramesValue,

// Include all timestamps of interest from trace of tab
observedTimeOrigin: traceOfTab.timings.timeOrigin,
Expand All @@ -105,6 +110,7 @@ class TimingSummary {
observedDomContentLoaded: traceOfTab.timings.domContentLoaded,
observedDomContentLoadedTs: traceOfTab.timestamps.domContentLoaded,
observedCumulativeLayoutShift: cumulativeLayoutShiftValue,
observedCumulativeLayoutShiftAllFrames: cumulativeLayoutShiftAllFramesValue,

// Include some visual metrics from speedline
observedFirstVisualChange: speedline.first,
Expand Down
1 change: 1 addition & 0 deletions lighthouse-core/lib/minify-trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const traceEventsToAlwaysKeep = new Set([
// Needed for CPU profiler task attribution
'Profile',
'ProfileChunk',
'LayoutShift',
]);

const traceEventsToKeepInToplevelTask = new Set([
Expand Down
88 changes: 48 additions & 40 deletions lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,61 @@

exports[`Performance: metrics evaluates valid input (with lcp from all frames) correctly 1`] = `
Object {
"cumulativeLayoutShift": 0,
"cumulativeLayoutShift": 0.0011656245471340055,
"cumulativeLayoutShiftAllFrames": 0.5436596106821069,
"estimatedInputLatency": 16,
"estimatedInputLatencyTs": undefined,
"firstCPUIdle": 668,
"firstCPUIdleTs": 10238937930,
"firstContentfulPaint": 668,
"firstContentfulPaintTs": 10238937930,
"firstMeaningfulPaint": 668,
"firstMeaningfulPaintTs": 10238937930,
"interactive": 668,
"interactiveTs": 10238937930,
"largestContentfulPaint": 668,
"largestContentfulPaintAllFrames": 2258,
"largestContentfulPaintAllFramesTs": 10240528038,
"largestContentfulPaintTs": 10238937930,
"firstCPUIdle": 688,
"firstCPUIdleTs": 46134430620,
"firstContentfulPaint": 688,
"firstContentfulPaintTs": 46134430620,
"firstMeaningfulPaint": 688,
"firstMeaningfulPaintTs": 46134430620,
"interactive": 688,
"interactiveTs": 46134430620,
"largestContentfulPaint": 688,
"largestContentfulPaintAllFrames": 5948,
"largestContentfulPaintAllFramesTs": 46139690898,
"largestContentfulPaintTs": 46134430620,
"maxPotentialFID": 16,
"observedCumulativeLayoutShift": 0,
"observedDomContentLoaded": 605,
"observedDomContentLoadedTs": 10238874657,
"observedFirstContentfulPaint": 668,
"observedFirstContentfulPaintTs": 10238937930,
"observedFirstMeaningfulPaint": 668,
"observedFirstMeaningfulPaintTs": 10238937930,
"observedFirstPaint": 668,
"observedFirstPaintTs": 10238937930,
"observedFirstVisualChange": 671,
"observedFirstVisualChangeTs": 10238940760,
"observedLargestContentfulPaint": 668,
"observedLargestContentfulPaintAllFrames": 2258,
"observedLargestContentfulPaintAllFramesTs": 10240528038,
"observedLargestContentfulPaintTs": 10238937930,
"observedLastVisualChange": 704,
"observedLastVisualChangeTs": 10238973760,
"observedLoad": 684,
"observedLoadTs": 10238953674,
"observedCumulativeLayoutShift": 0.0011656245471340055,
"observedCumulativeLayoutShiftAllFrames": 0.5436596106821069,
"observedDomContentLoaded": 617,
"observedDomContentLoadedTs": 46134359407,
"observedFirstContentfulPaint": 688,
"observedFirstContentfulPaintTs": 46134430620,
"observedFirstMeaningfulPaint": 688,
"observedFirstMeaningfulPaintTs": 46134430620,
"observedFirstPaint": 688,
"observedFirstPaintTs": 46134430620,
"observedFirstVisualChange": 679,
"observedFirstVisualChangeTs": 46134421490,
"observedLargestContentfulPaint": 688,
"observedLargestContentfulPaintAllFrames": 5948,
"observedLargestContentfulPaintAllFramesTs": 46139690898,
"observedLargestContentfulPaintTs": 46134430620,
"observedLastVisualChange": 5967,
"observedLastVisualChangeTs": 46139709490,
"observedLoad": 706,
"observedLoadTs": 46134448526,
"observedNavigationStart": 0,
"observedNavigationStartTs": 10238269760,
"observedSpeedIndex": 688,
"observedSpeedIndexTs": 10238957680,
"observedNavigationStartTs": 46133742490,
"observedSpeedIndex": 1370,
"observedSpeedIndexTs": 46135112850,
"observedTimeOrigin": 0,
"observedTimeOriginTs": 10238269760,
"observedTraceEnd": 5994,
"observedTraceEndTs": 10244264001,
"speedIndex": 688,
"speedIndexTs": 10238957760,
"observedTimeOriginTs": 46133742490,
"observedTraceEnd": 6019,
"observedTraceEndTs": 46139761594,
"speedIndex": 1370,
"speedIndexTs": 46135112490,
"totalBlockingTime": 0,
}
`;

exports[`Performance: metrics evaluates valid input (with lcp) correctly 1`] = `
Object {
"cumulativeLayoutShift": 0,
"cumulativeLayoutShiftAllFrames": 0,
"estimatedInputLatency": 543,
"estimatedInputLatencyTs": undefined,
"firstCPUIdle": 3677,
Expand All @@ -70,6 +73,7 @@ Object {
"largestContentfulPaintTs": undefined,
"maxPotentialFID": 1336,
"observedCumulativeLayoutShift": 0,
"observedCumulativeLayoutShiftAllFrames": 0,
"observedDomContentLoaded": 1513,
"observedDomContentLoadedTs": 713038536140,
"observedFirstContentfulPaint": 1122,
Expand Down Expand Up @@ -105,6 +109,7 @@ Object {
exports[`Performance: metrics evaluates valid input correctly (throttlingMethod=provided) 1`] = `
Object {
"cumulativeLayoutShift": 0,
"cumulativeLayoutShiftAllFrames": 0,
"estimatedInputLatency": 17,
"estimatedInputLatencyTs": undefined,
"firstCPUIdle": 1582,
Expand All @@ -121,6 +126,7 @@ Object {
"largestContentfulPaintTs": undefined,
"maxPotentialFID": 198,
"observedCumulativeLayoutShift": 0,
"observedCumulativeLayoutShiftAllFrames": 0,
"observedDomContentLoaded": 560,
"observedDomContentLoadedTs": 225414732309,
"observedFirstContentfulPaint": 499,
Expand Down Expand Up @@ -156,6 +162,7 @@ Object {
exports[`Performance: metrics evaluates valid input correctly 1`] = `
Object {
"cumulativeLayoutShift": 0,
"cumulativeLayoutShiftAllFrames": 0,
"estimatedInputLatency": 78,
"estimatedInputLatencyTs": undefined,
"firstCPUIdle": 3351,
Expand All @@ -172,6 +179,7 @@ Object {
"largestContentfulPaintTs": undefined,
"maxPotentialFID": 396,
"observedCumulativeLayoutShift": 0,
"observedCumulativeLayoutShiftAllFrames": 0,
"observedDomContentLoaded": 560,
"observedDomContentLoadedTs": 225414732309,
"observedFirstContentfulPaint": 499,
Expand Down
26 changes: 22 additions & 4 deletions lighthouse-core/test/audits/metrics-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const pwaDevtoolsLog = require('../fixtures/traces/progressive-app-m60.devtools.
const lcpTrace = require('../fixtures/traces/lcp-m78.json');
const lcpDevtoolsLog = require('../fixtures/traces/lcp-m78.devtools.log.json');

const lcpAllFramesTrace = require('../fixtures/traces/lcp-all-frames-m89.json');
const lcpAllFramesDevtoolsLog = require('../fixtures/traces/lcp-all-frames-m89.devtools.log.json');
const metricsAllFramesTrace = require('../fixtures/traces/frame-metrics-m89.json');
const metricsAllFramesDevtoolsLog = require('../fixtures/traces/frame-metrics-m89.devtools.log.json'); // eslint-disable-line max-len

const artifactsTrace = require('../results/artifacts/defaultPass.trace.json');
const artifactsDevtoolsLog = require('../results/artifacts/defaultPass.devtoolslog.json');
Expand Down Expand Up @@ -71,10 +71,10 @@ describe('Performance: metrics', () => {
it('evaluates valid input (with lcp from all frames) correctly', async () => {
const artifacts = {
traces: {
[MetricsAudit.DEFAULT_PASS]: lcpAllFramesTrace,
[MetricsAudit.DEFAULT_PASS]: metricsAllFramesTrace,
},
devtoolsLogs: {
[MetricsAudit.DEFAULT_PASS]: lcpAllFramesDevtoolsLog,
[MetricsAudit.DEFAULT_PASS]: metricsAllFramesDevtoolsLog,
},
};

Expand All @@ -99,6 +99,24 @@ describe('Performance: metrics', () => {
expect(details.items[0].observedCumulativeLayoutShift).toMatchInlineSnapshot(`0.42`);
});

it('evaluates valid input (with CLS from all frames) correctly', async () => {
const artifacts = {
traces: {
[MetricsAudit.DEFAULT_PASS]: metricsAllFramesTrace,
},
devtoolsLogs: {
[MetricsAudit.DEFAULT_PASS]: metricsAllFramesDevtoolsLog,
},
};

const context = {settings: {throttlingMethod: 'simulate'}, computedCache: new Map()};
const {details} = await MetricsAudit.audit(artifacts, context);
expect(details.items[0].cumulativeLayoutShift).toBeCloseTo(0.0011);
expect(details.items[0].observedCumulativeLayoutShift).toBeCloseTo(0.0011);
expect(details.items[0].cumulativeLayoutShiftAllFrames).toBeCloseTo(0.54);
expect(details.items[0].observedCumulativeLayoutShiftAllFrames).toBeCloseTo(0.54);
});

it('does not fail the entire audit when TTI errors', async () => {
const artifacts = {
traces: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @license Copyright 2020 The Lighthouse Authors. 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 CumulativeLayoutShiftAllFrames = require('../../../computed/metrics/cumulative-layout-shift-all-frames.js'); // eslint-disable-line max-len
const trace = require('../../fixtures/traces/frame-metrics-m89.json');
const invalidTrace = require('../../fixtures/traces/progressive-app-m60.json');
const createTestTrace = require('../../create-test-trace.js');

/* eslint-env jest */

describe('Metrics: CLS All Frames', () => {
it('should compute value', async () => {
const context = {computedCache: new Map()};
const result = await CumulativeLayoutShiftAllFrames.request(trace, context);
expect(result.value).toBeCloseTo(0.54);
});

it('should fail to compute a value for old trace', async () => {
const context = {computedCache: new Map()};
const result = await CumulativeLayoutShiftAllFrames.request(invalidTrace, context);
expect(result.value).toBe(0);
});

function makeTrace(shiftEventsData) {
const cumulativeScores = new Map();
const children = shiftEventsData.map(data => {
let cumulativeScore = cumulativeScores.get(data.pid) || 0;
if (!data.had_recent_input) cumulativeScore += data.score;
cumulativeScores.set(data.pid, cumulativeScore);
return {
name: 'LayoutShift',
cat: 'loading',
ph: 'I',
pid: data.pid,
tid: data.tid,
ts: 308559814315,
args: {
data: {
is_main_frame: true,
had_recent_input: data.had_recent_input,
score: data.score,
cumulative_score: cumulativeScore,
},
},
};
});

const trace = createTestTrace({});
trace.traceEvents.push(...children);
return trace;
}

it('collects layout shift data from all processes', async () => {
const context = {computedCache: new Map()};
const trace = makeTrace([
{pid: 1111, tid: 222, score: 1, had_recent_input: false},
{pid: 3333, tid: 444, score: 1, had_recent_input: false},
{pid: 3333, tid: 444, score: 1, had_recent_input: false},
{pid: 1111, tid: 222, score: 1, had_recent_input: false},
]);
const result = await CumulativeLayoutShiftAllFrames.request(trace, context);
expect(result.value).toBe(4);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
const assert = require('assert').strict;

const LargestContentfulPaintAllFrames = require('../../../computed/metrics/largest-contentful-paint-all-frames.js'); // eslint-disable-line max-len
const traceAllFrames = require('../../fixtures/traces/lcp-all-frames-m89.json');
const devtoolsLogAllFrames = require('../../fixtures/traces/lcp-all-frames-m89.devtools.log.json');
const traceAllFrames = require('../../fixtures/traces/frame-metrics-m89.json');
const devtoolsLogAllFrames = require('../../fixtures/traces/frame-metrics-m89.devtools.log.json');
const traceMainFrame = require('../../fixtures/traces/lcp-m78.json');
const devtoolsLogMainFrame = require('../../fixtures/traces/lcp-m78.devtools.log.json');
const invalidTrace = require('../../fixtures/traces/progressive-app-m60.json');
Expand All @@ -32,8 +32,8 @@ describe('Metrics: LCP from all frames', () => {
const context = {settings, computedCache: new Map()};
const result = await LargestContentfulPaintAllFrames.request({trace: traceAllFrames, devtoolsLog: devtoolsLogAllFrames, settings}, context); // eslint-disable-line max-len

assert.equal(Math.round(result.timing), 2258);
assert.equal(result.timestamp, 10240528038);
assert.equal(Math.round(result.timing), 5948);
assert.equal(result.timestamp, 46139690898);
});

it('should fail to compute an observed value for old trace', async () => {
Expand Down
Loading

0 comments on commit 5c5de46

Please sign in to comment.