nodeocc 1.0.8__tar.gz → 1.0.10__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.8 → nodeocc-1.0.10}/PKG-INFO +2 -1
  2. {nodeocc-1.0.8 → nodeocc-1.0.10}/pyproject.toml +2 -2
  3. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/controller/controller.py +48 -44
  4. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/PKG-INFO +2 -1
  5. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/requires.txt +1 -0
  6. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/readers/slurmreader.py +4 -2
  7. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/curses_multiwindow.py +76 -42
  8. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/utils.py +6 -2
  9. {nodeocc-1.0.8 → nodeocc-1.0.10}/README.md +0 -0
  10. {nodeocc-1.0.8 → nodeocc-1.0.10}/setup.cfg +0 -0
  11. {nodeocc-1.0.8 → nodeocc-1.0.10}/setup.py +0 -0
  12. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/__init__.py +0 -0
  13. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/model/infrastructure.py +0 -0
  14. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/model/job.py +0 -0
  15. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/SOURCES.txt +0 -0
  16. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/dependency_links.txt +0 -0
  17. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/entry_points.txt +0 -0
  18. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/nodeocc.egg-info/top_level.txt +0 -0
  19. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/readers/compute_wait_time.py +0 -0
  20. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/__init__.py +0 -0
  21. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/slurm_list.py +0 -0
  22. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/slurm_viz.py +0 -0
  23. {nodeocc-1.0.8 → nodeocc-1.0.10}/src/view/styles.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.8
3
+ Version: 1.0.10
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
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: pandas
16
16
  Requires-Dist: numpy
17
+ Requires-Dist: psutil
17
18
 
18
19
  # Slurm NodeOCC
