nodeocc 1.0.22__tar.gz → 1.0.24__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.22 → nodeocc-1.0.24}/PKG-INFO +2 -2
- {nodeocc-1.0.22 → nodeocc-1.0.24}/pyproject.toml +1 -1
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/controller/controller.py +14 -4
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/PKG-INFO +2 -2
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/readers/slurmreader.py +1 -1
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/curses_multiwindow.py +35 -23
- {nodeocc-1.0.22 → nodeocc-1.0.24}/README.md +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/setup.cfg +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/setup.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/__init__.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/model/job.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/requires.txt +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/__init__.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/styles.py +0 -0
- {nodeocc-1.0.22 → nodeocc-1.0.24}/src/view/utils.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.24"
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import random
|
|
1
2
|
import sys
|
|
2
3
|
import os
|
|
3
4
|
from pathlib import Path
|
|
@@ -64,11 +65,13 @@ BEGIN_DELIM = "!{$"
|
|
|
64
65
|
END_DELIM_ENCODED = "!}$".encode('utf-8')
|
|
65
66
|
EXTRA_MSG_BEGIN_DELIM = "!{#"
|
|
66
67
|
MAX_BUF = 65535
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
JOB_LIMIT_PER_MSG = 40
|
|
69
|
+
TIMEOUT_MAX = 3
|
|
70
|
+
n_timeout = 0
|
|
69
71
|
|
|
70
72
|
parser = argparse.ArgumentParser(description='Visualize slurm jobs')
|
|
71
73
|
parser.add_argument('--debug', action='store_true', help='Enable logging')
|
|
74
|
+
parser.add_argument('--override', action='store_true', help='Enable logging')
|
|
72
75
|
parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
|
|
73
76
|
parser.add_argument('--basepath', type=str, default='/tmp', help='Base path for nodeocc. Must be readable by all users')
|
|
74
77
|
args = parser.parse_args()
|
|
@@ -168,6 +171,7 @@ def decode_msg_slave(data, delim=BEGIN_DELIM):
|
|
|
168
171
|
|
|
169
172
|
|
|
170
173
|
async def get_data_slave(instance):
|
|
174
|
+
global n_timeout
|
|
171
175
|
inf, jobs, avg_wait_time = None, None, None
|
|
172
176
|
try:
|
|
173
177
|
# listen for data from master
|
|
@@ -210,12 +214,18 @@ async def get_data_slave(instance):
|
|
|
210
214
|
instance.timeme(f"- no data")
|
|
211
215
|
return None, None, None, None
|
|
212
216
|
|
|
217
|
+
n_timeout = 0
|
|
218
|
+
|
|
213
219
|
except BlockingIOError as e:
|
|
214
220
|
pass
|
|
215
221
|
|
|
216
222
|
except TimeoutError as e:
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
n_timeout += 1
|
|
224
|
+
instance.log(f"TIMEOUT {n_timeout}/{TIMEOUT_MAX}")
|
|
225
|
+
if n_timeout > TIMEOUT_MAX:
|
|
226
|
+
try_open_socket_as_slave(instance, force=True)
|
|
227
|
+
|
|
228
|
+
time.sleep(random.randint(100, 10000) / 1000)
|
|
219
229
|
|
|
220
230
|
return inf, jobs, avg_wait_time
|
|
221
231
|
|
|
@@ -36,7 +36,7 @@ def _match_str_wildcard(s, w):
|
|
|
36
36
|
|
|
37
37
|
def _node_from_sinfo(x):
|
|
38
38
|
n = Node(_node_preproc(x['NODELIST']), x.n_gpu, x.m_gpu, x.reserved, None, x.CPUS, x.MEMORY) # , feature=x.AVAIL_FEATURES)
|
|
39
|
-
if x['STATE']
|
|
39
|
+
if 'drng' in x['STATE'] or 'drain' in x['STATE']:
|
|
40
40
|
n.status = 'drain'
|
|
41
41
|
elif x['STATE'].upper() in ('MAINT', 'DOWN', 'DOWN*', 'FAIL', 'FAILING', 'FAIL*') or _match_str_wildcard(x['STATE'].lower(), 'inv*'):
|
|
42
42
|
n.status = 'down'
|
|
@@ -44,7 +44,7 @@ def is_file_readable_byall(file_path):
|
|
|
44
44
|
return os.stat(file_path).st_mode & stat.S_IROTH
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def try_open_socket_as_slave(instance):
|
|
47
|
+
def try_open_socket_as_slave(instance, force=False):
|
|
48
48
|
if len(instance.get_port_files()) == 0:
|
|
49
49
|
raise Exception("No master running")
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ def try_open_socket_as_slave(instance):
|
|
|
57
57
|
instance.log(f"- MASTER HAS CHANGED PORT: {instance.port} vs {cur_port}")
|
|
58
58
|
|
|
59
59
|
instance.port = cur_port
|
|
60
|
-
instance._open_socket_as_slave(instance.port)
|
|
60
|
+
instance._open_socket_as_slave(instance.port, force=force)
|
|
61
61
|
|
|
62
62
|
if instance.try_open_counter > 5:
|
|
63
63
|
instance.err(f"Could not open socket as slave on port {instance.port}")
|
|
@@ -90,7 +90,7 @@ class Singleton:
|
|
|
90
90
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S',
|
|
91
91
|
handlers=[handler])
|
|
92
92
|
|
|
93
|
-
def clean_port_files(self):
|
|
93
|
+
def clean_port_files(self, force=False):
|
|
94
94
|
nodename = socket.getfqdn().split('.')[0]
|
|
95
95
|
bp = os.path.join(self.basepath, nodename)
|
|
96
96
|
if not os.path.exists(bp):
|
|
@@ -100,7 +100,7 @@ class Singleton:
|
|
|
100
100
|
# read pid from file
|
|
101
101
|
pid = Path(os.path.join(bp, f)).read_text()
|
|
102
102
|
# assert no process is running
|
|
103
|
-
if psutil.pid_exists(int(pid)):
|
|
103
|
+
if psutil.pid_exists(int(pid)) and not force:
|
|
104
104
|
self.log(f"Process {pid} is still running, killing self")
|
|
105
105
|
return False
|
|
106
106
|
|
|
@@ -148,8 +148,8 @@ class Singleton:
|
|
|
148
148
|
|
|
149
149
|
self.is_master = not self.check_existing_master_running()
|
|
150
150
|
|
|
151
|
-
if self.is_master:
|
|
152
|
-
self.port = self.create_socket_as_master()
|
|
151
|
+
if self.is_master or args.override:
|
|
152
|
+
self.port = self.create_socket_as_master(force=args.override)
|
|
153
153
|
else:
|
|
154
154
|
try_open_socket_as_slave(self)
|
|
155
155
|
|
|
@@ -176,28 +176,14 @@ class Singleton:
|
|
|
176
176
|
portfiles = [f for f in portfiles if is_file_readable_byall(f) and is_file_writable_byall(f)]
|
|
177
177
|
return portfiles
|
|
178
178
|
|
|
179
|
-
def
|
|
179
|
+
def update_port_file(self, force=False):
|
|
180
180
|
nodename = socket.getfqdn().split('.')[0]
|
|
181
181
|
|
|
182
|
-
# create udp socket for broadcasting
|
|
183
|
-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
184
|
-
|
|
185
|
-
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
186
|
-
|
|
187
|
-
# Enable broadcasting mode
|
|
188
|
-
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
189
|
-
|
|
190
|
-
self.sock.bind(('', 0))
|
|
191
|
-
|
|
192
|
-
self.port = self.sock.getsockname()[1]
|
|
193
|
-
self.sock.settimeout(2)
|
|
194
|
-
self.log(f"Socket created as master on port {self.port}")
|
|
195
|
-
|
|
196
182
|
# get pid of current process
|
|
197
183
|
self.pid = os.getpid()
|
|
198
184
|
|
|
199
185
|
# clean up old port files
|
|
200
|
-
kill_ok = self.clean_port_files()
|
|
186
|
+
kill_ok = self.clean_port_files(force=force)
|
|
201
187
|
if not kill_ok:
|
|
202
188
|
return None
|
|
203
189
|
|
|
@@ -205,12 +191,19 @@ class Singleton:
|
|
|
205
191
|
# create file to store port with 666 permissions to file
|
|
206
192
|
# file is in folder basepath/nodename/master_{port}.portfile
|
|
207
193
|
bp = os.path.join(self.basepath, nodename)
|
|
194
|
+
created = False
|
|
208
195
|
if not os.path.exists(bp):
|
|
196
|
+
created = True
|
|
209
197
|
os.makedirs(bp, mode=0o777)
|
|
198
|
+
|
|
210
199
|
filepath = Path(os.path.join(bp, f"master_{self.port}.portfile"))
|
|
211
200
|
filepath.write_text(str(self.pid))
|
|
212
201
|
os.chmod(filepath, 0o666)
|
|
213
202
|
|
|
203
|
+
# force 777 permissions
|
|
204
|
+
if created:
|
|
205
|
+
os.chmod(bp, 0o777)
|
|
206
|
+
|
|
214
207
|
self.port_filepath = filepath
|
|
215
208
|
except Exception as e:
|
|
216
209
|
self.err(f"Could not create port file: {e}")
|
|
@@ -221,9 +214,26 @@ class Singleton:
|
|
|
221
214
|
os.system(f"rm {filepath}")
|
|
222
215
|
return None
|
|
223
216
|
|
|
217
|
+
def create_socket_as_master(self, force=False):
|
|
218
|
+
# create udp socket for broadcasting
|
|
219
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
220
|
+
|
|
221
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
222
|
+
|
|
223
|
+
# Enable broadcasting mode
|
|
224
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
225
|
+
|
|
226
|
+
self.sock.bind(('', 0))
|
|
227
|
+
|
|
228
|
+
self.port = self.sock.getsockname()[1]
|
|
229
|
+
self.sock.settimeout(2)
|
|
230
|
+
self.log(f"Socket created as master on port {self.port}")
|
|
231
|
+
|
|
232
|
+
self.update_port_file(force=force)
|
|
233
|
+
|
|
224
234
|
return self.port
|
|
225
235
|
|
|
226
|
-
def _open_socket_as_slave(self, port):
|
|
236
|
+
def _open_socket_as_slave(self, port, force=False):
|
|
227
237
|
try:
|
|
228
238
|
# create udp socket for broadcasting
|
|
229
239
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
@@ -238,6 +248,8 @@ class Singleton:
|
|
|
238
248
|
self.sock.settimeout(6.5)
|
|
239
249
|
self.log(f"Socket opened on port {self.port}")
|
|
240
250
|
|
|
251
|
+
self.update_port_file(force=force)
|
|
252
|
+
|
|
241
253
|
self.try_open_counter = 0
|
|
242
254
|
return self.port
|
|
243
255
|
except Exception as e:
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|