diff --git a/slurm-eff-tool.py b/slurm-eff-tool.py index aa1f5f0..18856e6 100755 --- a/slurm-eff-tool.py +++ b/slurm-eff-tool.py @@ -63,6 +63,8 @@ VERSION=0.3 # Default GB per CPUs for the cluster default_mempercpu_gb = 2.2 +SACCT_DELIM = "\x1f" # ASCII Unit Separator + SACCT_FIELDS = [ "JobIDRaw", "JobID", @@ -667,11 +669,13 @@ def pct(numerator: float | None, denominator: float | None) -> float | None: return None return 100.0 * numerator / denominator - +#TODO: Put the parsing on safer ground. The delimiter based parsing may +# fail in exotic cases. Better to use json (but slower) def run_sacct(args: argparse.Namespace) -> list[dict[str, str]]: cmd = [ "sacct", "-P", + f"--delimiter={SACCT_DELIM}", "-n", "--units=K", "--format=" + ",".join(SACCT_FIELDS), @@ -686,6 +690,8 @@ def run_sacct(args: argparse.Namespace) -> list[dict[str, str]]: if args.state: cmd += ["--state", args.state] + # print(f"DEBUG: sacct cmd: {cmd}") + # Include job steps because MaxRSS often lives on batch/extern/step rows. # We later collapse rows back to the base job ID. try: @@ -701,7 +707,7 @@ def run_sacct(args: argparse.Namespace) -> list[dict[str, str]]: def parse_pipe_rows(lines: Iterable[str]) -> list[dict[str, str]]: rows: list[dict[str, str]] = [] - reader = csv.reader(lines, delimiter="|") + reader = csv.reader(lines, delimiter=SACCT_DELIM) for parts in reader: if not parts: continue @@ -719,7 +725,7 @@ def write_cache_raw(path: str, rows: list[dict[str, str]]) -> None: sys.exit(1) with open(path, "w", newline="", encoding="utf-8") as f: - writer = csv.DictWriter(f, fieldnames=SACCT_FIELDS, delimiter="|", lineterminator="\n") + writer = csv.DictWriter(f, fieldnames=SACCT_FIELDS, delimiter=SACCT_DELIM, lineterminator="\n") writer.writeheader() writer.writerows(rows) @@ -727,7 +733,7 @@ def write_cache_raw(path: str, rows: list[dict[str, str]]) -> None: def read_cache_raw(path: str) -> list[dict[str, str]]: """Read a raw sacct output file.""" with open(path, newline="", encoding="utf-8") as f: - reader = csv.DictReader(f, delimiter="|") + reader = csv.DictReader(f, delimiter=SACCT_DELIM) missing = [x for x in SACCT_FIELDS if x not in (reader.fieldnames or [])] # Older cache files from versions before CPUTimeRAW can still be read. # CPU efficiency will be NA for those rows unless CPUTimeRAW is present.