19
20
  ![[screenshot](https://github.com/apanariello4/slurm_nodeocc/blob/master/sshot.jpg?raw=true)](sshot.jpg)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nodeocc"
7
- version = "1.0.8"
7
+ version = "1.0.10"
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"
@@ -27,7 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3",
28
28
  ]
29
29
 
30
- dependencies = ["pandas", "numpy"]
30
+ dependencies = ["pandas", "numpy", "psutil"]
31
31
 
32
32
  [project.urls]
33
33
  Repository = "https://github.com/apanariello4/slurm_nodeocc"
@@ -70,8 +70,7 @@ JOB_LIMIT_PER_MSG = 60
70
70
 
71
71
  parser = argparse.ArgumentParser(description='Visualize slurm jobs')
72
72
  parser.add_argument('--debug', action='store_true', help='Enable logging')
73
- parser.add_argument('--master', action='store_true', help='Start master process')
74
- parser.add_argument('--daemon_only', action='store_true', help='Disable all prints - only run in background')
73
+ parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
75
74
  parser.add_argument('--force_override', action='store_true', help='Force override of port file, kill previous instance')
76
75
  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')
77
76
  args = parser.parse_args()
@@ -80,7 +79,7 @@ args = parser.parse_args()
80
79
  assert os.access(args.basepath, os.R_OK), f"Base path {args.basepath} not readable"
81
80
 
82
81
  # export args
83
- Singleton.getInstance(args)
82
+ orig_instance = Singleton.getInstance(args)
84
83
 
85
84
  last_update = os.path.getmtime(conf_path + "/view/slurm_viz.py")
86
85
  if os.path.getmtime(conf_path + "/view/slurm_list.py") > last_update:
@@ -104,7 +103,8 @@ def get_avg_wait_time(instance: Singleton):
104
103
  instance.err(traceback.format_exc())
105
104
  return 'err', 'err'
106
105
 
107
- def send_msg(instance, msg, n_extra_msgs=0, is_extra=False):
106
+
107
+ def send_msg(instance: Singleton, msg, n_extra_msgs=0, is_extra=False):
108
108
  # msg to bytes
109
109
  msg = msg.encode('utf-8')
110
110
  begin_delim = EXTRA_MSG_BEGIN_DELIM if is_extra else BEGIN_DELIM
@@ -116,11 +116,11 @@ def send_msg(instance, msg, n_extra_msgs=0, is_extra=False):
116
116
 
117
117
  instance.sock.sendto(msg, ('<broadcast>', instance.port))
118
118
 
119
- def update_data_master(instance):
120
- if not instance.check_port_file_master():
121
- instance.log(f"Port file dead, killing current instance")
122
- instance.sock.close()
123
- del instance
119
+
120
+ def update_data_master(instance: Singleton):
121
+ if instance.check_existing_master_running():
122
+ instance.log(f"Master already running, killing current instance")
123
+ instance.cleanup()
124
124
  exit(0)
125
125
 
126
126
  inf = readers.slurmreader.read_infrastructure()
@@ -134,12 +134,11 @@ def update_data_master(instance):
134
134
  queue_jobs = [j.to_nested_dict() for j in jobs if j.state != 'R' and j.state != 'CG']
135
135
  queue_jobs = sorted(queue_jobs, key=lambda x: x['priority'], reverse=True)
136
136
  maxlen = min(JOB_LIMIT_PER_MSG, len(running_jobs) + len(queue_jobs))
137
-
137
+
138
138
  queue_jobs, extra_queue_jobs = queue_jobs[:maxlen - len(running_jobs)], queue_jobs[maxlen - len(running_jobs):]
139
139
  cur_timestamp = time.time()
140
140
  msg = json.dumps({'inf': inf.to_nested_dict(), 'jobs': running_jobs + queue_jobs, 'ts': str(cur_timestamp)})
141
141
 
142
-
143
142
  N_EXTRA_MSGS = (len(extra_queue_jobs) // JOB_LIMIT_PER_MSG) + 1 if len(extra_queue_jobs) > 0 else 0
144
143
  send_msg(instance, msg, n_extra_msgs=N_EXTRA_MSGS)
145
144
  instance.timeme("- main broadcast")
@@ -154,6 +153,7 @@ def update_data_master(instance):
154
153
 
155
154
  return inf, jobs
156
155
 
156
+
157
157
  def decode_msg_slave(data, delim=BEGIN_DELIM):
158
158
  data = data.split(END_DELIM_ENCODED)[0]
159
159
  data = data.decode('utf-8')
@@ -165,8 +165,9 @@ def decode_msg_slave(data, delim=BEGIN_DELIM):
165
165
  avg_wait_time = "N/A" # get_avg_wait_time(instance)
166
166
  return inf, jobs, avg_wait_time, msg
167
167
 
168
+
168
169
  async def get_data_slave(instance):
169
- inf, jobs = None, None
170
+ inf, jobs, avg_wait_time = None, None, None
170
171
  try:
171
172
  # listen for data from master
172
173
  # instance.sock.settimeout(6.5)
@@ -182,24 +183,24 @@ async def get_data_slave(instance):
182
183
  orig_timestamp = float(decoded_msg['ts'])
183
184
 
184
185
  total_msg_n = n_msgs_remaining
185
- msg_store = {i:[] for i in range(total_msg_n)}
186
- while n_msgs_remaining>0:
186
+ msg_store = {i: [] for i in range(total_msg_n)}
187
+ while n_msgs_remaining > 0:
187
188
  data, _ = instance.sock.recvfrom(MAX_BUF)
188
189
  back_msg_idx = int(data.decode('utf-8').split(EXTRA_MSG_BEGIN_DELIM)[0])
189
- msg_idx = total_msg_n - back_msg_idx - 1
190
+ msg_idx = total_msg_n - back_msg_idx - 1
190
191
  _, new_jobs, _, new_ts = decode_msg_slave(data, delim=EXTRA_MSG_BEGIN_DELIM)
191
192
 
192
193
  # check if timestamp is the same
193
194
  if float(new_ts['ts']) != orig_timestamp:
194
195
  instance.timeme(f"Timestamp mismatch, skipping")
195
- return inf, jobs, avg_wait_time # return what we have
196
-
196
+ return inf, jobs, avg_wait_time # return what we have
197
+
197
198
  msg_store[msg_idx] += new_jobs
198
199
 
199
200
  n_msgs_remaining -= 1
200
201
  instance.timeme(f"- extra receive, {n_msgs_remaining} remaining")
201
202
 
202
- # sort and merge
203
+ # sort and merge
203
204
  for msg_idx in range(total_msg_n):
204
205
  if len(msg_store[msg_idx]) == 0:
205
206
  instance.log(f"Empty message {msg_idx}")
@@ -226,8 +227,7 @@ async def get_all():
226
227
  updt = max(updt, os.path.getmtime(conf_path + "/view/styles.py"))
227
228
  updt = max(updt, os.path.getmtime(conf_path + "/readers/slurmreader.py"))
228
229
 
229
- instance = Singleton.getInstance()
230
- instance.timeme(f"Starting update")
230
+ orig_instance.timeme(f"Starting update")
231
231
 
232
232
  if updt > last_update:
233
233
  reload(readers.slurmreader)
@@ -236,21 +236,23 @@ async def get_all():
236
236
  reload(view.styles)
237
237
  last_update = updt
238
238
 
239
- instance.timeme(f"- reload")
239
+ orig_instance.timeme(f"- reload")
240
240
 
241
241
  inf, jobs, avg_wait_time = None, None, None
242
242
  try:
243
- if args.master:
244
- inf, jobs = update_data_master(instance)
243
+ if orig_instance.is_master:
244
+ inf, jobs = update_data_master(orig_instance)
245
+
246
+ time.sleep(5)
245
247
  else:
246
248
  # loop = asyncio.get_event_loop()
247
- instance.timeme(f"- listening for data")
249
+ orig_instance.timeme(f"- listening for data")
248
250
 
249
251
  # wait for data from master but async update the view
250
- inf, jobs, avg_wait_time = await get_data_slave(instance)
252
+ inf, jobs, avg_wait_time = await get_data_slave(orig_instance)
251
253
  except Exception as e:
252
- instance.err(f"Exception: {e}")
253
- instance.err(traceback.format_exc())
254
+ orig_instance.err(f"Exception: {e}")
255
+ orig_instance.err(traceback.format_exc())
254
256
  # instance.rens = 'Something went wrong'
255
257
  # instance.nocc = ':('
256
258
 
@@ -262,43 +264,45 @@ def display_main(stdscr):
262
264
 
263
265
 
264
266
  def _main():
265
- if args.daemon_only:
266
- assert args.master, "Daemon mode only available for master"
267
- instance = Singleton.getInstance()
267
+ if args.master_only:
268
+ assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
268
269
 
269
- instance.log(f"Starting master daemon")
270
+ orig_instance.log(f"Starting master daemon")
270
271
  # register atexit
271
272
  import atexit
272
273
 
273
274
  def exit_handler():
274
- instance.log(f"Exiting...")
275
- instance.sock.close()
275
+ orig_instance.log(f"Exiting...")
276
+ orig_instance.sock.close()
276
277
  # remove .port file
277
- if instance.port_file_exists():
278
- Path(instance.get_port_file_name()[1]).unlink()
278
+ if orig_instance.port_file_exists():
279
+ Path(orig_instance.get_port_file_name()[1]).unlink()
279
280
  atexit.register(exit_handler)
280
281
 
281
282
  while True:
282
- instance.timeme(f"Updating...")
283
+ orig_instance.timeme(f"Updating...")
283
284
  try:
284
- update_data_master(instance)
285
+ update_data_master(orig_instance)
285
286
  except Exception as e:
286
- instance.err(f"Exception: {e}")
287
- instance.err(traceback.format_exc())
287
+ orig_instance.err(f"Exception: {e}")
288
+ orig_instance.err(traceback.format_exc())
288
289
 
289
290
  time.sleep(5)
290
291
 
291
292
  else:
292
293
  # configure singleton
293
294
 
294
- Singleton.getInstance().signature = f"{program_name} v{version_number}"
295
- Singleton.getInstance().version = version_number
296
- Singleton.getInstance().updated = updated
297
- Singleton.getInstance().newest_version = newest_version
298
- Singleton.getInstance().fetch_fn = get_all
295
+ orig_instance.signature = f"{program_name} v{version_number}"
296
+ orig_instance.version = version_number
297
+ orig_instance.updated = updated
298
+ orig_instance.newest_version = newest_version
299
+ orig_instance.fetch_fn = get_all
299
300
 
300
301
  wrapper(display_main)
301
302
 
302
303
 
303
304
  if __name__ == '__main__':
304
305
  _main()
306
+
307
+ orig_instance.cleanup()
308
+ exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.8
3
+ Version: 1.0.10
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
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: pandas
16
16
  Requires-Dist: numpy
17
+ Requires-Dist: psutil
17
18
 
18
19
  # Slurm NodeOCC
19
20
  ![[screenshot](https://github.com/apanariello4/slurm_nodeocc/blob/master/sshot.jpg?raw=true)](sshot.jpg)
@@ -1,2 +1,3 @@
1
1
  pandas
2
2
  numpy
3
+ psutil
@@ -85,8 +85,10 @@ def _read_maintenances():
85
85
  if not len(reservations):
86
86
  return [], None, None
87
87
  reservations = pd.DataFrame.from_records(reservations)
88
- reservations['StartTime'] = str(pd.to_datetime(reservations['StartTime']))
89
- reservations['EndTime'] = str(pd.to_datetime(reservations['EndTime']))
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)))
90
+ # reservations['StartTime'] = str(pd.to_datetime(reservations['StartTime']))
91
+ # reservations['EndTime'] = str(pd.to_datetime(reservations['EndTime']))
90
92
  reservations = _split_column(reservations, 'Nodes')
91
93
  maint_flag = reservations['Flags'].str.contains('MAINT') & \
92
94
  reservations['Flags'].str.contains('SPEC_NODES') & \
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import curses
3
+ import random
4
+ import psutil
3
5
  import grp
4
6
  import logging
5
7
  import os
@@ -10,7 +12,6 @@ import time
10
12
  import traceback
11
13
  from logging.handlers import RotatingFileHandler
12
14
  from pathlib import Path
13
-
14
15
  from view import update_views
15
16
 
16
17
  a_filter_values = [None, 'me', 'prod', 'stud', 'cvcs']
@@ -56,19 +57,40 @@ class Singleton:
56
57
  __instance = None
57
58
 
58
59
  @staticmethod
59
- def getInstance(args=None):
60
+ def getInstance(args=None, create=True):
60
61
  """ Static access method. """
61
62
  if Singleton.__instance is None:
63
+ if not create:
64
+ return None
62
65
  Singleton(args)
63
66
  if args is not None:
64
67
  Singleton.__instance.args = args
65
68
  return Singleton.__instance
66
69
 
67
70
  # destructor
68
- def __del__(self):
69
- if self.args.master:
71
+ def cleanup(self):
72
+ if self.is_master:
70
73
  if self.port_file_exists():
71
- Path(self.get_port_file_name()[1]).unlink()
74
+ os.system(f"rm {self.get_port_file_name()[1]}")
75
+ if hasattr(self, 'sock') and self.sock is not None:
76
+ self.sock.close()
77
+
78
+ def setup_logging(self):
79
+ if self.args.master_only:
80
+ handler = RotatingFileHandler(os.path.join(self.basepath, '.master_log.txt'), maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
81
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S',
82
+ handlers=[handler])
83
+ # set logging file
84
+ elif self.args.debug:
85
+ # create rotating file handler
86
+ handler = RotatingFileHandler(os.path.expanduser('~') + '/.nodeocc_log.txt', maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
87
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S',
88
+ handlers=[handler])
89
+
90
+ def clean_port_files(self):
91
+ for f in os.listdir(self.basepath):
92
+ if f.endswith('.port'):
93
+ os.system(f"rm {os.path.join(self.basepath, f)}")
72
94
 
73
95
  def __init__(self, args=None):
74
96
  """ Virtually private constructor. """
@@ -76,6 +98,7 @@ class Singleton:
76
98
  raise Exception("This class is a singleton!")
77
99
  else:
78
100
  Singleton.__instance = self
101
+
79
102
  self.try_open_counter = 0
80
103
  self.voff = 0
81
104
  self.mouse_state = {}
@@ -106,36 +129,29 @@ class Singleton:
106
129
 
107
130
  self.basepath = self.args.basepath
108
131
 
109
- if self.args.daemon_only:
110
- handler = RotatingFileHandler(os.path.join(self.basepath, '.master_log.txt'), maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
111
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S',
112
- handlers=[handler])
113
- # set logging file
114
- elif self.args.debug:
115
- # create rotating file handler
116
- handler = RotatingFileHandler(os.path.expanduser('~') + '/.nodeocc_log.txt', maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
117
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S',
118
- handlers=[handler])
132
+ # check if any .port file exists
133
+ if args.force_override and self.port_file_exists():
134
+ _, port_filepath = self.get_port_file_name()
119
135
 
120
- if self.args.master:
121
- # check if any .port file exists
122
- if self.port_file_exists():
123
- if self.args.force_override:
124
- _, port_filepath = self.get_port_file_name()
136
+ # read pid from file
137
+ pid = Path(port_filepath).read_text()
138
+ # kill process
139
+ try:
140
+ os.kill(int(pid), signal.SIGTERM)
141
+ except Exception as e:
142
+ self.log("Could not kill process, deleting port file")
143
+ self.log(e)
125
144
 
126
- # read pid from file
127
- pid = Path(port_filepath).read_text()
128
- # kill process
129
- try:
130
- os.kill(int(pid), signal.SIGTERM)
131
- except Exception as e:
132
- self.log("Could not kill process, deleting port file")
133
- self.log(e)
145
+ # delete port file
146
+ Path(port_filepath).unlink()
134
147
 
135
- # delete port file
136
- Path(port_filepath).unlink()
137
- else:
138
- raise Exception("Master already running")
148
+ self.is_master = not self.check_existing_master_running()
149
+
150
+ self.setup_logging()
151
+
152
+ if self.is_master:
153
+ # clean up old port files
154
+ self.clean_port_files()
139
155
  self.port = self.create_socket_as_master()
140
156
  else:
141
157
  try_open_socket_as_slave(self)
@@ -146,11 +162,12 @@ class Singleton:
146
162
  return fname, os.path.join(self.basepath, fname)
147
163
  return None
148
164
 
149
- def check_port_file_master(self):
165
+ def check_existing_master_running(self):
150
166
  if self.port_file_exists():
151
- prev_port = int(self.get_port_file_name()[0].split('.')[0])
152
- if hasattr(self, 'port'):
153
- return self.port == prev_port
167
+ # check if process is still running
168
+ pid = Path(self.get_port_file_name()[1]).read_text()
169
+ if pid and int(pid) != os.getpid():
170
+ return psutil.pid_exists(int(pid))
154
171
  return False
155
172
 
156
173
  def port_file_exists(self):
@@ -213,11 +230,11 @@ class Singleton:
213
230
  self._ctime = _ntime
214
231
 
215
232
  def err(self, msg):
216
- if self.args.debug or self.args.daemon_only:
233
+ if self.args.debug or self.args.master_only:
217
234
  logging.error(msg)
218
235
 
219
236
  def log(self, msg):
220
- if self.args.debug or self.args.daemon_only:
237
+ if self.args.debug or self.args.master_only:
221
238
  logging.info(msg)
222
239
 
223
240
  async def fetch(self):
@@ -225,12 +242,14 @@ class Singleton:
225
242
 
226
243
  if self.fetch_fn is not None:
227
244
  inf, jobs, avg_wait_time = await self.fetch_fn() # a_filter_values[self.a_filter])
245
+ master_dead = inf is None
228
246
  self.inf = inf if inf is not None else self.inf
229
247
  self.jobs = jobs if jobs is not None else self.jobs
230
248
  self.avg_wait_time = avg_wait_time if avg_wait_time is not None else self.avg_wait_time
231
249
 
232
250
  _delta_t = time.time() - _ctime
233
251
  self.log(f"Fetch took {_delta_t:.2f} seconds")
252
+ return master_dead
234
253
 
235
254
  def add_button(self, y, x, width, action):
236
255
  if y not in self.mouse_state:
@@ -514,10 +533,25 @@ def get_char_async(stdscr, instance):
514
533
  os.kill(os.getpid(), signal.SIGINT)
515
534
 
516
535
 
517
- async def update_screen_info(stdscr, instance):
536
+ async def update_screen_info(stdscr, instance: Singleton):
518
537
  while instance.k not in BUTTON_ACTIONS['quit']:
519
- await instance.fetch()
520
- instance.log("GOT DATA FROM MASTER")
538
+ master_dead = await instance.fetch()
539
+ if not master_dead:
540
+ instance.log("GOT DATA FROM MASTER")
541
+ else:
542
+ if not instance.check_existing_master_running():
543
+ random_wait_time_ms = random.randint(100, 10000)
544
+ instance.log("MASTER DEAD, WAITING " + str(random_wait_time_ms) + "ms")
545
+
546
+ # WARNING: Possible race-condition here, if master dies and slave tries to become master
547
+ await asyncio.sleep(random_wait_time_ms / 1000)
548
+ if not instance.check_existing_master_running():
549
+ instance.log("MASTER STILL DEAD, TRYING TO BECOME MASTER")
550
+ instance.setup_logging()
551
+ instance.clean_port_files()
552
+ instance.port = instance.create_socket_as_master()
553
+ instance.is_master = True
554
+
521
555
  update_screen(stdscr, instance)
522
556
 
523
557
 
@@ -592,7 +626,7 @@ async def main(stdscr):
592
626
 
593
627
  def exit_handler(sig, frame):
594
628
  instance.log(f"FORCED EXIT...")
595
- instance.sock.close()
629
+ instance.cleanup()
596
630
  exit(0)
597
631
  signal.signal(signal.SIGINT, exit_handler)
598
632
 
@@ -22,8 +22,12 @@ def to_datetime(time_str):
22
22
  return np.datetime64(re.match(r'\d+\s+(.*)\n', str(time_str)).group(1))
23
23
 
24
24
 
25
- def to_datetime(time_str):
26
- return np.datetime64(re.match(r'\d+\s+(.*)\n', str(time_str)).group(1))
25
+ def to_datetime(time_str):
26
+ try:
27
+ dt = np.datetime64(re.match(r'\d+\s+(.*)\n', str(time_str)).group(1))
28
+ except:
29
+ dt = np.datetime64(str(time_str))
30
+ return dt
27
31
 
28
32
 
29
33
  def maintenance_status(infrastructure):
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