1
1
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
2
/* eslint-disable @typescript-eslint/no-var-requires */
3
- import { performance } from 'perf_hooks' ;
3
+ import { createHistogram } from 'perf_hooks' ;
4
4
import { readFile } from 'fs/promises' ;
5
5
import { cpus , totalmem } from 'os' ;
6
6
import { 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' ;
8
11
const exec = promisify ( execCb ) ;
9
12
10
13
const hw = cpus ( ) ;
@@ -23,27 +26,26 @@ export const systemInfo = iterations =>
23
26
export const readJSONFile = async path =>
24
27
JSON . parse ( await readFile ( new URL ( path , import . meta. url ) , { encoding : 'utf8' } ) ) ;
25
28
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 ) {
34
30
let thrownError = null ;
31
+ const histogram = createHistogram ( ) ;
35
32
for ( let i = 0 ; i < iterations ; i ++ ) {
36
- const start = performance . now ( ) ;
37
33
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
+ }
39
43
} catch ( error ) {
40
44
thrownError = error ;
41
45
break ;
42
46
}
43
- const end = performance . now ( ) ;
44
- measurements . push ( end - start ) ;
45
47
}
46
- return { result : average ( measurements ) . toFixed ( 8 ) , thrownError } ;
48
+ return { histogram , thrownError } ;
47
49
}
48
50
49
51
export function getCurrentLocalBSON ( libs ) {
@@ -73,12 +75,14 @@ export async function getLibs() {
73
75
lib : { ...legacyBSON , ...legacyBSON . prototype } ,
74
76
version : ( await readJSONFile ( '../../node_modules/bson_legacy/package.json' ) ) . version
75
77
} ;
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
+ // }))()
82
86
] ) . catch ( error => {
83
87
console . error ( error ) ;
84
88
console . error (
@@ -91,6 +95,27 @@ export async function getLibs() {
91
95
} ) ;
92
96
}
93
97
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
+
94
119
/**
95
120
* ```ts
96
121
* interface {
@@ -109,19 +134,23 @@ export async function runner({ iterations, setup, name, run, skip }) {
109
134
const BSONLibs = await getLibs ( ) ;
110
135
const setupResult = setup ?. ( BSONLibs ) ?? null ;
111
136
112
- console . log ( `\ntesting: ${ name } ` ) ;
137
+ console . log ( '-' . repeat ( 155 ) ) ;
113
138
114
139
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 ) ;
116
143
if ( thrownError != null ) {
117
144
console . log (
118
145
`${ bson . name . padEnd ( 14 , ' ' ) } - v ${ bson . version . padEnd ( 8 , ' ' ) } - error ${ thrownError } `
119
146
) ;
120
147
} 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 ) ;
124
149
}
150
+ const profile = v8Profiler . stopProfiling ( profileName ) ;
151
+ const result = await promisify ( profile . export . bind ( profile ) ) ( ) ;
152
+ await writeFile ( `${ profileName } .cpuprofile` , result ) ;
153
+ profile . delete ( ) ;
125
154
}
126
155
127
156
console . log ( ) ;
0 commit comments