nodeocc 1.0.14__tar.gz → 1.0.15__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.
Files changed (24) hide show
  1. {nodeocc-1.0.14 → nodeocc-1.0.15}/PKG-INFO +1 -1
  2. {nodeocc-1.0.14 → nodeocc-1.0.15}/pyproject.toml +1 -1
  3. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/PKG-INFO +1 -1
  4. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/readers/slurmreader.py +11 -7
  5. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/view/curses_multiwindow.py +4 -4
  6. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/view/slurm_viz.py +36 -2
  7. nodeocc-1.0.15/src/view/styles.py +79 -0
  8. nodeocc-1.0.14/src/view/styles.py +0 -76
  9. {nodeocc-1.0.14 → nodeocc-1.0.15}/README.md +0 -0
  10. {nodeocc-1.0.14 → nodeocc-1.0.15}/setup.cfg +0 -0
  11. {nodeocc-1.0.14 → nodeocc-1.0.15}/setup.py +0 -0
  12. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/__init__.py +0 -0
  13. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/controller/controller.py +0 -0
  14. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/model/infrastructure.py +0 -0
  15. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/model/job.py +0 -0
  16. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/SOURCES.txt +0 -0
  17. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/dependency_links.txt +0 -0
  18. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/entry_points.txt +0 -0
  19. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/requires.txt +0 -0
  20. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/nodeocc.egg-info/top_level.txt +0 -0
  21. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/readers/compute_wait_time.py +0 -0
  22. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/view/__init__.py +0 -0
  23. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/view/slurm_list.py +0 -0
  24. {nodeocc-1.0.14 → nodeocc-1.0.15}/src/view/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.14
3
+ Version: 1.0.15
4
4
  Summary: TUI application for viewing the status of GPU allocations on a Slurm cluster
5
5
  Author: Matteo Boschini
6
6
  Maintainer: Lorenzo Bonicelli, Aniello Panariello
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nodeocc"
7
- version = "1.0.14"
7
+ version = "1.0.15"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.14
3
+ Version: 1.0.15
4
4
  Summary: TUI application for viewing the status of GPU allocations on a Slurm cluster
5
5
  Author: Matteo Boschini
6
6
  Maintainer: Lorenzo Bonicelli, Aniello Panariello
@@ -30,11 +30,15 @@ def _split_column(df, column):
30
30
  return node_flat.merge(df[[x for x in df.columns if x != column]], left_index=True, right_index=True).reset_index(drop=True)
31
31
 
32
32
 
33
+ def _match_str_wildcard(s, w):
34
+ return re.match(w.replace('*', '.*'), s) is not None
35
+
36
+
33
37
  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)#, feature=x.AVAIL_FEATURES)
38
+ n = Node(_node_preproc(x['NODELIST']), x.n_gpu, x.m_gpu, x.reserved, None, x.CPUS, x.MEMORY) # , feature=x.AVAIL_FEATURES)
35
39
  if x['STATE'] == 'drng' or 'drain' in x['STATE']:
36
40
  n.status = 'drain'
37
- elif x['STATE'].upper() in ('MAINT', 'DOWN', 'DOWN*', 'FAIL', 'FAILING', 'FAIL*'):
41
+ elif x['STATE'].upper() in ('MAINT', 'DOWN', 'DOWN*', 'FAIL', 'FAILING', 'FAIL*') or _match_str_wildcard(x['STATE'].lower(), 'inv*'):
38
42
  n.status = 'down'
39
43
  else:
40
44
  n.status = 'ok'
@@ -56,9 +60,9 @@ def _parse_gpu_models(line):
56
60
  def _read_nodes(reservations=None, pending_res=None, return_raw=False):
57
61
  sinfo_df = pd.read_csv(StringIO(os.popen(r'sinfo -N -o "%N;%G;%t;%m;%c;%f"').read()), sep=';')
58
62
  sinfo_df = _split_column(sinfo_df, 'NODELIST').drop_duplicates()
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]))
63
+ 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
64
  # 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.loc[:,'m_gpu'] = sinfo_df['GRES'].str.split(';').apply(_parse_gpu_models)
