From 29126cdb36b0d8de89a5b5f12831b59eca25f215 Mon Sep 17 00:00:00 2001 From: Derek Feichtinger Date: Mon, 29 Jun 2026 09:09:01 +0200 Subject: [PATCH] added bin cache metadata info show. Get all states by default --- slurm-eff-tool.py | 69 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/slurm-eff-tool.py b/slurm-eff-tool.py index a75c853..c55a28f 100755 --- a/slurm-eff-tool.py +++ b/slurm-eff-tool.py @@ -29,6 +29,7 @@ import statistics import subprocess import sys import signal +import time from collections import defaultdict from dataclasses import dataclass, field, fields, asdict from pathlib import Path @@ -38,7 +39,7 @@ from typing import Any, Iterable, Mapping import msgpack import gzip -VERSION=0.2 +VERSION=0.21 # Default GB per CPUs for the cluster default_mempercpu_gb = 2.0 @@ -608,11 +609,30 @@ def write_binary_cache(records: list[JobRecord], filename): cache = [asdict(r) for r in records] cache_blob = { "slurm_eff_version": VERSION, + "cmdline": " ".join(sys.argv), + "created": time.time(), "records": cache } with gzip.open(filename, "wb") as f: msgpack.pack(cache_blob, f) +def read_binary_cache(filename: str) -> dict[str, str or list[JobRecord]]: + result = {} + with gzip.open(filename, "rb") as f: + blob = msgpack.unpack(f, raw=False) + for mdfield in ['slurm_eff_version', 'cmdline', 'created']: + result[mdfield] = blob.get(mdfield,'undefined') + + if result['slurm_eff_version'] != VERSION: + sys.stderr.write(f"WARNING: cache was written by version f{result['slurm_eff_version']}, but we are running version {VERSION}\n") + result['records'] = [JobRecord(**d) for d in blob["records"]] + return result + +def show_cache_info(filename: str): + cache = read_binary_cache(filename) + for mdfield in ['slurm_eff_version', 'cmdline', 'created']: + print(f'{mdfield}: {cache[mdfield]}') + def base_job_id(jobid: str) -> str: return re.split(r"[._]", jobid, maxsplit=1)[0] @@ -1051,21 +1071,30 @@ def parse_args(argv: list[str]) -> argparse.Namespace: Examples: # first get an overview (-U/--aggr-user) and write a cachefile - slurm-eff-tool -O sacct.cache -U + slurm-eff-tool -B sacct.cache -U + slurm-eff-tool.py -B sacct.cache --start 2026-05-01 --end 2026-05-22 -U + slurm-eff-tool.py -B sacct.cache --start 2026-05-01 --end now -U + # now you can read the cachefile for later runs and e.g. sort based on waste_Mem - slurm-eff-tool -F sacct.cache -U -s=-Y + slurm-eff-tool -L sacct.cache -U -s=-waste_mem + + # only list a specific user's summary lines + slurm-eff-tool.py -L sacct.cache -U -u dfeich + # list that user's single jobs + slurm-eff-tool.py -L sacct.cache -u dfeich - slurm-eff-tool.py -F sacct.cache --start 2026-05-01 --end 2026-05-22 -u dfeich - slurm-eff-tool.py -F sacct.cache -S 2026-05-01 -E now -u dfeich # supports multiple sort keys - slurm-eff-tool.py -F sacct.cache --aggr-user --sdev -s cpu,-mem,time - # cluster jobs by Regexps - slurm-eff-tool.py -F sacct.cache -u dfeich -R '^vasp','^gromacs' --json + slurm-eff-tool.py -L sacct.cache --aggr-user --sdev -s cpu,-mem,time + + # you can cluster jobs by Regexps applying to the job names + slurm-eff-tool.py -L sacct.cache -u dfeich -R '^vasp','^gromacs' + # supports flexibel output formatting - slurm-eff-tool.py -F sacct.cache -o username,Y - # only print rows that evaluate to true based on expression - slurm-eff-tool.py -F sacct.cache -U --expr "(waste_Mem > 2000 and Mem_Eff < 20) and MaxRSS_max/AllocMem < 0.5" - """ + slurm-eff-tool.py -L sacct.cache -o username,Y + + # only print rows that evaluate to true based on arithmetic expressions + slurm-eff-tool.py -L sacct.cache -U --expr "(waste_Mem > 2000 and Mem_Eff < 20) and MaxRSS_max/AllocMem < 0.5" +""" ) p.add_argument("-S", "--start", help="sacct start time, passed to sacct -S", @@ -1074,13 +1103,14 @@ def parse_args(argv: list[str]) -> argparse.Namespace: default="now") p.add_argument("-u", "--user", help="restrict to one user; passed as sacct -u unless reading from cache") p.add_argument("--state", "--job-state", dest="state", - default="COMPLETED", + default=None, help="sacct state filter, e.g. COMPLETED,FAILED,TIMEOUT") p.add_argument("-O", "--output-raw", help="write raw sacct output cache to this file") p.add_argument("-F", "--from-raw", help="read raw sacct output cache from this file instead of running sacct") p.add_argument("-B", "--write-binary-cache", help="write a binary cache file in msgpack format") p.add_argument("-L", "--load-binary-cache", help="load a binary cache file in msgpack format") + p.add_argument("-i", "--info", help="show information for the given binary cache file") p.add_argument("-U", "--aggr-user", action="store_true", help="aggregate jobs by user, CPUs, nodes, ReqMem, and timelimit") p.add_argument( "-R", @@ -1118,6 +1148,10 @@ def main(argv: list[str] | None = None) -> int: args = parse_args(argv or sys.argv[1:]) + if args.info: + show_cache_info(args.info) + sys.exit(0) + output_columns = PRESET_COLUMNS["default"] if args.preset: output_columns = PRESET_COLUMNS[args.preset] @@ -1131,13 +1165,8 @@ def main(argv: list[str] | None = None) -> int: # CREATE RECORDS FROM SACCT QUERY OR FILE, OR LOAD PROCESSED RECORDS FROM BINARY CACHE if args.load_binary_cache: - with gzip.open(args.load_binary_cache, "rb") as f: - blob = msgpack.unpack(f, raw=False) - version_str = blob["slurm_eff_version"] - if version_str != VERSION: - sys.stderr.write(f"WARNING: cache was written by version f{version_str}, but this is version {VERSION}\n") - # print(f"DEBUG: Loading from a binary cache created by version {version_str}") - records = [JobRecord(**d) for d in blob["records"]] + cache = read_binary_cache(args.load_binary_cache) + records = cache['records'] else: if args.from_raw: rows = read_cache_raw(args.from_raw)