22"""Run exact Criterion benchmarks and compare them with a main-branch artifact."""
33
44import argparse
5- import json
65import os
76import re
87import shlex
98import subprocess
109import sys
10+ import tomllib
1111from pathlib import Path
1212from typing import Any
1313
1818
1919def parse_args () -> argparse .Namespace :
2020 parser = argparse .ArgumentParser (description = __doc__ )
21- parser .add_argument ("--config" , required = True , help = "Path to benchmark tracking config JSON " )
21+ parser .add_argument ("--config" , required = True , help = "Path to benchmark tracking config TOML " )
2222 parser .add_argument ("--output-dir" , required = True , help = "Directory for result artifacts" )
2323 parser .add_argument (
2424 "--baseline" ,
25- help = "Path to a previous benchmark-tracking current.json artifact from main" ,
25+ help = "Path to a previous benchmark-tracking current.toml artifact from main" ,
2626 )
2727 parser .add_argument (
2828 "--skip-run" ,
@@ -32,9 +32,9 @@ def parse_args() -> argparse.Namespace:
3232 return parser .parse_args ()
3333
3434
35- def read_json (path : Path ) -> dict [str , Any ]:
36- with path .open ("r" , encoding = "utf-8 " ) as f :
37- return json .load (f )
35+ def read_toml (path : Path ) -> dict [str , Any ]:
36+ with path .open ("rb " ) as f :
37+ return tomllib .load (f )
3838
3939
4040def validate_config (config : dict [str , Any ]) -> list [dict [str , Any ]]:
@@ -164,10 +164,9 @@ def load_baseline(path: str | None) -> list[dict[str, Any]] | None:
164164 baseline_path = Path (path )
165165 if not baseline_path .exists ():
166166 return None
167- with baseline_path .open ("r" , encoding = "utf-8" ) as f :
168- baseline = json .load (f )
167+ baseline = read_toml (baseline_path ).get ("benchmarks" )
169168 if not isinstance (baseline , list ):
170- raise ValueError (f"baseline `{ path } ` must contain a JSON array" )
169+ raise ValueError (f"baseline `{ path } ` must contain a `benchmarks` array" )
171170 return baseline
172171
173172
@@ -200,6 +199,56 @@ def escape_cell(value: str) -> str:
200199 return value .replace ("|" , "\\ |" ).replace ("\n " , "<br>" )
201200
202201
202+ def toml_quote (value : str ) -> str :
203+ escaped = value .replace ("\\ " , "\\ \\ " ).replace ('"' , '\\ "' ).replace ("\n " , "\\ n" )
204+ return f'"{ escaped } "'
205+
206+
207+ def toml_value (value : Any ) -> str :
208+ if isinstance (value , bool ):
209+ return "true" if value else "false"
210+ if isinstance (value , int | float ):
211+ return str (value )
212+ if isinstance (value , str ):
213+ return toml_quote (value )
214+ if isinstance (value , list ):
215+ return "[" + ", " .join (toml_value (item ) for item in value ) + "]"
216+ raise TypeError (f"unsupported TOML value: { value !r} " )
217+
218+
219+ def write_toml_table (path : Path , values : dict [str , Any ]) -> None :
220+ lines = []
221+ for key , value in values .items ():
222+ if value is None :
223+ continue
224+ lines .append (f"{ key } = { toml_value (value )} " )
225+ path .write_text ("\n " .join (lines ) + "\n " , encoding = "utf-8" )
226+
227+
228+ def append_toml_values (lines : list [str ], values : dict [str , Any ], prefix : str ) -> None :
229+ nested = []
230+ for key , value in values .items ():
231+ if value is None :
232+ continue
233+ if isinstance (value , dict ):
234+ nested .append ((key , value ))
235+ else :
236+ lines .append (f"{ key } = { toml_value (value )} " )
237+ for key , value in nested :
238+ lines .extend (["" , f"[{ prefix } .{ key } ]" ])
239+ append_toml_values (lines , value , f"{ prefix } .{ key } " )
240+
241+
242+ def write_toml_array (path : Path , name : str , values : list [dict [str , Any ]]) -> None :
243+ lines = []
244+ for value in values :
245+ if lines :
246+ lines .append ("" )
247+ lines .append (f"[[{ name } ]]" )
248+ append_toml_values (lines , value , name )
249+ path .write_text ("\n " .join (lines ) + "\n " , encoding = "utf-8" )
250+
251+
203252def compare_results (
204253 current : list [dict [str , Any ]], baseline_data : list [dict [str , Any ]] | None
205254) -> list [dict [str , Any ]]:
@@ -301,7 +350,7 @@ def main() -> int:
301350 output_dir = Path (args .output_dir )
302351 output_dir .mkdir (parents = True , exist_ok = True )
303352
304- config = read_json (config_path )
353+ config = read_toml (config_path )
305354 benchmarks = validate_config (config )
306355
307356 if args .skip_run :
@@ -335,14 +384,13 @@ def main() -> int:
335384 "missing_baseline_count" : missing_count ,
336385 }
337386
338- (output_dir / "current.json" ).write_text (json .dumps (current , indent = 2 ) + "\n " , encoding = "utf-8" )
339- (output_dir / "comparison.json" ).write_text (
340- json .dumps (comparisons , indent = 2 ) + "\n " , encoding = "utf-8"
341- )
342- (output_dir / "summary.json" ).write_text (json .dumps (summary , indent = 2 ) + "\n " , encoding = "utf-8" )
387+ write_toml_array (output_dir / "current.toml" , "benchmarks" , current )
388+ write_toml_array (output_dir / "comparison.toml" , "comparisons" , comparisons )
389+ write_toml_table (output_dir / "summary.toml" , summary )
343390 (output_dir / "comment.md" ).write_text (render_markdown (comparisons ), encoding = "utf-8" )
344391
345- print (json .dumps (summary , indent = 2 ))
392+ for key , value in summary .items ():
393+ print (f"{ key } : { value } " )
346394 return 0
347395
348396
0 commit comments