65
+ sinfo_df.loc[:, 'm_gpu'] = sinfo_df['GRES'].str.split(';').apply(_parse_gpu_models)
62
66
  sinfo_df['reserved'] = 'no'
63
67
  if reservations is not None:
64
68
  sinfo_df.loc[sinfo_df['NODELIST'].isin(reservations.Nodes.unique()), 'reserved'] = 'yes'
@@ -66,7 +70,7 @@ def _read_nodes(reservations=None, pending_res=None, return_raw=False):
66
70
  sinfo_df.loc[sinfo_df['NODELIST'].isin(pending_res.Nodes.unique()), 'reserved'] = 'pending'
67
71
  if return_raw:
68
72
  return sinfo_df
69
-
73
+
70
74
  return sinfo_df.apply(_node_from_sinfo, axis=1).tolist()
71
75
 
72
76
 
@@ -85,8 +89,8 @@ def _read_maintenances():
85
89
  if not len(reservations):
86
90
  return [], None, None
87
91
  reservations = pd.DataFrame.from_records(reservations)
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)))
92
+ reservations['StartTime'] = reservations['StartTime'].apply(lambda x: str(pd.to_datetime(x)))
93
+ reservations['EndTime'] = reservations['EndTime'].apply(lambda x: str(pd.to_datetime(x)))
90
94
  # reservations['StartTime'] = str(pd.to_datetime(reservations['StartTime']))
91
95
  # reservations['EndTime'] = str(pd.to_datetime(reservations['EndTime']))
92
96
  reservations = _split_column(reservations, 'Nodes')
