-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new_audit: add responsiveness metric for timespans (#13917)
- Loading branch information
1 parent
1fd9f3e
commit f2db965
Showing
15 changed files
with
953 additions
and
5 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @license Copyright 2022 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 Audit = require('../audit.js'); | ||
const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js'); | ||
const i18n = require('../../lib/i18n/i18n.js'); | ||
|
||
const UIStrings = { | ||
/** Description of the Interaction to Next Paint metric. This description is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */ | ||
description: 'Interaction to Next Paint measures page responsiveness, how long it ' + | ||
'takes the page to visibly respond to user input. [Learn more](https://web.dev/inp/).', | ||
}; | ||
|
||
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); | ||
|
||
/** | ||
* @fileoverview This metric gives a high-percentile measure of responsiveness to input. | ||
*/ | ||
class ExperimentalInteractionToNextPaint extends Audit { | ||
/** | ||
* @return {LH.Audit.Meta} | ||
*/ | ||
static get meta() { | ||
return { | ||
id: 'experimental-interaction-to-next-paint', | ||
title: str_(i18n.UIStrings.interactionToNextPaint), | ||
description: str_(UIStrings.description), | ||
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC, | ||
supportedModes: ['timespan'], | ||
requiredArtifacts: ['traces'], | ||
}; | ||
} | ||
|
||
/** | ||
* @return {LH.Audit.ScoreOptions} | ||
*/ | ||
static get defaultOptions() { | ||
return { | ||
// https://web.dev/inp/ | ||
// This is using the same threshold as field tools since only supported in | ||
// unsimulated user flows for now. | ||
// see https://www.desmos.com/calculator/4xtrhg51th | ||
p10: 200, | ||
median: 500, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {LH.Artifacts} artifacts | ||
* @param {LH.Audit.Context} context | ||
* @return {Promise<LH.Audit.Product>} | ||
*/ | ||
static async audit(artifacts, context) { | ||
const {settings} = context; | ||
// TODO: responsiveness isn't yet supported by lantern. | ||
if (settings.throttlingMethod === 'simulate') { | ||
return {score: null, notApplicable: true}; | ||
} | ||
|
||
const trace = artifacts.traces[Audit.DEFAULT_PASS]; | ||
const metricData = {trace, settings}; | ||
const metricResult = await ComputedResponsivenes.request(metricData, context); | ||
|
||
// TODO: include the no-interaction state in the report instead of using n/a. | ||
if (metricResult === null) { | ||
return {score: null, notApplicable: true}; | ||
} | ||
|
||
return { | ||
score: Audit.computeLogNormalScore({p10: context.options.p10, median: context.options.median}, | ||
metricResult.timing), | ||
numericValue: metricResult.timing, | ||
numericUnit: 'millisecond', | ||
displayValue: str_(i18n.UIStrings.ms, {timeInMs: metricResult.timing}), | ||
}; | ||
} | ||
} | ||
|
||
module.exports = ExperimentalInteractionToNextPaint; | ||
module.exports.UIStrings = UIStrings; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/** | ||
* @license Copyright 2022 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'; | ||
|
||
/** | ||
* @fileoverview Returns a high-percentle (usually 98th) measure of how long it | ||
* takes the page to visibly respond to user input (or null, if there was no | ||
* user input in the provided trace). | ||
*/ | ||
|
||
const makeComputedArtifact = require('../computed-artifact.js'); | ||
const ProcessedTrace = require('../processed-trace.js'); | ||
|
||
class Responsiveness { | ||
/** | ||
* @param {LH.Artifacts.ProcessedTrace} processedTrace | ||
* @return {{timing: number}|null} | ||
*/ | ||
static getHighPercentileResponsiveness(processedTrace) { | ||
const durations = processedTrace.frameTreeEvents | ||
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/responsiveness_metrics.cc;l=146-150;drc=a1a2302f30b0a58f7669a41c80acdf1fa11958dd | ||
.filter(e => e.name === 'Responsiveness.Renderer.UserInteraction') | ||
.map(evt => evt.args.data?.maxDuration) | ||
.filter(/** @return {duration is number} */duration => duration !== undefined) | ||
.sort((a, b) => b - a); | ||
|
||
// If there were no interactions with the page, the metric is N/A. | ||
if (durations.length === 0) { | ||
return null; | ||
} | ||
|
||
// INP is the "nearest-rank"/inverted_cdf 98th percentile, except Chrome only | ||
// keeps the 10 worst events around, so it can never be more than the 10th from | ||
// last array element. To keep things simpler, sort desc and pick from front. | ||
// See https://source.chromium.org/chromium/chromium/src/+/main:components/page_load_metrics/browser/responsiveness_metrics_normalization.cc;l=45-59;drc=cb0f9c8b559d9c7c3cb4ca94fc1118cc015d38ad | ||
const index = Math.min(9, Math.floor(durations.length / 50)); | ||
|
||
return { | ||
timing: durations[index], | ||
}; | ||
} | ||
|
||
/** | ||
* @param {{trace: LH.Trace, settings: Immutable<LH.Config.Settings>}} data | ||
* @param {LH.Artifacts.ComputedContext} context | ||
* @return {Promise<LH.Artifacts.Metric|null>} | ||
*/ | ||
static async compute_(data, context) { | ||
if (data.settings.throttlingMethod === 'simulate') { | ||
throw new Error('Responsiveness currently unsupported by simulated throttling'); | ||
} | ||
|
||
const processedTrace = await ProcessedTrace.request(data.trace, context); | ||
return Responsiveness.getHighPercentileResponsiveness(processedTrace); | ||
} | ||
} | ||
|
||
module.exports = makeComputedArtifact(Responsiveness, [ | ||
'trace', | ||
'settings', | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
lighthouse-core/test/audits/metrics/experimental-interaction-to-next-paint-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* @license Copyright 2022 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 ExperimentalInteractionToNextPaint = | ||
require('../../../audits/metrics/experimental-interaction-to-next-paint.js'); | ||
const interactionTrace = require('../../fixtures/traces/timespan-responsiveness-m103.trace.json'); | ||
const noInteractionTrace = require('../../fixtures/traces/jumpy-cls-m90.json'); | ||
|
||
/* eslint-env jest */ | ||
|
||
describe('Interaction to Next Paint', () => { | ||
function getTestData() { | ||
const artifacts = { | ||
traces: { | ||
[ExperimentalInteractionToNextPaint.DEFAULT_PASS]: interactionTrace, | ||
}, | ||
}; | ||
|
||
const context = { | ||
settings: {throttlingMethod: 'devtools'}, | ||
computedCache: new Map(), | ||
options: ExperimentalInteractionToNextPaint.defaultOptions, | ||
}; | ||
|
||
return {artifacts, context}; | ||
} | ||
|
||
it('evaluates INP correctly', async () => { | ||
const {artifacts, context} = getTestData(); | ||
const result = await ExperimentalInteractionToNextPaint.audit(artifacts, context); | ||
expect(result).toEqual({ | ||
score: 0.63, | ||
numericValue: 392, | ||
numericUnit: 'millisecond', | ||
displayValue: expect.toBeDisplayString('390 ms'), | ||
}); | ||
}); | ||
|
||
it('is not applicable if using simulated throttling', async () => { | ||
const {artifacts, context} = getTestData(); | ||
context.settings.throttlingMethod = 'simulate'; | ||
const result = await ExperimentalInteractionToNextPaint.audit(artifacts, context); | ||
expect(result).toMatchObject({ | ||
score: null, | ||
notApplicable: true, | ||
}); | ||
}); | ||
|
||
it('is not applicable if no interactions occurred in trace', async () => { | ||
const {artifacts, context} = getTestData(); | ||
artifacts.traces[ExperimentalInteractionToNextPaint.DEFAULT_PASS] = noInteractionTrace; | ||
const result = await ExperimentalInteractionToNextPaint.audit(artifacts, context); | ||
expect(result).toMatchObject({ | ||
score: null, | ||
notApplicable: true, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.