slurmpast 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slurmpast-0.2.0/LICENSE +21 -0
- slurmpast-0.2.0/PKG-INFO +256 -0
- slurmpast-0.2.0/README.md +215 -0
- slurmpast-0.2.0/pyproject.toml +123 -0
- slurmpast-0.2.0/setup.cfg +4 -0
- slurmpast-0.2.0/src/slurmpast/__init__.py +47 -0
- slurmpast-0.2.0/src/slurmpast/__main__.py +6 -0
- slurmpast-0.2.0/src/slurmpast/_version.py +1 -0
- slurmpast-0.2.0/src/slurmpast/cli.py +644 -0
- slurmpast-0.2.0/src/slurmpast/demo.py +311 -0
- slurmpast-0.2.0/src/slurmpast/diagnose.py +622 -0
- slurmpast-0.2.0/src/slurmpast/duration.py +278 -0
- slurmpast-0.2.0/src/slurmpast/index.py +471 -0
- slurmpast-0.2.0/src/slurmpast/logs.py +271 -0
- slurmpast-0.2.0/src/slurmpast/model.py +820 -0
- slurmpast-0.2.0/src/slurmpast/nodes.py +326 -0
- slurmpast-0.2.0/src/slurmpast/patterns.py +347 -0
- slurmpast-0.2.0/src/slurmpast/py.typed +0 -0
- slurmpast-0.2.0/src/slurmpast/render.py +898 -0
- slurmpast-0.2.0/src/slurmpast/report.py +587 -0
- slurmpast-0.2.0/src/slurmpast/sacct.py +643 -0
- slurmpast-0.2.0/src/slurmpast/site.py +163 -0
- slurmpast-0.2.0/src/slurmpast/sizing.py +365 -0
- slurmpast-0.2.0/src/slurmpast/theme.py +77 -0
- slurmpast-0.2.0/src/slurmpast/tui.py +1687 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/PKG-INFO +256 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/SOURCES.txt +47 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/dependency_links.txt +1 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/entry_points.txt +3 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/requires.txt +11 -0
- slurmpast-0.2.0/src/slurmpast.egg-info/top_level.txt +1 -0
- slurmpast-0.2.0/tests/test_audit.py +392 -0
- slurmpast-0.2.0/tests/test_cli.py +155 -0
- slurmpast-0.2.0/tests/test_cpu_freq.py +117 -0
- slurmpast-0.2.0/tests/test_diagnose.py +272 -0
- slurmpast-0.2.0/tests/test_duration.py +140 -0
- slurmpast-0.2.0/tests/test_extraction.py +508 -0
- slurmpast-0.2.0/tests/test_grouping_names.py +90 -0
- slurmpast-0.2.0/tests/test_index.py +291 -0
- slurmpast-0.2.0/tests/test_layout.py +359 -0
- slurmpast-0.2.0/tests/test_nodes.py +247 -0
- slurmpast-0.2.0/tests/test_patterns.py +294 -0
- slurmpast-0.2.0/tests/test_portability.py +1040 -0
- slurmpast-0.2.0/tests/test_readability.py +774 -0
- slurmpast-0.2.0/tests/test_sacct.py +304 -0
- slurmpast-0.2.0/tests/test_sizing.py +193 -0
- slurmpast-0.2.0/tests/test_timespec.py +85 -0
- slurmpast-0.2.0/tests/test_tui.py +703 -0
- slurmpast-0.2.0/tests/test_ui_usability.py +636 -0
slurmpast-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 slurmpost contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
slurmpast-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slurmpast
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Why did your Slurm jobs fail? A post-mortem dashboard for finished jobs.
|
|
5
|
+
Author-email: Youzhi Yu <yuyouzhi666@icloud.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PursuitOfDataScience/slurmpast
|
|
8
|
+
Project-URL: Repository, https://github.com/PursuitOfDataScience/slurmpast.git
|
|
9
|
+
Project-URL: Documentation, https://github.com/PursuitOfDataScience/slurmpast#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/PursuitOfDataScience/slurmpast/issues
|
|
11
|
+
Keywords: slurm,hpc,sacct,postmortem,job-failure,tui,seff
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
24
|
+
Classifier: Topic :: System :: Monitoring
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: textual<9,>=0.86
|
|
31
|
+
Requires-Dist: rich>=13.3.3
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff<0.17,>=0.15; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy<3,>=2.3; extra == "dev"
|
|
38
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
<h1 align="center">slurmpast</h1>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<strong>How your finished Slurm jobs actually ran — and what the next one should ask for.</strong>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<a href="https://github.com/PursuitOfDataScience/slurmpast/actions/workflows/ci.yml"><img src="https://github.com/PursuitOfDataScience/slurmpast/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
50
|
+
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+">
|
|
51
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License">
|
|
52
|
+
<img src="https://img.shields.io/badge/tests-791-brightgreen.svg" alt="791 tests">
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<img src="assets/screenshot-overview.svg" width="900" alt="slurmpast overview: finished jobs rolled into workloads, ranked by resource use.">
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
**Before** the run, [`slurmate`](https://github.com/PursuitOfDataScience/slurmate)
|
|
60
|
+
builds the request. **During** it,
|
|
61
|
+
[`slurmwatch`](https://github.com/PursuitOfDataScience/slurmwatch) watches.
|
|
62
|
+
**After** it, `slurmpast` tells you what happened and what to change.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install slurmpast
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
slurmpast # dashboard, last 7 days
|
|
70
|
+
slurmpast --sizing # what to request next time, per workload
|
|
71
|
+
slurmpast 51170455 # one job, every field Slurm recorded
|
|
72
|
+
sp # short alias
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Keys: `enter` open · `q` back · digits jump to a row · `/` search · `f` filter ·
|
|
76
|
+
`s` sort · `n` nodes · `p` patterns · `y` copy · `?` help.
|
|
77
|
+
No Slurm to hand? `slurmpast --demo`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## What to request next time
|
|
82
|
+
|
|
83
|
+
The reason to read finished jobs. Over-requesting narrows which nodes can host a
|
|
84
|
+
job and reserves capacity nobody else can use; under-requesting kills the run.
|
|
85
|
+
Your own history settles both.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
argonne35-pretrain test · 101 runs
|
|
89
|
+
--time raise to 10:00:00 (requested 8h05m, observed 7h58m03s at p95)
|
|
90
|
+
p95 of 90 completed runs is 7h58m03s (longest 7h58m23s); +25% headroom.
|
|
91
|
+
--mem already about right
|
|
92
|
+
--cpus-per-task already about right
|
|
93
|
+
#SBATCH --time=10:00:00
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That workload was running on a **seven-minute margin**. Four rules keep the
|
|
97
|
+
advice honest:
|
|
98
|
+
|
|
99
|
+
- A **TIMEOUT never sizes walltime down** — its elapsed is truncated at the
|
|
100
|
+
limit, so it bounds the true runtime only from below.
|
|
101
|
+
- A **hung run is not evidence of needing more time.** One real workload hit a
|
|
102
|
+
30-minute wall 99 times on a median of 0.56 CPU-seconds; a longer limit buys a
|
|
103
|
+
longer hang.
|
|
104
|
+
- An **OOM kill outranks MaxRSS.** It is an event, not a sample.
|
|
105
|
+
- Below three usable runs it says **"not enough evidence"** rather than guessing.
|
|
106
|
+
|
|
107
|
+
## One job
|
|
108
|
+
|
|
109
|
+
Same row idiom as `slurmwatch`, so a job you watched running looks like the same
|
|
110
|
+
object afterwards. The marker carries the resource's identity, the bar and the
|
|
111
|
+
number carry the magnitude, and the verdict lives in the findings below.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
● TIME ████████████████▉░ 94.0% · 01:52:49 of the 02:00:00 limit
|
|
115
|
+
● CPU █████████████▎░░░░ 73.5% · 2.9 of 4 cores busy
|
|
116
|
+
● MEM ████████████████▋░ 92.3% · 184.6 GiB of the 200.0 GiB limit
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Three rows, because a gauge needs a ceiling to be a fraction of. Kernel share,
|
|
120
|
+
disk rate and GPU count have none, so they are printed as numbers below rather
|
|
121
|
+
than as bars that can never fill — an unfillable `░░░░` reads as a measured zero.
|
|
122
|
+
|
|
123
|
+
Below that: every field Slurm recorded, then the findings.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
gpu
|
|
127
|
+
devices 3 gpu-hours 5.6
|
|
128
|
+
utilization 87.4%
|
|
129
|
+
device memory 35.4 GiB peak
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
That `utilization` is real wherever `gres.conf` sets `AutoDetect=nvml`. Where the
|
|
133
|
+
cluster does not gather it, the row says so and names the setting instead of
|
|
134
|
+
printing a zero.
|
|
135
|
+
|
|
136
|
+
## Why the numbers differ from `seff`
|
|
137
|
+
|
|
138
|
+
Seven traps, each found on a real record, each with a regression test naming the
|
|
139
|
+
job id:
|
|
140
|
+
|
|
141
|
+
| Trap | Reality |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `ReqMem` is `0n` on **2,130 of 6,574** jobs | the real ceiling is in `AllocTRES` |
|
|
144
|
+
| `AllocTRES` `mem=` is the **allocation total** | `--mem` and the cgroup are per *node*; a 2-node `--mem=8G` job records `mem=16G`, so dividing MaxRSS by it understates memory by the node count |
|
|
145
|
+
| `MaxRSS` reports **51.25 GiB against a 40 GiB limit** that OOM-killed | under `jobacct_gather/linux` it sums RSS across the process tree, double-counting shared pages — so the caveat is read from your `JobAcctGatherType`, not assumed |
|
|
146
|
+
| `MaxRSS` differs **4000×** between steps of one job | take the max, not a step |
|
|
147
|
+
| `TotalCPU` exists **only on steps** | read it from `.batch` |
|
|
148
|
+
| `State=RUNNING, End=Unknown` months after death | `Elapsed` becomes *now − start*; one record was 65% of a GPU-hour total |
|
|
149
|
+
| `sacct --state=X` returns **zero rows** without `-E` | always pass an end time |
|
|
150
|
+
|
|
151
|
+
A value that cannot be read prints `n/a`, never `0`.
|
|
152
|
+
|
|
153
|
+
## Any Slurm cluster
|
|
154
|
+
|
|
155
|
+
Developed on one cluster, but nothing here is calibrated to it. Every place the
|
|
156
|
+
scheduler differs between sites is negotiated rather than assumed, and each of
|
|
157
|
+
these is a regression test in `tests/test_portability.py`:
|
|
158
|
+
|
|
159
|
+
| What varies | What it does about it |
|
|
160
|
+
|---|---|
|
|
161
|
+
| **Field names change.** Slurm 23.02 renamed `Reserved` → `Planned` | asks for whichever spelling your `sacct` accepts and reads it back under one name. Treating it as merely *optional* silently dropped queue wait on every cluster from 23.02 on |
|
|
162
|
+
| **`SLURM_TIME_FORMAT`** rewrites every timestamp | pinned to `standard` for the child process. A site exporting `relative` turns `2026-04-29T14:55:48` into `29 Apr 14:55`, which stops dates parsing and makes the chronological sort alphabetical on a month name |
|
|
163
|
+
| **`--constraint="v100\|a100"`** puts a pipe in a column, and `--parsable2` does not escape it | asks for an ASCII unit separator via `--delimiter` (in sacct since 17.11), falling back if absent. A pipe in field 32 shifted every memory, CPU and disk column after it |
|
|
164
|
+
| **Hostlists** can be `unit[0-31]rack[0-41]`, `node[0001-0010]-int`, `cn_[01-02]` | full expansion, differential-tested against `scontrol show hostnames`. The multi-range form used to yield `unit0rack[0-2]` *as a node name*, hiding every node in such an allocation |
|
|
165
|
+
| **GPUs** appear as `gres/gpu=4`, `gres/gpu:a100=4`, or pre-20.11 `AllocGRES=gpu:4` | all three read; typed entries summed |
|
|
166
|
+
| **`gres/gpuutil`** is gathered wherever `gres.conf` sets `AutoDetect=nvml` | real GPU utilization and peak HBM where recorded — and the idle-GPU finding becomes a measurement instead of an inference from host CPU |
|
|
167
|
+
| **`JobAcctGatherType`** decides what MaxRSS *is* | `jobacct_gather/cgroup` gives a genuine high-water mark, so the "double-counts shared pages" warning is withheld there rather than repeated |
|
|
168
|
+
| **`StdOut`/`StdErr`** exist from Slurm 21.08 | used, with `%j`/`%A`/`%a`/`%x`/`%N` expanded, before falling back to guessing a filename |
|
|
169
|
+
| **`--me`** needs Slurm 20.02 | falls back to `-u <you>`, not to an unfiltered `squeue` over the whole cluster |
|
|
170
|
+
| **Accounting may be off**, or `sacct` absent | a one-line explanation and exit 2, never a traceback; the query has a timeout so an unreachable `slurmdbd` cannot hang the dashboard |
|
|
171
|
+
|
|
172
|
+
`slurmpast --demo` pins a synthetic cluster config too, so the demo looks the
|
|
173
|
+
same on a login node and on a laptop.
|
|
174
|
+
|
|
175
|
+
## Built for a real history
|
|
176
|
+
|
|
177
|
+
6,581 jobs in seven months. A flat list of that is not an interface, so the
|
|
178
|
+
landing screen is a **workload rollup ranked by resource use** — a 5-run group
|
|
179
|
+
that cost 400 GPU-hours outranks 400 two-second probes. The top 20 rows carry
|
|
180
|
+
93.4% of it, and what is below the fold is stated rather than dropped.
|
|
181
|
+
|
|
182
|
+
Job names encode parameters, so raw names barely group: 1,624 distinct names roll
|
|
183
|
+
up to 1,687 groups. Collapsing digit runs (`s1e20`, `s2e47` → `s#e#`) folds that
|
|
184
|
+
to 592, and a `NAMES` column says how many real names a pattern covers.
|
|
185
|
+
|
|
186
|
+
No cache: the full seven-month query is 1.54s and the index builds in 0.04s,
|
|
187
|
+
which does not justify a database. Logs are read only when you open a job.
|
|
188
|
+
|
|
189
|
+
## Everything it extracts
|
|
190
|
+
|
|
191
|
+
85 `sacct` fields — every one of the 107 this Slurm offers that carries a
|
|
192
|
+
distinct measurement, with pure redundancy (`DBIndex`, `BlockID`, duplicate
|
|
193
|
+
spellings of the same TRES) left out. The wide query costs 2.26s over seven
|
|
194
|
+
months against 1.38s for a minimal 27, so nobody has to re-run it because the
|
|
195
|
+
number they wanted was never collected.
|
|
196
|
+
|
|
197
|
+
| group | captured |
|
|
198
|
+
|---|---|
|
|
199
|
+
| **cpu** | total, **user vs kernel split**, utilization, frequency, per-task min/average, slowest task with its node |
|
|
200
|
+
| **memory** | ceiling **per node and per allocation**, peak with the node and task that hit it, average, task imbalance, virtual size, page faults |
|
|
201
|
+
| **filesystem** | **read and write separately**, rate |
|
|
202
|
+
| **gpu** | devices, GPU-hours, and **utilization and peak HBM where the site gathers them** |
|
|
203
|
+
| **timing** | submit/start/end, elapsed, limit, queue wait, **backfill vs main scheduler**, priority |
|
|
204
|
+
| **outcome** | state, exit code and signal, worst step exit, reason |
|
|
205
|
+
| **provenance** | work directory, recorded stdout/stderr paths, **the submit line itself** |
|
|
206
|
+
| per step | all of the above, for `.batch`, `.extern` and each srun step |
|
|
207
|
+
|
|
208
|
+
Two were entirely invisible with a narrow `--format`: **disk writes** (one run
|
|
209
|
+
read 112 GB and wrote 139 GB) and the **user/kernel CPU split** (791 of 6,582
|
|
210
|
+
jobs exceed 30% kernel time — syscall overhead no other tool surfaces).
|
|
211
|
+
|
|
212
|
+
`--json` emits all of it — 174 values per job. Nothing is captured and then hidden; a test fails if a field is read but never surfaced.
|
|
213
|
+
|
|
214
|
+
## Also finds
|
|
215
|
+
|
|
216
|
+
**Repeated failure.** `seff` describes one job; `slurmwatch` describes one live
|
|
217
|
+
run. Neither can say *"you submitted this 115 times and it died 99 times."*
|
|
218
|
+
|
|
219
|
+
**Hand-searched memory.** One real series walked `48G → 32G → 32G → 17G → 12G →
|
|
220
|
+
12G → 14G → 16G → 18G`, then succeeded at 32G — a value that had already OOM'd,
|
|
221
|
+
proving `--mem` was never the variable.
|
|
222
|
+
|
|
223
|
+
**Nodes that eat jobs**, controlled for workload, with Wilson intervals and a
|
|
224
|
+
ready-to-paste `--exclude`. Uncontrolled, one node looked 25% bad almost entirely
|
|
225
|
+
because a buggy campaign landed there — so a node is only called `worse` when its
|
|
226
|
+
interval clears the baseline.
|
|
227
|
+
|
|
228
|
+
## Notes
|
|
229
|
+
|
|
230
|
+
Selecting text just works: mouse capture is off by default, so your terminal
|
|
231
|
+
handles selection as it does anywhere else. `M` hands the mouse to the app;
|
|
232
|
+
`y`/`Y` copy via OSC 52 and also write `~/.cache/slurmpast/clip.txt`.
|
|
233
|
+
|
|
234
|
+
The analysis modules import no third-party package and work as a library:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from slurmpast import History, Sacct
|
|
238
|
+
from slurmpast.sizing import recommend
|
|
239
|
+
|
|
240
|
+
history = History(Sacct().history(user="you", since="now-30days"))
|
|
241
|
+
for advice in recommend(history.groups[0].jobs):
|
|
242
|
+
print(advice.flag, advice.verdict, advice.suggestion)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Developed against Slurm 20.11.8 and Textual 0.89–8.2; verified against the Slurm
|
|
246
|
+
documentation and release notes from 17.11 to 26.05, and against `scontrol show
|
|
247
|
+
hostnames` for node-name expansion. CI covers Python 3.10–3.13, both Textual
|
|
248
|
+
ends, and a no-Slurm machine.
|
|
249
|
+
|
|
250
|
+
What is *measured* is portable; what is *judged* is not. The thresholds — when a
|
|
251
|
+
CPU share is low, when kernel time is heavy, when a node counts as worse — are
|
|
252
|
+
heuristics from one cluster's history. They are conservative and each one is a
|
|
253
|
+
named constant, so recalibrating means editing a number rather than the logic.
|
|
254
|
+
`--nodes` is the piece most likely to need it.
|
|
255
|
+
|
|
256
|
+
MIT.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<h1 align="center">slurmpast</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>How your finished Slurm jobs actually ran — and what the next one should ask for.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/PursuitOfDataScience/slurmpast/actions/workflows/ci.yml"><img src="https://github.com/PursuitOfDataScience/slurmpast/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+">
|
|
10
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License">
|
|
11
|
+
<img src="https://img.shields.io/badge/tests-791-brightgreen.svg" alt="791 tests">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="assets/screenshot-overview.svg" width="900" alt="slurmpast overview: finished jobs rolled into workloads, ranked by resource use.">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
**Before** the run, [`slurmate`](https://github.com/PursuitOfDataScience/slurmate)
|
|
19
|
+
builds the request. **During** it,
|
|
20
|
+
[`slurmwatch`](https://github.com/PursuitOfDataScience/slurmwatch) watches.
|
|
21
|
+
**After** it, `slurmpast` tells you what happened and what to change.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install slurmpast
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
slurmpast # dashboard, last 7 days
|
|
29
|
+
slurmpast --sizing # what to request next time, per workload
|
|
30
|
+
slurmpast 51170455 # one job, every field Slurm recorded
|
|
31
|
+
sp # short alias
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Keys: `enter` open · `q` back · digits jump to a row · `/` search · `f` filter ·
|
|
35
|
+
`s` sort · `n` nodes · `p` patterns · `y` copy · `?` help.
|
|
36
|
+
No Slurm to hand? `slurmpast --demo`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## What to request next time
|
|
41
|
+
|
|
42
|
+
The reason to read finished jobs. Over-requesting narrows which nodes can host a
|
|
43
|
+
job and reserves capacity nobody else can use; under-requesting kills the run.
|
|
44
|
+
Your own history settles both.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
argonne35-pretrain test · 101 runs
|
|
48
|
+
--time raise to 10:00:00 (requested 8h05m, observed 7h58m03s at p95)
|
|
49
|
+
p95 of 90 completed runs is 7h58m03s (longest 7h58m23s); +25% headroom.
|
|
50
|
+
--mem already about right
|
|
51
|
+
--cpus-per-task already about right
|
|
52
|
+
#SBATCH --time=10:00:00
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That workload was running on a **seven-minute margin**. Four rules keep the
|
|
56
|
+
advice honest:
|
|
57
|
+
|
|
58
|
+
- A **TIMEOUT never sizes walltime down** — its elapsed is truncated at the
|
|
59
|
+
limit, so it bounds the true runtime only from below.
|
|
60
|
+
- A **hung run is not evidence of needing more time.** One real workload hit a
|
|
61
|
+
30-minute wall 99 times on a median of 0.56 CPU-seconds; a longer limit buys a
|
|
62
|
+
longer hang.
|
|
63
|
+
- An **OOM kill outranks MaxRSS.** It is an event, not a sample.
|
|
64
|
+
- Below three usable runs it says **"not enough evidence"** rather than guessing.
|
|
65
|
+
|
|
66
|
+
## One job
|
|
67
|
+
|
|
68
|
+
Same row idiom as `slurmwatch`, so a job you watched running looks like the same
|
|
69
|
+
object afterwards. The marker carries the resource's identity, the bar and the
|
|
70
|
+
number carry the magnitude, and the verdict lives in the findings below.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
● TIME ████████████████▉░ 94.0% · 01:52:49 of the 02:00:00 limit
|
|
74
|
+
● CPU █████████████▎░░░░ 73.5% · 2.9 of 4 cores busy
|
|
75
|
+
● MEM ████████████████▋░ 92.3% · 184.6 GiB of the 200.0 GiB limit
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Three rows, because a gauge needs a ceiling to be a fraction of. Kernel share,
|
|
79
|
+
disk rate and GPU count have none, so they are printed as numbers below rather
|
|
80
|
+
than as bars that can never fill — an unfillable `░░░░` reads as a measured zero.
|
|
81
|
+
|
|
82
|
+
Below that: every field Slurm recorded, then the findings.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
gpu
|
|
86
|
+
devices 3 gpu-hours 5.6
|
|
87
|
+
utilization 87.4%
|
|
88
|
+
device memory 35.4 GiB peak
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That `utilization` is real wherever `gres.conf` sets `AutoDetect=nvml`. Where the
|
|
92
|
+
cluster does not gather it, the row says so and names the setting instead of
|
|
93
|
+
printing a zero.
|
|
94
|
+
|
|
95
|
+
## Why the numbers differ from `seff`
|
|
96
|
+
|
|
97
|
+
Seven traps, each found on a real record, each with a regression test naming the
|
|
98
|
+
job id:
|
|
99
|
+
|
|
100
|
+
| Trap | Reality |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `ReqMem` is `0n` on **2,130 of 6,574** jobs | the real ceiling is in `AllocTRES` |
|
|
103
|
+
| `AllocTRES` `mem=` is the **allocation total** | `--mem` and the cgroup are per *node*; a 2-node `--mem=8G` job records `mem=16G`, so dividing MaxRSS by it understates memory by the node count |
|
|
104
|
+
| `MaxRSS` reports **51.25 GiB against a 40 GiB limit** that OOM-killed | under `jobacct_gather/linux` it sums RSS across the process tree, double-counting shared pages — so the caveat is read from your `JobAcctGatherType`, not assumed |
|
|
105
|
+
| `MaxRSS` differs **4000×** between steps of one job | take the max, not a step |
|
|
106
|
+
| `TotalCPU` exists **only on steps** | read it from `.batch` |
|
|
107
|
+
| `State=RUNNING, End=Unknown` months after death | `Elapsed` becomes *now − start*; one record was 65% of a GPU-hour total |
|
|
108
|
+
| `sacct --state=X` returns **zero rows** without `-E` | always pass an end time |
|
|
109
|
+
|
|
110
|
+
A value that cannot be read prints `n/a`, never `0`.
|
|
111
|
+
|
|
112
|
+
## Any Slurm cluster
|
|
113
|
+
|
|
114
|
+
Developed on one cluster, but nothing here is calibrated to it. Every place the
|
|
115
|
+
scheduler differs between sites is negotiated rather than assumed, and each of
|
|
116
|
+
these is a regression test in `tests/test_portability.py`:
|
|
117
|
+
|
|
118
|
+
| What varies | What it does about it |
|
|
119
|
+
|---|---|
|
|
120
|
+
| **Field names change.** Slurm 23.02 renamed `Reserved` → `Planned` | asks for whichever spelling your `sacct` accepts and reads it back under one name. Treating it as merely *optional* silently dropped queue wait on every cluster from 23.02 on |
|
|
121
|
+
| **`SLURM_TIME_FORMAT`** rewrites every timestamp | pinned to `standard` for the child process. A site exporting `relative` turns `2026-04-29T14:55:48` into `29 Apr 14:55`, which stops dates parsing and makes the chronological sort alphabetical on a month name |
|
|
122
|
+
| **`--constraint="v100\|a100"`** puts a pipe in a column, and `--parsable2` does not escape it | asks for an ASCII unit separator via `--delimiter` (in sacct since 17.11), falling back if absent. A pipe in field 32 shifted every memory, CPU and disk column after it |
|
|
123
|
+
| **Hostlists** can be `unit[0-31]rack[0-41]`, `node[0001-0010]-int`, `cn_[01-02]` | full expansion, differential-tested against `scontrol show hostnames`. The multi-range form used to yield `unit0rack[0-2]` *as a node name*, hiding every node in such an allocation |
|
|
124
|
+
| **GPUs** appear as `gres/gpu=4`, `gres/gpu:a100=4`, or pre-20.11 `AllocGRES=gpu:4` | all three read; typed entries summed |
|
|
125
|
+
| **`gres/gpuutil`** is gathered wherever `gres.conf` sets `AutoDetect=nvml` | real GPU utilization and peak HBM where recorded — and the idle-GPU finding becomes a measurement instead of an inference from host CPU |
|
|
126
|
+
| **`JobAcctGatherType`** decides what MaxRSS *is* | `jobacct_gather/cgroup` gives a genuine high-water mark, so the "double-counts shared pages" warning is withheld there rather than repeated |
|
|
127
|
+
| **`StdOut`/`StdErr`** exist from Slurm 21.08 | used, with `%j`/`%A`/`%a`/`%x`/`%N` expanded, before falling back to guessing a filename |
|
|
128
|
+
| **`--me`** needs Slurm 20.02 | falls back to `-u <you>`, not to an unfiltered `squeue` over the whole cluster |
|
|
129
|
+
| **Accounting may be off**, or `sacct` absent | a one-line explanation and exit 2, never a traceback; the query has a timeout so an unreachable `slurmdbd` cannot hang the dashboard |
|
|
130
|
+
|
|
131
|
+
`slurmpast --demo` pins a synthetic cluster config too, so the demo looks the
|
|
132
|
+
same on a login node and on a laptop.
|
|
133
|
+
|
|
134
|
+
## Built for a real history
|
|
135
|
+
|
|
136
|
+
6,581 jobs in seven months. A flat list of that is not an interface, so the
|
|
137
|
+
landing screen is a **workload rollup ranked by resource use** — a 5-run group
|
|
138
|
+
that cost 400 GPU-hours outranks 400 two-second probes. The top 20 rows carry
|
|
139
|
+
93.4% of it, and what is below the fold is stated rather than dropped.
|
|
140
|
+
|
|
141
|
+
Job names encode parameters, so raw names barely group: 1,624 distinct names roll
|
|
142
|
+
up to 1,687 groups. Collapsing digit runs (`s1e20`, `s2e47` → `s#e#`) folds that
|
|
143
|
+
to 592, and a `NAMES` column says how many real names a pattern covers.
|
|
144
|
+
|
|
145
|
+
No cache: the full seven-month query is 1.54s and the index builds in 0.04s,
|
|
146
|
+
which does not justify a database. Logs are read only when you open a job.
|
|
147
|
+
|
|
148
|
+
## Everything it extracts
|
|
149
|
+
|
|
150
|
+
85 `sacct` fields — every one of the 107 this Slurm offers that carries a
|
|
151
|
+
distinct measurement, with pure redundancy (`DBIndex`, `BlockID`, duplicate
|
|
152
|
+
spellings of the same TRES) left out. The wide query costs 2.26s over seven
|
|
153
|
+
months against 1.38s for a minimal 27, so nobody has to re-run it because the
|
|
154
|
+
number they wanted was never collected.
|
|
155
|
+
|
|
156
|
+
| group | captured |
|
|
157
|
+
|---|---|
|
|
158
|
+
| **cpu** | total, **user vs kernel split**, utilization, frequency, per-task min/average, slowest task with its node |
|
|
159
|
+
| **memory** | ceiling **per node and per allocation**, peak with the node and task that hit it, average, task imbalance, virtual size, page faults |
|
|
160
|
+
| **filesystem** | **read and write separately**, rate |
|
|
161
|
+
| **gpu** | devices, GPU-hours, and **utilization and peak HBM where the site gathers them** |
|
|
162
|
+
| **timing** | submit/start/end, elapsed, limit, queue wait, **backfill vs main scheduler**, priority |
|
|
163
|
+
| **outcome** | state, exit code and signal, worst step exit, reason |
|
|
164
|
+
| **provenance** | work directory, recorded stdout/stderr paths, **the submit line itself** |
|
|
165
|
+
| per step | all of the above, for `.batch`, `.extern` and each srun step |
|
|
166
|
+
|
|
167
|
+
Two were entirely invisible with a narrow `--format`: **disk writes** (one run
|
|
168
|
+
read 112 GB and wrote 139 GB) and the **user/kernel CPU split** (791 of 6,582
|
|
169
|
+
jobs exceed 30% kernel time — syscall overhead no other tool surfaces).
|
|
170
|
+
|
|
171
|
+
`--json` emits all of it — 174 values per job. Nothing is captured and then hidden; a test fails if a field is read but never surfaced.
|
|
172
|
+
|
|
173
|
+
## Also finds
|
|
174
|
+
|
|
175
|
+
**Repeated failure.** `seff` describes one job; `slurmwatch` describes one live
|
|
176
|
+
run. Neither can say *"you submitted this 115 times and it died 99 times."*
|
|
177
|
+
|
|
178
|
+
**Hand-searched memory.** One real series walked `48G → 32G → 32G → 17G → 12G →
|
|
179
|
+
12G → 14G → 16G → 18G`, then succeeded at 32G — a value that had already OOM'd,
|
|
180
|
+
proving `--mem` was never the variable.
|
|
181
|
+
|
|
182
|
+
**Nodes that eat jobs**, controlled for workload, with Wilson intervals and a
|
|
183
|
+
ready-to-paste `--exclude`. Uncontrolled, one node looked 25% bad almost entirely
|
|
184
|
+
because a buggy campaign landed there — so a node is only called `worse` when its
|
|
185
|
+
interval clears the baseline.
|
|
186
|
+
|
|
187
|
+
## Notes
|
|
188
|
+
|
|
189
|
+
Selecting text just works: mouse capture is off by default, so your terminal
|
|
190
|
+
handles selection as it does anywhere else. `M` hands the mouse to the app;
|
|
191
|
+
`y`/`Y` copy via OSC 52 and also write `~/.cache/slurmpast/clip.txt`.
|
|
192
|
+
|
|
193
|
+
The analysis modules import no third-party package and work as a library:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from slurmpast import History, Sacct
|
|
197
|
+
from slurmpast.sizing import recommend
|
|
198
|
+
|
|
199
|
+
history = History(Sacct().history(user="you", since="now-30days"))
|
|
200
|
+
for advice in recommend(history.groups[0].jobs):
|
|
201
|
+
print(advice.flag, advice.verdict, advice.suggestion)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Developed against Slurm 20.11.8 and Textual 0.89–8.2; verified against the Slurm
|
|
205
|
+
documentation and release notes from 17.11 to 26.05, and against `scontrol show
|
|
206
|
+
hostnames` for node-name expansion. CI covers Python 3.10–3.13, both Textual
|
|
207
|
+
ends, and a no-Slurm machine.
|
|
208
|
+
|
|
209
|
+
What is *measured* is portable; what is *judged* is not. The thresholds — when a
|
|
210
|
+
CPU share is low, when kernel time is heavy, when a node counts as worse — are
|
|
211
|
+
heuristics from one cluster's history. They are conservative and each one is a
|
|
212
|
+
named constant, so recalibrating means editing a number rather than the logic.
|
|
213
|
+
`--nodes` is the piece most likely to need it.
|
|
214
|
+
|
|
215
|
+
MIT.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "slurmpast"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Why did your Slurm jobs fail? A post-mortem dashboard for finished jobs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Youzhi Yu", email = "yuyouzhi666@icloud.com" }]
|
|
13
|
+
keywords = ["slurm", "hpc", "sacct", "postmortem", "job-failure", "tui", "seff"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Natural Language :: English",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering",
|
|
26
|
+
"Topic :: System :: Distributed Computing",
|
|
27
|
+
"Topic :: System :: Monitoring",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
# Same floor as slurmwatch: >=0.86 is where the theme system landed, which the
|
|
33
|
+
# dashboard uses to register its own theme, and it covers DataTable/Header/
|
|
34
|
+
# Footer/Input. Matching the floor means the two tools resolve to one Textual
|
|
35
|
+
# in a shared environment instead of fighting over it.
|
|
36
|
+
"textual>=0.86,<9",
|
|
37
|
+
# Imported directly by render.py and tui.py (`rich.text.Text`), and render.py
|
|
38
|
+
# is on the plain-text path too, so `--plain` needs it as much as the
|
|
39
|
+
# dashboard does. It resolved anyway as a transitive dependency of textual,
|
|
40
|
+
# which is exactly why it was easy to miss -- a package you import belongs in
|
|
41
|
+
# your own dependency list, not borrowed from someone else's. Floor matches
|
|
42
|
+
# what textual itself requires.
|
|
43
|
+
"rich>=13.3.3",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/PursuitOfDataScience/slurmpast"
|
|
48
|
+
Repository = "https://github.com/PursuitOfDataScience/slurmpast.git"
|
|
49
|
+
Documentation = "https://github.com/PursuitOfDataScience/slurmpast#readme"
|
|
50
|
+
Issues = "https://github.com/PursuitOfDataScience/slurmpast/issues"
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
slurmpast = "slurmpast.cli:main"
|
|
54
|
+
# "sp" is a short alias for the same entry point -- same behaviour, less typing.
|
|
55
|
+
# Parallels slurmwatch's "sw"; verified free on PATH.
|
|
56
|
+
sp = "slurmpast.cli:main"
|
|
57
|
+
|
|
58
|
+
[project.optional-dependencies]
|
|
59
|
+
dev = [
|
|
60
|
+
"pytest>=7",
|
|
61
|
+
"pytest-asyncio>=0.23",
|
|
62
|
+
"pytest-cov>=4",
|
|
63
|
+
# Bounded on purpose: an unpinned linter makes CI a moving target, and a
|
|
64
|
+
# minor ruff release changing its rule defaults is exactly what broke the
|
|
65
|
+
# first CI run here.
|
|
66
|
+
"ruff>=0.15,<0.17",
|
|
67
|
+
"mypy>=2.3,<3", # >=2.3: 2.2 added the MRO override check CI tripped on
|
|
68
|
+
"build>=1.0",
|
|
69
|
+
"twine>=5.0",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.setuptools.dynamic]
|
|
73
|
+
version = { attr = "slurmpast._version.__version__" }
|
|
74
|
+
|
|
75
|
+
[tool.setuptools.packages.find]
|
|
76
|
+
where = ["src"]
|
|
77
|
+
include = ["slurmpast*"]
|
|
78
|
+
|
|
79
|
+
[tool.setuptools.package-data]
|
|
80
|
+
slurmpast = ["py.typed"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 100
|
|
84
|
+
target-version = "py310"
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
# Pinned explicitly, matching slurmwatch. Without a `select` the gate is whatever
|
|
88
|
+
# the installed ruff defaults to -- which is how CI failed on a commit that was
|
|
89
|
+
# clean locally: 0.15 and 0.16 disagree about the default set, and 0.16 surfaced
|
|
90
|
+
# 206 findings the local run never saw. An explicit list makes the gate mean the
|
|
91
|
+
# same thing on every machine.
|
|
92
|
+
select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "ARG", "C4", "T20"]
|
|
93
|
+
# UP031 (%-format -> f-string) fires on 206 sites. Ruff auto-converts 113 of them
|
|
94
|
+
# with all tests still passing, so the migration is safe -- but it also introduces
|
|
95
|
+
# 28 line-length violations and leaves 93 sites for hand conversion. That is a
|
|
96
|
+
# refactor, not a lint fix, and a half-migrated file is worse than a consistent
|
|
97
|
+
# one. Recorded here rather than silently dropped.
|
|
98
|
+
ignore = ["UP031"]
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.per-file-ignores]
|
|
101
|
+
# A CLI writes to stdout; that is the point of it.
|
|
102
|
+
"src/slurmpast/cli.py" = ["T201"]
|
|
103
|
+
# Tests deliberately accept arguments they do not use (stub runners that must
|
|
104
|
+
# match a real signature) and build fixtures with sacct's own CamelCase names.
|
|
105
|
+
"tests/*" = ["ARG", "N802", "N803", "N806", "N815"]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.format]
|
|
108
|
+
quote-style = "double"
|
|
109
|
+
|
|
110
|
+
[tool.mypy]
|
|
111
|
+
python_version = "3.10"
|
|
112
|
+
ignore_missing_imports = true
|
|
113
|
+
warn_redundant_casts = true
|
|
114
|
+
warn_unreachable = true
|
|
115
|
+
# NOT strict = true, which is what slurmwatch runs. Under --strict this codebase
|
|
116
|
+
# reports 495 findings, almost all missing parameter/return annotations across
|
|
117
|
+
# ~4,700 lines. That is worth doing, but it is an annotation project rather than
|
|
118
|
+
# part of setting up CI, so the gate is set where it currently passes clean and
|
|
119
|
+
# the gap is recorded here rather than silently skipped.
|
|
120
|
+
|
|
121
|
+
[tool.pytest.ini_options]
|
|
122
|
+
testpaths = ["tests"]
|
|
123
|
+
asyncio_mode = "strict"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""slurmpast — why did your Slurm jobs fail?
|
|
2
|
+
|
|
3
|
+
A post-mortem for finished jobs. ``slurmate`` builds the request, ``slurmwatch``
|
|
4
|
+
watches the run, this reads the wreckage.
|
|
5
|
+
|
|
6
|
+
The analysis modules (``sacct``, ``diagnose``, ``patterns``, ``nodes``,
|
|
7
|
+
``index``, ``site``) import no third-party package, so they are usable as a
|
|
8
|
+
library on a login node without a UI framework. Textual is only pulled in by
|
|
9
|
+
``tui``.
|
|
10
|
+
|
|
11
|
+
Nothing here is specific to one cluster. Field names, output delimiters and
|
|
12
|
+
timestamp formats are negotiated with the local ``sacct``; what a measurement
|
|
13
|
+
means where sites differ (``JobAcctGatherType``, whether GPUs reach accounting at
|
|
14
|
+
all) is read from ``scontrol show config`` by ``site``. See that module and the
|
|
15
|
+
traps listed in ``sacct``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
from .diagnose import diagnose, looks_like_noop
|
|
20
|
+
from .index import GroupStats, History, build_groups
|
|
21
|
+
from .model import CRITICAL, INFO, WARNING, Finding, Job, Step, Verdict
|
|
22
|
+
from .sacct import Sacct, SacctError
|
|
23
|
+
|
|
24
|
+
# Only the type, deliberately. Re-exporting the `site()` accessor here would bind
|
|
25
|
+
# the name `slurmpast.site` to a function and shadow the submodule of the same
|
|
26
|
+
# name, so `slurmpast.site.reset_cache` would stop resolving. Import it as
|
|
27
|
+
# `from slurmpast.site import site`.
|
|
28
|
+
from .site import Site
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"__version__",
|
|
32
|
+
"Sacct",
|
|
33
|
+
"SacctError",
|
|
34
|
+
"Job",
|
|
35
|
+
"Step",
|
|
36
|
+
"Finding",
|
|
37
|
+
"Verdict",
|
|
38
|
+
"History",
|
|
39
|
+
"GroupStats",
|
|
40
|
+
"Site",
|
|
41
|
+
"build_groups",
|
|
42
|
+
"diagnose",
|
|
43
|
+
"looks_like_noop",
|
|
44
|
+
"CRITICAL",
|
|
45
|
+
"WARNING",
|
|
46
|
+
"INFO",
|
|
47
|
+
]
|