Skip to content

Commit

Permalink
Performance Tests: Improve collection and reporting (#61450)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy authored and pull[bot] committed Nov 12, 2024
1 parent 051a72f commit bbe171a
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 113 additions & 2 deletions bin/plugin/commands/performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,79 @@ 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<Object>} 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';
for ( let i = 0; i < headers.length; i++ ) {
result += '| ------ ';
}
result += '|\n';

for ( const row of rows ) {
for ( const value of Object.values( row ) ) {
result += `| ${ value } `;
}
result += '|\n';
}

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.
*
Expand Down Expand Up @@ -387,7 +460,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 ) {
Expand All @@ -401,6 +474,7 @@ async function runPerformanceTests( branches, options ) {
}
}
}

const calculatedResultsPath = path.join(
ARTIFACTS_PATH,
testSuite + RESULTS_FILE_SUFFIX
Expand All @@ -424,6 +498,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 ) );

Expand All @@ -435,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
);
}
}

Expand All @@ -457,7 +538,37 @@ async function runPerformanceTests( branches, options ) {

// Print the results.
console.table( invertedResult );

// 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 = {
Expand Down
91 changes: 91 additions & 0 deletions packages/e2e-tests/mu-plugins/server-timing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

add_filter(
'template_include',
static function ( $template ) {

global $timestart, $wpdb;

$server_timing_values = array();
$template_start = microtime( true );

$server_timing_values['wpBeforeTemplate'] = $template_start - $timestart;

ob_start();

add_action(
'shutdown',
static function () use ( $server_timing_values, $template_start, $wpdb ) {
$output = ob_get_clean();

$server_timing_values['wpTemplate'] = microtime( true ) - $template_start;

$server_timing_values['wpTotal'] = $server_timing_values['wpBeforeTemplate'] + $server_timing_values['wpTemplate'];

/*
* 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
);

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
);
17 changes: 16 additions & 1 deletion test/performance/config/performance-reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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 {
Expand All @@ -59,6 +64,11 @@ export interface WPPerformanceResults {
loadPatterns?: number;
listViewOpen?: number;
navigate?: number;
wpBeforeTemplate?: number;
wpTemplate?: number;
wpTotal?: number;
wpMemoryUsage?: number;
wpDbQueries?: number;
}

/**
Expand Down Expand Up @@ -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 (
Expand Down
7 changes: 7 additions & 0 deletions test/performance/specs/front-end-block-theme.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
} );
}
Expand Down
7 changes: 7 additions & 0 deletions test/performance/specs/front-end-classic-theme.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
} );
}
Expand Down
9 changes: 9 additions & 0 deletions test/performance/specs/post-editor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
} );
}
Expand Down
9 changes: 9 additions & 0 deletions test/performance/specs/site-editor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
} );
}
Expand Down

0 comments on commit bbe171a

Please sign in to comment.