1- import os , sys , uuid , subprocess , dataclasses , typing , math , traceback
1+ import os , sys , uuid , subprocess , dataclasses , typing , math , traceback , time
22
33import rich .table
44
@@ -53,6 +53,8 @@ def bench(targets = None):
5353
5454 failed_cases = []
5555
56+ max_attempts = 2
57+
5658 for i , case in enumerate (CASES ):
5759 summary_filepath = os .path .join (bench_dirpath , f"{ case .slug } .yaml" )
5860 log_filepath = os .path .join (bench_dirpath , f"{ case .slug } .out" )
@@ -64,71 +66,94 @@ def bench(targets = None):
6466 cons .print (f"> Summary: [bold]{ os .path .relpath (summary_filepath )} [/bold]" )
6567
6668 try :
67- with open (log_filepath , "w" ) as log_file :
68- result = system (
69- ["./mfc.sh" , "run" , case .path , "--case-optimization" ] +
70- ["--targets" ] + [t .name for t in targets ] +
71- ["--output-summary" , summary_filepath ] +
72- case .args +
73- ["--" , "--gbpp" , str (ARG ('mem' ))],
74- stdout = log_file ,
75- stderr = subprocess .STDOUT )
76-
77- # Check return code (handle CompletedProcess or int defensively)
78- rc = result .returncode if hasattr (result , "returncode" ) else result
79- if rc != 0 :
80- cons .print (f"[bold red]ERROR[/bold red]: Case { case .slug } failed with exit code { rc } " )
81- cons .print (f"[bold red] Check log at: { log_filepath } [/bold red]" )
82- failed_cases .append (case .slug )
83- continue
84-
85- # Validate summary file exists
86- if not os .path .exists (summary_filepath ):
87- cons .print (f"[bold red]ERROR[/bold red]: Summary file not created for { case .slug } " )
88- cons .print (f"[bold red] Expected: { summary_filepath } [/bold red]" )
89- failed_cases .append (case .slug )
90- continue
91-
92- # Load summary
93- summary = file_load_yaml (summary_filepath )
94-
95- # Validate all targets have required data
96- validation_failed = False
97- for target in targets :
98- if target .name not in summary :
99- cons .print (f"[bold red]ERROR[/bold red]: Target { target .name } missing from summary for { case .slug } " )
100- validation_failed = True
101- break
102-
103- if "exec" not in summary [target .name ]:
104- cons .print (f"[bold red]ERROR[/bold red]: 'exec' time missing for { target .name } in { case .slug } " )
105- validation_failed = True
69+ for attempt in range (1 , max_attempts + 1 ):
70+ try :
71+ with open (log_filepath , "w" ) as log_file :
72+ result = system (
73+ ["./mfc.sh" , "run" , case .path , "--case-optimization" ] +
74+ ["--targets" ] + [t .name for t in targets ] +
75+ ["--output-summary" , summary_filepath ] +
76+ case .args +
77+ ["--" , "--gbpp" , str (ARG ('mem' ))],
78+ stdout = log_file ,
79+ stderr = subprocess .STDOUT )
80+
81+ # Check return code (handle CompletedProcess or int defensively)
82+ rc = result .returncode if hasattr (result , "returncode" ) else result
83+ if rc != 0 :
84+ if attempt < max_attempts :
85+ cons .print (f"[bold yellow]WARNING[/bold yellow]: Case { case .slug } failed with exit code { rc } (attempt { attempt } /{ max_attempts } )" )
86+ cons .print (f"Retrying in 5s..." )
87+ time .sleep (5 )
88+ continue
89+ cons .print (f"[bold red]ERROR[/bold red]: Case { case .slug } failed with exit code { rc } " )
90+ cons .print (f"[bold red] Check log at: { log_filepath } [/bold red]" )
91+ failed_cases .append (case .slug )
92+ break
93+
94+ # Validate summary file exists
95+ if not os .path .exists (summary_filepath ):
96+ if attempt < max_attempts :
97+ cons .print (f"[bold yellow]WARNING[/bold yellow]: Summary file not created for { case .slug } (attempt { attempt } /{ max_attempts } )" )
98+ cons .print (f"Retrying in 5s..." )
99+ time .sleep (5 )
100+ continue
101+ cons .print (f"[bold red]ERROR[/bold red]: Summary file not created for { case .slug } " )
102+ cons .print (f"[bold red] Expected: { summary_filepath } [/bold red]" )
103+ failed_cases .append (case .slug )
104+ break
105+
106+ # Load summary
107+ summary = file_load_yaml (summary_filepath )
108+
109+ # Validate all targets have required data
110+ validation_failed = False
111+ for target in targets :
112+ if target .name not in summary :
113+ cons .print (f"[bold red]ERROR[/bold red]: Target { target .name } missing from summary for { case .slug } " )
114+ validation_failed = True
115+ break
116+
117+ if "exec" not in summary [target .name ]:
118+ cons .print (f"[bold red]ERROR[/bold red]: 'exec' time missing for { target .name } in { case .slug } " )
119+ validation_failed = True
120+ break
121+
122+ if target .name == "simulation" and "grind" not in summary [target .name ]:
123+ cons .print (f"[bold red]ERROR[/bold red]: 'grind' time missing for simulation in { case .slug } " )
124+ validation_failed = True
125+ break
126+
127+ if validation_failed :
128+ failed_cases .append (case .slug )
129+ break
130+
131+ # Add to results
132+ results ["cases" ][case .slug ] = {
133+ "description" : dataclasses .asdict (case ),
134+ "output_summary" : summary ,
135+ }
136+ cons .print (f"[bold green]✓[/bold green] Case { case .slug } completed successfully" )
106137 break
107138
108- if target .name == "simulation" and "grind" not in summary [target .name ]:
109- cons .print (f"[bold red]ERROR[/bold red]: 'grind' time missing for simulation in { case .slug } " )
110- validation_failed = True
111- break
139+ except Exception as e :
140+ if attempt < max_attempts :
141+ cons .print (f"[bold yellow]WARNING[/bold yellow]: Unexpected error running { case .slug } (attempt { attempt } /{ max_attempts } ): { e } " )
142+ cons .print (f"Retrying in 5s..." )
143+ time .sleep (5 )
144+ continue
145+ cons .print (f"[bold red]ERROR[/bold red]: Unexpected error running { case .slug } : { e } " )
146+ cons .print (f"[dim]{ traceback .format_exc ()} [/dim]" )
147+ failed_cases .append (case .slug )
112148
113- if validation_failed :
114- failed_cases .append (case .slug )
115- continue
116-
117- # Add to results
118- results ["cases" ][case .slug ] = {
119- "description" : dataclasses .asdict (case ),
120- "output_summary" : summary ,
121- }
122- cons .print (f"[bold green]✓[/bold green] Case { case .slug } completed successfully" )
123-
124- except Exception as e :
125- cons .print (f"[bold red]ERROR[/bold red]: Unexpected error running { case .slug } : { e } " )
126- cons .print (f"[dim]{ traceback .format_exc ()} [/dim]" )
127- failed_cases .append (case .slug )
128149 finally :
129150 cons .unindent ()
130151
131- # Report results
152+ # Always write results (even partial) so diff() can compare the intersection
153+ file_dump_yaml (ARG ("output" ), results )
154+ cons .print (f"Wrote results to [bold magenta]{ os .path .relpath (ARG ('output' ))} [/bold magenta]." )
155+
156+ # Report failures after writing results
132157 if failed_cases :
133158 cons .print ()
134159 cons .print (f"[bold red]Failed cases ({ len (failed_cases )} ):[/bold red]" )
@@ -137,11 +162,6 @@ def bench(targets = None):
137162 cons .print ()
138163 raise MFCException (f"Benchmarking failed: { len (failed_cases )} /{ len (CASES )} cases failed" )
139164
140- # Write output
141- file_dump_yaml (ARG ("output" ), results )
142-
143- cons .print (f"Wrote results to [bold magenta]{ os .path .relpath (ARG ('output' ))} [/bold magenta]." )
144-
145165 finally :
146166 cons .unindent ()
147167
0 commit comments