richqueue 1.11.2__tar.gz → 1.12.1__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.
- {richqueue-1.11.2 → richqueue-1.12.1}/PKG-INFO +2 -4
- {richqueue-1.11.2 → richqueue-1.12.1}/pyproject.toml +1 -4
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/main.py +0 -2
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/slurm.py +387 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/.gitignore +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/LICENSE +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/README.md +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/__init__.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/console.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/job.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/layout.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/log.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/submit.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/table.py +0 -0
- {richqueue-1.11.2 → richqueue-1.12.1}/richqueue/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: richqueue
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.1
|
|
4
4
|
Summary: RichQueue
|
|
5
5
|
Project-URL: Homepage, https://github.com/mwinokan/RichQueue/issues
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/mwinokan/RichQueue/issues
|
|
@@ -10,8 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: ipython
|
|
14
|
-
Requires-Dist: mpytools
|
|
15
13
|
Requires-Dist: pandas
|
|
16
14
|
Requires-Dist: rich
|
|
17
15
|
Requires-Dist: typer
|
|
@@ -3,14 +3,13 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
[project]
|
|
5
5
|
name = "richqueue"
|
|
6
|
-
version = "1.
|
|
6
|
+
version = "1.12.1"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Max Winokan", email = "max@winokan.com" },
|
|
9
9
|
]
|
|
10
10
|
description = "RichQueue"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
|
-
requires = []
|
|
14
13
|
classifiers = [
|
|
15
14
|
"Programming Language :: Python :: 3",
|
|
16
15
|
"License :: OSI Approved :: MIT License",
|
|
@@ -20,8 +19,6 @@ dependencies = [
|
|
|
20
19
|
"typer",
|
|
21
20
|
"rich",
|
|
22
21
|
"pandas",
|
|
23
|
-
"mpytools",
|
|
24
|
-
"IPython",
|
|
25
22
|
]
|
|
26
23
|
[project.urls]
|
|
27
24
|
"Homepage" = "https://github.com/mwinokan/RichQueue/issues"
|
|
@@ -3,6 +3,8 @@ from rich.panel import Panel
|
|
|
3
3
|
from rich.text import Text
|
|
4
4
|
import subprocess
|
|
5
5
|
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
6
8
|
from .console import console
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
import datetime
|
|
@@ -13,6 +15,9 @@ from .tools import human_timedelta
|
|
|
13
15
|
|
|
14
16
|
METADATA = {}
|
|
15
17
|
|
|
18
|
+
SLURM_VERSION = None
|
|
19
|
+
CLUSTER_NAME = None
|
|
20
|
+
|
|
16
21
|
PANEL_PADDING = 4
|
|
17
22
|
|
|
18
23
|
### CONSTRUCT LAYOUT
|
|
@@ -162,6 +167,9 @@ def get_squeue(
|
|
|
162
167
|
user: str | None = None, job: int | None = None, **kwargs
|
|
163
168
|
) -> "pandas.DataFrame":
|
|
164
169
|
|
|
170
|
+
if not has_json():
|
|
171
|
+
return get_squeue_text(user=user, job=job, **kwargs)
|
|
172
|
+
|
|
165
173
|
if job:
|
|
166
174
|
assert isinstance(job, int)
|
|
167
175
|
command = f"squeue --job={job} --json"
|
|
@@ -246,6 +254,11 @@ def get_sacct(
|
|
|
246
254
|
|
|
247
255
|
hist = hist or 4
|
|
248
256
|
|
|
257
|
+
if not has_json():
|
|
258
|
+
return get_sacct_text(
|
|
259
|
+
user=user, hist=hist, job=job, hist_unit=hist_unit, **kwargs
|
|
260
|
+
)
|
|
261
|
+
|
|
249
262
|
if job:
|
|
250
263
|
command = f"sacct --job={job} --json"
|
|
251
264
|
elif user:
|
|
@@ -313,6 +326,9 @@ def get_sacct(
|
|
|
313
326
|
|
|
314
327
|
def get_sinfo(**kwargs) -> "pandas.DataFrame":
|
|
315
328
|
|
|
329
|
+
if not has_json():
|
|
330
|
+
return get_sinfo_text(**kwargs)
|
|
331
|
+
|
|
316
332
|
command = "sinfo -N --json"
|
|
317
333
|
|
|
318
334
|
try:
|
|
@@ -393,6 +409,310 @@ def combined_df(**kwargs) -> "DataFrame":
|
|
|
393
409
|
return df
|
|
394
410
|
|
|
395
411
|
|
|
412
|
+
### SLURM < 21.08 (NO --json)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def get_slurm_version() -> tuple:
|
|
416
|
+
"""(major, minor) from e.g. 'slurm 20.11.9', empty if unknown"""
|
|
417
|
+
|
|
418
|
+
global SLURM_VERSION
|
|
419
|
+
|
|
420
|
+
if SLURM_VERSION is None:
|
|
421
|
+
try:
|
|
422
|
+
output = subprocess.run(
|
|
423
|
+
["squeue", "--version"], capture_output=True, text=True
|
|
424
|
+
).stdout
|
|
425
|
+
except OSError:
|
|
426
|
+
output = ""
|
|
427
|
+
# e.g. 'slurm-wlm 20.11.4' from Debian packages
|
|
428
|
+
match = re.search(r"(\d+)\.(\d+)", output)
|
|
429
|
+
SLURM_VERSION = (int(match[1]), int(match[2])) if match else ()
|
|
430
|
+
|
|
431
|
+
return SLURM_VERSION
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def has_json() -> bool:
|
|
435
|
+
"""squeue/sacct/sinfo --json were added in Slurm 21.08"""
|
|
436
|
+
version = get_slurm_version()
|
|
437
|
+
return not version or version >= (21, 8)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def get_cluster_name() -> str:
|
|
441
|
+
"""Text output has no metadata, so ask scontrol once"""
|
|
442
|
+
|
|
443
|
+
global CLUSTER_NAME
|
|
444
|
+
|
|
445
|
+
if CLUSTER_NAME is None:
|
|
446
|
+
output = subprocess.run(
|
|
447
|
+
["scontrol", "show", "config"], capture_output=True, text=True
|
|
448
|
+
).stdout
|
|
449
|
+
match = re.search(r"^ClusterName\s*=\s*(\S+)", output, re.MULTILINE)
|
|
450
|
+
CLUSTER_NAME = match[1] if match else "unknown HPC"
|
|
451
|
+
|
|
452
|
+
return CLUSTER_NAME
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def run_text(command: list[str], columns: list[str]) -> "pandas.DataFrame":
|
|
456
|
+
"""Run a Slurm command that prints TEXT_SEP after every field"""
|
|
457
|
+
|
|
458
|
+
# user's SQUEUE_*, SACCT_* and SINFO_* variables can filter or duplicate rows
|
|
459
|
+
env = {
|
|
460
|
+
k: v
|
|
461
|
+
for k, v in os.environ.items()
|
|
462
|
+
if not k.startswith(("SQUEUE_", "SACCT_", "SINFO_"))
|
|
463
|
+
}
|
|
464
|
+
env["SLURM_TIME_FORMAT"] = "standard"
|
|
465
|
+
|
|
466
|
+
process = subprocess.run(command, capture_output=True, env=env)
|
|
467
|
+
|
|
468
|
+
if process.returncode:
|
|
469
|
+
stderr = process.stderr.decode(errors="replace").strip()
|
|
470
|
+
raise NotOnClusterError(f"{command[0]} failed: {stderr}")
|
|
471
|
+
|
|
472
|
+
# split on TEXT_SEP + newline in case a field contains a newline
|
|
473
|
+
records = process.stdout.decode(errors="replace").split(TEXT_SEP + "\n")[:-1]
|
|
474
|
+
rows = [record.split(TEXT_SEP) for record in records]
|
|
475
|
+
return DataFrame(rows, columns=columns)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def get_squeue_text(
|
|
479
|
+
user: str | None = None, job: int | None = None, **kwargs
|
|
480
|
+
) -> "pandas.DataFrame":
|
|
481
|
+
|
|
482
|
+
fields = ",".join(f"{field}:0{TEXT_SEP}" for field in TEXT_FIELDS["squeue"])
|
|
483
|
+
command = ["squeue", "--noheader", "--noconvert", "--array-unique", "-O", fields]
|
|
484
|
+
|
|
485
|
+
if job:
|
|
486
|
+
command.append(f"--jobs={job}")
|
|
487
|
+
elif user:
|
|
488
|
+
command += ["-u", user]
|
|
489
|
+
|
|
490
|
+
columns = list(TEXT_FIELDS["squeue"].values())
|
|
491
|
+
|
|
492
|
+
try:
|
|
493
|
+
df = run_text(command, columns)
|
|
494
|
+
except NotOnClusterError as e:
|
|
495
|
+
if job and "Invalid job id specified" in str(e):
|
|
496
|
+
return get_sacct(user=user, job=job, **kwargs)
|
|
497
|
+
raise
|
|
498
|
+
|
|
499
|
+
# recently finished jobs are known to slurmctld but hidden by default
|
|
500
|
+
if job and not len(df):
|
|
501
|
+
df = run_text(command + ["--states=all"], columns)
|
|
502
|
+
|
|
503
|
+
if job and not len(df):
|
|
504
|
+
return get_sacct(user=user, job=job, **kwargs)
|
|
505
|
+
|
|
506
|
+
global METADATA
|
|
507
|
+
|
|
508
|
+
METADATA = {"cluster_name": get_cluster_name()}
|
|
509
|
+
|
|
510
|
+
if not len(df):
|
|
511
|
+
return DataFrame(columns=COLUMNS["squeue"])
|
|
512
|
+
|
|
513
|
+
for key in ["job_id", "node_count", "cpus", "tasks", "restart_cnt"]:
|
|
514
|
+
df[key] = df[key].astype(int)
|
|
515
|
+
|
|
516
|
+
for key in ["name", "qos", "command", "dependency"]:
|
|
517
|
+
df[key] = df[key].replace("(null)", "")
|
|
518
|
+
|
|
519
|
+
df["cpus_per_task"] = df["cpus_per_task"].map(
|
|
520
|
+
lambda x: None if x == "N/A" else int(x)
|
|
521
|
+
)
|
|
522
|
+
df["threads_per_core"] = df["threads_per_core"].map(
|
|
523
|
+
lambda x: None if x == "*" else int(x)
|
|
524
|
+
)
|
|
525
|
+
df[["memory_per_cpu", "memory_per_node"]] = df.apply(
|
|
526
|
+
split_text_memory, axis=1, result_type="expand"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
df["start_time"] = df["start_time"].map(parse_text_time)
|
|
530
|
+
df["submit_time"] = df["submit_time"].map(parse_text_time)
|
|
531
|
+
df["time_limit"] = df["time_limit"].map(parse_text_time_limit)
|
|
532
|
+
|
|
533
|
+
df["requeue"] = df["requeue"] == "1"
|
|
534
|
+
df["exclusive"] = df["exclusive"].map(lambda x: EXCLUSIVE.get(x, ""))
|
|
535
|
+
|
|
536
|
+
df["standard_output"] = df.apply(
|
|
537
|
+
lambda x: text_log_path(x, x["standard_output"]), axis=1
|
|
538
|
+
)
|
|
539
|
+
df["standard_error"] = df.apply(
|
|
540
|
+
lambda x: text_log_path(x, x["standard_error"]), axis=1
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
return df[COLUMNS["squeue"]]
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def get_sacct_text(
|
|
547
|
+
user: str | None = None,
|
|
548
|
+
hist: int | None = 4,
|
|
549
|
+
job: int | None = None,
|
|
550
|
+
hist_unit: str = "weeks",
|
|
551
|
+
**kwargs,
|
|
552
|
+
) -> "pandas.DataFrame":
|
|
553
|
+
|
|
554
|
+
fields = ",".join(TEXT_FIELDS["sacct"])
|
|
555
|
+
command = [
|
|
556
|
+
"sacct",
|
|
557
|
+
"-X",
|
|
558
|
+
"-p",
|
|
559
|
+
"-n",
|
|
560
|
+
f"--delimiter={TEXT_SEP}",
|
|
561
|
+
f"--format={fields}",
|
|
562
|
+
]
|
|
563
|
+
|
|
564
|
+
if job:
|
|
565
|
+
command.append(f"--jobs={job}")
|
|
566
|
+
else:
|
|
567
|
+
command.append(f"--starttime=now-{hist}{hist_unit}")
|
|
568
|
+
if user:
|
|
569
|
+
command += ["-u", user]
|
|
570
|
+
|
|
571
|
+
df = run_text(command, list(TEXT_FIELDS["sacct"].values()))
|
|
572
|
+
|
|
573
|
+
global METADATA
|
|
574
|
+
|
|
575
|
+
METADATA = {
|
|
576
|
+
"cluster_name": get_cluster_name(),
|
|
577
|
+
"hist": hist,
|
|
578
|
+
"hist_unit": hist_unit,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
df["job_id"] = df["job_id"].astype(int)
|
|
582
|
+
|
|
583
|
+
# e.g. "CANCELLED by 1234"
|
|
584
|
+
df["job_state"] = df["job_state"].str.split(" ").str[0]
|
|
585
|
+
|
|
586
|
+
for key in ["submit_time", "start_time", "end_time"]:
|
|
587
|
+
df[key] = df[key].map(parse_text_time)
|
|
588
|
+
|
|
589
|
+
df = df[df["job_state"] != "RUNNING"]
|
|
590
|
+
df = df[df["job_state"] != "PENDING"]
|
|
591
|
+
|
|
592
|
+
return df
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def get_sinfo_text(**kwargs) -> "pandas.DataFrame":
|
|
596
|
+
|
|
597
|
+
fields = ",".join(f"{field}:0{TEXT_SEP}" for field in TEXT_FIELDS["sinfo"])
|
|
598
|
+
command = ["sinfo", "-N", "--noheader", "-O", fields]
|
|
599
|
+
|
|
600
|
+
df = run_text(command, list(TEXT_FIELDS["sinfo"].values()))
|
|
601
|
+
|
|
602
|
+
global METADATA
|
|
603
|
+
|
|
604
|
+
METADATA = {"cluster_name": get_cluster_name()}
|
|
605
|
+
|
|
606
|
+
# e.g. "idle~", "down*"
|
|
607
|
+
df["node_state"] = df["node_state"].str.rstrip("*~#%$@+").str.upper()
|
|
608
|
+
|
|
609
|
+
# allocated/idle/other/total
|
|
610
|
+
cpus = df["cpus"].str.split("/", expand=True).astype(int)
|
|
611
|
+
df["cpus_allocated"] = cpus[0]
|
|
612
|
+
df["cpus_idle"] = cpus[1]
|
|
613
|
+
df["cpus_max"] = cpus[3]
|
|
614
|
+
df["cpu_string"] = df.apply(lambda x: f"{x.cpus_idle}/{x.cpus_max}", axis=1)
|
|
615
|
+
|
|
616
|
+
df["memory_max"] = df["memory_max"].astype(int)
|
|
617
|
+
df["memory_free"] = df["memory_free"].replace("N/A", "0").astype(int)
|
|
618
|
+
df["memory_allocated"] = df["memory_allocated"].astype(int)
|
|
619
|
+
|
|
620
|
+
df["features"] = df["features"].replace("(null)", "")
|
|
621
|
+
|
|
622
|
+
# sinfo has no per-node reservation field
|
|
623
|
+
df["reservation"] = ""
|
|
624
|
+
|
|
625
|
+
return df.drop(columns=["cpus"])
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def parse_text_time(value: str) -> datetime.datetime:
|
|
629
|
+
"""Unset times print as N/A or Unknown, the JSON path gives them as 0"""
|
|
630
|
+
try:
|
|
631
|
+
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")
|
|
632
|
+
except ValueError:
|
|
633
|
+
return datetime.datetime.fromtimestamp(0)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def parse_text_time_limit(value: str) -> str:
|
|
637
|
+
"""[days-]hours:minutes:seconds or minutes:seconds, or UNLIMITED etc."""
|
|
638
|
+
|
|
639
|
+
if ":" not in value:
|
|
640
|
+
return ""
|
|
641
|
+
|
|
642
|
+
days, _, clock = value.rpartition("-")
|
|
643
|
+
|
|
644
|
+
seconds = 0
|
|
645
|
+
for part in clock.split(":"):
|
|
646
|
+
seconds = 60 * seconds + int(part)
|
|
647
|
+
|
|
648
|
+
return human_timedelta(datetime.timedelta(days=int(days or 0), seconds=seconds))
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def mb_from_text(value: str) -> float:
|
|
652
|
+
"""e.g. '2500M', '1.50G' or '0'"""
|
|
653
|
+
match = re.fullmatch(r"([\d.]+)([KMGTP]?)", value)
|
|
654
|
+
return float(match[1]) * MB_PER_UNIT.get(match[2], 1)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def split_text_memory(row) -> tuple:
|
|
658
|
+
"""squeue doesn't say if min memory is per CPU or per node, so compare
|
|
659
|
+
with the job's total memory in its TRES string"""
|
|
660
|
+
|
|
661
|
+
memory = mb_from_text(row["min_memory"])
|
|
662
|
+
total = re.search(r"mem=([\d.]+[KMGTP]?)", row["tres"])
|
|
663
|
+
|
|
664
|
+
if total:
|
|
665
|
+
total = mb_from_text(total[1])
|
|
666
|
+
per_cpu = abs(total - memory * row["cpus"])
|
|
667
|
+
per_node = abs(total - memory * row["node_count"])
|
|
668
|
+
if per_cpu < per_node:
|
|
669
|
+
return memory, None
|
|
670
|
+
|
|
671
|
+
return None, memory
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def text_log_path(row, path: str) -> str:
|
|
675
|
+
"""Expand squeue's raw stdout/stderr path like Slurm's slurm_get_job_stdout"""
|
|
676
|
+
|
|
677
|
+
cwd = row["current_working_directory"]
|
|
678
|
+
task = row["array_task_id"]
|
|
679
|
+
|
|
680
|
+
# printed by squeue when unset
|
|
681
|
+
default = f"{cwd}/slurm-{row['job_id']}.out"
|
|
682
|
+
|
|
683
|
+
if path == "N/A" or (path == default and row["batch_flag"] == "0"):
|
|
684
|
+
return ""
|
|
685
|
+
|
|
686
|
+
if path == default:
|
|
687
|
+
if task.isdigit():
|
|
688
|
+
return f"{cwd}/slurm-{row['array_job_id']}_{task}.out"
|
|
689
|
+
return default
|
|
690
|
+
|
|
691
|
+
values = {
|
|
692
|
+
"A": row["array_job_id"],
|
|
693
|
+
"a": task if task.isdigit() else NO_VAL,
|
|
694
|
+
"j": row["job_id"],
|
|
695
|
+
"u": row["user_name"],
|
|
696
|
+
"x": row["name"],
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
# e.g. %j, %4a or %% as in Slurm 23.11's _fname_format
|
|
700
|
+
def substitute(match):
|
|
701
|
+
if match[0] == "%%":
|
|
702
|
+
return "%"
|
|
703
|
+
value = str(values[match[2]])
|
|
704
|
+
if match[2] in "Aaj":
|
|
705
|
+
value = value.zfill(min(int(match[1] or 0), 10))
|
|
706
|
+
return value
|
|
707
|
+
|
|
708
|
+
path = re.sub(r"%%|%(\d*)([Aajux])", substitute, path)
|
|
709
|
+
|
|
710
|
+
if path.startswith("/"):
|
|
711
|
+
return path
|
|
712
|
+
|
|
713
|
+
return f"{cwd}/{path}"
|
|
714
|
+
|
|
715
|
+
|
|
396
716
|
### ADD COLUMNS
|
|
397
717
|
|
|
398
718
|
|
|
@@ -512,5 +832,72 @@ COLUMNS = {
|
|
|
512
832
|
],
|
|
513
833
|
}
|
|
514
834
|
|
|
835
|
+
# ASCII unit separator, won't appear in names or paths
|
|
836
|
+
TEXT_SEP = "\x1f"
|
|
837
|
+
|
|
838
|
+
NO_VAL = 4294967294
|
|
839
|
+
|
|
840
|
+
# Slurm field: column, for text output
|
|
841
|
+
TEXT_FIELDS = {
|
|
842
|
+
"squeue": {
|
|
843
|
+
"jobid": "job_id",
|
|
844
|
+
"arrayjobid": "array_job_id",
|
|
845
|
+
"arraytaskid": "array_task_id",
|
|
846
|
+
"batchflag": "batch_flag",
|
|
847
|
+
"name": "name",
|
|
848
|
+
"state": "job_state",
|
|
849
|
+
"username": "user_name",
|
|
850
|
+
"groupname": "group_name",
|
|
851
|
+
"partition": "partition",
|
|
852
|
+
"qos": "qos",
|
|
853
|
+
"nodelist": "nodes",
|
|
854
|
+
"numnodes": "node_count",
|
|
855
|
+
"numcpus": "cpus",
|
|
856
|
+
"numtasks": "tasks",
|
|
857
|
+
"cpus-per-task": "cpus_per_task",
|
|
858
|
+
"threads": "threads_per_core",
|
|
859
|
+
"minmemory": "min_memory",
|
|
860
|
+
"tres-alloc": "tres",
|
|
861
|
+
"starttime": "start_time",
|
|
862
|
+
"submittime": "submit_time",
|
|
863
|
+
"timelimit": "time_limit",
|
|
864
|
+
"command": "command",
|
|
865
|
+
"workdir": "current_working_directory",
|
|
866
|
+
"stdout": "standard_output",
|
|
867
|
+
"stderr": "standard_error",
|
|
868
|
+
"dependency": "dependency",
|
|
869
|
+
"requeue": "requeue",
|
|
870
|
+
"restartcnt": "restart_cnt",
|
|
871
|
+
"oversubscribe": "exclusive",
|
|
872
|
+
"derivedec": "derived_exit_code",
|
|
873
|
+
},
|
|
874
|
+
"sacct": {
|
|
875
|
+
"JobIDRaw": "job_id",
|
|
876
|
+
"State": "job_state",
|
|
877
|
+
"JobName": "name",
|
|
878
|
+
"NodeList": "nodes",
|
|
879
|
+
"Partition": "partition",
|
|
880
|
+
"User": "user_name",
|
|
881
|
+
"Submit": "submit_time",
|
|
882
|
+
"Start": "start_time",
|
|
883
|
+
"End": "end_time",
|
|
884
|
+
},
|
|
885
|
+
"sinfo": {
|
|
886
|
+
"NodeList": "node_name",
|
|
887
|
+
"PartitionName": "partition",
|
|
888
|
+
"StateLong": "node_state",
|
|
889
|
+
"CPUsState": "cpus",
|
|
890
|
+
"Memory": "memory_max",
|
|
891
|
+
"FreeMem": "memory_free",
|
|
892
|
+
"AllocMem": "memory_allocated",
|
|
893
|
+
"Features": "features",
|
|
894
|
+
},
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
# squeue oversubscribe: JSON exclusive
|
|
898
|
+
EXCLUSIVE = {"NO": "true", "YES": "false", "USER": "user", "MCS": "mcs"}
|
|
899
|
+
|
|
900
|
+
MB_PER_UNIT = {"K": 1 / 1024, "M": 1, "G": 1024, "T": 1024**2, "P": 1024**3}
|
|
901
|
+
|
|
515
902
|
|
|
516
903
|
class NotOnClusterError(Exception): ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|