richqueue 0.5__tar.gz → 0.6__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-0.5 → richqueue-0.6}/PKG-INFO +28 -2
- richqueue-0.6/README.md +28 -0
- {richqueue-0.5 → richqueue-0.6}/pyproject.toml +1 -1
- {richqueue-0.5 → richqueue-0.6}/richqueue/slurm.py +127 -7
- richqueue-0.5/README.md +0 -2
- {richqueue-0.5 → richqueue-0.6}/.gitignore +0 -0
- {richqueue-0.5 → richqueue-0.6}/LICENSE +0 -0
- {richqueue-0.5 → richqueue-0.6}/richqueue/__init__.py +0 -0
- {richqueue-0.5 → richqueue-0.6}/richqueue/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: richqueue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6
|
|
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
|
|
@@ -16,4 +16,30 @@ Requires-Dist: typer
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# RichQueue
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
21
|
+
|
|
22
|
+
Installation from a modern python environment (>3.10) should be as simple as:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
|
|
26
|
+
pip install --upgrade richqueue
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To see your live-updating SLURM queue:
|
|
30
|
+
|
|
31
|
+
```shell
|
|
32
|
+
rq
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Other options
|
|
38
|
+
|
|
39
|
+
To see more detail:
|
|
40
|
+
|
|
41
|
+
```rq --long```
|
|
42
|
+
|
|
43
|
+
To see someone else's queue:
|
|
44
|
+
|
|
45
|
+
```rq --user USER```
|
richqueue-0.6/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# RichQueue
|
|
2
|
+
|
|
3
|
+
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
4
|
+
|
|
5
|
+
Installation from a modern python environment (>3.10) should be as simple as:
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
|
|
9
|
+
pip install --upgrade richqueue
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
To see your live-updating SLURM queue:
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
rq
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Other options
|
|
21
|
+
|
|
22
|
+
To see more detail:
|
|
23
|
+
|
|
24
|
+
```rq --long```
|
|
25
|
+
|
|
26
|
+
To see someone else's queue:
|
|
27
|
+
|
|
28
|
+
```rq --user USER```
|
|
@@ -5,6 +5,7 @@ from rich.console import Console
|
|
|
5
5
|
from rich.panel import Panel
|
|
6
6
|
from rich.layout import Layout
|
|
7
7
|
from pandas import DataFrame
|
|
8
|
+
import subprocess
|
|
8
9
|
|
|
9
10
|
console = Console()
|
|
10
11
|
|
|
@@ -28,6 +29,16 @@ def color_by_state(state):
|
|
|
28
29
|
else:
|
|
29
30
|
return state
|
|
30
31
|
|
|
32
|
+
def color_by_node_state(state):
|
|
33
|
+
|
|
34
|
+
if state == "ALLOCATED":
|
|
35
|
+
return "[orange1]Allocated"
|
|
36
|
+
elif state == "MIXED":
|
|
37
|
+
return "[bright_yellow]Mixed"
|
|
38
|
+
elif state == "IDLE":
|
|
39
|
+
return "[bright_green]Idle"
|
|
40
|
+
else:
|
|
41
|
+
return f"[red]{state}"
|
|
31
42
|
|
|
32
43
|
def extract_inner(df, key, inner):
|
|
33
44
|
|
|
@@ -71,6 +82,72 @@ def extract_list(df, key):
|
|
|
71
82
|
|
|
72
83
|
df[key] = df.apply(inner, axis=1)
|
|
73
84
|
|
|
85
|
+
def mem_string(mb):
|
|
86
|
+
|
|
87
|
+
# gb, mb = divmod(mb, 1024)
|
|
88
|
+
|
|
89
|
+
# return f"{gb}G {mb}M"
|
|
90
|
+
|
|
91
|
+
return f"{mb/1024:.0f}G"
|
|
92
|
+
|
|
93
|
+
def node_table(
|
|
94
|
+
df,
|
|
95
|
+
title: str = None,
|
|
96
|
+
box: bool = False,
|
|
97
|
+
panel: bool = True,
|
|
98
|
+
idle: bool = True,
|
|
99
|
+
):
|
|
100
|
+
|
|
101
|
+
if idle:
|
|
102
|
+
df = df[df["node"].isin(["IDLE", "MIXED"])]
|
|
103
|
+
title = title or "idle nodes"
|
|
104
|
+
else:
|
|
105
|
+
title = title or "nodes"
|
|
106
|
+
|
|
107
|
+
from rich.table import Table
|
|
108
|
+
|
|
109
|
+
if box:
|
|
110
|
+
from rich.box import SIMPLE
|
|
111
|
+
|
|
112
|
+
box = SIMPLE
|
|
113
|
+
|
|
114
|
+
table = Table(title=title, box=box, header_style="")
|
|
115
|
+
|
|
116
|
+
table.add_column("[bold underline]Node", justify='left', no_wrap=True, style="bold")
|
|
117
|
+
table.add_column("[bold underline]State", justify='left', no_wrap=True, style="bold")
|
|
118
|
+
table.add_column("[green_yellow underline]Partition", justify='left', no_wrap=True, style="green_yellow")
|
|
119
|
+
table.add_column("[magenta underline]#C", justify='right', no_wrap=True, style="magenta")
|
|
120
|
+
table.add_column("[magenta underline]Memory", justify='right', no_wrap=True, style="magenta")
|
|
121
|
+
table.add_column("[dodger_blue2 underline]Features", justify='left', no_wrap=True, style="dodger_blue2")
|
|
122
|
+
|
|
123
|
+
reservation = any(df["reservation"].values)
|
|
124
|
+
|
|
125
|
+
if reservation:
|
|
126
|
+
table.add_column("[bold underline]Reservation", justify='left', no_wrap=True, style="bold")
|
|
127
|
+
|
|
128
|
+
for i,row in df.iterrows():
|
|
129
|
+
|
|
130
|
+
values = []
|
|
131
|
+
|
|
132
|
+
values.append(row.nodes)
|
|
133
|
+
# values.append(row.nodes)
|
|
134
|
+
values.append(color_by_node_state(row.node))
|
|
135
|
+
values.append(row.partition)
|
|
136
|
+
values.append(str(row.cpus_idle))
|
|
137
|
+
values.append(mem_string(row.memory_free))
|
|
138
|
+
values.append(row.features)
|
|
139
|
+
|
|
140
|
+
if reservation:
|
|
141
|
+
values.append(row.reservation)
|
|
142
|
+
|
|
143
|
+
table.add_row(*values)
|
|
144
|
+
|
|
145
|
+
# table.add_row(*[str(v) for v in row.values])
|
|
146
|
+
|
|
147
|
+
if panel:
|
|
148
|
+
return Panel(table, expand=False)
|
|
149
|
+
|
|
150
|
+
return table
|
|
74
151
|
|
|
75
152
|
def job_table(
|
|
76
153
|
df,
|
|
@@ -289,11 +366,11 @@ def show_queue(
|
|
|
289
366
|
user: None | str = None,
|
|
290
367
|
long: bool = False,
|
|
291
368
|
return_table: bool = False,
|
|
369
|
+
hist: int = 2,
|
|
370
|
+
hist_unit: str= "weeks",
|
|
292
371
|
box: bool = False,
|
|
293
372
|
):
|
|
294
373
|
|
|
295
|
-
import subprocess
|
|
296
|
-
|
|
297
374
|
if user == "all":
|
|
298
375
|
user = None
|
|
299
376
|
elif user is None:
|
|
@@ -304,7 +381,10 @@ def show_queue(
|
|
|
304
381
|
if user:
|
|
305
382
|
s_command = f"{command} -u {user} --json"
|
|
306
383
|
else:
|
|
307
|
-
s_command = f"{command} --json"
|
|
384
|
+
s_command = f"{command} -u {user} --json"
|
|
385
|
+
|
|
386
|
+
if command == "sacct":
|
|
387
|
+
s_command += f" -S now-{hist}{hist_unit}"
|
|
308
388
|
|
|
309
389
|
x = subprocess.Popen([s_command], shell=True, stdout=subprocess.PIPE)
|
|
310
390
|
output = x.communicate()
|
|
@@ -318,7 +398,7 @@ def show_queue(
|
|
|
318
398
|
cluster = METADATA["cluster_name"]
|
|
319
399
|
|
|
320
400
|
if command == "sacct":
|
|
321
|
-
title = f"[bold]{user}'s
|
|
401
|
+
title = f"[bold]{user}'s previous jobs (last {hist} {hist_unit})"
|
|
322
402
|
|
|
323
403
|
elif user:
|
|
324
404
|
df = df[df["user_name"] == user]
|
|
@@ -366,13 +446,54 @@ def dual_layout(
|
|
|
366
446
|
else:
|
|
367
447
|
return layout
|
|
368
448
|
|
|
449
|
+
def parse_sinfo_json(payload: dict) -> "DataFrame":
|
|
450
|
+
|
|
451
|
+
df = DataFrame(payload['sinfo'])
|
|
452
|
+
|
|
453
|
+
df2 = df.drop(columns=["port", "weight", "disk", "sockets", "threads", "cluster", "comment", "extra", "gres", "reason", "cores"])
|
|
454
|
+
df2["node"] = df2.apply(lambda x: x["node"]["state"][0], axis=1)
|
|
455
|
+
df2["nodes"] = df2.apply(lambda x: x["nodes"]["nodes"][0], axis=1)
|
|
456
|
+
df2["cpus_max"] = df2.apply(lambda x: x["cpus"]["maximum"], axis=1)
|
|
457
|
+
df2["cpus_idle"] = df2.apply(lambda x: x["cpus"]["idle"], axis=1)
|
|
458
|
+
df2["cpus_allocated"] = df2.apply(lambda x: x["cpus"]["allocated"], axis=1)
|
|
459
|
+
df2["memory_max"] = df2.apply(lambda x: x["memory"]["maximum"], axis=1)
|
|
460
|
+
df2["memory_free"] = df2.apply(lambda x: x["memory"]["free"]["maximum"]["number"], axis=1)
|
|
461
|
+
df2["memory_allocated"] = df2.apply(lambda x: x["memory"]["allocated"], axis=1)
|
|
462
|
+
df2["features"] = df2.apply(lambda x: x["features"]["total"], axis=1)
|
|
463
|
+
df2["partition"] = df2.apply(lambda x: x["partition"]["name"], axis=1)
|
|
464
|
+
|
|
465
|
+
df2.drop(columns=["cpus", "memory"], inplace=True)
|
|
466
|
+
|
|
467
|
+
return df2
|
|
468
|
+
|
|
469
|
+
def idle_queue():
|
|
470
|
+
|
|
471
|
+
s_command = "sinfo -N --json"
|
|
472
|
+
|
|
473
|
+
x = subprocess.Popen([s_command], shell=True, stdout=subprocess.PIPE)
|
|
474
|
+
output = x.communicate()
|
|
475
|
+
|
|
476
|
+
payload = json.loads(output[0])
|
|
477
|
+
|
|
478
|
+
df = parse_sinfo_json(payload)
|
|
479
|
+
|
|
480
|
+
table = node_table(df)
|
|
481
|
+
|
|
482
|
+
console.print(table)
|
|
369
483
|
|
|
370
484
|
@app.command()
|
|
371
|
-
def show(user: None | str = None, long: bool = False):
|
|
485
|
+
def show(user: None | str = None, long: bool = False, idle: bool = False, hist: int | None = None, hist_unit: str = "weeks"):
|
|
372
486
|
|
|
373
487
|
loop = True
|
|
374
488
|
|
|
375
|
-
if
|
|
489
|
+
if hist:
|
|
490
|
+
|
|
491
|
+
show_queue(user=user, command="sacct", long=long, hist=hist, hist_unit=hist_unit)
|
|
492
|
+
|
|
493
|
+
elif idle:
|
|
494
|
+
idle_queue()
|
|
495
|
+
|
|
496
|
+
elif loop:
|
|
376
497
|
|
|
377
498
|
from rich.live import Live
|
|
378
499
|
import time
|
|
@@ -393,7 +514,6 @@ def show(user: None | str = None, long: bool = False):
|
|
|
393
514
|
# show_queue(command="squeue", user=user, long=long)
|
|
394
515
|
# show_queue(command="sacct", user=user, long=long)
|
|
395
516
|
|
|
396
|
-
|
|
397
517
|
def main():
|
|
398
518
|
app()
|
|
399
519
|
|
richqueue-0.5/README.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|