1- import { readFile } from "node:fs/promises" ;
1+ import { readFile , writeFile } from "node:fs/promises" ;
22
33function parsePositiveFloat ( value , label ) {
44 const parsed = Number . parseFloat ( value ) ;
@@ -89,9 +89,45 @@ function ensureModeEntries(mapByProfile, profileName, modeName) {
8989 return byMode . get ( modeName ) ;
9090}
9191
92- const logPath = process . argv [ 2 ] ;
92+ let logPath = "" ;
93+ let emitJson = false ;
94+ let jsonOutPath = "" ;
95+ let enforceThresholds = true ;
96+
97+ const args = process . argv . slice ( 2 ) ;
98+ for ( let i = 0 ; i < args . length ; ++ i ) {
99+ const arg = args [ i ] ;
100+ if ( arg === "--emit-json" ) {
101+ emitJson = true ;
102+ continue ;
103+ }
104+ if ( arg === "--no-validate" ) {
105+ enforceThresholds = false ;
106+ continue ;
107+ }
108+ if ( arg === "--json-out" ) {
109+ const nextValue = args [ i + 1 ] ;
110+ if ( ! nextValue ) {
111+ throw new Error ( "--json-out requires a file path" ) ;
112+ }
113+ jsonOutPath = nextValue ;
114+ i += 1 ;
115+ continue ;
116+ }
117+ if ( arg . startsWith ( "--" ) ) {
118+ throw new Error ( `Unknown option '${ arg } '` ) ;
119+ }
120+ if ( ! logPath ) {
121+ logPath = arg ;
122+ continue ;
123+ }
124+ throw new Error ( "Only one runtime-smoke-log-path argument is supported" ) ;
125+ }
126+
93127if ( ! logPath ) {
94- throw new Error ( "Usage: node validate_dispatch_bench.mjs <runtime-smoke-log-path>" ) ;
128+ throw new Error (
129+ "Usage: node validate_dispatch_bench.mjs <runtime-smoke-log-path> [--emit-json] [--json-out <path>] [--no-validate]"
130+ ) ;
95131}
96132
97133const logText = await readFile ( logPath , "utf8" ) ;
@@ -113,6 +149,7 @@ for (const summary of summaries) {
113149}
114150
115151const requiredProfiles = normalizeRequiredProfiles ( ) ;
152+ const reportProfiles = [ ] ;
116153for ( const profileName of requiredProfiles ) {
117154 const fastSamples = ensureModeEntries ( benchmarksByProfile , profileName , "fast_wasm" ) ;
118155 const rawSamples = ensureModeEntries ( benchmarksByProfile , profileName , "raw_llvm_ir" ) ;
@@ -121,16 +158,59 @@ for (const profileName of requiredProfiles) {
121158 const rawAvgMs = average ( rawSamples ) ;
122159 const speedup = rawAvgMs / fastAvgMs ;
123160 const minSpeedup = envThresholdForProfile ( profileName ) ;
161+ const profilePass = speedup >= minSpeedup ;
162+
163+ reportProfiles . push ( {
164+ name : profileName ,
165+ fast_wasm_avg_ms : fastAvgMs ,
166+ raw_llvm_ir_avg_ms : rawAvgMs ,
167+ speedup_x : speedup ,
168+ required_min_speedup_x : minSpeedup ,
169+ pass : profilePass
170+ } ) ;
124171
125172 console . log (
126173 `[bench] profile=${ profileName } fast_wasm_avg_ms=${ fastAvgMs . toFixed ( 6 ) } raw_llvm_ir_avg_ms=${ rawAvgMs . toFixed ( 6 ) } speedup=${ speedup . toFixed ( 3 ) } x required>=${ minSpeedup . toFixed ( 3 ) } x`
127174 ) ;
128175
129- if ( speedup < minSpeedup ) {
176+ if ( enforceThresholds && speedup < minSpeedup ) {
130177 throw new Error (
131178 `Benchmark gate failed for profile='${ profileName } ': observed speedup ${ speedup . toFixed ( 3 ) } x < required ${ minSpeedup . toFixed ( 3 ) } x`
132179 ) ;
133180 }
134181}
135182
136- console . log ( "[bench] runtime benchmark gate passed" ) ;
183+ const speedupValues = reportProfiles . map ( ( profile ) => profile . speedup_x ) ;
184+ const minSpeedupObserved = Math . min ( ...speedupValues ) ;
185+ const maxSpeedupObserved = Math . max ( ...speedupValues ) ;
186+ const avgSpeedupObserved = average ( speedupValues ) ;
187+ const geometricMeanSpeedupObserved = Math . exp (
188+ speedupValues . reduce ( ( sum , value ) => sum + Math . log ( value ) , 0.0 ) / speedupValues . length
189+ ) ;
190+
191+ const report = {
192+ required_profiles : requiredProfiles ,
193+ profiles : reportProfiles ,
194+ summary : {
195+ min_speedup_x : minSpeedupObserved ,
196+ max_speedup_x : maxSpeedupObserved ,
197+ avg_speedup_x : avgSpeedupObserved ,
198+ geomean_speedup_x : geometricMeanSpeedupObserved ,
199+ all_profiles_pass : reportProfiles . every ( ( profile ) => profile . pass )
200+ }
201+ } ;
202+
203+ if ( jsonOutPath ) {
204+ await writeFile ( jsonOutPath , JSON . stringify ( report , null , 2 ) + "\n" , "utf8" ) ;
205+ }
206+
207+ if ( emitJson ) {
208+ console . log ( "[bench] benchmark report json" ) ;
209+ console . log ( JSON . stringify ( report , null , 2 ) ) ;
210+ }
211+
212+ if ( enforceThresholds ) {
213+ console . log ( "[bench] runtime benchmark gate passed" ) ;
214+ } else {
215+ console . log ( "[bench] runtime benchmark report generated without threshold validation" ) ;
216+ }
0 commit comments