Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: add TBT impact to third party audits #15385

Merged
merged 9 commits into from
Aug 16, 2023
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
18 changes: 13 additions & 5 deletions core/audits/third-party-facades.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import {EntityClassification} from '../computed/entity-classification.js';
import thirdPartyWeb from '../lib/third-party-web.js';
import {NetworkRecords} from '../computed/network-records.js';
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
import ThirdPartySummary from './third-party-summary.js';
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';

const UIStrings = {
/** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when no resources have facade alternatives available. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
Expand Down Expand Up @@ -86,7 +86,7 @@
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
supportedModes: ['navigation'],
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
};
}

Expand Down Expand Up @@ -148,19 +148,23 @@
*/
static async audit(artifacts, context) {
const settings = context.settings;
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
const classifiedEntities = await EntityClassification.request(
{URL: artifacts.URL, devtoolsLog}, context);
const tasks = await MainThreadTasks.request(trace, context);

const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);

const multiplier = settings.throttlingMethod === 'simulate' ?
settings.throttling.cpuSlowdownMultiplier : 1;
const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier,
const summaries = ThirdPartySummary.getSummaries(networkRecords, tbtImpactTasks, multiplier,
classifiedEntities);
const facadableProducts =
ThirdPartyFacades.getProductsWithFacade(summaries.byURL, classifiedEntities);

let tbtImpact = 0;

/** @type {LH.Audit.Details.TableItem[]} */
const results = [];
for (const {product, entity} of facadableProducts) {
Expand All @@ -179,6 +183,8 @@
const entitySummary = summaries.byEntity.get(entity);
if (!urls || !entitySummary) continue;

tbtImpact += entitySummary.tbtImpact;

Check warning on line 187 in core/audits/third-party-facades.js

View check run for this annotation

Codecov / codecov/patch

core/audits/third-party-facades.js#L186-L187

Added lines #L186 - L187 were not covered by tests
const items = Array.from(urls).map((url) => {
const urlStats = summaries.byURL.get(url);
return /** @type {import('./third-party-summary.js').URLSummary} */ ({url, ...urlStats});
Expand All @@ -198,6 +204,7 @@
return {
score: 1,
notApplicable: true,
metricSavings: {TBT: 0},
};
}

Expand All @@ -216,6 +223,7 @@
itemCount: results.length,
}),
details: Audit.makeTableDetails(headings, results),
metricSavings: {TBT: tbtImpact},

Check warning on line 226 in core/audits/third-party-facades.js

View check run for this annotation

Codecov / codecov/patch

core/audits/third-party-facades.js#L226

Added line #L226 was not covered by tests
};
}
}
Expand Down
35 changes: 24 additions & 11 deletions core/audits/third-party-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import {EntityClassification} from '../computed/entity-classification.js';
import * as i18n from '../lib/i18n/i18n.js';
import {NetworkRecords} from '../computed/network-records.js';
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';

const UIStrings = {
/** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */
Expand Down Expand Up @@ -38,12 +38,14 @@
* @property {number} mainThreadTime
* @property {number} transferSize
* @property {number} blockingTime
* @property {number} tbtImpact
*/

/**
* @typedef URLSummary
* @property {number} transferSize
* @property {number} blockingTime
* @property {number} tbtImpact
* @property {string | LH.IcuMessage} url
*/

Expand Down Expand Up @@ -72,24 +74,24 @@
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
};
}

/**
*
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
* @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
* @param {number} cpuMultiplier
* @param {LH.Artifacts.EntityClassification} entityClassification
* @return {SummaryMaps}
*/
static getSummaries(networkRecords, mainThreadTasks, cpuMultiplier, entityClassification) {
static getSummaries(networkRecords, tbtImpactTasks, cpuMultiplier, entityClassification) {
/** @type {Map<string, Summary>} */
const byURL = new Map();
/** @type {Map<LH.Artifacts.Entity, Summary>} */
const byEntity = new Map();
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0, tbtImpact: 0};

for (const request of networkRecords) {
const urlSummary = byURL.get(request.url) || {...defaultSummary};
Expand All @@ -99,7 +101,7 @@

const jsURLs = getJavaScriptURLs(networkRecords);

for (const task of mainThreadTasks) {
for (const task of tbtImpactTasks) {
const attributableURL = getAttributableURLForTask(task, jsURLs);

const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
Expand All @@ -110,6 +112,7 @@
// Note that this is not totally equivalent to the TBT definition since it fails to account for FCP,
// but a majority of third-party work occurs after FCP and should yield largely similar numbers.
urlSummary.blockingTime += Math.max(taskDuration - 50, 0);
urlSummary.tbtImpact += task.selfTbtImpact;
byURL.set(attributableURL, urlSummary);
}

Expand All @@ -127,6 +130,7 @@
entitySummary.transferSize += urlSummary.transferSize;
entitySummary.mainThreadTime += urlSummary.mainThreadTime;
entitySummary.blockingTime += urlSummary.blockingTime;
entitySummary.tbtImpact += urlSummary.tbtImpact;
byEntity.set(entity, entitySummary);

const entityURLs = urls.get(entity) || [];
Expand All @@ -152,7 +156,7 @@
// Sort by blocking time first, then transfer size to break ties.
.sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));

const subitemSummary = {transferSize: 0, blockingTime: 0};
const subitemSummary = {transferSize: 0, blockingTime: 0, tbtImpact: 0};
const minTransferSize = Math.max(MIN_TRANSFER_SIZE_FOR_SUBITEMS, stats.transferSize / 20);
const maxSubItems = Math.min(MAX_SUBITEMS, items.length);
let numSubItems = 0;
Expand All @@ -167,6 +171,7 @@
numSubItems++;
subitemSummary.transferSize += nextSubItem.transferSize;
subitemSummary.blockingTime += nextSubItem.blockingTime;
subitemSummary.tbtImpact += nextSubItem.tbtImpact;

Check warning on line 174 in core/audits/third-party-summary.js

View check run for this annotation

Codecov / codecov/patch

core/audits/third-party-summary.js#L174

Added line #L174 was not covered by tests
}
if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
// Don't bother breaking down if there are no large resources.
Expand All @@ -179,6 +184,7 @@
url: str_(i18n.UIStrings.otherResourcesLabel),
transferSize: stats.transferSize - subitemSummary.transferSize,
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,

