nodeocc 1.0.12__tar.gz → 1.0.13__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.12 → nodeocc-1.0.13}/PKG-INFO +1 -1
- {nodeocc-1.0.12 → nodeocc-1.0.13}/pyproject.toml +1 -1
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/controller/controller.py +0 -13
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/PKG-INFO +1 -1
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/view/curses_multiwindow.py +59 -55
- {nodeocc-1.0.12 → nodeocc-1.0.13}/README.md +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/setup.cfg +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/setup.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/__init__.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/model/job.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/requires.txt +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/readers/slurmreader.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/view/__init__.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/src/view/styles.py +0 -0
- {nodeocc-1.0.12 → nodeocc-1.0.13}/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.13"
|
|
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"
|
|
@@ -70,7 +70,6 @@ JOB_LIMIT_PER_MSG = 60
|
|
|
70
70
|
parser = argparse.ArgumentParser(description='Visualize slurm jobs')
|
|
71
71
|
parser.add_argument('--debug', action='store_true', help='Enable logging')
|
|
72
72
|
parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
|
|
73
|
-
parser.add_argument('--force_override', action='store_true', help='Force override of port file, kill previous instance')
|
|
74
73
|
parser.add_argument('--basepath', type=str, default='/nas/softechict-nas-2/mboschini/cool_scripts/new_nodeocc/', help='Base path for nodeocc. Must be readable by all users')
|
|
75
74
|
args = parser.parse_args()
|
|
76
75
|
|
|
@@ -123,7 +122,6 @@ def send_msg(instance: Singleton, msg, n_extra_msgs=0, is_extra=False):
|
|
|
123
122
|
def update_data_master(instance: Singleton):
|
|
124
123
|
if instance.check_existing_master_running():
|
|
125
124
|
instance.log(f"Master already running, killing current instance")
|
|
126
|
-
instance.cleanup()
|
|
127
125
|
exit(0)
|
|
128
126
|
|
|
129
127
|
inf = readers.slurmreader.read_infrastructure()
|
|
@@ -271,16 +269,6 @@ def _main():
|
|
|
271
269
|
assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
|
|
272
270
|
|
|
273
271
|
orig_instance.log(f"Starting master daemon")
|
|
274
|
-
# register atexit
|
|
275
|
-
import atexit
|
|
276
|
-
|
|
277
|
-
def exit_handler():
|
|
278
|
-
orig_instance.log(f"Exiting...")
|
|
279
|
-
orig_instance.sock.close()
|
|
280
|
-
# remove .port file
|
|
281
|
-
if orig_instance.port_file_exists():
|
|
282
|
-
Path(orig_instance.get_port_file_name()[1]).unlink()
|
|
283
|
-
atexit.register(exit_handler)
|
|
284
272
|
|
|
285
273
|
while True:
|
|
286
274
|
orig_instance.timeme(f"Updating...")
|
|
@@ -306,5 +294,4 @@ def _main():
|
|
|
306
294
|
|
|
307
295
|
_main()
|
|
308
296
|
|
|
309
|
-
orig_instance.cleanup()
|
|
310
297
|
exit(0)
|
|
@@ -45,10 +45,12 @@ def is_file_readable_byall(file_path):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def try_open_socket_as_slave(instance):
|
|
48
|
-
if
|
|
48
|
+
if len(instance.get_port_files()) == 0:
|
|
49
49
|
raise Exception("No master running")
|
|
50
50
|
|
|
51
|
-
cur_port = int(
|
|
51
|
+
cur_port = [int(f.split('.')[0].split('master_')[1]) for f in instance.get_port_files()
|
|
52
|
+
if (pid := Path(f).read_text()) and psutil.pid_exists(int(pid))][0]
|
|
53
|
+
|
|
52
54
|
if hasattr(instance, 'port'):
|
|
53
55
|
# check if port file has same name
|
|
54
56
|
if instance.port != cur_port:
|
|
@@ -76,14 +78,6 @@ class Singleton:
|
|
|
76
78
|
Singleton.__instance.args = args
|
|
77
79
|
return Singleton.__instance
|
|
78
80
|
|
|
79
|
-
# destructor
|
|
80
|
-
def cleanup(self):
|
|
81
|
-
if self.is_master:
|
|
82
|
-
if self.port_file_exists():
|
|
83
|
-
os.system(f"rm {self.get_port_file_name()[1]}")
|
|
84
|
-
if hasattr(self, 'sock') and self.sock is not None:
|
|
85
|
-
self.sock.close()
|
|
86
|
-
|
|
87
81
|
def setup_logging(self):
|
|
88
82
|
if self.args.master_only:
|
|
89
83
|
handler = RotatingFileHandler(os.path.join(self.basepath, '.master_log.txt'), maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
|
|
@@ -97,9 +91,18 @@ class Singleton:
|
|
|
97
91
|
handlers=[handler])
|
|
98
92
|
|
|
99
93
|
def clean_port_files(self):
|
|
94
|
+
nodename = socket.getfqdn().split('.')[0]
|
|
100
95
|
for f in os.listdir(self.basepath):
|
|
101
|
-
if f.endswith('.
|
|
96
|
+
if nodename in f and 'master_' in f and f.endswith('.portfile') and is_file_writable_byall(os.path.join(self.basepath, f)):
|
|
97
|
+
# read pid from file
|
|
98
|
+
pid = Path(os.path.join(self.basepath, f)).read_text()
|
|
99
|
+
# assert no process is running
|
|
100
|
+
if psutil.pid_exists(int(pid)):
|
|
101
|
+
self.log(f"Process {pid} is still running, killing self")
|
|
102
|
+
return False
|
|
103
|
+
|
|
102
104
|
os.system(f"rm {os.path.join(self.basepath, f)}")
|
|
105
|
+
return True
|
|
103
106
|
|
|
104
107
|
def __init__(self, args=None):
|
|
105
108
|
""" Virtually private constructor. """
|
|
@@ -140,60 +143,36 @@ class Singleton:
|
|
|
140
143
|
|
|
141
144
|
self.basepath = self.args.basepath
|
|
142
145
|
|
|
143
|
-
# check if any .port file exists
|
|
144
|
-
if args.force_override and self.port_file_exists():
|
|
145
|
-
_, port_filepath = self.get_port_file_name()
|
|
146
|
-
|
|
147
|
-
# read pid from file
|
|
148
|
-
pid = Path(port_filepath).read_text()
|
|
149
|
-
# kill process
|
|
150
|
-
try:
|
|
151
|
-
os.kill(int(pid), signal.SIGTERM)
|
|
152
|
-
except Exception as e:
|
|
153
|
-
self.log("Could not kill process, deleting port file")
|
|
154
|
-
self.log(e)
|
|
155
|
-
|
|
156
|
-
# delete port file
|
|
157
|
-
Path(port_filepath).unlink()
|
|
158
|
-
|
|
159
146
|
self.is_master = not self.check_existing_master_running()
|
|
160
147
|
|
|
161
148
|
if self.is_master:
|
|
162
|
-
# clean up old port files
|
|
163
|
-
self.clean_port_files()
|
|
164
149
|
self.port = self.create_socket_as_master()
|
|
165
150
|
else:
|
|
166
151
|
try_open_socket_as_slave(self)
|
|
167
152
|
|
|
168
|
-
def get_port_file_name(self):
|
|
169
|
-
if self.port_file_exists():
|
|
170
|
-
portfiles = [f for f in os.listdir(self.basepath) if f.endswith('.port')]
|
|
171
|
-
portfiles = [(f, os.path.join(self.basepath, f)) for f in portfiles]
|
|
172
|
-
portfiles = [(fname, fpath) for fname, fpath in portfiles if is_file_readable_byall(fpath) and is_file_writable_byall(fpath)][0]
|
|
173
|
-
|
|
174
|
-
return portfiles[0], portfiles[1]
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
153
|
def check_existing_master_running(self):
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
154
|
+
portfiles = self.get_port_files()
|
|
155
|
+
if len(portfiles) > 0:
|
|
156
|
+
for f in portfiles:
|
|
157
|
+
# check if process is still running
|
|
158
|
+
pid = Path(f).read_text()
|
|
159
|
+
if pid and int(pid) != os.getpid():
|
|
160
|
+
return psutil.pid_exists(int(pid))
|
|
183
161
|
return False
|
|
184
162
|
|
|
185
|
-
def
|
|
163
|
+
def get_port_files(self):
|
|
186
164
|
"""
|
|
187
165
|
Check if a port file exists in the basepath and the file has 666 permissions
|
|
188
166
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
portfiles = [os.path.join(self.basepath, f) for f in portfiles]
|
|
167
|
+
nodename = socket.getfqdn().split('.')[0]
|
|
168
|
+
portfiles = [os.path.join(self.basepath, f) for f in os.listdir(self.basepath)
|
|
169
|
+
if f.endswith('.portfile') and 'master_' in f and nodename in f]
|
|
193
170
|
portfiles = [f for f in portfiles if is_file_readable_byall(f) and is_file_writable_byall(f)]
|
|
194
|
-
return
|
|
171
|
+
return portfiles
|
|
195
172
|
|
|
196
173
|
def create_socket_as_master(self):
|
|
174
|
+
nodename = socket.getfqdn().split('.')[0]
|
|
175
|
+
|
|
197
176
|
# create udp socket for broadcasting
|
|
198
177
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
199
178
|
|
|
@@ -211,10 +190,26 @@ class Singleton:
|
|
|
211
190
|
# get pid of current process
|
|
212
191
|
self.pid = os.getpid()
|
|
213
192
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
193
|
+
# clean up old port files
|
|
194
|
+
kill_ok = self.clean_port_files()
|
|
195
|
+
if not kill_ok:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# create file to store port with 666 permissions to file
|
|
200
|
+
filepath = Path(self.basepath, f'{nodename}_master_{self.port}.portfile')
|
|
201
|
+
filepath.write_text(str(self.pid))
|
|
202
|
+
os.chmod(filepath, 0o666)
|
|
203
|
+
|
|
204
|
+
self.port_filepath = filepath
|
|
205
|
+
except Exception as e:
|
|
206
|
+
self.err(f"Could not create port file: {e}")
|
|
207
|
+
self.err(traceback.format_exc())
|
|
208
|
+
# check if file exists
|
|
209
|
+
if filepath.exists():
|
|
210
|
+
# delete file
|
|
211
|
+
os.system(f"rm {filepath}")
|
|
212
|
+
return None
|
|
218
213
|
|
|
219
214
|
return self.port
|
|
220
215
|
|
|
@@ -570,9 +565,12 @@ async def update_screen_info(stdscr, instance: Singleton):
|
|
|
570
565
|
if not instance.check_existing_master_running():
|
|
571
566
|
instance.log("MASTER STILL DEAD, TRYING TO BECOME MASTER")
|
|
572
567
|
instance.setup_logging()
|
|
573
|
-
instance.clean_port_files()
|
|
574
568
|
instance.port = instance.create_socket_as_master()
|
|
575
569
|
instance.is_master = True
|
|
570
|
+
else:
|
|
571
|
+
try_open_socket_as_slave(instance)
|
|
572
|
+
else:
|
|
573
|
+
try_open_socket_as_slave(instance)
|
|
576
574
|
|
|
577
575
|
update_screen(stdscr, instance)
|
|
578
576
|
|
|
@@ -648,7 +646,13 @@ async def main(stdscr):
|
|
|
648
646
|
|
|
649
647
|
def exit_handler(sig, frame):
|
|
650
648
|
instance.log(f"FORCED EXIT...")
|
|
651
|
-
instance.cleanup()
|
|
649
|
+
# instance.cleanup()
|
|
650
|
+
if hasattr(instance, 'port_filepath'):
|
|
651
|
+
os.system(f"rm {instance.port_filepath}")
|
|
652
|
+
|
|
653
|
+
if hasattr(instance, 'sock'):
|
|
654
|
+
instance.sock.close()
|
|
655
|
+
|
|
652
656
|
exit(0)
|
|
653
657
|
signal.signal(signal.SIGINT, exit_handler)
|
|
654
658
|
|
|
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
|