11/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
22/* eslint-disable @typescript-eslint/no-var-requires */
3- import { performance } from 'perf_hooks' ;
3+ import { createHistogram } from 'perf_hooks' ;
44import { readFile } from 'fs/promises' ;
55import { cpus , totalmem } from 'os' ;
66import { exec as execCb } from 'child_process' ;
7- import { promisify } from 'util' ;
7+ import { promisify , types } from 'util' ;
8+ import { writeFile } from 'fs/promises' ;
9+ import v8Profiler from 'v8-profiler-next' ;
10+ import chalk from 'chalk' ;
811const exec = promisify ( execCb ) ;
912
1013const hw = cpus ( ) ;
@@ -23,27 +26,26 @@ export const systemInfo = iterations =>
2326export const readJSONFile = async path =>
2427 JSON . parse ( await readFile ( new URL ( path , import . meta. url ) , { encoding : 'utf8' } ) ) ;
2528
26- function average ( array ) {
27- let sum = 0 ;
28- for ( const value of array ) sum += value ;
29- return sum / array . length ;
30- }
31-
32- function testPerformance ( lib , [ fn , arg ] , iterations ) {
33- let measurements = [ ] ;
29+ async function testPerformance ( lib , [ fn , arg ] , iterations ) {
3430 let thrownError = null ;
31+ const histogram = createHistogram ( ) ;
3532 for ( let i = 0 ; i < iterations ; i ++ ) {
36- const start = performance . now ( ) ;
3733 try {
38- fn ( i , lib , arg ) ;
34+ if ( types . isAsyncFunction ( fn ) ) {
35+ histogram . recordDelta ( ) ;
36+ await fn ( i , lib , arg ) ;
37+ histogram . recordDelta ( ) ;
38+ } else {
39+ histogram . recordDelta ( ) ;
40+ fn ( i , lib , arg ) ;
41+ histogram . recordDelta ( ) ;
42+ }
3943 } catch ( error ) {
4044 thrownError = error ;
4145 break ;
4246 }
43- const end = performance . now ( ) ;
44- measurements . push ( end - start ) ;
4547 }
46- return { result : average ( measurements ) . toFixed ( 8 ) , thrownError } ;
48+ return { histogram , thrownError } ;
4749}
4850
4951export function getCurrentLocalBSON ( libs ) {
@@ -73,12 +75,14 @@ export async function getLibs() {
7375 lib : { ...legacyBSON , ...legacyBSON . prototype } ,
7476 version : ( await readJSONFile ( '../../node_modules/bson_legacy/package.json' ) ) . version
7577 } ;
76- } ) ( ) ,
77- ( async ( ) => ( {
78- name : 'bson-ext' ,
79- lib : await import ( '../../node_modules/bson_ext/lib/index.js' ) ,
80- version : ( await readJSONFile ( '../../node_modules/bson_ext/package.json' ) ) . version
81- } ) ) ( )
78+ } ) ( )
79+ // BSON-EXT is EOL so we do not need to keep testing it, and it has issues installing it
80+ // in this no-save way on M1 currently that are not worth fixing.
81+ // (async () => ({
82+ // name: 'bson-ext',
83+ // lib: await import('../../node_modules/bson_ext/lib/index.js'),
84+ // version: (await readJSONFile('../../node_modules/bson_ext/package.json')).version
85+ // }))()
8286 ] ) . catch ( error => {
8387 console . error ( error ) ;
8488 console . error (
@@ -91,6 +95,27 @@ export async function getLibs() {
9195 } ) ;
9296}
9397
98+ const printHistogram = ( name , h ) => {
99+ const makeReadableTime = nanoseconds => ( nanoseconds / 1e6 ) . toFixed ( 3 ) . padStart ( 7 , ' ' ) ;
100+ console . log ( ) ;
101+ console . log ( chalk . green ( name ) ) ;
102+ console . log ( '-' . repeat ( 155 ) ) ;
103+ process . stdout . write ( `| ${ chalk . cyan ( 'max' ) } : ${ chalk . red ( makeReadableTime ( h . max ) ) } ms |` ) ;
104+ process . stdout . write ( ` ${ chalk . cyan ( 'min' ) } : ${ chalk . red ( makeReadableTime ( h . min ) ) } ms |` ) ;
105+ process . stdout . write ( ` ${ chalk . cyan ( 'mean' ) } : ${ chalk . red ( makeReadableTime ( h . mean ) ) } ms |` ) ;
106+ process . stdout . write ( ` ${ chalk . cyan ( 'stddev' ) } : ${ chalk . red ( makeReadableTime ( h . stddev ) ) } ms |` ) ;
107+ process . stdout . write (
108+ ` ${ chalk . cyan ( 'p90th' ) } : ${ chalk . red ( makeReadableTime ( h . percentile ( 90 ) ) ) } ms |`
109+ ) ;
110+ process . stdout . write (
111+ ` ${ chalk . cyan ( 'p95th' ) } : ${ chalk . red ( makeReadableTime ( h . percentile ( 95 ) ) ) } ms |`
112+ ) ;
113+ process . stdout . write (
114+ ` ${ chalk . cyan ( 'p99th' ) } : ${ chalk . red ( makeReadableTime ( h . percentile ( 99 ) ) ) } ms |`
115+ ) ;
116+ console . log ( '\n' + '-' . repeat ( 155 ) ) ;
117+ } ;
118+
94119/**
95120 * ```ts
96121 * interface {
@@ -109,19 +134,23 @@ export async function runner({ iterations, setup, name, run, skip }) {
109134 const BSONLibs = await getLibs ( ) ;
110135 const setupResult = setup ?. ( BSONLibs ) ?? null ;
111136
112- console . log ( `\ntesting: ${ name } ` ) ;
137+ console . log ( '-' . repeat ( 155 ) ) ;
113138
114139 for ( const bson of BSONLibs ) {
115- const { result : perf , thrownError } = testPerformance ( bson , [ run , setupResult ] , iterations ) ;
140+ const profileName = `${ bson . name } _${ name } ` ;
141+ v8Profiler . startProfiling ( profileName , true ) ;
142+ const { histogram, thrownError } = await testPerformance ( bson , [ run , setupResult ] , iterations ) ;
116143 if ( thrownError != null ) {
117144 console . log (
118145 `${ bson . name . padEnd ( 14 , ' ' ) } - v ${ bson . version . padEnd ( 8 , ' ' ) } - error ${ thrownError } `
119146 ) ;
120147 } else {
121- console . log (
122- `${ bson . name . padEnd ( 14 , ' ' ) } - v ${ bson . version . padEnd ( 8 , ' ' ) } - avg ${ perf } ms`
123- ) ;
148+ printHistogram ( `${ chalk . greenBright ( bson . name ) } - ${ chalk . blue ( name ) } ` , histogram ) ;
124149 }
150+ const profile = v8Profiler . stopProfiling ( profileName ) ;
151+ const result = await promisify ( profile . export . bind ( profile ) ) ( ) ;
152+ await writeFile ( `${ profileName } .cpuprofile` , result ) ;
153+ profile . delete ( ) ;
125154 }
126155
127156 console . log ( ) ;
0 commit comments