From 53b75a10fce4e0bae751e5a17b4c72835b2dde83 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 2 May 2024 13:57:49 +0000 Subject: Build/Test Tools: Overhaul performance tests to improve stability and cover more scenarios. Simplifies the tests setup by leveraging a test matrix, improving maintenance and making it much easier to test more scenarios. With this change, tests are now also run with an external object cache (Memcached). Additional information such as memory usage and the number of database queries is now collected as well. Improves test setup and cleanup by disabling external HTTP requests and cron for the tests, as well as deleting expired transients and flushing the cache in-between. This should aid the test stability. When testing the previous commit / target branch, this now leverages the already built artifact from the build process workflow. Raw test results are now also uploaded as artifacts to aid debugging. Props swissspidy, adamsilverstein, joemcgill, mukesh27, desrosj, youknowriad, flixos90. Fixes #59900 git-svn-id: https://develop.svn.wordpress.org/trunk@58076 602fd350-edb4-49c9-b593-d223f7449a82 --- tests/performance/utils.js | 177 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 165 insertions(+), 12 deletions(-) (limited to 'tests/performance/utils.js') diff --git a/tests/performance/utils.js b/tests/performance/utils.js index f56380e9c2..4d023be586 100644 --- a/tests/performance/utils.js +++ b/tests/performance/utils.js @@ -1,3 +1,26 @@ +/** + * External dependencies. + */ +const { readFileSync, existsSync } = require( 'node:fs' ); +const { join } = require( 'node:path' ); + +process.env.WP_ARTIFACTS_PATH ??= join( process.cwd(), 'artifacts' ); + +/** + * Parse test files into JSON objects. + * + * @param {string} fileName The name of the file. + * @return {Array<{file: string, title: string, results: Record[]}>} Parsed object. + */ +function parseFile( fileName ) { + const file = join( process.env.WP_ARTIFACTS_PATH, fileName ); + if ( ! existsSync( file ) ) { + return []; + } + + return JSON.parse( readFileSync( file, 'utf8' ) ); +} + /** * Computes the median number from an array numbers. * @@ -13,27 +36,157 @@ function median( array ) { : ( numbers[ mid - 1 ] + numbers[ mid ] ) / 2; } +function camelCaseDashes( str ) { + return str.replace( /-([a-z])/g, function ( g ) { + return g[ 1 ].toUpperCase(); + } ); +} + /** - * Gets the result file name. + * Formats an array of objects as a Markdown table. + * + * For example, this array: * - * @param {string} fileName File name. + * [ + * { + * foo: 123, + * bar: 456, + * baz: 'Yes', + * }, + * { + * foo: 777, + * bar: 999, + * baz: 'No', + * } + * ] * - * @return {string} Result file name. + * Will result in the following table: + * + * | foo | bar | baz | + * |-----|-----|-----| + * | 123 | 456 | Yes | + * | 777 | 999 | No | + * + * @param {Array} rows Table rows. + * @returns {string} Markdown table content. */ -function getResultsFilename( fileName ) { - const prefix = process.env.TEST_RESULTS_PREFIX; - const fileNamePrefix = prefix ? `${ prefix }-` : ''; - return `${fileNamePrefix + fileName}.results.json`; +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 ( 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; } -function camelCaseDashes( str ) { - return str.replace( /-([a-z])/g, function( g ) { - return g[ 1 ].toUpperCase(); - } ); +/** + * Nicely formats a given value. + * + * @param {string} metric Metric. + * @param {number} value + */ +function formatValue( metric, value ) { + if ( null === value ) { + return 'N/A'; + } + + if ( 'wpMemoryUsage' === metric ) { + return `${ ( value / Math.pow( 10, 6 ) ).toFixed( 2 ) } MB`; + } + + if ( 'wpExtObjCache' === metric ) { + return 1 === value ? 'yes' : 'no'; + } + + if ( 'wpDbQueries' === metric ) { + return value; + } + + return `${ value.toFixed( 2 ) } ms`; +} + +/** + * Returns a Markdown link to a Git commit on the current GitHub repository. + * + * For example, turns `a5c3785ed8d6a35868bc169f07e40e889087fd2e` + * into (https://github.com/wordpress/wordpress-develop/commit/36fe58a8c64dcc83fc21bddd5fcf054aef4efb27)[36fe58a]. + * + * @param {string} sha Commit SHA. + * @return string Link + */ +function linkToSha( sha ) { + const repoName = + process.env.GITHUB_REPOSITORY || 'wordpress/wordpress-develop'; + + return `[${ sha.slice( + 0, + 7 + ) }](https://github.com/${ repoName }/commit/${ sha })`; +} + +function standardDeviation( array = [] ) { + if ( ! array.length ) { + return 0; + } + + const mean = array.reduce( ( a, b ) => a + b ) / array.length; + return Math.sqrt( + array + .map( ( x ) => Math.pow( x - mean, 2 ) ) + .reduce( ( a, b ) => a + b ) / array.length + ); +} + +function medianAbsoluteDeviation( array = [] ) { + if ( ! array.length ) { + return 0; + } + + const med = median( array ); + return median( array.map( ( a ) => Math.abs( a - med ) ) ); +} + +/** + * + * @param {Array>} results + * @returns {Record} + */ +function accumulateValues( results ) { + return results.reduce( ( acc, result ) => { + for ( const [ metric, values ] of Object.entries( result ) ) { + acc[ metric ] = acc[ metric ] ?? []; + acc[ metric ].push( ...values ); + } + return acc; + }, {} ); } module.exports = { + parseFile, median, - getResultsFilename, camelCaseDashes, + formatAsMarkdownTable, + formatValue, + linkToSha, + standardDeviation, + medianAbsoluteDeviation, + accumulateValues, }; -- cgit v1.2.3