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

Performance Tests: Improve collection and reporting #61450

Merged
merged 7 commits into from
May 13, 2024
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
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
Loading