Check warning on line 187 in core/audits/third-party-summary.js

View check run for this annotation

Codecov / codecov/patch

core/audits/third-party-summary.js#L187

Added line #L187 was not covered by tests
};
if (remainder.transferSize > minTransferSize) {
items.push(remainder);
Expand All @@ -193,19 +199,21 @@
*/
static async audit(artifacts, context) {
const settings = context.settings || {};
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
const classifiedEntities = await EntityClassification.request(
{URL: artifacts.URL, devtoolsLog}, context);
const firstPartyEntity = classifiedEntities.firstParty;
const tasks = await MainThreadTasks.request(trace, context);

const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);

const multiplier = settings.throttlingMethod === 'simulate' ?
settings.throttling.cpuSlowdownMultiplier : 1;

const summaries = ThirdPartySummary.getSummaries(
networkRecords, tasks, multiplier, classifiedEntities);
const overallSummary = {wastedBytes: 0, wastedMs: 0};
networkRecords, tbtImpactTasks, multiplier, classifiedEntities);
const overallSummary = {wastedBytes: 0, wastedMs: 0, tbtImpact: 0};

const results = Array.from(summaries.byEntity.entries())
// Don't consider the page we're on to be third-party.
Expand All @@ -214,6 +222,7 @@
.map(([entity, stats]) => {
overallSummary.wastedBytes += stats.transferSize;
overallSummary.wastedMs += stats.blockingTime;
overallSummary.tbtImpact += stats.tbtImpact;

return {
...stats,
Expand All @@ -240,6 +249,7 @@
return {
score: 1,
notApplicable: true,
metricSavings: {TBT: 0},
};
}

Expand All @@ -252,6 +262,9 @@
timeInMs: overallSummary.wastedMs,
}),
details,
metricSavings: {
TBT: overallSummary.tbtImpact,
},
};
}
}
Expand Down
10 changes: 4 additions & 6 deletions core/computed/tbt-impact-tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import {TotalBlockingTime} from './metrics/total-blocking-time.js';
import {ProcessedTrace} from './processed-trace.js';
import {calculateTbtImpactForEvent} from './metrics/tbt-utils.js';

/** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */

class TBTImpactTasks {
/**
* @param {LH.Artifacts.TaskNode} task
Expand Down Expand Up @@ -66,7 +64,7 @@ class TBTImpactTasks {
* @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
*/
static createImpactTasks(tasks, taskToImpact) {
/** @type {TBTImpactTask[]} */
/** @type {LH.Artifacts.TBTImpactTask[]} */
const tbtImpactTasks = [];

for (const task of tasks) {
Expand All @@ -92,7 +90,7 @@ class TBTImpactTasks {
* @param {LH.Artifacts.TaskNode[]} tasks
* @param {number} startTimeMs
* @param {number} endTimeMs
* @return {TBTImpactTask[]}
* @return {LH.Artifacts.TBTImpactTask[]}
*/
static computeImpactsFromObservedTasks(tasks, startTimeMs, endTimeMs) {
/** @type {Map<LH.Artifacts.TaskNode, number>} */
Expand Down Expand Up @@ -125,7 +123,7 @@ class TBTImpactTasks {
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
* @param {number} startTimeMs
* @param {number} endTimeMs
* @return {TBTImpactTask[]}
* @return {LH.Artifacts.TBTImpactTask[]}
*/
static computeImpactsFromLantern(tasks, tbtNodeTimings, startTimeMs, endTimeMs) {
/** @type {Map<LH.Artifacts.TaskNode, number>} */
Expand Down Expand Up @@ -193,7 +191,7 @@ class TBTImpactTasks {
/**
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
* @param {LH.Artifacts.ComputedContext} context
* @return {Promise<TBTImpactTask[]>}
* @return {Promise<LH.Artifacts.TBTImpactTask[]>}
*/
static async compute_(metricComputationData, context) {
const tbtResult = await TotalBlockingTime.request(metricComputationData, context);
Expand Down
Loading
Loading