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

Track new front-end metric: LCP-TTFB #48288

Merged
merged 6 commits into from
Feb 22, 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
86 changes: 34 additions & 52 deletions bin/plugin/commands/performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -50,37 +51,36 @@ 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=} 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.
* @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.
*/

/**
Expand Down Expand Up @@ -109,19 +109,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.
*
Expand All @@ -148,14 +135,9 @@ 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 ),
lcpMinusTtfb: median( results.lcpMinusTtfb ),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => {
const results = {
timeToFirstByte: [],
largestContentfulPaint: [],
lcpMinusTtfb: [],
};

beforeAll( async () => {
Expand All @@ -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.
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
}
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => {
const results = {
timeToFirstByte: [],
largestContentfulPaint: [],
lcpMinusTtfb: [],
};

beforeAll( async () => {
Expand All @@ -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.
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
}
} );
} );