From 88a00eae88078b9c17e726a13ec654af49056822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:30:23 +0100 Subject: [PATCH 1/6] Remove percentile calculation Median is stable enough, we can stick to it. --- bin/plugin/commands/performance.js | 83 +++++++++++------------------- 1 file changed, 31 insertions(+), 52 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 433f9931334e61..621fb3cbe665c6 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -50,37 +50,35 @@ const config = require( '../config' ); /** * @typedef WPPerformanceResults * - * @property {number=} timeToFirstByteMedian Represents the time since the browser started the request until it received a response (median). - * @property {number=} timeToFirstByteP75 Represents the time since the browser started the request until it received a response (75th percentile). - * @property {number=} largestContentfulPaintMedian Represents the time when the main content of the page has likely loaded (median). - * @property {number=} largestContentfulPaintP75 Represents the time when the main content of the page has likely loaded (75th percentile). - * @property {number=} serverResponse Represents the time the server takes to respond. - * @property {number=} firstPaint Represents the time when the user agent first rendered after navigation. - * @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. - * @property {number=} loaded Represents the time when the load event of the current document is completed. - * @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media. - * @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM. - * @property {number=} type Average type time. - * @property {number=} minType Minimum type time. - * @property {number=} maxType Maximum type time. - * @property {number=} typeContainer Average type time within a container. - * @property {number=} minTypeContainer Minimum type time within a container. - * @property {number=} maxTypeContainer Maximum type time within a container. - * @property {number=} focus Average block selection time. - * @property {number=} minFocus Min block selection time. - * @property {number=} maxFocus Max block selection time. - * @property {number=} inserterOpen Average time to open global inserter. - * @property {number=} minInserterOpen Min time to open global inserter. - * @property {number=} maxInserterOpen Max time to open global inserter. - * @property {number=} inserterSearch Average time to open global inserter. - * @property {number=} minInserterSearch Min time to open global inserter. - * @property {number=} maxInserterSearch Max time to open global inserter. - * @property {number=} inserterHover Average time to move mouse between two block item in the inserter. - * @property {number=} minInserterHover Min time to move mouse between two block item in the inserter. - * @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter. - * @property {number=} listViewOpen Average time to open list view. - * @property {number=} minListViewOpen Min time to open list view. - * @property {number=} maxListViewOpen Max time to open list view. + * @property {number=} timeToFirstByte Represents the time since the browser started the request until it received a response. + * @property {number=} largestContentfulPaint Represents the time when the main content of the page has likely loaded. + * @property {number=} serverResponse Represents the time the server takes to respond. + * @property {number=} firstPaint Represents the time when the user agent first rendered after navigation. + * @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. + * @property {number=} loaded Represents the time when the load event of the current document is completed. + * @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media. + * @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM. + * @property {number=} type Average type time. + * @property {number=} minType Minimum type time. + * @property {number=} maxType Maximum type time. + * @property {number=} typeContainer Average type time within a container. + * @property {number=} minTypeContainer Minimum type time within a container. + * @property {number=} maxTypeContainer Maximum type time within a container. + * @property {number=} focus Average block selection time. + * @property {number=} minFocus Min block selection time. + * @property {number=} maxFocus Max block selection time. + * @property {number=} inserterOpen Average time to open global inserter. + * @property {number=} minInserterOpen Min time to open global inserter. + * @property {number=} maxInserterOpen Max time to open global inserter. + * @property {number=} inserterSearch Average time to open global inserter. + * @property {number=} minInserterSearch Min time to open global inserter. + * @property {number=} maxInserterSearch Max time to open global inserter. + * @property {number=} inserterHover Average time to move mouse between two block item in the inserter. + * @property {number=} minInserterHover Min time to move mouse between two block item in the inserter. + * @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter. + * @property {number=} listViewOpen Average time to open list view. + * @property {number=} minListViewOpen Min time to open list view. + * @property {number=} maxListViewOpen Max time to open list view. */ /** @@ -109,19 +107,6 @@ function median( array ) { : ( numbers[ mid - 1 ] + numbers[ mid ] ) / 2; } -/** - * Computes the 75th percentile from an array of numbers. - * - * @param {number[]} array - * - * @return {number} 75th percentile of the given dataset. - */ -function percentile75( array ) { - const ascending = array.sort( ( a, b ) => a - b ); - const position = Math.floor( ( 75 / 100 ) * array.length ); - return ascending[ position ]; -} - /** * Rounds and format a time passed in milliseconds. * @@ -148,14 +133,8 @@ function curateResults( testSuite, results ) { testSuite === 'front-end-block-theme' ) { return { - timeToFirstByteMedian: median( results.timeToFirstByte ), - timeToFirstByteP75: percentile75( results.timeToFirstByte ), - largestContentfulPaintMedian: median( - results.largestContentfulPaint - ), - largestContentfulPaintP75: percentile75( - results.largestContentfulPaint - ), + timeToFirstByte: median( results.timeToFirstByte ), + largestContentfulPaint: median( results.largestContentfulPaint ), }; } From eb51a4b5efb719d7870f47c557b2ab509eba7910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:35:58 +0100 Subject: [PATCH 2/6] New metric: LCP-TTFB --- bin/plugin/commands/performance.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 621fb3cbe665c6..ec68c2bb22da24 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -52,6 +52,7 @@ const config = require( '../config' ); * * @property {number=} timeToFirstByte Represents the time since the browser started the request until it received a response. * @property {number=} largestContentfulPaint Represents the time when the main content of the page has likely loaded. + * @property {number=} lcpMinusTtfb Represents the difference between LCP and TTFB. * @property {number=} serverResponse Represents the time the server takes to respond. * @property {number=} firstPaint Represents the time when the user agent first rendered after navigation. * @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. @@ -132,9 +133,12 @@ function curateResults( testSuite, results ) { testSuite === 'front-end-classic-theme' || testSuite === 'front-end-block-theme' ) { + const timeToFirstByte = median( results.timeToFirstByte ); + const largestContentfulPaint = median( results.largestContentfulPaint ); return { - timeToFirstByte: median( results.timeToFirstByte ), - largestContentfulPaint: median( results.largestContentfulPaint ), + timeToFirstByte, + largestContentfulPaint, + lcpMinusTtfb: largestContentfulPaint - timeToFirstByte, }; } From 16a4a648dd130ba1bc29492ab7632cdaa704fdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 22 Feb 2023 09:45:57 +0100 Subject: [PATCH 3/6] Front end block theme: simplify tests and calculate LCP-TTFB --- .../performance/front-end-block-theme.test.js | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js index 89879e29605362..308b24e3c60f62 100644 --- a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], + lcpMinusTTFB: [], }; beforeAll( async () => { @@ -29,24 +30,7 @@ describe( 'Front End Performance', () => { ); } ); - it( 'Time To First Byte (TTFB)', async () => { - // We derive the 75th percentile of the TTFB based on these results. - // By running it 16 times, the percentile value would be (75/100)*16=12, - // meaning that we discard the worst 4 values. - let i = 16; - while ( i-- ) { - await page.goto( createURL( '/' ) ); - const navigationTimingJson = await page.evaluate( () => - JSON.stringify( performance.getEntriesByType( 'navigation' ) ) - ); - const [ navigationTiming ] = JSON.parse( navigationTimingJson ); - results.timeToFirstByte.push( - navigationTiming.responseStart - navigationTiming.startTime - ); - } - } ); - - it( 'Largest Contentful Paint (LCP)', async () => { + it( 'Report TTFB, LCP, and LCP-TTFB', async () => { // Based on https://addyosmani.com/blog/puppeteer-recipes/#performance-observer-lcp function calcLCP() { // By using -1 we know when it didn't record any event. @@ -74,9 +58,6 @@ describe( 'Front End Performance', () => { } ); } - // We derive the 75th percentile of the TTFB based on these results. - // By running it 16 times, the percentile value would be (75/100)*16=12, - // meaning that we discard the worst 4 values. let i = 16; while ( i-- ) { await page.evaluateOnNewDocument( calcLCP ); @@ -85,10 +66,18 @@ describe( 'Front End Performance', () => { // https://pptr.dev/api/puppeteer.page.goto#remarks await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } ); - const lcp = await page.evaluate( - () => window.largestContentfulPaint - ); + const { lcp, ttfb } = await page.evaluate( () => { + const [ { responseStart, startTime } ] = + performance.getEntriesByType( 'navigation' ); + return { + lcp: window.largestContentfulPaint, + ttfb: responseStart - startTime, + }; + } ); + results.largestContentfulPaint.push( lcp ); + results.timeToFirstByte.push( ttfb ); + results.lcpMinusTTFB.push( lcp - ttfb ); } } ); } ); From f4fc4c5e94e9ac9871a29c8d38a596a77f3d90a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 22 Feb 2023 09:48:06 +0100 Subject: [PATCH 4/6] Front end classic theme: simplify tests and calculate LCP-TTFB --- .../front-end-classic-theme.test.js | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js index f5ab7078ba5a7b..7c183401701302 100644 --- a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], + lcpMinusTTFB: [], }; beforeAll( async () => { @@ -28,24 +29,7 @@ describe( 'Front End Performance', () => { ); } ); - it( 'Time To First Byte (TTFB)', async () => { - // We derive the 75th percentile of the TTFB based on these results. - // By running it 16 times, the percentile value would be (75/100)*16=12, - // meaning that we discard the worst 4 values. - let i = 16; - while ( i-- ) { - await page.goto( createURL( '/' ) ); - const navigationTimingJson = await page.evaluate( () => - JSON.stringify( performance.getEntriesByType( 'navigation' ) ) - ); - const [ navigationTiming ] = JSON.parse( navigationTimingJson ); - results.timeToFirstByte.push( - navigationTiming.responseStart - navigationTiming.startTime - ); - } - } ); - - it( 'Largest Contentful Paint (LCP)', async () => { + it( 'Report TTFB, LCP, and LCP-TTFB', async () => { // Based on https://addyosmani.com/blog/puppeteer-recipes/#performance-observer-lcp function calcLCP() { // By using -1 we know when it didn't record any event. @@ -73,9 +57,6 @@ describe( 'Front End Performance', () => { } ); } - // We derive the 75th percentile of the TTFB based on these results. - // By running it 16 times, the percentile value would be (75/100)*16=12, - // meaning that we discard the worst 4 values. let i = 16; while ( i-- ) { await page.evaluateOnNewDocument( calcLCP ); @@ -84,10 +65,18 @@ describe( 'Front End Performance', () => { // https://pptr.dev/api/puppeteer.page.goto#remarks await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } ); - const lcp = await page.evaluate( - () => window.largestContentfulPaint - ); + const { lcp, ttfb } = await page.evaluate( () => { + const [ { responseStart, startTime } ] = + performance.getEntriesByType( 'navigation' ); + return { + lcp: window.largestContentfulPaint, + ttfb: responseStart - startTime, + }; + } ); + results.largestContentfulPaint.push( lcp ); + results.timeToFirstByte.push( ttfb ); + results.lcpMinusTTFB.push( lcp - ttfb ); } } ); } ); From 8b548fae3266601fa6fbdf9a4042457c4a62ab9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 22 Feb 2023 09:49:50 +0100 Subject: [PATCH 5/6] Adapt perf command to calculate the LCP-TTFB median from the given results --- bin/plugin/commands/performance.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index ec68c2bb22da24..869f36de4c87bc 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -32,6 +32,7 @@ const config = require( '../config' ); * * @property {number[]} timeToFirstByte Represents the time since the browser started the request until it received a response. * @property {number[]} largestContentfulPaint Represents the time when the main content of the page has likely loaded. + * @property {number[]} lcpMinusTtfb Represents the difference between LCP and TTFB. * @property {number[]} serverResponse Represents the time the server takes to respond. * @property {number[]} firstPaint Represents the time when the user agent first rendered after navigation. * @property {number[]} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. @@ -133,12 +134,10 @@ function curateResults( testSuite, results ) { testSuite === 'front-end-classic-theme' || testSuite === 'front-end-block-theme' ) { - const timeToFirstByte = median( results.timeToFirstByte ); - const largestContentfulPaint = median( results.largestContentfulPaint ); return { - timeToFirstByte, - largestContentfulPaint, - lcpMinusTtfb: largestContentfulPaint - timeToFirstByte, + timeToFirstByte: median( results.timeToFirstByte ), + largestContentfulPaint: median( results.largestContentfulPaint ), + lcpMinusTtfb: median( results.lcpMinusTtfb ), }; } From 9309f15adad75590eb81bde03303d005f96a4997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:19:36 +0100 Subject: [PATCH 6/6] Fix typo --- .../e2e-tests/specs/performance/front-end-block-theme.test.js | 4 ++-- .../specs/performance/front-end-classic-theme.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js index 308b24e3c60f62..ebbd898e1c25b8 100644 --- a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js @@ -13,7 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], - lcpMinusTTFB: [], + lcpMinusTtfb: [], }; beforeAll( async () => { @@ -77,7 +77,7 @@ describe( 'Front End Performance', () => { results.largestContentfulPaint.push( lcp ); results.timeToFirstByte.push( ttfb ); - results.lcpMinusTTFB.push( lcp - ttfb ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } ); diff --git a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js index 7c183401701302..5ef281e8dd0e84 100644 --- a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js @@ -13,7 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], - lcpMinusTTFB: [], + lcpMinusTtfb: [], }; beforeAll( async () => { @@ -76,7 +76,7 @@ describe( 'Front End Performance', () => { results.largestContentfulPaint.push( lcp ); results.timeToFirstByte.push( ttfb ); - results.lcpMinusTTFB.push( lcp - ttfb ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } );