|
10 | 10 | python run.py --serial # one process (easier to debug) |
11 | 11 | python run.py --verify # also run the test suite and feed the release gate |
12 | 12 | python run.py --view [FOLDER] # open the results as report.html (+ tables.xlsx); no run |
| 13 | + python run.py --status [FOLDER] # what a running (or the last) study is doing; no run |
| 14 | + python run.py --log-level verbose # quiet | normal (default) | verbose |
| 15 | + python run.py --heartbeat 30 # seconds between heartbeats (default 60) |
13 | 16 |
|
14 | 17 | Exit codes: 0 ok, 1 a task failed, 2 the inputs are unusable (or release mode refused), |
15 | 18 | 3 release mode finished but the release gate blocked the result. |
|
22 | 25 | from collections.abc import Sequence |
23 | 26 | from pathlib import Path |
24 | 27 |
|
| 28 | +from sagetrans.io import runlog |
25 | 29 | from sagetrans.io.inputs import InputError, load_inputs |
26 | 30 | from sagetrans.io.settings import ALL_ANALYSES |
27 | 31 | from sagetrans.io.study import EXIT_INPUTS, EXIT_OK, preflight, run_study |
@@ -54,13 +58,43 @@ def build_parser() -> argparse.ArgumentParser: |
54 | 58 | "study.yaml): writes report.html and tables.xlsx there and opens the page; " |
55 | 59 | "runs nothing") |
56 | 60 | p.add_argument("--no-open", action="store_true", help="with --view: do not open the browser") |
| 61 | + p.add_argument("--log-level", choices=runlog.LEVELS, default="normal", |
| 62 | + help="how much a run prints: quiet (banner, failures, summary), normal, or " |
| 63 | + "verbose (every search iteration); the files always have everything") |
| 64 | + p.add_argument("--heartbeat", type=float, default=60.0, metavar="SECONDS", |
| 65 | + help="seconds between heartbeats (status.json and the run log; the terminal " |
| 66 | + "block at `normal` comes every 15 min, at `verbose` every heartbeat)") |
| 67 | + p.add_argument("--status", nargs="?", const="", default=None, metavar="FOLDER", |
| 68 | + help="print what a running (or the last) study in FOLDER is doing, from " |
| 69 | + "another terminal (default: output_dir/run_name of study.yaml); runs and " |
| 70 | + "writes nothing; exit 2 when there is no status") |
57 | 71 | return p |
58 | 72 |
|
59 | 73 |
|
60 | 74 | def main(argv: Sequence[str] | None = None) -> int: |
61 | 75 | parser = build_parser() |
62 | 76 | args = parser.parse_args(argv) |
63 | 77 | config = Path(args.config) |
| 78 | + if args.heartbeat <= 0: |
| 79 | + parser.error("--heartbeat must be a positive number of seconds") |
| 80 | + if args.status is not None: |
| 81 | + clash = [f for f, on in (("--only", args.only), ("--force", args.force), |
| 82 | + ("--verify", args.verify), ("--check", args.check), |
| 83 | + ("--fresh-cache", args.fresh_cache), |
| 84 | + ("--view", args.view is not None), ("--serial", args.serial), |
| 85 | + ("--workers", args.workers)) if on] |
| 86 | + if clash: |
| 87 | + parser.error(f"--status only reads a running or finished study; it cannot be " |
| 88 | + f"combined with {', '.join(clash)}") |
| 89 | + from sagetrans.io.view import default_folder |
| 90 | + |
| 91 | + folder = Path(args.status) if args.status else default_folder(config, args.out) |
| 92 | + lines = runlog.read_status(folder) |
| 93 | + if lines is None: |
| 94 | + print(f"no status in {folder}") |
| 95 | + return EXIT_INPUTS |
| 96 | + print("\n".join(lines)) |
| 97 | + return EXIT_OK |
64 | 98 | if args.view is not None: |
65 | 99 | clash = [f for f, on in (("--only", args.only), ("--force", args.force), |
66 | 100 | ("--verify", args.verify), ("--check", args.check), |
@@ -95,17 +129,19 @@ def main(argv: Sequence[str] | None = None) -> int: |
95 | 129 | outcome = run_study( |
96 | 130 | config, mode=args.mode, only=only, force=args.force, fresh_cache=args.fresh_cache, |
97 | 131 | workers=1 if args.serial else args.workers, out_dir=args.out, junit=args.junit, |
98 | | - verify=args.verify, |
| 132 | + verify=args.verify, log_level=args.log_level, heartbeat=args.heartbeat, |
99 | 133 | ) # fmt: skip |
100 | 134 | for m in outcome.messages: |
101 | | - print(m, file=sys.stderr) |
| 135 | + print(runlog.ascii_line(m), file=sys.stderr) |
102 | 136 | if outcome.folder is not None: |
103 | | - print(f"\nresults: {outcome.folder}") |
104 | | - print(f" report: {outcome.folder / 'report.md'}") |
105 | | - print(f" run record: {outcome.folder / 'run_record.json'} [{outcome.label}]") |
| 137 | + out = [f"\nresults: {outcome.folder}", |
| 138 | + f" report: {outcome.folder / 'report.md'}", |
| 139 | + f" run record: {outcome.folder / 'run_record.json'} [{outcome.label}]"] |
106 | 140 | if outcome.gate is not None: |
107 | | - print(outcome.gate.summary()) |
108 | | - print(f"done in {outcome.seconds / 60:.1f} min") |
| 141 | + out.append(outcome.gate.summary()) |
| 142 | + out.append(f"done in {outcome.seconds / 60:.1f} min") |
| 143 | + for line in out: |
| 144 | + print(runlog.ascii_line(line)) |
109 | 145 | return outcome.exit_code |
110 | 146 |
|
111 | 147 |
|
|
0 commit comments