nodeocc 1.0.23__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.23 → nodeocc-1.0.24}/PKG-INFO +2 -2
- {nodeocc-1.0.23 → nodeocc-1.0.24}/pyproject.toml +1 -1
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/controller/controller.py +14 -4
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/PKG-INFO +2 -2
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/view/curses_multiwindow.py +28 -23
- {nodeocc-1.0.23 → nodeocc-1.0.24}/README.md +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/setup.cfg +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/setup.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/__init__.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/model/job.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/requires.txt +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/readers/slurmreader.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/view/__init__.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.23 → nodeocc-1.0.24}/src/view/styles.py +0 -0
- {nodeocc-1.0.23 → 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
|
|
|
@@ -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
|
|
|
@@ -228,9 +214,26 @@ class Singleton:
|
|
|
228
214
|
os.system(f"rm {filepath}")
|
|
229
215
|
return None
|
|
230
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
|
+
|
|
231
234
|
return self.port
|
|
232
235
|
|
|
233
|
-
def _open_socket_as_slave(self, port):
|
|
236
|
+
def _open_socket_as_slave(self, port, force=False):
|
|
234
237
|
try:
|
|
235
238
|
# create udp socket for broadcasting
|
|
236
239
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
@@ -245,6 +248,8 @@ class Singleton:
|
|
|
245
248
|
self.sock.settimeout(6.5)
|
|
246
249
|
self.log(f"Socket opened on port {self.port}")
|
|
247
250
|
|
|
251
|
+
self.update_port_file(force=force)
|
|
252
|
+
|
|
248
253
|
self.try_open_counter = 0
|
|
249
254
|
return self.port
|
|
250
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
|
|
File without changes
|