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(third-party-summary): add blocking time impact #9486

Merged
merged 9 commits into from
Aug 16, 2019
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
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. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there might have been an issue with describing the "main thread" to the tc... @exterkamp? Or was it for this audit and it's already been clarified :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem was with "thread" being used, in regards to the description in first-interactive

I think this is fine here. Maybe add something about threads. But I think that is unnecessary. This LGTM.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe put some version of the comment in the PR description

It shows up as failing if the impact to TBT is >250ms (which would push you out of a ~90 score range on TBT)

in here to give motivation to the particular number?

Copy link
Collaborator Author

@patrickhulce patrickhulce Aug 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sg

Suggested change
// A page passes when all third-party code blocks for less than 250 ms.
// A page fails this audit if the main-thread impact is >250ms (which would push you out of a ~90 score range on TBT).

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a TODO or worth reexamining at some point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO not a TODO ever worth fixing since it should be rare that third party code actually blocks FCP and if it does I'm fine pointing the finger at it anyway. Our slight decoupling of the audit from TBT in external descriptions also makes this less of an issue.

// 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just interesting to see the impact here drop (104ms -> 18ms)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I'm a bit disappointed to see the very busy third parties disappear, but if it's an incentive to script responsibly than I guess it's still a very positive outcome so I'm :)

My biggest concern is that no one is going to understand what "main-thread blocking time" actually is and they'll assume it's the time spent which would be sad.

FWIW this is also not as drastic in the lantern case, it goes from 419 -> 250. While still a big drop, the number is still noticeable.

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