nodeocc 1.0.12__tar.gz → 1.0.14__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 (23) hide show
  1. {nodeocc-1.0.12 → nodeocc-1.0.14}/PKG-INFO +1 -1
  2. {nodeocc-1.0.12 → nodeocc-1.0.14}/pyproject.toml +1 -1
  3. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/controller/controller.py +5 -17
  4. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/PKG-INFO +1 -1
  5. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/curses_multiwindow.py +59 -55
  6. {nodeocc-1.0.12 → nodeocc-1.0.14}/README.md +0 -0
  7. {nodeocc-1.0.12 → nodeocc-1.0.14}/setup.cfg +0 -0
  8. {nodeocc-1.0.12 → nodeocc-1.0.14}/setup.py +0 -0
  9. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/__init__.py +0 -0
  10. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/model/infrastructure.py +0 -0
  11. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/model/job.py +0 -0
  12. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/SOURCES.txt +0 -0
  13. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/dependency_links.txt +0 -0
  14. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/entry_points.txt +0 -0
  15. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/requires.txt +0 -0
  16. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/nodeocc.egg-info/top_level.txt +0 -0
  17. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/readers/compute_wait_time.py +0 -0
  18. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/readers/slurmreader.py +0 -0
  19. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/__init__.py +0 -0
  20. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/slurm_list.py +0 -0
  21. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/slurm_viz.py +0 -0
  22. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/styles.py +0 -0
  23. {nodeocc-1.0.12 → nodeocc-1.0.14}/src/view/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.12
3
+ Version: 1.0.14
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.12"
7
+ version = "1.0.14"
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"
@@ -38,15 +38,15 @@ try:
38
38
  import requests
39
39
  newest_version = json.loads(requests.get(url).text)['info']['version']
40
40
  print(BBLUE, end='')
41
- if newest_version.split('.')[0] > version_number.split('.')[0]:
41
+ if int(newest_version.split('.')[0]) > int(version_number.split('.')[0]):
42
42
  print(f"New major version available: {newest_version} (current: {version_number})")
43
43
  print(f"Please update with 'pip install {program_name} --upgrade'")
44
44
  time.sleep(7)
45
- elif newest_version.split('.')[1] > version_number.split('.')[1]:
45
+ elif int(newest_version.split('.')[1]) > int(version_number.split('.')[1]):
46
46
  print(f"New minor version available: {newest_version} (current: {version_number})")
47
47
  print(f"Please update with 'pip install {program_name} --upgrade'")
48
48
  time.sleep(7)
49
- elif newest_version.split('.')[2] > version_number.split('.')[2]:
49
+ elif int(newest_version.split('.')[2]) > int(version_number.split('.')[2]):
50
50
  print(f"New patch version available: {newest_version} (current: {version_number})")
51
51
  print(f"Please update with 'pip install {program_name} --upgrade'")
52
52
  time.sleep(7)
@@ -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()
@@ -138,6 +136,7 @@ def update_data_master(instance: Singleton):
138
136
  queue_jobs = sorted(queue_jobs, key=lambda x: x['priority'], reverse=True)
139
137
  maxlen = min(JOB_LIMIT_PER_MSG, len(running_jobs) + len(queue_jobs))
140
138
 
139
+
141
140
  queue_jobs, extra_queue_jobs = queue_jobs[:maxlen - len(running_jobs)], queue_jobs[maxlen - len(running_jobs):]
142
141
  cur_timestamp = time.time()
143
142
  msg = json.dumps({'inf': inf.to_nested_dict(), 'jobs': running_jobs + queue_jobs, 'ts': str(cur_timestamp)})
@@ -196,7 +195,7 @@ async def get_data_slave(instance):
196
195
  # check if timestamp is the same
197
196
  if float(new_ts['ts']) != orig_timestamp:
198
197
  instance.timeme(f"Timestamp mismatch, skipping")
199
- return inf, jobs, avg_wait_time # return what we have
198
+ return inf, jobs, avg_wait_time # return what we have
200
199
 
201
200
  msg_store[msg_idx] += new_jobs
202
201
 
@@ -271,16 +270,6 @@ def _main():
271
270
  assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
272
271
 
273
272
  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
273
 
285
274
  while True:
286
275
  orig_instance.timeme(f"Updating...")
@@ -306,5 +295,4 @@ def _main():
306
295
 
307
296
  _main()
308
297
 
309
- orig_instance.cleanup()
310
298
  exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.12
3
+ Version: 1.0.14
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
@@ -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 not instance.port_file_exists():
48
+ if len(instance.get_port_files()) == 0:
49
49
  raise Exception("No master running")
50
50
 
51
- cur_port = int(instance.get_port_file_name()[0].split('.')[0])
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('.port') and is_file_writable_byall(os.path.join(self.basepath, f)):
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
- if self.port_file_exists():
179
- # check if process is still running
180
- pid = Path(self.get_port_file_name()[1]).read_text()
181
- if pid and int(pid) != os.getpid():
182
- return psutil.pid_exists(int(pid))
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 port_file_exists(self):
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
- portfiles = [f for f in os.listdir(self.basepath) if f.endswith('.port')]
190
- if len(portfiles) == 0:
191
- return False
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 len(portfiles) > 0
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
- # create file to store port with 666 permissions to file
215
- filepath = Path(self.basepath, f'{self.port}.port')
216
- filepath.write_text(str(self.pid))
217
- os.chmod(filepath, 0o666)
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