nodeocc 1.0.7__tar.gz → 1.0.9__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.
- {nodeocc-1.0.7 → nodeocc-1.0.9}/PKG-INFO +1 -1
- {nodeocc-1.0.7 → nodeocc-1.0.9}/pyproject.toml +1 -1
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/controller/controller.py +1 -1
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/model/infrastructure.py +1 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/PKG-INFO +1 -1
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/readers/slurmreader.py +12 -8
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/__init__.py +6 -2
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/slurm_viz.py +36 -20
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/utils.py +6 -2
- {nodeocc-1.0.7 → nodeocc-1.0.9}/README.md +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/setup.cfg +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/setup.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/__init__.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/model/job.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/requires.txt +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/curses_multiwindow.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.7 → nodeocc-1.0.9}/src/view/styles.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nodeocc"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.9"
|
|
8
8
|
authors = [{ name = "Matteo Boschini" }]
|
|
9
9
|
maintainers = [{ name = "Lorenzo Bonicelli" }, { name = "Aniello Panariello" }]
|
|
10
10
|
description = "TUI application for viewing the status of GPU allocations on a Slurm cluster"
|
|
@@ -66,7 +66,7 @@ END_DELIM_ENCODED = "!}$".encode('utf-8')
|
|
|
66
66
|
EXTRA_MSG_BEGIN_DELIM = "!{#"
|
|
67
67
|
MAX_BUF = 65535
|
|
68
68
|
MAX_MSG_LEN = 1024 * 1024 * 1024
|
|
69
|
-
JOB_LIMIT_PER_MSG =
|
|
69
|
+
JOB_LIMIT_PER_MSG = 60
|
|
70
70
|
|
|
71
71
|
parser = argparse.ArgumentParser(description='Visualize slurm jobs')
|
|
72
72
|
parser.add_argument('--debug', action='store_true', help='Enable logging')
|
|
@@ -10,6 +10,7 @@ class Node:
|
|
|
10
10
|
def __init__(self, name, n_gpus, gpu_model, reserved, status, cpus, mem) -> None:
|
|
11
11
|
self.name = name
|
|
12
12
|
self.n_gpus = n_gpus
|
|
13
|
+
# self.feature = feature
|
|
13
14
|
self.gpu_model = gpu_model
|
|
14
15
|
self.reserved = reserved
|
|
15
16
|
assert reserved in ('no', 'yes', 'pending')
|
|
@@ -31,7 +31,7 @@ def _split_column(df, column):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _node_from_sinfo(x):
|
|
34
|
-
n = Node(_node_preproc(x['NODELIST']), x.n_gpu, x.m_gpu, x.reserved, None, x.CPUS, x.MEMORY)
|
|
34
|
+
n = Node(_node_preproc(x['NODELIST']), x.n_gpu, x.m_gpu, x.reserved, None, x.CPUS, x.MEMORY)#, feature=x.AVAIL_FEATURES)
|
|
35
35
|
if x['STATE'] == 'drng' or 'drain' in x['STATE']:
|
|
36
36
|
n.status = 'drain'
|
|
37
37
|
elif x['STATE'].upper() in ('MAINT', 'DOWN', 'DOWN*', 'FAIL', 'FAILING', 'FAIL*'):
|
|
@@ -53,18 +53,20 @@ def _parse_gpu_models(line):
|
|
|
53
53
|
return ','.join([x.split(':')[1] for x in line])
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def _read_nodes(reservations, pending_res):
|
|
57
|
-
sinfo_df = pd.read_csv(StringIO(os.popen(r'sinfo -N -o "%N;%G;%t;%m;%c"').read()), sep=';')
|
|
56
|
+
def _read_nodes(reservations=None, pending_res=None, return_raw=False):
|
|
57
|
+
sinfo_df = pd.read_csv(StringIO(os.popen(r'sinfo -N -o "%N;%G;%t;%m;%c;%f"').read()), sep=';')
|
|
58
58
|
sinfo_df = _split_column(sinfo_df, 'NODELIST').drop_duplicates()
|
|
59
|
-
sinfo_df['n_gpu'] = sinfo_df['GRES'].apply(lambda x: 0 if x.split('(')[0].split(':')[0] in ('(null)', '') else int(x.split('(')[0].split(':')[-1]))
|
|
59
|
+
sinfo_df.loc[:,'n_gpu'] = sinfo_df['GRES'].apply(lambda x: 0 if x.split('(')[0].split(':')[0] in ('(null)', '') else int(x.split('(')[0].split(':')[-1]))
|
|
60
60
|
# sinfo_df['m_gpu'] = sinfo_df['GRES'].str.split(':').apply(lambda x: 'n/a' if x[0] in ('(null)', '') else x[1])
|
|
61
|
-
sinfo_df['m_gpu'] = sinfo_df['GRES'].str.split(';').apply(_parse_gpu_models)
|
|
61
|
+
sinfo_df.loc[:,'m_gpu'] = sinfo_df['GRES'].str.split(';').apply(_parse_gpu_models)
|
|
62
62
|
sinfo_df['reserved'] = 'no'
|
|
63
63
|
if reservations is not None:
|
|
64
64
|
sinfo_df.loc[sinfo_df['NODELIST'].isin(reservations.Nodes.unique()), 'reserved'] = 'yes'
|
|
65
65
|
if pending_res is not None:
|
|
66
66
|
sinfo_df.loc[sinfo_df['NODELIST'].isin(pending_res.Nodes.unique()), 'reserved'] = 'pending'
|
|
67
|
-
|
|
67
|
+
if return_raw:
|
|
68
|
+
return sinfo_df
|
|
69
|
+
|
|
68
70
|
return sinfo_df.apply(_node_from_sinfo, axis=1).tolist()
|
|
69
71
|
|
|
70
72
|
|
|
@@ -83,8 +85,10 @@ def _read_maintenances():
|
|
|
83
85
|
if not len(reservations):
|
|
84
86
|
return [], None, None
|
|
85
87
|
reservations = pd.DataFrame.from_records(reservations)
|
|
86
|
-
reservations['StartTime'] = str(pd.to_datetime(
|
|
87
|
-
reservations['EndTime'] = str(pd.to_datetime(
|
|
88
|
+
reservations['StartTime'] = reservations['StartTime'].apply(lambda x:str(pd.to_datetime(x)))
|
|
89
|
+
reservations['EndTime'] = reservations['EndTime'].apply(lambda x:str(pd.to_datetime(x)))
|
|
90
|
+
# reservations['StartTime'] = str(pd.to_datetime(reservations['StartTime']))
|
|
91
|
+
# reservations['EndTime'] = str(pd.to_datetime(reservations['EndTime']))
|
|
88
92
|
reservations = _split_column(reservations, 'Nodes')
|
|
89
93
|
maint_flag = reservations['Flags'].str.contains('MAINT') & \
|
|
90
94
|
reservations['Flags'].str.contains('SPEC_NODES') & \
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import curses
|
|
2
2
|
from . import slurm_list, slurm_viz, styles
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
def update_views(stdscr, instance, filter):
|
|
5
6
|
if instance.jobs is None or instance.inf is None:
|
|
6
7
|
return
|
|
7
8
|
|
|
8
|
-
instance.rens = slurm_list.view_list(
|
|
9
|
+
instance.rens = slurm_list.view_list(
|
|
10
|
+
instance, instance.jobs, filter, stylefn=styles.crsstyler, width=instance.left_width if hasattr(
|
|
11
|
+
instance, 'left_width') else 72, jit=instance.job_id_type if hasattr(
|
|
12
|
+
instance, 'job_id_type') else 'agg')
|
|
9
13
|
instance.timeme(f"- print job list")
|
|
10
14
|
|
|
11
15
|
instance.nocc = slurm_viz.view_viz(instance.inf, instance.jobs, stylefn=styles.crsstyler, mode=instance.view_mode if hasattr(instance, 'view_mode') else 'gpu')
|
|
12
|
-
instance.timeme(f"- view nodeocc")
|
|
16
|
+
instance.timeme(f"- view nodeocc")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import random
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
4
5
|
|
|
5
6
|
from view.styles import _format_to, cmdstyle
|
|
6
7
|
from view.utils import is_cvcs_viz, is_student_viz, maintenance_status, to_font
|
|
@@ -99,6 +100,8 @@ cpu_pendr = '⧖'
|
|
|
99
100
|
cpu_paused = '▚'
|
|
100
101
|
cpu_down = '⨯'
|
|
101
102
|
|
|
103
|
+
GLOBAL_NODES = None
|
|
104
|
+
|
|
102
105
|
|
|
103
106
|
def get_ram_block(megs):
|
|
104
107
|
return int(round(megs / 1024 / mem_size))
|
|
@@ -356,9 +359,6 @@ def get_cpu_icon(stat):
|
|
|
356
359
|
}.get(stat, cpu_avail)
|
|
357
360
|
|
|
358
361
|
|
|
359
|
-
GPU_MEM = {"K80": 12, "P100": 16, "1080": 11, "TitanXP": 12, "2080": 11, "RTX5000": 16, "RTX6000": 24, "RTXA5000": 24, "A40": 48}
|
|
360
|
-
|
|
361
|
-
|
|
362
362
|
def view_viz_cpu(infrastructure, jobs, work=True, stylefn=cmdstyle, current_user=None):
|
|
363
363
|
# this is for hot reload
|
|
364
364
|
if not work:
|
|
@@ -490,13 +490,22 @@ def view_viz_info(infrastructure, jobs, work=True, stylefn=cmdstyle, current_use
|
|
|
490
490
|
if not work:
|
|
491
491
|
return "UPDATE IN PROGRESS - PLZ W8 M8 B8"
|
|
492
492
|
|
|
493
|
+
global GLOBAL_NODES
|
|
494
|
+
if GLOBAL_NODES is None:
|
|
495
|
+
from readers.slurmreader import _read_nodes, _node_preproc
|
|
496
|
+
GLOBAL_NODES = _read_nodes(return_raw=True)
|
|
497
|
+
GLOBAL_NODES.loc[:, 'nodename'] = GLOBAL_NODES.NODELIST.apply(_node_preproc)
|
|
498
|
+
prior = ['n/a', None, 'A40', 'RTXA5000', 'RTX6000', 'RTX5000', '2080', 'V100', '1080', 'P100', 'K80']
|
|
499
|
+
reord_idx = sorted(zip(GLOBAL_NODES['m_gpu'], GLOBAL_NODES.index), key=lambda g: prior.index(g[0].split(',')[0]) if g[0].split(',')[0] in prior else -1)
|
|
500
|
+
GLOBAL_NODES = GLOBAL_NODES.loc[np.array([i for x, i in reord_idx])].reset_index(drop=True)
|
|
501
|
+
|
|
493
502
|
# who is the current user?
|
|
494
503
|
if current_user is None:
|
|
495
504
|
import os
|
|
496
505
|
current_user = os.path.basename(os.environ['HOME'])
|
|
497
506
|
|
|
498
|
-
nodes = infrastructure.get_sorted_nodes()
|
|
499
|
-
infrast_down = all([x.status == 'down' for x in
|
|
507
|
+
# nodes = infrastructure.get_sorted_nodes()
|
|
508
|
+
infrast_down = all([x.status == 'down' for x in infrastructure.get_sorted_nodes()])
|
|
500
509
|
|
|
501
510
|
class RetScope:
|
|
502
511
|
return_string = ''
|
|
@@ -506,27 +515,34 @@ def view_viz_info(infrastructure, jobs, work=True, stylefn=cmdstyle, current_use
|
|
|
506
515
|
|
|
507
516
|
if not infrast_down:
|
|
508
517
|
stalled_jobs = sum([j.state == 'S' for j in jobs])
|
|
509
|
-
max_gpu_len = max([len(n.gpu_model) + len(str(n.n_gpus)) if n.gpu_model is not None else 0 for n in nodes])
|
|
510
|
-
max_n_gpu_len = max([len(str(n.n_gpus)) for n in nodes])
|
|
511
|
-
max_cpu_len = max([len(str(n.cpus)) for n in nodes] + [len('CPUs')])
|
|
512
|
-
max_ram_len = max([len(str(n.mem // 1024)) for n in nodes])
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
518
|
+
# max_gpu_len = max([len(n.gpu_model) + len(str(n.n_gpus)) if n.gpu_model is not None else 0 for n in nodes])
|
|
519
|
+
# max_n_gpu_len = max([len(str(n.n_gpus)) for n in nodes])
|
|
520
|
+
# max_cpu_len = max([len(str(n.cpus)) for n in nodes] + [len('CPUs')])
|
|
521
|
+
# max_ram_len = max([len(str(n.mem // 1024)) for n in nodes])
|
|
522
|
+
feat_str = stylefn("GREEN", "FEATURES")
|
|
523
|
+
RetScope.return_string += f'{"":{gpu_name_chars}} GPU {feat_str}\n'
|
|
524
|
+
for i, n in GLOBAL_NODES.iterrows():
|
|
525
|
+
gpu_model = n.m_gpu # if n.m_gpu is not None else 'N/A'
|
|
516
526
|
# gpu_mem = n.gpu_mem if n.gpu_mem is not None else 'N/A'
|
|
517
|
-
mem = n.
|
|
518
|
-
n_gpus = n.
|
|
519
|
-
n_cpus = n.
|
|
527
|
+
mem = n.MEMORY // 1024 if n.MEMORY is not None else 'N/A'
|
|
528
|
+
n_gpus = n.n_gpu if n.n_gpu is not None else 'N/A'
|
|
529
|
+
n_cpus = n.CPUS # if n.CPUS is not None else 'N/A'
|
|
520
530
|
# gpu_mem = GPU_MEM.get(gpu_model, 'N/A')
|
|
521
|
-
|
|
531
|
+
|
|
532
|
+
gpu_feature = n.AVAIL_FEATURES # if n.feature is not None else '(null)'
|
|
533
|
+
|
|
534
|
+
# gpu_mem = ','.join([str(GPU_MEM.get(x, 'N/A')) for x in gpu_model.split(',')])
|
|
522
535
|
# gpu_str = f'{n_gpus}x{gpu_model:{max_gpu_len-(1*(len(str(n_gpus))))}}' if n_gpus != 0 else f'{"N/A":{max_gpu_len+1}}'
|
|
523
536
|
if n_gpus != 0:
|
|
524
537
|
gpu_str = f'{stylefn("RED", str(n_gpus))}'
|
|
525
|
-
|
|
526
|
-
|
|
538
|
+
if gpu_feature != '(null)':
|
|
539
|
+
gpu_str += f'x{stylefn("GREEN", gpu_feature)}'
|
|
540
|
+
else:
|
|
541
|
+
gpu_str += f'@({stylefn("YELLOW", gpu_model)})'
|
|
527
542
|
else:
|
|
528
|
-
gpu_str = f'{"N/A":{max_gpu_len+1}}'
|
|
529
|
-
|
|
543
|
+
gpu_str = "N/A" # f'{"N/A":{max_gpu_len+1}}'
|
|
544
|
+
|
|
545
|
+
RetScope.return_string += f'{_format_to(n.nodename, gpu_name_chars, "right")} {gpu_str}\n'
|
|
530
546
|
# RetScope.return_string += f'{"":{gpu_name_chars}} {"VRAM":{max_gpu_len+1}} {f"{mem}G"}\n'
|
|
531
547
|
|
|
532
548
|
onmain, waitString = maintenance_status(infrastructure)
|
|
@@ -22,8 +22,12 @@ def to_datetime(time_str):
|
|
|
22
22
|
return np.datetime64(re.match(r'\d+\s+(.*)\n', str(time_str)).group(1))
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def to_datetime(time_str):
|
|
26
|
-
|
|
25
|
+
def to_datetime(time_str):
|
|
26
|
+
try:
|
|
27
|
+
dt = np.datetime64(re.match(r'\d+\s+(.*)\n', str(time_str)).group(1))
|
|
28
|
+
except:
|
|
29
|
+
dt = np.datetime64(str(time_str))
|
|
30
|
+
return dt
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
def maintenance_status(infrastructure):
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|