11import argparse
2+ import ast
23import datetime
34import getpass
45import os
56import pathlib
7+ import re
8+ import sys
69import tempfile
710import typing
811import warnings
1619optional_software = ["flax" , "maxtext" , "transformer-engine" ]
1720
1821
22+ _INFO_LOG_ARGUMENTS_RE = re .compile (
23+ r"^\[INFO\]\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+Arguments:\s*$"
24+ )
25+ _INFO_LOG_ARGUMENT_RE = re .compile (
26+ r"^\[INFO\]\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+"
27+ r"(?P<name>[A-Za-z_][A-Za-z0-9_]*):\s*(?P<value>.*)$"
28+ )
29+
30+
31+ def logged_arg_value (s : str ) -> typing .Any :
32+ try :
33+ return ast .literal_eval (s )
34+ except (SyntaxError , ValueError ):
35+ return s
36+
37+
38+ def load_args_from_info_log (info_log : pathlib .Path ) -> typing .Dict [str , str ]:
39+ ret : typing .Dict [str , str ] = {}
40+ in_arguments = False
41+ with info_log .open ("r" , encoding = "utf-8" ) as f :
42+ for line in f :
43+ if not in_arguments :
44+ if _INFO_LOG_ARGUMENTS_RE .match (line ):
45+ in_arguments = True
46+ continue
47+ match = _INFO_LOG_ARGUMENT_RE .match (line )
48+ if not match :
49+ if ret :
50+ break
51+ continue
52+ ret [match .group ("name" )] = match .group ("value" )
53+ if not ret :
54+ raise Exception (f"Could not read arguments from { info_log } " )
55+ return ret
56+
57+
58+ def parser_actions_by_dest (parser : argparse .ArgumentParser ) -> typing .Dict [str , argparse .Action ]:
59+ return {
60+ action .dest : action
61+ for action in parser ._actions
62+ if action .dest != argparse .SUPPRESS
63+ }
64+
65+
66+ def provided_arg_dests (
67+ parser : argparse .ArgumentParser , args : typing .Sequence [str ]
68+ ) -> typing .Set [str ]:
69+ option_actions = {
70+ option : action for action in parser ._actions for option in action .option_strings
71+ }
72+ ret : typing .Set [str ] = set ()
73+ i = 0
74+ while i < len (args ):
75+ arg = args [i ]
76+ if arg == "--" :
77+ if i + 1 < len (args ):
78+ ret .add ("test_command" )
79+ break
80+ option = arg .split ("=" , 1 )[0 ]
81+ action = option_actions .get (option )
82+ if action is None :
83+ ret .add ("test_command" )
84+ break
85+ ret .add (action .dest )
86+ if "=" in arg or action .nargs == 0 :
87+ i += 1
88+ elif action .nargs is None :
89+ i += 2
90+ elif isinstance (action .nargs , int ):
91+ i += 1 + action .nargs
92+ else :
93+ i += 1
94+ return ret
95+
96+
97+ def coerce_logged_arg (
98+ action : argparse .Action , value : str
99+ ) -> typing .Any :
100+ value = logged_arg_value (value )
101+ if isinstance (value , str ) and action .type is not None :
102+ return action .type (value )
103+ return value
104+
105+
19106def parse_cherry_picks (s : str ) -> typing .Dict [str , typing .List [str ]]:
20107 ret : typing .Dict [str , typing .List [str ]] = {}
21108 for part in s .split ("," ):
@@ -128,9 +215,16 @@ def parse_args(args=None) -> argparse.Namespace:
128215 container. Careful use of this option, along with --start-date, --end-date and
129216 --threshold-days, allows the container-level search to be skipped.""" ,
130217 )
218+ parser .add_argument (
219+ "--no-skip-precondition-checks" ,
220+ dest = "skip_precondition_checks" ,
221+ action = "store_false" ,
222+ default = False ,
223+ help = "Re-enable precondition checks when restarting from a run that skipped them." ,
224+ )
131225 parser .add_argument (
132226 "test_command" ,
133- nargs = "+ " ,
227+ nargs = "* " ,
134228 help = """
135229 Command to execute inside the container. This should be as targeted as
136230 possible.""" ,
@@ -273,6 +367,13 @@ def parse_args(args=None) -> argparse.Namespace:
273367 action = "store_true" ,
274368 help = "Exclude transformer-engine from the list of optional software to triage." ,
275369 )
370+ version_search_args .add_argument (
371+ "--no-exclude-transformer-engine" ,
372+ dest = "exclude_transformer_engine" ,
373+ action = "store_false" ,
374+ default = False ,
375+ help = "Include transformer-engine when restarting from a run that excluded it." ,
376+ )
276377 version_search_args .add_argument (
277378 "--transformer-engine-ccache-env" ,
278379 action = "append" ,
@@ -316,7 +417,9 @@ def parse_args(args=None) -> argparse.Namespace:
316417 default = "main" ,
317418 help = "The name of the main branch (e.g. main) to derive cherry-picks from" ,
318419 )
319- args = parser .parse_args (args = args )
420+ raw_args = sys .argv [1 :] if args is None else list (args )
421+ provided_args = provided_arg_dests (parser , raw_args )
422+ args = parser .parse_args (args = raw_args )
320423 if args .restart :
321424 if args .output_prefix is None :
322425 raise Exception ("--restart requires --output-prefix" )
@@ -326,11 +429,21 @@ def parse_args(args=None) -> argparse.Namespace:
326429 raise Exception (
327430 f"--output-prefix must contain summary.json: { summary_file } "
328431 )
432+ info_log = args .output_prefix / "info.log"
433+ if not info_log .exists ():
434+ raise Exception (f"--output-prefix must contain info.log: { info_log } " )
435+ actions_by_dest = parser_actions_by_dest (parser )
436+ for arg , value in load_args_from_info_log (info_log ).items ():
437+ if arg in provided_args or arg not in actions_by_dest :
438+ continue
439+ setattr (args , arg , coerce_logged_arg (actions_by_dest [arg ], value ))
329440 else :
330441 if args .output_prefix is None :
331442 args .output_prefix = pathlib .Path (
332443 datetime .datetime .now ().strftime ("triage-%Y-%m-%d-%H-%M-%S" )
333444 )
445+ if not args .test_command :
446+ raise Exception ("test_command must be passed or restored from info.log" )
334447
335448 assert args .container_runtime in {
336449 "docker" ,
@@ -426,3 +539,4 @@ def parse_args(args=None) -> argparse.Namespace:
426539 args .optional_software .remove ("transformer-engine" )
427540
428541 return args
542+
0 commit comments