From 2a30df38c90ac81be76ad774c87d6bb9c0eff535 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 15:12:05 +0200 Subject: [PATCH 1/6] Collect Server-Timing metrics --- .../e2e-tests/mu-plugins/server-timing.php | 91 +++++++++++++++++++ .../specs/front-end-block-theme.spec.js | 7 ++ .../specs/front-end-classic-theme.spec.js | 7 ++ test/performance/specs/post-editor.spec.js | 9 ++ test/performance/specs/site-editor.spec.js | 9 ++ 5 files changed, 123 insertions(+) create mode 100644 packages/e2e-tests/mu-plugins/server-timing.php diff --git a/packages/e2e-tests/mu-plugins/server-timing.php b/packages/e2e-tests/mu-plugins/server-timing.php new file mode 100644 index 00000000000000..84771f980ff7fc --- /dev/null +++ b/packages/e2e-tests/mu-plugins/server-timing.php @@ -0,0 +1,91 @@ +num_queries; + + $header_values = array(); + foreach ( $server_timing_values as $slug => $value ) { + if ( is_float( $value ) ) { + $value = round( $value * 1000.0, 2 ); + } + $header_values[] = sprintf( '%1$s;dur=%2$s', $slug, $value ); + } + header( 'Server-Timing: ' . implode( ', ', $header_values ) ); + + echo $output; + }, + PHP_INT_MIN + ); + + return $template; + }, + PHP_INT_MAX +); + +add_action( + 'admin_init', + static function () { + global $timestart, $wpdb; + + ob_start(); + + add_action( + 'shutdown', + static function () use ( $wpdb, $timestart ) { + $output = ob_get_clean(); + + $server_timing_values = array(); + + $server_timing_values['wpTotal'] = microtime( true ) - $timestart; + + /* + * While values passed via Server-Timing are intended to be durations, + * any numeric value can actually be passed. + * This is a nice little trick as it allows to easily get this information in JS. + */ + $server_timing_values['wpMemoryUsage'] = memory_get_usage(); + $server_timing_values['wpDbQueries'] = $wpdb->num_queries; + + $header_values = array(); + foreach ( $server_timing_values as $slug => $value ) { + if ( is_float( $value ) ) { + $value = round( $value * 1000.0, 2 ); + } + $header_values[] = sprintf( '%1$s;dur=%2$s', $slug, $value ); + } + header( 'Server-Timing: ' . implode( ', ', $header_values ) ); + + echo $output; + }, + PHP_INT_MIN + ); + }, + PHP_INT_MAX +); diff --git a/test/performance/specs/front-end-block-theme.spec.js b/test/performance/specs/front-end-block-theme.spec.js index ca48535a21a467..8a0f7b6cf391a2 100644 --- a/test/performance/specs/front-end-block-theme.spec.js +++ b/test/performance/specs/front-end-block-theme.spec.js @@ -52,6 +52,13 @@ test.describe( 'Front End Performance', () => { results.largestContentfulPaint.push( lcp ); results.timeToFirstByte.push( ttfb ); results.lcpMinusTtfb.push( lcp - ttfb ); + + const serverTiming = await metrics.getServerTiming(); + + for ( const [ key, value ] of Object.entries( serverTiming ) ) { + results[ key ] ??= []; + results[ key ].push( value ); + } } } ); } diff --git a/test/performance/specs/front-end-classic-theme.spec.js b/test/performance/specs/front-end-classic-theme.spec.js index 0b6c3ec22c0465..64929086079fe6 100644 --- a/test/performance/specs/front-end-classic-theme.spec.js +++ b/test/performance/specs/front-end-classic-theme.spec.js @@ -51,6 +51,13 @@ test.describe( 'Front End Performance', () => { results.largestContentfulPaint.push( lcp ); results.timeToFirstByte.push( ttfb ); results.lcpMinusTtfb.push( lcp - ttfb ); + + const serverTiming = await metrics.getServerTiming(); + + for ( const [ key, value ] of Object.entries( serverTiming ) ) { + results[ key ] ??= []; + results[ key ].push( value ); + } } } ); } diff --git a/test/performance/specs/post-editor.spec.js b/test/performance/specs/post-editor.spec.js index 30baa8b7993906..3d9621783dce4c 100644 --- a/test/performance/specs/post-editor.spec.js +++ b/test/performance/specs/post-editor.spec.js @@ -89,6 +89,15 @@ test.describe( 'Post Editor Performance', () => { } } ); + + const serverTiming = await metrics.getServerTiming(); + + for ( const [ key, value ] of Object.entries( + serverTiming + ) ) { + results[ key ] ??= []; + results[ key ].push( value ); + } } } ); } diff --git a/test/performance/specs/site-editor.spec.js b/test/performance/specs/site-editor.spec.js index c4c83f0d4c140a..e8a4e3529334dd 100644 --- a/test/performance/specs/site-editor.spec.js +++ b/test/performance/specs/site-editor.spec.js @@ -101,6 +101,15 @@ test.describe( 'Site Editor Performance', () => { } } ); + + const serverTiming = await metrics.getServerTiming(); + + for ( const [ key, value ] of Object.entries( + serverTiming + ) ) { + results[ key ] ??= []; + results[ key ].push( value ); + } } } ); } From a9f7c31d9fde0b326fcd7857f08f95c5bd8583c7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 17:05:59 +0200 Subject: [PATCH 2/6] Generate workflow summary --- .github/workflows/performance.yml | 3 + bin/plugin/commands/performance.js | 122 ++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index b78ff9532c22d3..4afa2dc5dcddeb 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -84,6 +84,9 @@ jobs: run: | ./bin/plugin/cli.js perf $(echo $BRANCHES | tr ',' ' ') --tests-branch $GITHUB_SHA --wp-version "$WP_VERSION" + - name: Add workflow summary + run: cat ${{ env.WP_ARTIFACTS_PATH }}/summary.md >> $GITHUB_STEP_SUMMARY + - name: Archive performance results if: success() uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 9d9b39fce09845..9fa0c2ef365526 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -96,6 +96,62 @@ async function runTestSuite( testSuite, testRunnerDir, runKey ) { ); } +/** + * Formats an array of objects as a Markdown table. + * + * For example, this array: + * + * [ + * { + * foo: 123, + * bar: 456, + * baz: 'Yes', + * }, + * { + * foo: 777, + * bar: 999, + * baz: 'No', + * } + * ] + * + * Will result in the following table: + * + * | foo | bar | baz | + * |-----|-----|-----| + * | 123 | 456 | Yes | + * | 777 | 999 | No | + * + * @param {Array} rows Table rows. + * @return {string} Markdown table content. + */ +function formatAsMarkdownTable( rows ) { + let result = ''; + + if ( ! rows.length ) { + return result; + } + + const headers = Object.keys( rows[ 0 ] ); + for ( const header of headers ) { + result += `| ${ header } `; + } + result += '|\n'; + // eslint-disable-next-line no-unused-vars + for ( const header of headers ) { + result += '| ------ '; + } + result += '|\n'; + + for ( const row of rows ) { + for ( const value of Object.values( row ) ) { + result += `| ${ value } `; + } + result += '|\n'; + } + + return result; +} + /** * Runs the performances tests on an array of branches and output the result. * @@ -387,7 +443,7 @@ async function runPerformanceTests( branches, options ) { return readJSONFile( file ); } ); - const metrics = Object.keys( resultsRounds[ 0 ] ); + const metrics = Object.keys( resultsRounds[ 0 ] ?? {} ); results[ testSuite ][ branch ] = {}; for ( const metric of metrics ) { @@ -401,6 +457,7 @@ async function runPerformanceTests( branches, options ) { } } } + const calculatedResultsPath = path.join( ARTIFACTS_PATH, testSuite + RESULTS_FILE_SUFFIX @@ -424,6 +481,10 @@ async function runPerformanceTests( branches, options ) { ) ); + let summaryMarkdown = `## Performance Test Results\n\n`; + + summaryMarkdown += `Please note that client side metrics **exclude** the server response time.\n\n`; + for ( const testSuite of testSuites ) { logAtIndent( 0, formats.success( testSuite ) ); @@ -457,7 +518,66 @@ async function runPerformanceTests( branches, options ) { // Print the results. console.table( invertedResult ); + + /** + * Formats an array of objects as a Markdown table. + * + * For example, this array: + * + * [ + * { + * foo: 123, + * bar: 456, + * baz: 'Yes', + * }, + * { + * foo: 777, + * bar: 999, + * baz: 'No', + * } + * ] + * + * Will result in the following table: + * + * | foo | bar | baz | + * |-----|-----|-----| + * | 123 | 456 | Yes | + * | 777 | 999 | No | + * + * @param {Array} rows Table rows. + * @return {string} Markdown table content. + */ + + // Use yet another structure to generate a Markdown table. + + const rows = []; + + for ( const [ metric, resultBranches ] of Object.entries( + invertedResult + ) ) { + /** + * @type {Record< string, string >} + */ + const row = { + Metric: metric, + }; + + for ( const [ branch, value ] of Object.entries( + resultBranches + ) ) { + row[ branch ] = value; + } + rows.push( row ); + } + + summaryMarkdown += `**${ testSuite }**\n\n`; + summaryMarkdown += `${ formatAsMarkdownTable( rows ) }\n`; } + + fs.writeFileSync( + path.join( ARTIFACTS_PATH, 'summary.md' ), + summaryMarkdown + ); } module.exports = { From 8df8dd28fe40f81b791a1817b67823af8f0e073e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 17:15:18 +0200 Subject: [PATCH 3/6] Use for loop --- bin/plugin/commands/performance.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 9fa0c2ef365526..d71f0c2e43447a 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -136,8 +136,7 @@ function formatAsMarkdownTable( rows ) { result += `| ${ header } `; } result += '|\n'; - // eslint-disable-next-line no-unused-vars - for ( const header of headers ) { + for ( let i = 0; i < headers.length; i++ ) { result += '| ------ '; } result += '|\n'; From ff3bca30d4ab244bbff0b18f81c55a0af15c11ee Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 18:24:29 +0200 Subject: [PATCH 4/6] Remove comment --- bin/plugin/commands/performance.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index d71f0c2e43447a..86310feaa98402 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -518,35 +518,6 @@ async function runPerformanceTests( branches, options ) { // Print the results. console.table( invertedResult ); - /** - * Formats an array of objects as a Markdown table. - * - * For example, this array: - * - * [ - * { - * foo: 123, - * bar: 456, - * baz: 'Yes', - * }, - * { - * foo: 777, - * bar: 999, - * baz: 'No', - * } - * ] - * - * Will result in the following table: - * - * | foo | bar | baz | - * |-----|-----|-----| - * | 123 | 456 | Yes | - * | 777 | 999 | No | - * - * @param {Array} rows Table rows. - * @return {string} Markdown table content. - */ - // Use yet another structure to generate a Markdown table. const rows = []; From d366d2e8febcd0360b17d5ef568bf0d326d8beca Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 18:34:10 +0200 Subject: [PATCH 5/6] Expand allowlist --- test/performance/config/performance-reporter.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/performance/config/performance-reporter.ts b/test/performance/config/performance-reporter.ts index c82c092f304788..b449e36540404e 100644 --- a/test/performance/config/performance-reporter.ts +++ b/test/performance/config/performance-reporter.ts @@ -13,7 +13,7 @@ import type { /** * Internal dependencies */ -import { average, median, minimum, maximum, round } from '../utils'; +import { average, median, round } from '../utils'; export interface WPRawPerformanceResults { timeToFirstByte: number[]; @@ -36,6 +36,11 @@ export interface WPRawPerformanceResults { loadPatterns: number[]; listViewOpen: number[]; navigate: number[]; + wpBeforeTemplate: number[]; + wpTemplate: number[]; + wpTotal: number[]; + wpMemoryUsage: number[]; + wpDbQueries: number[]; } export interface WPPerformanceResults { @@ -59,6 +64,11 @@ export interface WPPerformanceResults { loadPatterns?: number; listViewOpen?: number; navigate?: number; + wpBeforeTemplate?: number; + wpTemplate?: number; + wpTotal?: number; + wpMemoryUsage?: number; + wpDbQueries?: number; } /** @@ -92,6 +102,11 @@ export function curateResults( loadPatterns: average( results.loadPatterns ), listViewOpen: average( results.listViewOpen ), navigate: median( results.navigate ), + wpBeforeTemplate: median( results.wpBeforeTemplate ), + wpTemplate: median( results.wpTemplate ), + wpTotal: median( results.wpTotal ), + wpMemoryUsage: median( results.wpMemoryUsage ), + wpDbQueries: median( results.wpDbQueries ), }; return ( From b58fd12cc01ce4fc50214ab2d315ac8a3741bae1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 7 May 2024 18:40:16 +0200 Subject: [PATCH 6/6] Improve formatting --- bin/plugin/commands/performance.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 86310feaa98402..65b8a770d3764a 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -151,6 +151,24 @@ function formatAsMarkdownTable( rows ) { return result; } +/** + * Nicely formats a given value. + * + * @param {string} metric Metric. + * @param {number} value + */ +function formatValue( metric, value ) { + if ( 'wpMemoryUsage' === metric ) { + return `${ ( value / Math.pow( 10, 6 ) ).toFixed( 2 ) } MB`; + } + + if ( 'wpDbQueries' === metric ) { + return value.toString(); + } + + return `${ value } ms`; +} + /** * Runs the performances tests on an array of branches and output the result. * @@ -495,7 +513,10 @@ async function runPerformanceTests( branches, options ) { ) ) { for ( const [ metric, value ] of Object.entries( metrics ) ) { invertedResult[ metric ] = invertedResult[ metric ] || {}; - invertedResult[ metric ][ branch ] = `${ value } ms`; + invertedResult[ metric ][ branch ] = formatValue( + metric, + value + ); } }