Skip to content

Commit

Permalink
core(third-party-summary): add blocking time impact (#9486)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored Aug 16, 2019
1 parent f552253 commit 48be37a
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 52 deletions.
62 changes: 36 additions & 26 deletions lighthouse-core/audits/third-party-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,28 @@ const NetworkRecords = require('../computed/network-records.js');
const MainThreadTasks = require('../computed/main-thread-tasks.js');

const UIStrings = {
/** Title of a Lighthouse audit that identifies the code on the page that the user doesn't control. This is shown in a list of audits that Lighthouse generates. */
title: 'Third-Party Usage',
/** 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. */
title: 'Third-Party usage',
/** 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 imperative title is shown to users when there is a significant amount of page execution time caused by third-party code that should be reduced. */
failureTitle: 'Reduce the impact of third-party code',
/** Description of a Lighthouse audit that identifies the code on the page that the user doesn't control. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
description: 'Third-party code can significantly impact load performance. ' +
'Limit the number of redundant third-party providers and try to load third-party code after ' +
'your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).',
/** Label for a table column that displays the name of a third-party provider that potentially links to their website. */
columnThirdParty: 'Third-Party',
/** Label for a table column that displays how much time each row spent executing on the main thread, entries will be the number of milliseconds spent. */
columnMainThreadTime: 'Main Thread Time',
/** Summary text for the result of a Lighthouse audit that identifies the code on the page that the user doesn't control. This text summarizes the number of distinct entities that were found on the page. */
displayValue: `{itemCount, plural,
=1 {1 Third-Party Found}
other {# Third-Parties Found}
}`,
/** Label for a table column that displays how much time each row spent blocking other work on the main thread, entries will be the number of milliseconds spent. */
columnBlockingTime: 'Main-Thread Blocking Time',
/** Summary text for the result of a Lighthouse audit that identifies the code on a web page that the user doesn't control (referred to as "third-party code"). This text summarizes the number of distinct entities that were found on the page. */
displayValue: 'Third-party code blocked the main thread for ' +
`{timeInMs, number, milliseconds}\xa0ms`,
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

// A page passes when all third-party code blocks for less than 250 ms.
const PASS_THRESHOLD_IN_MS = 250;

/** @typedef {import("third-party-web").IEntity} ThirdPartyEntity */

class ThirdPartySummary extends Audit {
Expand All @@ -43,8 +46,8 @@ class ThirdPartySummary extends Audit {
return {
id: 'third-party-summary',
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
requiredArtifacts: ['traces', 'devtoolsLogs'],
};
}
Expand All @@ -69,17 +72,18 @@ class ThirdPartySummary extends Audit {
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
* @param {number} cpuMultiplier
* @return {Map<ThirdPartyEntity, {mainThreadTime: number, transferSize: number}>}
* @return {Map<ThirdPartyEntity, {mainThreadTime: number, transferSize: number, blockingTime: number}>}
*/
static getSummaryByEntity(networkRecords, mainThreadTasks, cpuMultiplier) {
/** @type {Map<ThirdPartyEntity, {mainThreadTime: number, transferSize: number}>} */
/** @type {Map<ThirdPartyEntity, {mainThreadTime: number, transferSize: number, blockingTime: number}>} */
const entities = new Map();
const defaultEntityStat = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};

for (const request of networkRecords) {
const entity = ThirdPartySummary.getEntitySafe(request.url);
if (!entity) continue;

const entityStats = entities.get(entity) || {mainThreadTime: 0, transferSize: 0};
const entityStats = entities.get(entity) || {...defaultEntityStat};
entityStats.transferSize += request.transferSize;
entities.set(entity, entityStats);
}
Expand All @@ -91,8 +95,14 @@ class ThirdPartySummary extends Audit {
const entity = ThirdPartySummary.getEntitySafe(attributeableURL);
if (!entity) continue;

const entityStats = entities.get(entity) || {mainThreadTime: 0, transferSize: 0};
entityStats.mainThreadTime += task.selfTime * cpuMultiplier;
const entityStats = entities.get(entity) || {...defaultEntityStat};
const taskDuration = task.selfTime * cpuMultiplier;
// The amount of time spent on main thread is the sum of all durations.
entityStats.mainThreadTime += taskDuration;
// The amount of time spent *blocking* on main thread is the sum of all time longer than 50ms.
// 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.
entityStats.blockingTime += Math.max(taskDuration - 50, 0);
entities.set(entity, entityStats);
}

Expand All @@ -117,15 +127,10 @@ class ThirdPartySummary extends Audit {

const summary = {wastedBytes: 0, wastedMs: 0};

// Sort by a combined measure of bytes + main thread time.
// 1KB ~= 1 ms
/** @param {{transferSize: number, mainThreadTime: number}} stats */
const computeSortValue = stats => stats.transferSize / 1024 + stats.mainThreadTime;

const results = Array.from(summaryByEntity.entries())
.map(([entity, stats]) => {
summary.wastedBytes += stats.transferSize;
summary.wastedMs += stats.mainThreadTime;
summary.wastedMs += stats.blockingTime;

return {
entity: /** @type {LH.Audit.Details.LinkValue} */ ({
Expand All @@ -135,17 +140,19 @@ class ThirdPartySummary extends Audit {
}),
transferSize: stats.transferSize,
mainThreadTime: stats.mainThreadTime,
blockingTime: stats.blockingTime,
};
})
.sort((a, b) => computeSortValue(b) - computeSortValue(a));
// Sort by blocking time first, then transfer size to break ties.
.sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'entity', itemType: 'link', text: str_(UIStrings.columnThirdParty)},
{key: 'transferSize', granularity: 1, itemType: 'bytes',
text: str_(i18n.UIStrings.columnSize)},
{key: 'mainThreadTime', granularity: 1, itemType: 'ms',
text: str_(UIStrings.columnMainThreadTime)},
{key: 'blockingTime', granularity: 1, itemType: 'ms',
text: str_(UIStrings.columnBlockingTime)},
];

if (!results.length) {
Expand All @@ -156,8 +163,11 @@ class ThirdPartySummary extends Audit {
}

return {
score: Number(results.length === 0),
displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
score: Number(summary.wastedMs <= PASS_THRESHOLD_IN_MS),
displayValue: str_(UIStrings.displayValue, {
itemCount: results.length,
timeInMs: summary.wastedMs,
}),
details: Audit.makeTableDetails(headings, results, summary),
};
}
Expand Down
11 changes: 7 additions & 4 deletions lighthouse-core/lib/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,8 +1073,8 @@
"lighthouse-core/audits/themed-omnibox.js | title": {
"message": "Sets a theme color for the address bar."
},
"lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": {
"message": "Main Thread Time"
"lighthouse-core/audits/third-party-summary.js | columnBlockingTime": {
"message": "Main-Thread Blocking Time"
},
"lighthouse-core/audits/third-party-summary.js | columnThirdParty": {
"message": "Third-Party"
Expand All @@ -1083,10 +1083,13 @@
"message": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/)."
},
"lighthouse-core/audits/third-party-summary.js | displayValue": {
"message": "{itemCount, plural,\n =1 {1 Third-Party Found}\n other {# Third-Parties Found}\n }"
"message": "Third-party code blocked the main thread for {timeInMs, number, milliseconds} ms"
},
"lighthouse-core/audits/third-party-summary.js | failureTitle": {
"message": "Reduce the impact of third-party code"
},
"lighthouse-core/audits/third-party-summary.js | title": {
"message": "Third-Party Usage"
"message": "Third-Party usage"
},
"lighthouse-core/audits/time-to-first-byte.js | description": {
"message": "Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/ttfb)."
Expand Down
11 changes: 7 additions & 4 deletions lighthouse-core/lib/i18n/locales/en-XL.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,8 +1073,8 @@
"lighthouse-core/audits/themed-omnibox.js | title": {
"message": "Ŝét̂ś â t́ĥém̂é ĉól̂ór̂ f́ôŕ t̂h́ê ád̂d́r̂éŝś b̂ár̂."
},
"lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": {
"message": "M̂áîń T̂h́r̂éâd́ T̂ím̂é"
"lighthouse-core/audits/third-party-summary.js | columnBlockingTime": {
"message": "M̂áîń-T̂h́r̂éâd́ B̂ĺôćk̂ín̂ǵ T̂ím̂é"
},
"lighthouse-core/audits/third-party-summary.js | columnThirdParty": {
"message": "T̂h́îŕd̂-Ṕâŕt̂ý"
Expand All @@ -1083,10 +1083,13 @@
"message": "T̂h́îŕd̂-ṕâŕt̂ý ĉód̂é ĉán̂ śîǵn̂íf̂íĉán̂t́l̂ý îḿp̂áĉt́ l̂óâd́ p̂ér̂f́ôŕm̂án̂ćê. Ĺîḿît́ t̂h́ê ńûḿb̂ér̂ óf̂ ŕêd́ûńd̂án̂t́ t̂h́îŕd̂-ṕâŕt̂ý p̂ŕôv́îd́êŕŝ án̂d́ t̂ŕŷ t́ô ĺôád̂ t́ĥír̂d́-p̂ár̂t́ŷ ćôd́ê áf̂t́êŕ ŷóûŕ p̂áĝé ĥáŝ ṕr̂ím̂ár̂íl̂ý f̂ín̂íŝh́êd́ l̂óâd́îńĝ. [Ĺêár̂ń m̂ór̂é](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/)."
},
"lighthouse-core/audits/third-party-summary.js | displayValue": {
"message": "{itemCount, plural,\n =1 {1 T̂h́îŕd̂-Ṕâŕt̂ý F̂óûńd̂}\n other {# T́ĥír̂d́-P̂ár̂t́îéŝ F́ôún̂d́}\n }"
"message": "T̂h́îŕd̂-ṕâŕt̂ý ĉód̂é b̂ĺôćk̂éd̂ t́ĥé m̂áîń t̂h́r̂éâd́ f̂ór̂ {timeInMs, number, milliseconds} ḿŝ"
},
"lighthouse-core/audits/third-party-summary.js | failureTitle": {
"message": "R̂éd̂úĉé t̂h́ê ím̂ṕâćt̂ óf̂ t́ĥír̂d́-p̂ár̂t́ŷ ćôd́ê"
},
"lighthouse-core/audits/third-party-summary.js | title": {
"message": "T̂h́îŕd̂-Ṕâŕt̂ý Ûśâǵê"
"message": "T̂h́îŕd̂-Ṕâŕt̂ý ûśâǵê"
},
"lighthouse-core/audits/time-to-first-byte.js | description": {
"message": "T̂ím̂é T̂ó F̂ír̂śt̂ B́ŷt́ê íd̂én̂t́îf́îéŝ t́ĥé t̂ím̂é ât́ ŵh́îćĥ ýôúr̂ śêŕv̂ér̂ śêńd̂ś â ŕêśp̂ón̂śê. [Ĺêár̂ń m̂ór̂é](https://developers.google.com/web/tools/lighthouse/audits/ttfb)."
Expand Down
10 changes: 9 additions & 1 deletion lighthouse-core/test/audits/third-party-summary-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ describe('Third party summary', () => {

const results = await ThirdPartySummary.audit(artifacts, {computedCache: new Map()});

expect(results.displayValue).toBeDisplayString('2 Third-Parties Found');
expect(results.score).toBe(1);
expect(results.displayValue).toBeDisplayString(
'Third-party code blocked the main thread for 20 ms'
);
expect(results.details.items).toEqual([
{
entity: {
Expand All @@ -31,6 +34,7 @@ describe('Third party summary', () => {
url: 'https://marketingplatform.google.com/about/tag-manager/',
},
mainThreadTime: 104.70300000000002,
blockingTime: 18.186999999999998,
transferSize: 30827,
},
{
Expand All @@ -40,6 +44,7 @@ describe('Third party summary', () => {
url: 'https://www.google.com/analytics/analytics/',
},
mainThreadTime: 87.576,
blockingTime: 0,
transferSize: 20913,
},
]);
Expand All @@ -54,9 +59,12 @@ describe('Third party summary', () => {
const settings = {throttlingMethod: 'simulate', throttling: {cpuSlowdownMultiplier: 4}};
const results = await ThirdPartySummary.audit(artifacts, {computedCache: new Map(), settings});

expect(results.score).toBe(0);
expect(results.details.items).toHaveLength(2);
expect(Math.round(results.details.items[0].mainThreadTime)).toEqual(419);
expect(Math.round(results.details.items[0].blockingTime)).toEqual(250);
expect(Math.round(results.details.items[1].mainThreadTime)).toEqual(350);
expect(Math.round(results.details.items[1].blockingTime)).toEqual(157);
});

it('be not applicable when no third parties are present', async () => {
Expand Down
22 changes: 12 additions & 10 deletions lighthouse-core/test/results/sample_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -1481,11 +1481,11 @@
},
"third-party-summary": {
"id": "third-party-summary",
"title": "Third-Party Usage",
"title": "Third-Party usage",
"description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).",
"score": null,
"scoreDisplayMode": "informative",
"displayValue": "1 Third-Party Found",
"score": 1,
"scoreDisplayMode": "binary",
"displayValue": "Third-party code blocked the main thread for 20 ms",
"details": {
"type": "table",
"headings": [
Expand All @@ -1501,10 +1501,10 @@
"text": "Size"
},
{
"key": "mainThreadTime",
"key": "blockingTime",
"granularity": 1,
"itemType": "ms",
"text": "Main Thread Time"
"text": "Main-Thread Blocking Time"
}
],
"items": [
Expand All @@ -1515,12 +1515,13 @@
"url": "https://developers.google.com/speed/libraries/"
},
"transferSize": 30174,
"mainThreadTime": 82.74100000000001
"mainThreadTime": 82.74100000000001,
"blockingTime": 22.918000000000006
}
],
"summary": {
"wastedBytes": 30174,
"wastedMs": 82.74100000000001
"wastedMs": 22.918000000000006
}
}
},
Expand Down Expand Up @@ -5603,7 +5604,8 @@
"lighthouse-core/audits/third-party-summary.js | displayValue": [
{
"values": {
"itemCount": 1
"itemCount": 1,
"timeInMs": 22.918000000000006
},
"path": "audits[third-party-summary].displayValue"
}
Expand All @@ -5621,7 +5623,7 @@
"audits[uses-text-compression].details.headings[1].label",
"audits[tap-targets].details.headings[1].text"
],
"lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": [
"lighthouse-core/audits/third-party-summary.js | columnBlockingTime": [
"audits[third-party-summary].details.headings[2].text"
],
"lighthouse-core/audits/manual/pwa-cross-browser.js | title": [
Expand Down
15 changes: 8 additions & 7 deletions proto/sample_v2_round_trip.json
Original file line number Diff line number Diff line change
Expand Up @@ -2578,12 +2578,13 @@
{
"granularity": 1.0,
"itemType": "ms",
"key": "mainThreadTime",
"text": "Main Thread Time"
"key": "blockingTime",
"text": "Main-Thread Blocking Time"
}
],
"items": [
{
"blockingTime": 22.918000000000006,
"entity": {
"text": "Google CDN",
"type": "link",
Expand All @@ -2595,15 +2596,15 @@
],
"summary": {
"wastedBytes": 30174.0,
"wastedMs": 82.74100000000001
"wastedMs": 22.918000000000006
},
"type": "table"
},
"displayValue": "1 Third-Party Found",
"displayValue": "Third-party code blocked the main thread for 20\u00a0ms",
"id": "third-party-summary",
"score": null,
"scoreDisplayMode": "informative",
"title": "Third-Party Usage"
"score": 1.0,
"scoreDisplayMode": "binary",
"title": "Third-Party usage"
},
"time-to-first-byte": {
"description": "Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/ttfb).",
Expand Down

0 comments on commit 48be37a

Please sign in to comment.