richqueue 0.6__tar.gz → 0.7__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.6 → richqueue-0.7}/PKG-INFO +13 -1
- {richqueue-0.6 → richqueue-0.7}/README.md +12 -0
- {richqueue-0.6 → richqueue-0.7}/pyproject.toml +1 -1
- richqueue-0.7/richqueue/console.py +3 -0
- richqueue-0.7/richqueue/layout.py +29 -0
- richqueue-0.7/richqueue/main.py +90 -0
- richqueue-0.7/richqueue/slurm.py +418 -0
- richqueue-0.7/richqueue/table.py +368 -0
- {richqueue-0.6 → richqueue-0.7}/richqueue/tools.py +7 -0
- richqueue-0.6/richqueue/slurm.py +0 -565
- {richqueue-0.6 → richqueue-0.7}/.gitignore +0 -0
- {richqueue-0.6 → richqueue-0.7}/LICENSE +0 -0
- {richqueue-0.6 → richqueue-0.7}/richqueue/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: richqueue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7
|
|
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
|
|
@@ -43,3 +43,15 @@ To see more detail:
|
|
|
43
43
|
To see someone else's queue:
|
|
44
44
|
|
|
45
45
|
```rq --user USER```
|
|
46
|
+
|
|
47
|
+
To see the last `X` weeks history:
|
|
48
|
+
|
|
49
|
+
```rq --hist X```
|
|
50
|
+
|
|
51
|
+
To list available nodes on the cluster:
|
|
52
|
+
|
|
53
|
+
```rq --idle```
|
|
54
|
+
|
|
55
|
+
To show a static view:
|
|
56
|
+
|
|
57
|
+
```rq --no-loop```
|
|
@@ -26,3 +26,15 @@ To see more detail:
|
|
|
26
26
|
To see someone else's queue:
|
|
27
27
|
|
|
28
28
|
```rq --user USER```
|
|
29
|
+
|
|
30
|
+
To see the last `X` weeks history:
|
|
31
|
+
|
|
32
|
+
```rq --hist X```
|
|
33
|
+
|
|
34
|
+
To list available nodes on the cluster:
|
|
35
|
+
|
|
36
|
+
```rq --idle```
|
|
37
|
+
|
|
38
|
+
To show a static view:
|
|
39
|
+
|
|
40
|
+
```rq --no-loop```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from rich.layout import Layout
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from .slurm import PANEL_PADDING
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def dual_layout(function, **kwargs):
|
|
8
|
+
|
|
9
|
+
layout = Layout()
|
|
10
|
+
|
|
11
|
+
upper, lower = function(**kwargs)
|
|
12
|
+
|
|
13
|
+
upper = Layout(renderable=upper, name="upper")
|
|
14
|
+
lower = Layout(renderable=lower, name="lower")
|
|
15
|
+
|
|
16
|
+
upper.size = upper.renderable.renderable.row_count + PANEL_PADDING
|
|
17
|
+
lower.size = lower.renderable.renderable.row_count + PANEL_PADDING
|
|
18
|
+
|
|
19
|
+
layout.split_column(
|
|
20
|
+
upper,
|
|
21
|
+
lower,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return layout
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def simple_layout():
|
|
28
|
+
|
|
29
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typer import Typer
|
|
2
|
+
from .layout import dual_layout
|
|
3
|
+
from rich.live import Live
|
|
4
|
+
import time
|
|
5
|
+
from .console import console
|
|
6
|
+
from .tools import curry
|
|
7
|
+
from .slurm import get_layout_pair, get_node_layout, get_hist_layout
|
|
8
|
+
|
|
9
|
+
# set up singletons
|
|
10
|
+
app = Typer()
|
|
11
|
+
|
|
12
|
+
# main CLI command
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def show(
|
|
17
|
+
user: None | str = None,
|
|
18
|
+
long: bool = False,
|
|
19
|
+
idle: bool = False,
|
|
20
|
+
loop: bool = True,
|
|
21
|
+
hist: int | None = None,
|
|
22
|
+
hist_unit: str = "weeks",
|
|
23
|
+
# screen: bool = True,
|
|
24
|
+
# disappear: bool = True,
|
|
25
|
+
):
|
|
26
|
+
|
|
27
|
+
screen = True
|
|
28
|
+
disappear = not screen
|
|
29
|
+
|
|
30
|
+
kwargs = {
|
|
31
|
+
"user": user,
|
|
32
|
+
"long": long,
|
|
33
|
+
"idle": idle,
|
|
34
|
+
"loop": loop,
|
|
35
|
+
"hist": hist,
|
|
36
|
+
"hist_unit": hist_unit,
|
|
37
|
+
"screen": screen,
|
|
38
|
+
"disappear": disappear,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# console.print(kwargs)
|
|
42
|
+
|
|
43
|
+
match (bool(idle), bool(hist)):
|
|
44
|
+
case (True, False):
|
|
45
|
+
loop = False
|
|
46
|
+
layout_func = get_node_layout
|
|
47
|
+
case (False, True):
|
|
48
|
+
loop = False
|
|
49
|
+
layout_func = get_hist_layout
|
|
50
|
+
case (False, False):
|
|
51
|
+
layout_func = curry(dual_layout, get_layout_pair)
|
|
52
|
+
case _:
|
|
53
|
+
raise Exception("Unsupported CLI options")
|
|
54
|
+
|
|
55
|
+
# live updating layout
|
|
56
|
+
if loop:
|
|
57
|
+
|
|
58
|
+
layout = layout_func(**kwargs)
|
|
59
|
+
|
|
60
|
+
with Live(
|
|
61
|
+
layout,
|
|
62
|
+
refresh_per_second=4,
|
|
63
|
+
screen=screen,
|
|
64
|
+
transient=disappear,
|
|
65
|
+
vertical_overflow="visible",
|
|
66
|
+
) as live:
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
while True:
|
|
70
|
+
layout = layout_func(**kwargs)
|
|
71
|
+
live.update(layout)
|
|
72
|
+
time.sleep(1)
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
live.stop()
|
|
75
|
+
|
|
76
|
+
# static layout
|
|
77
|
+
else:
|
|
78
|
+
|
|
79
|
+
layout = layout_func(**kwargs)
|
|
80
|
+
console.print(layout)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# start Typer app
|
|
84
|
+
def main():
|
|
85
|
+
app()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# start Typer app
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
app()
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
from pandas import concat, DataFrame, isnull
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
import subprocess
|
|
5
|
+
import json
|
|
6
|
+
from .console import console
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import datetime
|
|
9
|
+
from .table import running_job_table, history_job_table, node_table
|
|
10
|
+
from .tools import human_timedelta
|
|
11
|
+
|
|
12
|
+
# from numpy import isnat
|
|
13
|
+
|
|
14
|
+
METADATA = {}
|
|
15
|
+
|
|
16
|
+
PANEL_PADDING = 4
|
|
17
|
+
|
|
18
|
+
### CONSTRUCT LAYOUT
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_layout_pair(user: str | None, **kwargs):
|
|
22
|
+
|
|
23
|
+
if user == "all":
|
|
24
|
+
user = None
|
|
25
|
+
elif user is None:
|
|
26
|
+
x = subprocess.Popen(["whoami"], shell=True, stdout=subprocess.PIPE)
|
|
27
|
+
output = x.communicate()
|
|
28
|
+
user = output[0].strip().decode("utf-8")
|
|
29
|
+
|
|
30
|
+
df = combined_df(user=user, **kwargs)
|
|
31
|
+
|
|
32
|
+
n_rows = len(df)
|
|
33
|
+
n_running = len(df[df["job_state"] == "RUNNING"])
|
|
34
|
+
n_pending = len(df[df["job_state"] == "PENDING"])
|
|
35
|
+
|
|
36
|
+
hide_pending = False
|
|
37
|
+
|
|
38
|
+
running_df = df[df["job_state"].isin(["RUNNING", "PENDING"])]
|
|
39
|
+
history_df = df[~df["job_state"].isin(["RUNNING", "PENDING"])]
|
|
40
|
+
|
|
41
|
+
console_height = console.size.height
|
|
42
|
+
max_rows = console_height - 2 * PANEL_PADDING
|
|
43
|
+
|
|
44
|
+
if n_rows > max_rows:
|
|
45
|
+
|
|
46
|
+
# hide history?
|
|
47
|
+
if n_running + n_pending + PANEL_PADDING + 1 < console_height:
|
|
48
|
+
history_limit = 0
|
|
49
|
+
running_limit = None
|
|
50
|
+
|
|
51
|
+
# hide pending?
|
|
52
|
+
elif n_rows - n_pending < max_rows:
|
|
53
|
+
# running_df = running_df[running_df["job_state"]=="RUNNING"]
|
|
54
|
+
running_limit = None
|
|
55
|
+
history_limit = None
|
|
56
|
+
hide_pending = n_pending
|
|
57
|
+
|
|
58
|
+
# fallback clip
|
|
59
|
+
else:
|
|
60
|
+
running_limit = 5
|
|
61
|
+
history_limit = 5
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
running_limit = None
|
|
65
|
+
history_limit = None
|
|
66
|
+
|
|
67
|
+
running = Panel(
|
|
68
|
+
running_job_table(
|
|
69
|
+
running_df,
|
|
70
|
+
limit=running_limit,
|
|
71
|
+
hide_pending=hide_pending,
|
|
72
|
+
user=user,
|
|
73
|
+
**kwargs,
|
|
74
|
+
),
|
|
75
|
+
expand=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if history_limit == 0:
|
|
79
|
+
history = Text("history hidden, resize window or use --hist")
|
|
80
|
+
else:
|
|
81
|
+
history = Panel(
|
|
82
|
+
history_job_table(history_df, limit=history_limit, user=user, **kwargs),
|
|
83
|
+
expand=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return running, history
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_hist_layout(user: str | None, **kwargs):
|
|
90
|
+
|
|
91
|
+
if user == "all":
|
|
92
|
+
user = None
|
|
93
|
+
elif user is None:
|
|
94
|
+
x = subprocess.Popen(["whoami"], shell=True, stdout=subprocess.PIPE)
|
|
95
|
+
output = x.communicate()
|
|
96
|
+
user = output[0].strip().decode("utf-8")
|
|
97
|
+
|
|
98
|
+
df = combined_df(user=user, **kwargs)
|
|
99
|
+
|
|
100
|
+
history_df = df[~df["job_state"].isin(["RUNNING", "PENDING"])]
|
|
101
|
+
|
|
102
|
+
history = Panel(
|
|
103
|
+
history_job_table(history_df, limit=None, user=user, **kwargs),
|
|
104
|
+
expand=False,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return history
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_node_layout(idle: bool = True, **kwargs):
|
|
111
|
+
|
|
112
|
+
df = get_sinfo(**kwargs)
|
|
113
|
+
|
|
114
|
+
if idle:
|
|
115
|
+
df = df[df["node_state"].isin(["IDLE", "MIXED"])]
|
|
116
|
+
|
|
117
|
+
table = node_table(df, **kwargs)
|
|
118
|
+
|
|
119
|
+
return Panel(table, expand=False)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### GET QUEUE DFs
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_squeue(user: str | None = None, **kwargs) -> "pandas.DataFrame":
|
|
126
|
+
|
|
127
|
+
if user:
|
|
128
|
+
command = f"squeue -u {user} --json"
|
|
129
|
+
else:
|
|
130
|
+
command = f"squeue --json"
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
process = subprocess.Popen(
|
|
134
|
+
[command], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
135
|
+
)
|
|
136
|
+
output = process.communicate()
|
|
137
|
+
payload = json.loads(output[0])
|
|
138
|
+
except json.JSONDecodeError:
|
|
139
|
+
# console.print('[orange1 bold]Warning: using example data')
|
|
140
|
+
payload = json.load(
|
|
141
|
+
open(
|
|
142
|
+
Path(__file__).parent.parent / "example_data" / "squeue_long.json", "rt"
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
global METADATA
|
|
147
|
+
|
|
148
|
+
METADATA = {
|
|
149
|
+
"cluster_name": payload["meta"]["slurm"]["cluster"],
|
|
150
|
+
"user": payload["meta"]["client"]["user"],
|
|
151
|
+
"group": payload["meta"]["client"]["group"],
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# parse payload
|
|
155
|
+
df = DataFrame(payload["jobs"])
|
|
156
|
+
|
|
157
|
+
# filter columns
|
|
158
|
+
columns = COLUMNS["squeue"]
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
df = df[columns]
|
|
162
|
+
except KeyError:
|
|
163
|
+
for key in columns:
|
|
164
|
+
if key not in df.columns:
|
|
165
|
+
raise KeyError(key)
|
|
166
|
+
|
|
167
|
+
extract_inner(df, "cpus", "number")
|
|
168
|
+
extract_inner(df, "node_count", "number")
|
|
169
|
+
extract_inner(df, "cpus_per_task", "number")
|
|
170
|
+
extract_inner(df, "threads_per_core", "number")
|
|
171
|
+
|
|
172
|
+
extract_time(df, "start_time")
|
|
173
|
+
extract_time(df, "submit_time")
|
|
174
|
+
extract_time(df, "time_limit")
|
|
175
|
+
|
|
176
|
+
extract_list(df, "job_state")
|
|
177
|
+
|
|
178
|
+
return df
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_sacct(
|
|
182
|
+
user: str | None = None, hist: int | None = 4, hist_unit: str = "weeks", **kwargs
|
|
183
|
+
) -> "pandas.DataFrame":
|
|
184
|
+
|
|
185
|
+
hist = hist or 4
|
|
186
|
+
|
|
187
|
+
if user:
|
|
188
|
+
command = f"sacct -u {user} --json -S now-{hist}{hist_unit}"
|
|
189
|
+
else:
|
|
190
|
+
command = f"sacct --json -S now-{hist}{hist_unit}"
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
process = subprocess.Popen(
|
|
194
|
+
[command], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
195
|
+
)
|
|
196
|
+
output = process.communicate()
|
|
197
|
+
payload = json.loads(output[0])
|
|
198
|
+
except json.JSONDecodeError:
|
|
199
|
+
# console.print('[orange1 bold]Warning: using example data')
|
|
200
|
+
payload = json.load(
|
|
201
|
+
open(Path(__file__).parent.parent / "example_data" / "sacct.json", "rt")
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
global METADATA
|
|
205
|
+
|
|
206
|
+
METADATA = {
|
|
207
|
+
"cluster_name": payload["meta"]["slurm"]["cluster"],
|
|
208
|
+
"user": payload["meta"]["client"]["user"],
|
|
209
|
+
"group": payload["meta"]["client"]["group"],
|
|
210
|
+
"hist": hist,
|
|
211
|
+
"hist_unit": hist_unit,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# parse payload
|
|
215
|
+
df = DataFrame(payload["jobs"])
|
|
216
|
+
|
|
217
|
+
# filter columns
|
|
218
|
+
columns = COLUMNS["sacct"]
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
df = df[columns]
|
|
222
|
+
except KeyError:
|
|
223
|
+
for key in columns:
|
|
224
|
+
if key not in df.columns:
|
|
225
|
+
raise KeyError(key)
|
|
226
|
+
|
|
227
|
+
df = df.rename(columns={"user": "user_name", "state": "job_state"})
|
|
228
|
+
|
|
229
|
+
extract_inner(df, "job_state", "current")
|
|
230
|
+
|
|
231
|
+
extract_sacct_times(df)
|
|
232
|
+
|
|
233
|
+
extract_list(df, "job_state")
|
|
234
|
+
|
|
235
|
+
df = df[df["job_state"] != "RUNNING"]
|
|
236
|
+
df = df[df["job_state"] != "PENDING"]
|
|
237
|
+
|
|
238
|
+
return df
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_sinfo(**kwargs) -> "pandas.DataFrame":
|
|
242
|
+
|
|
243
|
+
command = "sinfo -N --json"
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
process = subprocess.Popen(
|
|
247
|
+
[command], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
248
|
+
)
|
|
249
|
+
output = process.communicate()
|
|
250
|
+
payload = json.loads(output[0])
|
|
251
|
+
except json.JSONDecodeError:
|
|
252
|
+
payload = json.load(
|
|
253
|
+
open(Path(__file__).parent.parent / "example_data" / "sinfo_N.json", "rt")
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
global METADATA
|
|
257
|
+
|
|
258
|
+
METADATA = {
|
|
259
|
+
"cluster_name": payload["meta"]["slurm"]["cluster"],
|
|
260
|
+
"user": payload["meta"]["client"]["user"],
|
|
261
|
+
"group": payload["meta"]["client"]["group"],
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
df = DataFrame(payload["sinfo"])
|
|
265
|
+
|
|
266
|
+
df.drop(
|
|
267
|
+
columns=[
|
|
268
|
+
"port",
|
|
269
|
+
"weight",
|
|
270
|
+
"disk",
|
|
271
|
+
"sockets",
|
|
272
|
+
"threads",
|
|
273
|
+
"cluster",
|
|
274
|
+
"comment",
|
|
275
|
+
"extra",
|
|
276
|
+
"gres",
|
|
277
|
+
"reason",
|
|
278
|
+
"cores",
|
|
279
|
+
],
|
|
280
|
+
inplace=True,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
df["node"] = df.apply(lambda x: x["node"]["state"][0], axis=1)
|
|
284
|
+
df["nodes"] = df.apply(lambda x: x["nodes"]["nodes"][0], axis=1)
|
|
285
|
+
df["cpus_max"] = df.apply(lambda x: x["cpus"]["maximum"], axis=1)
|
|
286
|
+
df["cpus_idle"] = df.apply(lambda x: x["cpus"]["idle"], axis=1)
|
|
287
|
+
df["cpus_allocated"] = df.apply(lambda x: x["cpus"]["allocated"], axis=1)
|
|
288
|
+
df["cpu_string"] = df.apply(lambda x: f"{x.cpus_idle}/{x.cpus_max}", axis=1)
|
|
289
|
+
df["memory_max"] = df.apply(lambda x: x["memory"]["maximum"], axis=1)
|
|
290
|
+
df["memory_free"] = df.apply(
|
|
291
|
+
lambda x: x["memory"]["free"]["maximum"]["number"], axis=1
|
|
292
|
+
)
|
|
293
|
+
df["memory_allocated"] = df.apply(lambda x: x["memory"]["allocated"], axis=1)
|
|
294
|
+
df["features"] = df.apply(lambda x: x["features"]["total"], axis=1)
|
|
295
|
+
df["partition"] = df.apply(lambda x: x["partition"]["name"], axis=1)
|
|
296
|
+
|
|
297
|
+
df.drop(columns=["cpus", "memory"], inplace=True)
|
|
298
|
+
|
|
299
|
+
df.rename(columns={"node": "node_state", "nodes": "node_name"}, inplace=True)
|
|
300
|
+
|
|
301
|
+
return df
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def combined_df(**kwargs) -> "DataFrame":
|
|
305
|
+
"""Get combined DataFrame of SLURM job information"""
|
|
306
|
+
df1 = get_squeue(**kwargs)
|
|
307
|
+
df2 = get_sacct(**kwargs)
|
|
308
|
+
df = concat([df1, df2], ignore_index=True)
|
|
309
|
+
df["run_time"] = add_run_time(df)
|
|
310
|
+
df = df.sort_values(by="submit_time", ascending=True)
|
|
311
|
+
return df
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
### ADD COLUMNS
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def add_run_time(df):
|
|
318
|
+
|
|
319
|
+
def inner(row):
|
|
320
|
+
# print(f'{row.end_time=} {type(row.end_time)=}')
|
|
321
|
+
|
|
322
|
+
if row.job_state == "PENDING":
|
|
323
|
+
return ""
|
|
324
|
+
|
|
325
|
+
if isnull(row.end_time):
|
|
326
|
+
return human_timedelta(datetime.datetime.now() - row.start_time)
|
|
327
|
+
else:
|
|
328
|
+
return human_timedelta(row.end_time - row.start_time)
|
|
329
|
+
|
|
330
|
+
return df.apply(inner, axis=1)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
### EXTRACTORS
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def extract_inner(df, key, inner):
|
|
337
|
+
|
|
338
|
+
def _inner(x):
|
|
339
|
+
d = x[key]
|
|
340
|
+
if "set" in d:
|
|
341
|
+
if d["set"]:
|
|
342
|
+
return d[inner]
|
|
343
|
+
else:
|
|
344
|
+
return None
|
|
345
|
+
else:
|
|
346
|
+
return d[inner]
|
|
347
|
+
|
|
348
|
+
df[key] = df.apply(_inner, axis=1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def extract_time(df, key):
|
|
352
|
+
df[key] = df.apply(
|
|
353
|
+
lambda x: datetime.datetime.fromtimestamp(x[key]["number"]), axis=1
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def extract_sacct_times(df):
|
|
358
|
+
df["start_time"] = df.apply(
|
|
359
|
+
lambda x: datetime.datetime.fromtimestamp(x["time"]["start"]), axis=1
|
|
360
|
+
)
|
|
361
|
+
df["end_time"] = df.apply(
|
|
362
|
+
lambda x: datetime.datetime.fromtimestamp(x["time"]["end"]), axis=1
|
|
363
|
+
)
|
|
364
|
+
df["submit_time"] = df.apply(
|
|
365
|
+
lambda x: datetime.datetime.fromtimestamp(x["time"]["submission"]), axis=1
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def extract_list(df, key):
|
|
370
|
+
def inner(x):
|
|
371
|
+
if len(x[key]) == 1:
|
|
372
|
+
return x[key][0]
|
|
373
|
+
else:
|
|
374
|
+
return x[key]
|
|
375
|
+
|
|
376
|
+
df[key] = df.apply(inner, axis=1)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
COLUMNS = {
|
|
380
|
+
"sacct": [
|
|
381
|
+
"job_id",
|
|
382
|
+
"state",
|
|
383
|
+
"name",
|
|
384
|
+
"nodes",
|
|
385
|
+
"partition",
|
|
386
|
+
"user",
|
|
387
|
+
"time",
|
|
388
|
+
],
|
|
389
|
+
"squeue": [
|
|
390
|
+
"command",
|
|
391
|
+
"cpus_per_task",
|
|
392
|
+
"dependency",
|
|
393
|
+
"derived_exit_code",
|
|
394
|
+
"group_name",
|
|
395
|
+
"job_id",
|
|
396
|
+
"job_state",
|
|
397
|
+
"name",
|
|
398
|
+
"nodes",
|
|
399
|
+
"node_count",
|
|
400
|
+
"cpus",
|
|
401
|
+
"tasks",
|
|
402
|
+
"partition",
|
|
403
|
+
"memory_per_cpu",
|
|
404
|
+
"memory_per_node",
|
|
405
|
+
"qos",
|
|
406
|
+
"restart_cnt",
|
|
407
|
+
"requeue",
|
|
408
|
+
"exclusive",
|
|
409
|
+
"start_time",
|
|
410
|
+
"standard_error",
|
|
411
|
+
"standard_output",
|
|
412
|
+
"submit_time",
|
|
413
|
+
"time_limit",
|
|
414
|
+
"threads_per_core",
|
|
415
|
+
"user_name",
|
|
416
|
+
"current_working_directory",
|
|
417
|
+
],
|
|
418
|
+
}
|