nodeocc 1.0.9__tar.gz → 1.0.11__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.11}/PKG-INFO +2 -1
- {nodeocc-1.0.9 → nodeocc-1.0.11}/pyproject.toml +2 -2
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/controller/controller.py +60 -54
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/PKG-INFO +2 -1
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/requires.txt +1 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/view/curses_multiwindow.py +77 -43
- {nodeocc-1.0.9 → nodeocc-1.0.11}/README.md +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/setup.cfg +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/setup.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/__init__.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/model/job.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/readers/slurmreader.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/view/__init__.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/src/view/styles.py +0 -0
- {nodeocc-1.0.9 → nodeocc-1.0.11}/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.11
|
|
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.11"
|
|
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"
|
|
@@ -60,7 +60,6 @@ except Exception as e:
|
|
|
60
60
|
newest_version = '?.?.?'
|
|
61
61
|
time.sleep(5)
|
|
62
62
|
|
|
63
|
-
|
|
64
63
|
BEGIN_DELIM = "!{$"
|
|
65
64
|
END_DELIM_ENCODED = "!}$".encode('utf-8')
|
|
66
65
|
EXTRA_MSG_BEGIN_DELIM = "!{#"
|
|
@@ -70,8 +69,7 @@ JOB_LIMIT_PER_MSG = 60
|
|
|
70
69
|
|
|
71
70
|
parser = argparse.ArgumentParser(description='Visualize slurm jobs')
|
|
72
71
|
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')
|
|
72
|
+
parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
|
|
75
73
|
parser.add_argument('--force_override', action='store_true', help='Force override of port file, kill previous instance')
|
|
76
74
|
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
75
|
args = parser.parse_args()
|
|
@@ -80,15 +78,19 @@ args = parser.parse_args()
|
|
|
80
78
|
assert os.access(args.basepath, os.R_OK), f"Base path {args.basepath} not readable"
|
|
81
79
|
|
|
82
80
|
# export args
|
|
83
|
-
Singleton.getInstance(args)
|
|
81
|
+
orig_instance = Singleton.getInstance(args)
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
try:
|
|
84
|
+
last_update = os.path.getmtime(conf_path + "/view/slurm_viz.py")
|
|
85
|
+
if os.path.getmtime(conf_path + "/view/slurm_list.py") > last_update:
|
|
86
|
+
last_update = os.path.getmtime(conf_path + "/view/slurm_list.py")
|
|
87
|
+
if os.path.getmtime(conf_path + "/view/styles.py") > last_update:
|
|
88
|
+
last_update = os.path.getmtime(conf_path + "/view/styles.py")
|
|
89
|
+
if os.path.getmtime(conf_path + "/readers/slurmreader.py") > last_update:
|
|
90
|
+
last_update = os.path.getmtime(conf_path + "/readers/slurmreader.py")
|
|
91
|
+
except Exception as e:
|
|
92
|
+
orig_instance.err(f"Could not get last update time: {e}")
|
|
93
|
+
last_update = time.time()
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
def get_avg_wait_time(instance: Singleton):
|
|
@@ -104,7 +106,8 @@ def get_avg_wait_time(instance: Singleton):
|
|
|
104
106
|
instance.err(traceback.format_exc())
|
|
105
107
|
return 'err', 'err'
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
|
|
110
|
+
def send_msg(instance: Singleton, msg, n_extra_msgs=0, is_extra=False):
|
|
108
111
|
# msg to bytes
|
|
109
112
|
msg = msg.encode('utf-8')
|
|
110
113
|
begin_delim = EXTRA_MSG_BEGIN_DELIM if is_extra else BEGIN_DELIM
|
|
@@ -116,11 +119,11 @@ def send_msg(instance, msg, n_extra_msgs=0, is_extra=False):
|
|
|
116
119
|
|
|
117
120
|
instance.sock.sendto(msg, ('<broadcast>', instance.port))
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
instance.
|
|
123
|
-
|
|
122
|
+
|
|
123
|
+
def update_data_master(instance: Singleton):
|
|
124
|
+
if instance.check_existing_master_running():
|
|
125
|
+
instance.log(f"Master already running, killing current instance")
|
|
126
|
+
instance.cleanup()
|
|
124
127
|
exit(0)
|
|
125
128
|
|
|
126
129
|
inf = readers.slurmreader.read_infrastructure()
|
|
@@ -134,12 +137,11 @@ def update_data_master(instance):
|
|
|
134
137
|
queue_jobs = [j.to_nested_dict() for j in jobs if j.state != 'R' and j.state != 'CG']
|
|
135
138
|
queue_jobs = sorted(queue_jobs, key=lambda x: x['priority'], reverse=True)
|
|
136
139
|
maxlen = min(JOB_LIMIT_PER_MSG, len(running_jobs) + len(queue_jobs))
|
|
137
|
-
|
|
140
|
+
|
|
138
141
|
queue_jobs, extra_queue_jobs = queue_jobs[:maxlen - len(running_jobs)], queue_jobs[maxlen - len(running_jobs):]
|
|
139
142
|
cur_timestamp = time.time()
|
|
140
143
|
msg = json.dumps({'inf': inf.to_nested_dict(), 'jobs': running_jobs + queue_jobs, 'ts': str(cur_timestamp)})
|
|
141
144
|
|
|
142
|
-
|
|
143
145
|
N_EXTRA_MSGS = (len(extra_queue_jobs) // JOB_LIMIT_PER_MSG) + 1 if len(extra_queue_jobs) > 0 else 0
|
|
144
146
|
send_msg(instance, msg, n_extra_msgs=N_EXTRA_MSGS)
|
|
145
147
|
instance.timeme("- main broadcast")
|
|
@@ -154,6 +156,7 @@ def update_data_master(instance):
|
|
|
154
156
|
|
|
155
157
|
return inf, jobs
|
|
156
158
|
|
|
159
|
+
|
|
157
160
|
def decode_msg_slave(data, delim=BEGIN_DELIM):
|
|
158
161
|
data = data.split(END_DELIM_ENCODED)[0]
|
|
159
162
|
data = data.decode('utf-8')
|
|
@@ -165,8 +168,9 @@ def decode_msg_slave(data, delim=BEGIN_DELIM):
|
|
|
165
168
|
avg_wait_time = "N/A" # get_avg_wait_time(instance)
|
|
166
169
|
return inf, jobs, avg_wait_time, msg
|
|
167
170
|
|
|
171
|
+
|
|
168
172
|
async def get_data_slave(instance):
|
|
169
|
-
inf, jobs = None, None
|
|
173
|
+
inf, jobs, avg_wait_time = None, None, None
|
|
170
174
|
try:
|
|
171
175
|
# listen for data from master
|
|
172
176
|
# instance.sock.settimeout(6.5)
|
|
@@ -182,24 +186,24 @@ async def get_data_slave(instance):
|
|
|
182
186
|
orig_timestamp = float(decoded_msg['ts'])
|
|
183
187
|
|
|
184
188
|
total_msg_n = n_msgs_remaining
|
|
185
|
-
msg_store = {i:[] for i in range(total_msg_n)}
|
|
186
|
-
while n_msgs_remaining>0:
|
|
189
|
+
msg_store = {i: [] for i in range(total_msg_n)}
|
|
190
|
+
while n_msgs_remaining > 0:
|
|
187
191
|
data, _ = instance.sock.recvfrom(MAX_BUF)
|
|
188
192
|
back_msg_idx = int(data.decode('utf-8').split(EXTRA_MSG_BEGIN_DELIM)[0])
|
|
189
|
-
msg_idx = total_msg_n - back_msg_idx - 1
|
|
193
|
+
msg_idx = total_msg_n - back_msg_idx - 1
|
|
190
194
|
_, new_jobs, _, new_ts = decode_msg_slave(data, delim=EXTRA_MSG_BEGIN_DELIM)
|
|
191
195
|
|
|
192
196
|
# check if timestamp is the same
|
|
193
197
|
if float(new_ts['ts']) != orig_timestamp:
|
|
194
198
|
instance.timeme(f"Timestamp mismatch, skipping")
|
|
195
|
-
return inf, jobs, avg_wait_time
|
|
196
|
-
|
|
199
|
+
return inf, jobs, avg_wait_time # return what we have
|
|
200
|
+
|
|
197
201
|
msg_store[msg_idx] += new_jobs
|
|
198
202
|
|
|
199
203
|
n_msgs_remaining -= 1
|
|
200
204
|
instance.timeme(f"- extra receive, {n_msgs_remaining} remaining")
|
|
201
205
|
|
|
202
|
-
# sort and merge
|
|
206
|
+
# sort and merge
|
|
203
207
|
for msg_idx in range(total_msg_n):
|
|
204
208
|
if len(msg_store[msg_idx]) == 0:
|
|
205
209
|
instance.log(f"Empty message {msg_idx}")
|
|
@@ -226,8 +230,7 @@ async def get_all():
|
|
|
226
230
|
updt = max(updt, os.path.getmtime(conf_path + "/view/styles.py"))
|
|
227
231
|
updt = max(updt, os.path.getmtime(conf_path + "/readers/slurmreader.py"))
|
|
228
232
|
|
|
229
|
-
|
|
230
|
-
instance.timeme(f"Starting update")
|
|
233
|
+
orig_instance.timeme(f"Starting update")
|
|
231
234
|
|
|
232
235
|
if updt > last_update:
|
|
233
236
|
reload(readers.slurmreader)
|
|
@@ -236,21 +239,23 @@ async def get_all():
|
|
|
236
239
|
reload(view.styles)
|
|
237
240
|
last_update = updt
|
|
238
241
|
|
|
239
|
-
|
|
242
|
+
orig_instance.timeme(f"- reload")
|
|
240
243
|
|
|
241
244
|
inf, jobs, avg_wait_time = None, None, None
|
|
242
245
|
try:
|
|
243
|
-
if
|
|
244
|
-
inf, jobs = update_data_master(
|
|
246
|
+
if orig_instance.is_master:
|
|
247
|
+
inf, jobs = update_data_master(orig_instance)
|
|
248
|
+
|
|
249
|
+
time.sleep(5)
|
|
245
250
|
else:
|
|
246
251
|
# loop = asyncio.get_event_loop()
|
|
247
|
-
|
|
252
|
+
orig_instance.timeme(f"- listening for data")
|
|
248
253
|
|
|
249
254
|
# wait for data from master but async update the view
|
|
250
|
-
inf, jobs, avg_wait_time = await get_data_slave(
|
|
255
|
+
inf, jobs, avg_wait_time = await get_data_slave(orig_instance)
|
|
251
256
|
except Exception as e:
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
orig_instance.err(f"Exception: {e}")
|
|
258
|
+
orig_instance.err(traceback.format_exc())
|
|
254
259
|
# instance.rens = 'Something went wrong'
|
|
255
260
|
# instance.nocc = ':('
|
|
256
261
|
|
|
@@ -262,43 +267,44 @@ def display_main(stdscr):
|
|
|
262
267
|
|
|
263
268
|
|
|
264
269
|
def _main():
|
|
265
|
-
if args.
|
|
266
|
-
assert
|
|
267
|
-
instance = Singleton.getInstance()
|
|
270
|
+
if args.master_only:
|
|
271
|
+
assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
orig_instance.log(f"Starting master daemon")
|
|
270
274
|
# register atexit
|
|
271
275
|
import atexit
|
|
272
276
|
|
|
273
277
|
def exit_handler():
|
|
274
|
-
|
|
275
|
-
|
|
278
|
+
orig_instance.log(f"Exiting...")
|
|
279
|
+
orig_instance.sock.close()
|
|
276
280
|
# remove .port file
|
|
277
|
-
if
|
|
278
|
-
Path(
|
|
281
|
+
if orig_instance.port_file_exists():
|
|
282
|
+
Path(orig_instance.get_port_file_name()[1]).unlink()
|
|
279
283
|
atexit.register(exit_handler)
|
|
280
284
|
|
|
281
285
|
while True:
|
|
282
|
-
|
|
286
|
+
orig_instance.timeme(f"Updating...")
|
|
283
287
|
try:
|
|
284
|
-
update_data_master(
|
|
288
|
+
update_data_master(orig_instance)
|
|
285
289
|
except Exception as e:
|
|
286
|
-
|
|
287
|
-
|
|
290
|
+
orig_instance.err(f"Exception: {e}")
|
|
291
|
+
orig_instance.err(traceback.format_exc())
|
|
288
292
|
|
|
289
293
|
time.sleep(5)
|
|
290
294
|
|
|
291
295
|
else:
|
|
292
296
|
# configure singleton
|
|
293
297
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
orig_instance.signature = f"{program_name} v{version_number}"
|
|
299
|
+
orig_instance.version = version_number
|
|
300
|
+
orig_instance.updated = updated
|
|
301
|
+
orig_instance.newest_version = newest_version
|
|
302
|
+
orig_instance.fetch_fn = get_all
|
|
299
303
|
|
|
300
304
|
wrapper(display_main)
|
|
301
305
|
|
|
302
306
|
|
|
303
|
-
|
|
304
|
-
|
|
307
|
+
_main()
|
|
308
|
+
|
|
309
|
+
orig_instance.cleanup()
|
|
310
|
+
exit(0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nodeocc
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
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,10 @@ class Singleton:
|
|
|
76
98
|
raise Exception("This class is a singleton!")
|
|
77
99
|
else:
|
|
78
100
|
Singleton.__instance = self
|
|
101
|
+
|
|
102
|
+
self.args = args
|
|
103
|
+
self.setup_logging()
|
|
104
|
+
|
|
79
105
|
self.try_open_counter = 0
|
|
80
106
|
self.voff = 0
|
|
81
107
|
self.mouse_state = {}
|
|
@@ -87,7 +113,6 @@ class Singleton:
|
|
|
87
113
|
self.fetch_fn = None
|
|
88
114
|
self.view_mode = 'gpu'
|
|
89
115
|
self.job_id_type = 'agg'
|
|
90
|
-
self.args = args
|
|
91
116
|
|
|
92
117
|
self.show_account = False
|
|
93
118
|
self.show_prio = False
|
|
@@ -106,36 +131,27 @@ class Singleton:
|
|
|
106
131
|
|
|
107
132
|
self.basepath = self.args.basepath
|
|
108
133
|
|
|
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])
|
|
134
|
+
# check if any .port file exists
|
|
135
|
+
if args.force_override and self.port_file_exists():
|
|
136
|
+
_, port_filepath = self.get_port_file_name()
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
# read pid from file
|
|
139
|
+
pid = Path(port_filepath).read_text()
|
|
140
|
+
# kill process
|
|
141
|
+
try:
|
|
142
|
+
os.kill(int(pid), signal.SIGTERM)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.log("Could not kill process, deleting port file")
|
|
145
|
+
self.log(e)
|
|
125
146
|
|
|
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)
|
|
147
|
+
# delete port file
|
|
148
|
+
Path(port_filepath).unlink()
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
self.is_master = not self.check_existing_master_running()
|
|
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
|