nodeocc 1.0.9__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.
- {nodeocc-1.0.9 → nodeocc-1.0.10}/PKG-INFO +2 -1
- {nodeocc-1.0.9 → nodeocc-1.0.10}/pyproject.toml +2 -2
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/controller/controller.py +48 -44
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/PKG-INFO +2 -1
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/requires.txt +1 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/curses_multiwindow.py +76 -42
- {nodeocc-1.0.9 → nodeocc-1.0.10}/README.md +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/setup.cfg +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/setup.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/__init__.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/model/job.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/readers/slurmreader.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/__init__.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/styles.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.10}/src/view/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nodeocc
|
|
3
|
-
Version: 1.0.
|
|
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
|
](sshot.jpg)
|
|
@@ -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.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('--
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
instance.
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
239
|
+
orig_instance.timeme(f"- reload")
|
|
240
240
|
|
|
241
241
|
inf, jobs, avg_wait_time = None, None, None
|
|
242
242
|
try:
|
|
243
|
-
if
|
|
244
|
-
inf, jobs = update_data_master(
|
|
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
|
-
|
|
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(
|
|
252
|
+
inf, jobs, avg_wait_time = await get_data_slave(orig_instance)
|
|
251
253
|
except Exception as e:
|
|
252
|
-
|
|
253
|
-
|
|
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.
|
|
266
|
-
assert
|
|
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
|
-
|
|
270
|
+
orig_instance.log(f"Starting master daemon")
|
|
270
271
|
# register atexit
|
|
271
272
|
import atexit
|
|
272
273
|
|
|
273
274
|
def exit_handler():
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
orig_instance.log(f"Exiting...")
|
|
276
|
+
orig_instance.sock.close()
|
|
276
277
|
# remove .port file
|
|
277
|
-
if
|
|
278
|
-
Path(
|
|
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
|
-
|
|
283
|
+
orig_instance.timeme(f"Updating...")
|
|
283
284
|
try:
|
|
284
|
-
update_data_master(
|
|
285
|
+
update_data_master(orig_instance)
|
|
285
286
|
except Exception as e:
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
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
|
](sshot.jpg)
|
|
@@ -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
|
|
69
|
-
if self.
|
|
71
|
+
def cleanup(self):
|
|
72
|
+
if self.is_master:
|
|
70
73
|
if self.port_file_exists():
|
|
71
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
165
|
+
def check_existing_master_running(self):
|
|
150
166
|
if self.port_file_exists():
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
629
|
+
instance.cleanup()
|
|
596
630
|
exit(0)
|
|
597
631
|
signal.signal(signal.SIGINT, exit_handler)
|
|
598
632
|
|
|
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
|