@@ -24,7 +24,7 @@ BUTTON_ACTIONS = {
24
24
  'right_1': [ord('d'), curses.KEY_RIGHT, ord('l')],
25
25
  'up_tab': [curses.KEY_NPAGE],
26
26
  'down_tab': [curses.KEY_PPAGE],
27
- 'quit': [ord('q'), 'q'],
27
+ 'quit': [ord('q'), 'q', 27], # 27 is ESC
28
28
  'sort_prio': [ord('S')],
29
29
  'res_view_mode': [ord('g')],
30
30
  'job_id_type': [ord('b')],
@@ -403,7 +403,7 @@ def handle_keys(stdscr, instance):
403
403
  elif k in BUTTON_ACTIONS['sort_prio']:
404
404
  instance.sort_by_prio = not instance.sort_by_prio
405
405
  elif k in BUTTON_ACTIONS['res_view_mode']:
406
- instance.view_mode = {"gpu": "ram", "ram": "cpu", "cpu": "gpu"}[instance.view_mode]
406
+ instance.view_mode = {"gpu": "ram", "ram": "cpu", "cpu": "gpu", "info": "gpu"}[instance.view_mode]
407
407
  instance.right_width = 33 if instance.view_mode != 'info' else 39
408
408
  stdscr.clear()
409
409
  elif k in BUTTON_ACTIONS['job_id_type']:
@@ -446,7 +446,7 @@ def update_screen(stdscr, instance):
446
446
  totsize += 10
447
447
  if instance.show_prio:
448
448
  totsize += 8
449
- totsize += 10 # from /etc/update-motd.d/02-wait-times
449
+ # totsize += 10 # from /etc/update-motd.d/02-wait-times
450
450
 
451
451
  if columns < totsize:
452
452
  stdscr.addstr(1, 1, "MINIMUM TERM. WIDTH")
@@ -532,7 +532,7 @@ def update_screen(stdscr, instance):
532
532
  instance.add_button(lines - 1, xoffset + 50 + 2, '[T:ACCOUNT]', BUTTON_ACTIONS['show_account'][0])
533
533
 
534
534
  # get slurm user partition
535
- stdscr.addstr(lines - 1, xoffset + 62 + 2, f'(Avg time {instance.avg_wait_time})', curses.color_pair(2))
535
+ # stdscr.addstr(lines - 1, xoffset + 62 + 2, f'(Avg time {instance.avg_wait_time})', curses.color_pair(2))
536
536
 
537
537
  signature = instance.signature if instance.updated else f'{instance.version} -> {instance.newest_version}'
538
538
  stdscr.addstr(lines - 1, columns - 2 - len(signature), signature, curses.color_pair(2) if instance.updated else curses.color_pair(3))
@@ -111,6 +111,33 @@ def get_cpu_block(cpus):
111
111
  return cpus // cpu_size
112
112
 
113
113
 
114
+ def get_node_status(node, jobs):
115
+ ram_occs = 0
116
+ cpu_occs = 0
117
+ for j in sorted(jobs, key=lambda x: (x.partition, x.user)):
118
+ for jj in j.joblets:
119
+ if node.name == jj.node:
120
+ ram_occs += jj.mem
121
+ cpu_occs += jj.cpus
122
+
123
+ node_total_ram = node.mem
124
+ node_total_cpus = node.cpus
125
+
126
+ # compute the usage percentage for both ram and cpu
127
+ ram_usage = (ram_occs / node_total_ram)
128
+ cpu_usage = (cpu_occs / node_total_cpus)
129
+
130
+ # get worst case usage
131
+ usage = max(ram_usage, cpu_usage)
132
+
133
+ if usage > 0.9:
134
+ return 'RED'
135
+ elif usage > 0.7:
136
+ return 'YELLOW'
137
+ else:
138
+ return None # white?
139
+
140
+
114
141
  def view_viz(infrastructure, jobs, work=True, stylefn=cmdstyle, current_user=None, mode='gpu'):
115
142
  if mode == 'gpu':
116
143
  return view_viz_gpu(infrastructure, jobs, work, stylefn, current_user)
@@ -294,6 +321,7 @@ def view_viz_gpu(infrastructure, jobs, work=True, stylefn=cmdstyle, current_user
294
321
 
295
322
  joblet_icons = [(ji[0] + (' ' if i != len(joblet_icons) - 1 else ''), ji[1]) for i, ji in enumerate(joblet_icons)]
296
323
 
324
+ node_status = get_node_status(n, jobs)
297
325
  jobsplit = [""]
298
326
  count = 0
299
327
  for ic, c in joblet_icons:
@@ -301,7 +329,12 @@ def view_viz_gpu(infrastructure, jobs, work=True, stylefn=cmdstyle, current_user
301
329
  if count == gpu_box_chars:
302
330
  jobsplit.append("")
303
331
  count = 0
304
- jobsplit[-1] += stylefn(c, i) if c is not None else i
332
+ if c is not None:
333
+ jobsplit[-1] += stylefn(c, i)
334
+ elif node_status is not None:
335
+ jobsplit[-1] += stylefn(node_status, i)
336
+ else:
337
+ jobsplit[-1] += i
305
338
  count += 1
306
339
 
307
340
  for i, l in enumerate(jobsplit):
@@ -323,7 +356,8 @@ def view_viz_gpu(infrastructure, jobs, work=True, stylefn=cmdstyle, current_user
323
356
  cust_print('')
324
357
 
325
358
  # print summary
326
- cust_print(''.join([' [' + gpu_occ + 'run', gpu_paused + 'hld', gpu_drain + 'drn', gpu_pendr + 'pnd', gpu_down + 'dwn', '()res]']))
359
+ cust_print(''.join([' [' + gpu_occ + 'run ()resv ', stylefn('YELLOW', gpu_avail) + '>70% ', stylefn('RED', gpu_avail), '>90%]']))
360
+ cust_print(''.join([' [', gpu_paused + 'hld ', gpu_drain + 'drn ', gpu_pendr + 'pnd ', gpu_down + 'dwn', ']']))
327
361
  gpuc = 'RED'
328
362
  if infrastructure.gpu_limit_pu > 3:
329
363
  gpuc = 'YELLOW'
@@ -0,0 +1,79 @@
1
+ def _format_to(st, width, dir='left'):
2
+ ts = str(st)[:width]
3
+ if dir == 'left':
4
+ return ts + ' ' * (width - len(ts))
5
+ else:
6
+ return ' ' * (width - len(ts)) + ts
7
+
8
+
9
+ def cmdstyle(color, string):
10
+ """
11
+ style formatting function for unix terminals
12
+ """
13
+ strings = string.split('\n')
14
+ styles = {
15
+ 'RED': lambda x: '\033[31m' + str(x) + '\033[0m',
16
+ 'YELLOW': lambda x: '\033[33m' + str(x) + '\033[0m',
17
+ 'GREEN': lambda x: '\033[32m' + str(x) + '\033[0m',
18
+ 'MAGENTA': lambda x: '\033[35m' + str(x) + '\033[0m',
19
+ 'BLUE': lambda x: '\033[34m' + str(x) + '\033[0m',
20
+
21
+ 'LRED': lambda x: '\033[91m' + str(x) + '\033[0m',
22
+ 'LYELLOW': lambda x: '\033[93m' + str(x) + '\033[0m',
23
+ 'LGREEN': lambda x: '\033[92m' + str(x) + '\033[0m',
24
+ 'LMAGENTA': lambda x: '\033[95m' + str(x) + '\033[0m',
25
+ 'LBLUE': lambda x: '\033[94m' + str(x) + '\033[0m',
26
+ 'LCYAN': lambda x: '\033[96m' + str(x) + '\033[0m',
27
+ 'LWHITE': lambda x: '\033[97m' + str(x) + '\033[0m',
28
+
29
+ 'CYAN': lambda x: '\033[36m' + str(x) + '\033[0m',
30
+ 'BG_CYAN': lambda x: '\033[33m' + str(x) + '\033[0m',
31
+ 'WHITE': lambda x: '\033[37m' + str(x) + '\033[0m',
32
+
33
+ 'BG_RED': lambda x: '\033[41m\033[37m' + str(x) + '\033[0m',
34
+ 'BG_GREEN': lambda x: '\033[42m\033[37m' + str(x) + '\033[0m',
35
+ 'BG_YELLOW': lambda x: '\033[43m' + str(x) + '\033[0m',
36
+ 'BG_MAGENTA': lambda x: '\033[45m\033[37m' + str(x) + '\033[0m'
37
+
38
+ }
39
+ return '\n'.join([styles[color](x) for x in strings])
40
+
41
+
42
+ def crsstyler(color, string):
43
+ """
44
+ custom style formatting function for curses applications
45
+ """
46
+ strings = string.split('\n')
47
+ styles = {
48
+ 'RED': lambda x: '<*3~' + str(x) + ':*>',
49
+ 'YELLOW': lambda x: '<*4~' + str(x) + ':*>',
50
+ 'GREEN': lambda x: '<*5~' + str(x) + ':*>',
51
+ 'MAGENTA': lambda x: '<*6~' + str(x) + ':*>',
52
+ 'BLUE': lambda x: '<*7~' + str(x) + ':*>',
53
+
54
+ 'LRED': str,
55
+ 'LYELLOW': str,
56
+ 'LGREEN': str,
57
+ 'LMAGENTA': str,
58
+ 'LBLUE': str,
59
+ 'LCYAN': str,
60
+ 'LWHITE': str,
61
+ 'LBG_CYAN': str,
62
+
63
+ 'CYAN': lambda x: '<*8~' + str(x) + ':*>',
64
+ 'BG_CYAN': lambda x: '<*15~' + str(x) + ':*>',
65
+ 'WHITE': lambda x: '<*2~' + str(x) + ':*>',
66
+
67
+ 'BG_RED': lambda x: '<*10~' + str(x) + ':*>',
68
+ 'BG_GREEN': lambda x: '<*11~' + str(x) + ':*>',
69
+ 'BG_YELLOW': lambda x: '<*12~' + str(x) + ':*>',
70
+ 'BG_MAGENTA': lambda x: '<*13~' + str(x) + ':*>',
71
+ }
72
+ return '\n'.join([styles[color](x) for x in strings])
73
+
74
+
75
+ def mockstyler(color, string):
76
+ """
77
+ mock style formatting function for debug purposes
78
+ """
79
+ return string
@@ -1,76 +0,0 @@
1
- def _format_to(st, width, dir='left'):
2
- ts = str(st)[:width]
3
- if dir == 'left':
4
- return ts + ' ' * (width - len(ts))
5
- else:
6
- return ' ' * (width - len(ts)) + ts
7
-
8
- def cmdstyle(color, string):
9
- """
10
- style formatting function for unix terminals
11
- """
12
- strings = string.split('\n')
13
- styles = {
14
- 'RED' : lambda x: '\033[31m' + str(x) + '\033[0m',
15
- 'YELLOW' : lambda x: '\033[33m' + str(x) + '\033[0m',
16
- 'GREEN' : lambda x: '\033[32m' + str(x) + '\033[0m',
17
- 'MAGENTA' : lambda x: '\033[35m' + str(x) + '\033[0m',
18
- 'BLUE' : lambda x: '\033[34m' + str(x) + '\033[0m',
19
-
20
- 'LRED' : lambda x: '\033[91m' + str(x) + '\033[0m',
21
- 'LYELLOW' : lambda x: '\033[93m' + str(x) + '\033[0m',
22
- 'LGREEN' : lambda x: '\033[92m' + str(x) + '\033[0m',
23
- 'LMAGENTA' : lambda x: '\033[95m' + str(x) + '\033[0m',
24
- 'LBLUE' : lambda x: '\033[94m' + str(x) + '\033[0m',
25
- 'LCYAN' : lambda x: '\033[96m' + str(x) + '\033[0m',
26
- 'LWHITE' : lambda x: '\033[97m' + str(x) + '\033[0m',
27
-
28
- 'CYAN' : lambda x: '\033[36m' + str(x) + '\033[0m',
29
- 'BG_CYAN': lambda x: '\033[33m' + str(x) + '\033[0m',
30
- 'WHITE' : lambda x: '\033[37m' + str(x) + '\033[0m',
31
-
32
- 'BG_RED': lambda x: '\033[41m\033[37m' + str(x) + '\033[0m',
33
- 'BG_GREEN': lambda x: '\033[42m\033[37m' + str(x) + '\033[0m',
34
- 'BG_YELLOW': lambda x: '\033[43m' + str(x) + '\033[0m',
35
- 'BG_MAGENTA': lambda x: '\033[45m\033[37m' + str(x) + '\033[0m'
36
-
37
- }
38
- return '\n'.join([styles[color](x) for x in strings])
39
-
40
- def crsstyler(color, string):
41
- """
42
- custom style formatting function for curses applications
43
- """
44
- strings = string.split('\n')
45
- styles = {
46
- 'RED' : lambda x: '<*3~' + str(x) + ':*>',
47
- 'YELLOW' : lambda x: '<*4~' + str(x) + ':*>',
48
- 'GREEN' : lambda x: '<*5~' + str(x) + ':*>',
49
- 'MAGENTA' : lambda x: '<*6~' + str(x) + ':*>',
50
- 'BLUE' : lambda x: '<*7~' + str(x) + ':*>',
51
-
52
- 'LRED' : str,
53
- 'LYELLOW' : str,
54
- 'LGREEN' : str,
55
- 'LMAGENTA' : str,
56
- 'LBLUE' : str,
57
- 'LCYAN' : str,
58
- 'LWHITE' : str,
59
- 'LBG_CYAN' : str,
60
-
61
- 'CYAN' : lambda x: '<*8~' + str(x) + ':*>',
62
- 'BG_CYAN': lambda x: '<*15~' + str(x) + ':*>',
63
- 'WHITE' : lambda x: '<*2~' + str(x) + ':*>',
64
-
65
- 'BG_RED': lambda x: '<*10~' + str(x) + ':*>',
66
- 'BG_GREEN': lambda x: '<*11~' + str(x) + ':*>',
67
- 'BG_YELLOW': lambda x: '<*12~' + str(x) + ':*>',
68
- 'BG_MAGENTA': lambda x: '<*13~' + str(x) + ':*>'
69
- }
70
- return '\n'.join([styles[color](x) for x in strings])
71
-
72
- def mockstyler(color, string):
73
- """
74
- mock style formatting function for debug purposes
75
- """
76
- return string
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes