Correct where babel-slurm-resources gets its wall times, and what a retry does to them - #1019
Open
gaurav wants to merge 6 commits into
Open
Correct where babel-slurm-resources gets its wall times, and what a retry does to them#1019gaurav wants to merge 6 commits into
gaurav wants to merge 6 commits into
Conversation
docs/tools/Resources.md claimed the tool read "elapsed wall time" from the SLURM efficiency report, and that the per-rule logs' start/end timestamps fed the sizing. Neither is true: every duration in the report is the benchmark TSV's `s` column, and the only efficiency-report columns read are RequestedMem_MB and NCPUS. The logs contribute their declared `resources:` line and nothing else. The distinction matters for sizing a runtime: Snakemake times a job from the inside, so `s` excludes SLURM queue time, while the efficiency report's Elapsed_sec does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EfficiencyRow.elapsed_sec was parsed and merged across shards, and then read by nobody -- which is how the docs came to claim it was the tool's source of wall time. Drop the field and pin the real source with a test that makes the efficiency report disagree with the benchmark by two orders of magnitude. max_rss_mb and total_cpu_sec stay: they are also unread, but reading 0 out of them is how you confirm this cluster's accounting is missing rather than assume it, and test_parse asserts exactly that. RuleLog.start/end/failed are the same pattern -- parsed, tested, unread by the sizing report -- and are left alone here; the errors subcommand is the plausible consumer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g one conservative The previous commit said the benchmark's `s` "excludes SLURM queue time -- the conservative choice for sizing a time limit". Both halves were wrong, and the run under data/ says so: - sacct's Elapsed (the efficiency report's Elapsed_sec) also excludes queue time; it spans job start to end. The number that includes queueing is babel-slurm-errors', which subtracts the Snakemake *submit* timestamp -- median 35s and up to 306s more than Elapsed_sec across the 57 rules in both. - --time polices Elapsed, which was >= the benchmark's `s` for 57 of 57 rules (median +5s, the job's setup and teardown). Sizing from `s` therefore understates the policed span slightly rather than overstating it. Records the measured numbers so the claim is checkable against a rerun. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Corrects SLURM resource-sizing documentation and removes unused elapsed-time parsing.
Changes:
- Documents benchmark TSVs as the wall-time source.
- Removes
EfficiencyRow.elapsed_sec. - Adds regression coverage for wall-time selection.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
docs/tools/Resources.md |
Clarifies timing sources and semantics. |
src/tools/slurm/CLAUDE.md |
Updates maintainer guidance. |
src/tools/slurm/parse.py |
Removes unused elapsed-time parsing. |
tests/tools/slurm/test_resources.py |
Tests benchmark-derived wall time. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+52
to
+55
| The two measure different things -- Snakemake times the job from the inside, SLURM times the | ||
| allocation -- so a report mixing them would size a time limit against a number that includes | ||
| queue time. The docs claimed the efficiency report's elapsed column was used; it never was, and | ||
| it is no longer even parsed. |
Comment on lines
+58
to
+60
| That includes wall time: **every duration in the report is the benchmark TSV's `s` column** | ||
| (`Benchmark.seconds`, the per-column worst case across a rule's rows), never the efficiency | ||
| report's `Elapsed_sec` or the span between a log's timestamps. |
Comment on lines
+10
to
+11
| columns. Wall time too: every duration `babel-slurm-resources` reports is the benchmark's `s` | ||
| column, and the only efficiency-report columns it reads are `RequestedMem_MB` and `NCPUS`. |
Reverses this branch's deletion of EfficiencyRow.elapsed_sec. The docs drifted because they claimed the field was used, not because the field existed -- deleting data was the wrong lever, and after measuring the three clocks the column now has a documented meaning (the allocation span, ~5s above the benchmark's execution span) that someone would otherwise have to rediscover. EfficiencyRow's docstring now says which fields are consumed and why each of the others is kept: max_rss_mb/total_cpu_sec read 0 on a cluster with no per-step accounting, so a non-zero one is the signal that Hatteras started recording it. RuleLog.start/end/failed stay too, but they are the riskier kind of unread data: they come from matching free-form log text, so a Snakemake format change would break them silently with no consumer to notice. Their test now runs against lines copied verbatim from logs/rule_process_ec_ids/52504.log (the babel-1.18 run of 2026-07-13) rather than an invented log -- a real resources: line carries mem_mib and disk_mib beside mem_mb, which is precisely what _MEM_RE has to not confuse, and the invented one had neither. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-derive them A frozen fixture cannot protect a regex over free-form log text: it pins the format it was copied from, not the one the cluster emits next year. So these three fields could rot silently no matter how real their test data was, and nothing consumed them. Deleting them takes _BRACKET_TS_RE, _FAILURE_RE and _parse_bracket_timestamps with them -- they had no other caller -- and lets read_rule_logs() read one log per rule instead of every retry's log in full, which it was doing only to compute the unread `failed` flag. What took the work was knowing which marker means what, so that is written down above RuleLog: the bracketed-timestamp format and that the span is the job's own execution, the three failure markers and that they must be checked in every attempt's log rather than the newest, a real log to look at, and a pointer to parse_job_events() as the better source. EfficiencyRow.elapsed_sec stays: a named CSV column read into a float cannot quietly start meaning something else, so keeping it costs one line and no risk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/tools/slurm/test_resources.py:55
- This docstring contradicts both the implementation and the new documentation: sacct's
Elapsed_secexcludes queue time, andread_efficiency_report()still parses it intoEfficiencyRow. Please describe the allocation span and the deliberately retained field accurately.
The two measure different things -- Snakemake times the job from the inside, SLURM times the
allocation -- so a report mixing them would size a time limit against a number that includes
queue time. The docs claimed the efficiency report's elapsed column was used; it never was, and
it is no longer even parsed.
src/tools/slurm/CLAUDE.md:11
- This still leaves contradictory guidance in two places:
read_efficiency_report()parses five report columns, so these are only the two columns it consumes, whilesrc/tools/slurm/__init__.py:15retains the old claim that elapsed wall time is consumed from the efficiency report. Please change “reads” to “consumes” here and correct the package docstring as part of this documentation fix.
columns. Wall time too: every duration `babel-slurm-resources` reports is the benchmark's `s`
column, and the only efficiency-report columns it reads are `RequestedMem_MB` and `NCPUS`.
A Babel build takes several sbatch runs, and rules fail and retry inside each one, so "how long did this rule take" has three different answers. Measured on the 2026jul22 and babel-1.17 builds: - Benchmarks are the last *successful* execution. Snakemake rewrites the TSV per execution (all 355 files in 2026jul22 hold one row) and writes nothing for a failed job -- the two rules whose every attempt failed left no file at all. So a rule that died at 30s and then succeeded in 2h reports 2h, and the per-column max only ever fires for repeat(). leftover_umls is the worked example: failed at 9885s/17254s/2148s, succeeded at ~2367s, benchmark 2292s. - Across runs that makes the benchmark set a mixture -- each rule's numbers come from whichever run last succeeded at it -- and a success is sticky: a rule that succeeded in run 1 and failed in run 3 still reports run 1. - The efficiency report maxes over every attempt including failures; rows are per job step and no state column is consulted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
docs/tools/Resources.mdsaidbabel-slurm-resourcesreads "elapsed wall time" from the SLURM efficiency report, and that the per-rule logs' start/end timestamps feed the sizing. Checking the code, neither is true.Every duration the report prints — the runtime-fit percentage, the recommendation, the "slowest rule still on the default" line, the tables and the CSV — comes from
Benchmark.seconds, whichread_benchmarks()takes from the benchmark TSV'sscolumn (per-column worst case across a rule's rows).EfficiencyRow.elapsed_secis parsed and merged across shards but read by nobody; the logs contribute their declaredresources:line and nothing else.The distinction is not cosmetic, and getting it right took measuring rather than reasoning. A run records a job's duration over three different spans: the benchmark's
s(the rule's execution, timed from inside the job), sacct'sElapsed_sec(job start → end), andbabel-slurm-errors' figure (submit → finish, so it includes time spent pending in the queue). Across the run underdata/:Elapsed_sec≥sfor 57 of 57 rules, median +5s — job setup and teardown; and submit→finish exceededElapsed_secby a median of 35s, max 306s, over 60s for 15 rules.--timepolicesElapsed, so sizing fromsunderstates the policed span slightly, which is an argument against trimming a runtime to a hair above the benchmark.What a retry, or a second run, does to each number
A Babel build takes several
sbatchruns and retries rules inside each, so "how long did this rule take" has three answers. Measured across the 2026jul22 and babel-1.17 builds (21 rules there have both failed and successful attempts):leftover_umlson babel-1.17: failed at 9885s, 17254s, 2148s, succeeded at ~2367s, benchmark2292s. The per-column max inread_benchmarks()therefore only fires forrepeat().53155.0,.1, …), several per attempt, and no state column is consulted. Harmless for the two fields consumed, and one more reasonElapsed_secis the wrong source for a duration: a job killed at its time limit would win themax.babel-slurm-errors— one entry per attempt, marked failed or not; the only one of the three that can say a rule failed twice before it worked.This turned up a real bug in that third tool — a failed attempt's reported duration is the time from submit to the end of the whole run (
process_ec_ids: died in 39s, reported 84,260s), because Snakemake logsError in rulea second time in its end-of-run summary andparse_job_events()keeps the later timestamp. Fixed separately in #1020; nothing in this PR depends on it.What changed
RequestedMem_MBandNCPUS, those two columns and no others), and a new "Three clocks, and which one a time limit polices" section giving the table above with its measured gaps — so the next person comparing ababel-slurm-errorsduration against anElapsed_secknows why they differ.src/tools/slurm/CLAUDE.mdgets the one-line version, since that is where someone touching the parser looks first.EfficiencyRow.elapsed_secis kept, and now explains itself. An earlier commit on this branch deleted it as dead data; that was the wrong lever. The docs drifted because they claimed the field was used, not because it existed, and the column is reliable (unlikeMaxRSS/TotalCPUhere) with a meaning worth keeping wired up.EfficiencyRow's docstring now states which two fields are consumed and why each of the others is deliberately kept —max_rss_mb/total_cpu_secread 0 on a cluster without per-step accounting, so a non-zero one is the signal that Hatteras started recording it.read_benchmarks()andread_efficiency_report()— those docstrings are where someone lands when they wonder whether a number survived a retry.test_wall_time_comes_from_the_benchmark_not_the_efficiency_reportbuilds a run whose efficiency report disagrees with the benchmark by two orders of magnitude (99999s vs 100s) and asserts the recommendation reports 100.Unread data is not automatically dead, but it isn't automatically safe either
Two kinds, and this PR treats them differently:
elapsed_sec,max_rss_mb,total_cpu_seconEfficiencyRow. These cannot quietly start meaning something else, and if the cluster begins populating the usage columns they are already correct. Kept, with a docstring saying which two fields are consumed and why each of the rest earns its line.RuleLog.start/end/failed, from_BRACKET_TS_REand_FAILURE_RE. These rot silently when Snakemake changes its output, and no test can prevent it: a fixture pins the format it was copied from, not the one the cluster emits next year. Removed, and replaced by a comment aboveRuleLoggiving the recipe — the bracketed-timestamp format and the fact that the span is the job's own execution (so it trackss, notElapsed), the three failure markers and the fact that they must be checked in every attempt's log rather than the newest, a real log to look at, and a pointer toparse_job_events()as the better source anyway.Deleting them takes
_BRACKET_TS_RE,_FAILURE_REand_parse_bracket_timestamps()with them — no other caller — and letsread_rule_logs()read one log per rule instead of every retry's log in full, which it was doing solely to compute the unreadfailedflag.The rule-log test keeps its verbatim fixture from
logs/rule_process_ec_ids/52504.log(babel-1.18, 2026-07-13), because the point it proves is still live: a realresources:line readsdisk_mb=50000, disk=50 GB, disk_mib=47684, mem_mb=16000, mem=16 GB, mem_mib=15259, somem_mibanddisk_mbsit right beside the value_MEM_REhas to pick out. The fixture it replaced had neither. Re-verified after the change: all 5 real logs underdata/logsparse withmem_mbpopulated.Checks
uv run pytest -m unit -q— 507 passed.ruff check,ruff format --check,rumdl checkclean.🤖 Generated with Claude Code