Skip to content

Commit

Permalink
core: add TBT impact to third party audits (#15385)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine authored Aug 16, 2023
1 parent b2bbea9 commit 586135f
Show file tree
Hide file tree
Showing 11 changed files with 10,085 additions and 310 deletions.
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 * as i18n from '../lib/i18n/i18n.js';
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 @@ class ThirdPartyFacades extends Audit {
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 @@ class ThirdPartyFacades extends Audit {
*/
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 @@ class ThirdPartyFacades extends Audit {
const entitySummary = summaries.byEntity.get(entity);
if (!urls || !entitySummary) continue;

tbtImpact += entitySummary.tbtImpact;

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 @@ class ThirdPartyFacades extends Audit {
return {
score: 1,
notApplicable: true,
metricSavings: {TBT: 0},
};
}

Expand All @@ -216,6 +223,7 @@ class ThirdPartyFacades extends Audit {
itemCount: results.length,
}),
details: Audit.makeTableDetails(headings, results),
metricSavings: {TBT: tbtImpact},
};
}
}
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 {Audit} from './audit.js';
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 @@ const PASS_THRESHOLD_IN_MS = 250;
* @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 @@ class ThirdPartySummary extends Audit {
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 @@ class ThirdPartySummary extends Audit {

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 @@ class ThirdPartySummary extends Audit {
// 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 @@ class ThirdPartySummary extends Audit {
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 @@ class ThirdPartySummary extends Audit {
// 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 @@ class ThirdPartySummary extends Audit {
numSubItems++;
subitemSummary.transferSize += nextSubItem.transferSize;
subitemSummary.blockingTime += nextSubItem.blockingTime;
subitemSummary.tbtImpact += nextSubItem.tbtImpact;
}
if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
// Don't bother breaking down if there are no large resources.
Expand All @@ -179,6 +184,7 @@ class ThirdPartySummary extends Audit {
url: str_(i18n.UIStrings.otherResourcesLabel),
transferSize: stats.transferSize - subitemSummary.transferSize,
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,
};
if (remainder.transferSize > minTransferSize) {
items.push(remainder);
Expand All @@ -193,19 +199,21 @@ class ThirdPartySummary extends Audit {
*/
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 @@ class ThirdPartySummary extends Audit {
.map(([entity, stats]) => {
overallSummary.wastedBytes += stats.transferSize;
overallSummary.wastedMs += stats.blockingTime;
overallSummary.tbtImpact += stats.tbtImpact;

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

Expand All @@ -252,6 +262,9 @@ class ThirdPartySummary extends Audit {
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

0 comments on commit 586135f

Please sign in to comment.