vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.0__py3-none-any.whl
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.
- vortex/__init__.py +75 -47
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +944 -618
- vortex/algo/mpitools.py +802 -497
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +436 -227
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +540 -417
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +416 -275
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +215 -122
- vortex/nwp/algo/monitoring.py +137 -76
- vortex/nwp/algo/mpitools.py +330 -190
- vortex/nwp/algo/odbtools.py +637 -353
- vortex/nwp/algo/oopsroot.py +454 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +110 -121
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +341 -162
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +655 -299
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +403 -334
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1234 -643
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +378 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.1.0.dist-info/METADATA +67 -0
- vortex_nwp-2.1.0.dist-info/RECORD +144 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/layout/monitor.py
CHANGED
|
@@ -26,24 +26,32 @@ __all__ = []
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
#: Class for possible states of a :class:`InputMonitorEntry` object
|
|
29
|
-
EntryStateTuple = namedtuple(
|
|
30
|
-
|
|
29
|
+
EntryStateTuple = namedtuple(
|
|
30
|
+
"EntryStateTuple", ["ufo", "expected", "available", "failed"]
|
|
31
|
+
)
|
|
31
32
|
|
|
32
33
|
#: Predefined :class:`InputMonitorEntry` state values
|
|
33
|
-
EntrySt = EntryStateTuple(
|
|
34
|
-
|
|
34
|
+
EntrySt = EntryStateTuple(
|
|
35
|
+
ufo="ufo", expected="expected", available="available", failed="failed"
|
|
36
|
+
)
|
|
35
37
|
|
|
36
38
|
#: Class for possible states of a :class:`_Gang` object
|
|
37
|
-
GangStateTuple = namedtuple(
|
|
38
|
-
|
|
39
|
+
GangStateTuple = namedtuple(
|
|
40
|
+
"GangStateTuple", ["ufo", "collectable", "pcollectable", "failed"]
|
|
41
|
+
)
|
|
39
42
|
|
|
40
43
|
#: Predefined :class:`_Gang` state values
|
|
41
|
-
GangSt = GangStateTuple(
|
|
42
|
-
|
|
44
|
+
GangSt = GangStateTuple(
|
|
45
|
+
ufo="undecided",
|
|
46
|
+
collectable="collectable",
|
|
47
|
+
pcollectable="collectable_partial",
|
|
48
|
+
failed="failed",
|
|
49
|
+
)
|
|
43
50
|
|
|
44
51
|
|
|
45
52
|
class LayoutMonitorError(Exception):
|
|
46
53
|
"""The default exception for this module."""
|
|
54
|
+
|
|
47
55
|
pass
|
|
48
56
|
|
|
49
57
|
|
|
@@ -74,8 +82,9 @@ class _StateFull:
|
|
|
74
82
|
previous = self._state
|
|
75
83
|
self._state = newstate
|
|
76
84
|
self._state_changed(previous, self._state)
|
|
77
|
-
self._obsboard.notify_upd(
|
|
78
|
-
|
|
85
|
+
self._obsboard.notify_upd(
|
|
86
|
+
self, dict(state=self._state, previous_state=previous)
|
|
87
|
+
)
|
|
79
88
|
|
|
80
89
|
state = property(_get_state, _set_state, doc="The entry's state.")
|
|
81
90
|
|
|
@@ -84,7 +93,7 @@ class _StateFullMembersList:
|
|
|
84
93
|
"""Defines an abstract interface: a class with members."""
|
|
85
94
|
|
|
86
95
|
_mstates = EntrySt # The name of possible member's states
|
|
87
|
-
_mcontainer = set
|
|
96
|
+
_mcontainer = set # The container class for the members
|
|
88
97
|
|
|
89
98
|
def __init__(self):
|
|
90
99
|
"""Initialise the members list."""
|
|
@@ -116,7 +125,6 @@ class _StateFullMembersList:
|
|
|
116
125
|
|
|
117
126
|
|
|
118
127
|
class InputMonitorEntry(_StateFull):
|
|
119
|
-
|
|
120
128
|
def __init__(self, section):
|
|
121
129
|
"""An entry manipulated by a :class:`BasicInputMonitor` object.
|
|
122
130
|
|
|
@@ -150,9 +158,14 @@ class _MonitorSilencer(ParallelSilencer):
|
|
|
150
158
|
|
|
151
159
|
def export_result(self, key, ts, prevstate, state):
|
|
152
160
|
"""Returns the recorded data, plus state related informations."""
|
|
153
|
-
return dict(
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
return dict(
|
|
162
|
+
report=super().export_result(),
|
|
163
|
+
name="Input #{!s}".format(key),
|
|
164
|
+
key=key,
|
|
165
|
+
prevstate=prevstate,
|
|
166
|
+
state=state,
|
|
167
|
+
timestamp=ts,
|
|
168
|
+
)
|
|
156
169
|
|
|
157
170
|
|
|
158
171
|
class ManualInputMonitor(_StateFullMembersList):
|
|
@@ -164,8 +177,14 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
164
177
|
|
|
165
178
|
_mcontainer = OrderedDict
|
|
166
179
|
|
|
167
|
-
def __init__(
|
|
168
|
-
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
context,
|
|
183
|
+
targets,
|
|
184
|
+
caching_freq=20,
|
|
185
|
+
crawling_threshold=100,
|
|
186
|
+
mute=False,
|
|
187
|
+
):
|
|
169
188
|
"""
|
|
170
189
|
If the list of inputs is too long (see the *crawling_threshold*
|
|
171
190
|
option), not all of the inputs will be checked at once: The first
|
|
@@ -211,22 +230,24 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
211
230
|
has_term = 0
|
|
212
231
|
map_term = defaultdict(int)
|
|
213
232
|
for e in toclassify:
|
|
214
|
-
if hasattr(e.section.rh.resource,
|
|
233
|
+
if hasattr(e.section.rh.resource, "term"):
|
|
215
234
|
has_term += 1
|
|
216
235
|
map_term[e.section.rh.resource.term.fmthm] += 1
|
|
217
236
|
if toclassify and has_term == len(toclassify):
|
|
218
237
|
toclassify.sort(key=lambda e: e.section.rh.resource.term)
|
|
219
238
|
# Use a crawling threshold that is large enough to span a little bit
|
|
220
239
|
# more than one term.
|
|
221
|
-
self._crawling_threshold = max(
|
|
222
|
-
|
|
240
|
+
self._crawling_threshold = max(
|
|
241
|
+
self._crawling_threshold, int(max(map_term.values()) * 1.25)
|
|
242
|
+
)
|
|
223
243
|
|
|
224
244
|
# Create key/value pairs
|
|
225
245
|
toclassify = [(i, e) for i, e in enumerate(toclassify)]
|
|
226
246
|
|
|
227
247
|
# Classify the input depending on there stage
|
|
228
|
-
self._map_stages = dict(
|
|
229
|
-
|
|
248
|
+
self._map_stages = dict(
|
|
249
|
+
expected=EntrySt.expected, get=EntrySt.available
|
|
250
|
+
)
|
|
230
251
|
while toclassify:
|
|
231
252
|
e = toclassify.pop(0)
|
|
232
253
|
self._append_entry(self._find_state(e[1], onfails=EntrySt.ufo), e)
|
|
@@ -234,9 +255,9 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
234
255
|
def start(self):
|
|
235
256
|
"""Start the background updater task."""
|
|
236
257
|
self._mpjob = multiprocessing.Process(
|
|
237
|
-
name=
|
|
258
|
+
name="BackgroundUpdater",
|
|
238
259
|
target=self._background_updater_job,
|
|
239
|
-
args=()
|
|
260
|
+
args=(),
|
|
240
261
|
)
|
|
241
262
|
self._mpjob.start()
|
|
242
263
|
|
|
@@ -249,18 +270,20 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
249
270
|
t0 = date.now()
|
|
250
271
|
self._mpjob.join(5)
|
|
251
272
|
waiting = date.now() - t0
|
|
252
|
-
logger.info(
|
|
253
|
-
|
|
273
|
+
logger.info(
|
|
274
|
+
"Waiting for the background process to stop took %f seconds",
|
|
275
|
+
waiting.total_seconds(),
|
|
276
|
+
)
|
|
254
277
|
# Be less nice if needed...
|
|
255
278
|
if self._mpjob.is_alive():
|
|
256
|
-
logger.warning(
|
|
279
|
+
logger.warning("Force termination of the background process")
|
|
257
280
|
self._mpjob.terminate()
|
|
258
281
|
time.sleep(1) # Allow some time for the process to terminate
|
|
259
282
|
# Wrap up
|
|
260
283
|
rc = not self._mperror.is_set()
|
|
261
|
-
logger.info(
|
|
284
|
+
logger.info("Server still alive ? %s", str(self._mpjob.is_alive()))
|
|
262
285
|
if not rc:
|
|
263
|
-
raise LayoutMonitorError(
|
|
286
|
+
raise LayoutMonitorError("The background process ended badly.")
|
|
264
287
|
|
|
265
288
|
def __enter__(self):
|
|
266
289
|
self.start()
|
|
@@ -288,11 +311,11 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
288
311
|
|
|
289
312
|
def _key_update(self, res):
|
|
290
313
|
"""Process a result dictionary of the _background_updater method."""
|
|
291
|
-
e = self._members[res[
|
|
314
|
+
e = self._members[res["prevstate"]].pop(res["key"], None)
|
|
292
315
|
# The entry might be missing if someone mess with the _memebers dicitonary
|
|
293
316
|
if e is not None:
|
|
294
|
-
self._append_entry(res[
|
|
295
|
-
self._inactive_since = res[
|
|
317
|
+
self._append_entry(res["state"], (res["key"], e))
|
|
318
|
+
self._inactive_since = res["timestamp"]
|
|
296
319
|
|
|
297
320
|
def _background_updater(self):
|
|
298
321
|
"""This method loops on itself regularly to update the entry's state."""
|
|
@@ -302,14 +325,17 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
302
325
|
kangaroo_idx = 0
|
|
303
326
|
|
|
304
327
|
# Stop if we are asked to or if there is nothing more to do
|
|
305
|
-
while
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
328
|
+
while not self._mpquit.is_set() and not (
|
|
329
|
+
len(self._members[EntrySt.expected]) == 0
|
|
330
|
+
and len(self._members[EntrySt.ufo]) == 0
|
|
331
|
+
):
|
|
309
332
|
# Tweak the caching_frequency
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
333
|
+
if (
|
|
334
|
+
len(self._members[EntrySt.ufo])
|
|
335
|
+
and len(self._members[EntrySt.expected])
|
|
336
|
+
<= self._crawling_threshold
|
|
337
|
+
and not len(self._members[EntrySt.available])
|
|
338
|
+
):
|
|
313
339
|
# If UFO are still there and not much resources are expected,
|
|
314
340
|
# decrease the caching time
|
|
315
341
|
eff_caching_freq = max(3, self._caching_freq / 5)
|
|
@@ -319,37 +345,63 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
319
345
|
curtime = time.time()
|
|
320
346
|
# Crawl into the monitored input if sensible
|
|
321
347
|
if curtime > last_refresh + eff_caching_freq:
|
|
322
|
-
|
|
323
348
|
last_refresh = curtime
|
|
324
349
|
result_stack = list()
|
|
325
350
|
|
|
326
351
|
# Crawl into the ufo list
|
|
327
352
|
# Always process the first self._crawling_threshold elements
|
|
328
|
-
for k, e in islice(
|
|
329
|
-
|
|
353
|
+
for k, e in islice(
|
|
354
|
+
self._members[EntrySt.ufo].items(),
|
|
355
|
+
self._crawling_threshold,
|
|
356
|
+
):
|
|
330
357
|
if self._mpquit.is_set(): # Are we ordered to stop ?
|
|
331
358
|
break
|
|
332
|
-
with _MonitorSilencer(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
359
|
+
with _MonitorSilencer(
|
|
360
|
+
self._ctx, "inputmonitor_updater"
|
|
361
|
+
) as psi:
|
|
362
|
+
logger.info(
|
|
363
|
+
"First get on local file: %s",
|
|
364
|
+
e.section.rh.container.localpath(),
|
|
365
|
+
)
|
|
366
|
+
e.section.get(
|
|
367
|
+
incache=True, fatal=False
|
|
368
|
+
) # Do not crash at this stage
|
|
369
|
+
res = psi.export_result(
|
|
370
|
+
k, curtime, e.state, self._find_state(e)
|
|
371
|
+
)
|
|
337
372
|
self._mpqueue.put_nowait(res)
|
|
338
373
|
result_stack.append(res)
|
|
339
374
|
|
|
340
375
|
# What are the expected elements we will look for ?
|
|
341
376
|
# 1. The first self._crawling_threshold elements
|
|
342
|
-
exp_compress = [
|
|
343
|
-
|
|
377
|
+
exp_compress = [
|
|
378
|
+
1,
|
|
379
|
+
] * min(
|
|
380
|
+
self._crawling_threshold,
|
|
381
|
+
len(self._members[EntrySt.expected]),
|
|
382
|
+
)
|
|
344
383
|
# 2. An additional set of self._crawling_threshold rotating elements
|
|
345
|
-
for i in range(
|
|
384
|
+
for i in range(
|
|
385
|
+
max(
|
|
386
|
+
0,
|
|
387
|
+
len(self._members[EntrySt.expected])
|
|
388
|
+
- self._crawling_threshold,
|
|
389
|
+
)
|
|
390
|
+
):
|
|
346
391
|
kdiff = i - kangaroo_idx
|
|
347
|
-
exp_compress.append(
|
|
392
|
+
exp_compress.append(
|
|
393
|
+
1
|
|
394
|
+
if kdiff >= 0 and kdiff < self._crawling_threshold
|
|
395
|
+
else 0
|
|
396
|
+
)
|
|
348
397
|
|
|
349
398
|
# Crawl into the chosen items of the expected list
|
|
350
399
|
(visited, found, kangaroo_incr) = (0, 0, 0)
|
|
351
|
-
for i, (k, e) in enumerate(
|
|
352
|
-
|
|
400
|
+
for i, (k, e) in enumerate(
|
|
401
|
+
compress(
|
|
402
|
+
self._members[EntrySt.expected].items(), exp_compress
|
|
403
|
+
)
|
|
404
|
+
):
|
|
353
405
|
if self._mpquit.is_set(): # Are we ordered to stop ?
|
|
354
406
|
break
|
|
355
407
|
|
|
@@ -360,31 +412,49 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
360
412
|
# If a lot of resources were already found, avoid harassment
|
|
361
413
|
break
|
|
362
414
|
|
|
363
|
-
logger.debug(
|
|
364
|
-
|
|
415
|
+
logger.debug(
|
|
416
|
+
"Checking local file: %s (kangaroo=%s)",
|
|
417
|
+
e.section.rh.container.localpath(),
|
|
418
|
+
kangaroo,
|
|
419
|
+
)
|
|
365
420
|
e.check_done()
|
|
366
421
|
# Is the promise file still there or not ?
|
|
367
422
|
if e.section.rh.is_grabable():
|
|
368
423
|
visited += 1
|
|
369
|
-
with _MonitorSilencer(
|
|
424
|
+
with _MonitorSilencer(
|
|
425
|
+
self._ctx, "inputmonitor_updater"
|
|
426
|
+
) as psi:
|
|
370
427
|
if e.section.rh.is_grabable(check_exists=True):
|
|
371
|
-
logger.info(
|
|
372
|
-
|
|
428
|
+
logger.info(
|
|
429
|
+
"The local resource %s becomes available",
|
|
430
|
+
e.section.rh.container.localpath(),
|
|
431
|
+
)
|
|
373
432
|
# This will crash in case of an error, but this should
|
|
374
433
|
# not happen since we checked the resource just above
|
|
375
434
|
e.section.get(incache=True)
|
|
376
435
|
found += 1
|
|
377
|
-
res = psi.export_result(
|
|
436
|
+
res = psi.export_result(
|
|
437
|
+
k, curtime, e.state, self._find_state(e)
|
|
438
|
+
)
|
|
378
439
|
else:
|
|
379
|
-
logger.warning(
|
|
380
|
-
|
|
381
|
-
|
|
440
|
+
logger.warning(
|
|
441
|
+
"The local resource %s has failed",
|
|
442
|
+
e.section.rh.container.localpath(),
|
|
443
|
+
)
|
|
444
|
+
res = psi.export_result(
|
|
445
|
+
k, curtime, e.state, EntrySt.failed
|
|
446
|
+
)
|
|
382
447
|
self._mpqueue.put_nowait(res)
|
|
383
448
|
result_stack.append(res)
|
|
384
449
|
|
|
385
450
|
# Update the kangaroo index
|
|
386
451
|
kangaroo_idx = kangaroo_idx + kangaroo_incr - visited
|
|
387
|
-
if
|
|
452
|
+
if (
|
|
453
|
+
kangaroo_idx
|
|
454
|
+
> len(self._members[EntrySt.expected])
|
|
455
|
+
- self._crawling_threshold
|
|
456
|
+
- 1
|
|
457
|
+
):
|
|
388
458
|
kangaroo_idx = 0
|
|
389
459
|
|
|
390
460
|
# Effectively update the internal _members dictionary
|
|
@@ -401,9 +471,9 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
401
471
|
self._background_updater()
|
|
402
472
|
except Exception:
|
|
403
473
|
(exc_type, exc_value, exc_traceback) = sys.exc_info()
|
|
404
|
-
print(
|
|
405
|
-
print(
|
|
406
|
-
print(
|
|
474
|
+
print("Exception type: {!s}".format(exc_type))
|
|
475
|
+
print("Exception info: {!s}".format(exc_value))
|
|
476
|
+
print("Traceback:")
|
|
407
477
|
print("\n".join(traceback.format_tb(exc_traceback)))
|
|
408
478
|
# Alert the main process of the error
|
|
409
479
|
self._mperror.set()
|
|
@@ -415,7 +485,7 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
415
485
|
# That's bad...
|
|
416
486
|
if self._mperror.is_set():
|
|
417
487
|
self.stop()
|
|
418
|
-
raise LayoutMonitorError(
|
|
488
|
+
raise LayoutMonitorError("The background process ended badly.")
|
|
419
489
|
# Process all the available update messages
|
|
420
490
|
while True:
|
|
421
491
|
try:
|
|
@@ -425,8 +495,9 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
425
495
|
if prp is None:
|
|
426
496
|
prp = ParallelResultParser(self._ctx)
|
|
427
497
|
if not self._mute:
|
|
428
|
-
self._ctx.system.highlight(
|
|
429
|
-
|
|
498
|
+
self._ctx.system.highlight(
|
|
499
|
+
"The InputMonitor got news for: {!s}".format(r["name"])
|
|
500
|
+
)
|
|
430
501
|
prp(r)
|
|
431
502
|
print()
|
|
432
503
|
self._key_update(r)
|
|
@@ -435,8 +506,10 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
435
506
|
def all_done(self):
|
|
436
507
|
"""Are there any ufo or expected sections left ?"""
|
|
437
508
|
self._refresh()
|
|
438
|
-
return (
|
|
439
|
-
|
|
509
|
+
return (
|
|
510
|
+
len(self._members[EntrySt.expected]) == 0
|
|
511
|
+
and len(self._members[EntrySt.ufo]) == 0
|
|
512
|
+
)
|
|
440
513
|
|
|
441
514
|
@property
|
|
442
515
|
def inactive_time(self):
|
|
@@ -479,9 +552,13 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
479
552
|
time_now = time.time()
|
|
480
553
|
if time_now - self._last_healthcheck > interval:
|
|
481
554
|
self._last_healthcheck = time_now
|
|
482
|
-
logger.info(
|
|
483
|
-
|
|
484
|
-
|
|
555
|
+
logger.info(
|
|
556
|
+
"Still waiting (ufo=%d, expected=%d, available=%d, failed=%d)...",
|
|
557
|
+
len(self._members[EntrySt.ufo]),
|
|
558
|
+
len(self._members[EntrySt.expected]),
|
|
559
|
+
len(self._members[EntrySt.available]),
|
|
560
|
+
len(self._members[EntrySt.failed]),
|
|
561
|
+
)
|
|
485
562
|
|
|
486
563
|
def is_timedout(self, timeout, exception=None):
|
|
487
564
|
"""Check if a timeout occurred.
|
|
@@ -493,9 +570,15 @@ class ManualInputMonitor(_StateFullMembersList):
|
|
|
493
570
|
self._refresh()
|
|
494
571
|
if (timeout > 0) and (self.inactive_time > timeout):
|
|
495
572
|
logger.error("The waiting loop timed out (%d seconds)", timeout)
|
|
496
|
-
logger.error(
|
|
497
|
-
|
|
498
|
-
|
|
573
|
+
logger.error(
|
|
574
|
+
"The following files are still unaccounted for: %s",
|
|
575
|
+
",".join(
|
|
576
|
+
[
|
|
577
|
+
e.section.rh.container.localpath()
|
|
578
|
+
for e in self.expected.values()
|
|
579
|
+
]
|
|
580
|
+
),
|
|
581
|
+
)
|
|
499
582
|
rc = True
|
|
500
583
|
if rc and exception is not None:
|
|
501
584
|
raise exception("The waiting loop timed-out")
|
|
@@ -511,8 +594,15 @@ class BasicInputMonitor(ManualInputMonitor):
|
|
|
511
594
|
|
|
512
595
|
_mcontainer = OrderedDict
|
|
513
596
|
|
|
514
|
-
def __init__(
|
|
515
|
-
|
|
597
|
+
def __init__(
|
|
598
|
+
self,
|
|
599
|
+
context,
|
|
600
|
+
role=None,
|
|
601
|
+
kind=None,
|
|
602
|
+
caching_freq=20,
|
|
603
|
+
crawling_threshold=100,
|
|
604
|
+
mute=False,
|
|
605
|
+
):
|
|
516
606
|
"""
|
|
517
607
|
If the list of inputs is too long (see the *crawling_threshold*
|
|
518
608
|
option), not all of the inputs will be checked at once: The first
|
|
@@ -540,13 +630,19 @@ class BasicInputMonitor(ManualInputMonitor):
|
|
|
540
630
|
self._role = role
|
|
541
631
|
self._kind = kind
|
|
542
632
|
assert not (self._role is None and self._kind is None)
|
|
543
|
-
ManualInputMonitor.__init__(
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
633
|
+
ManualInputMonitor.__init__(
|
|
634
|
+
self,
|
|
635
|
+
context,
|
|
636
|
+
[
|
|
637
|
+
InputMonitorEntry(x)
|
|
638
|
+
for x in context.sequence.filtered_inputs(
|
|
639
|
+
role=self._role, kind=self._kind
|
|
640
|
+
)
|
|
641
|
+
],
|
|
642
|
+
caching_freq=caching_freq,
|
|
643
|
+
crawling_threshold=crawling_threshold,
|
|
644
|
+
mute=mute,
|
|
645
|
+
)
|
|
550
646
|
|
|
551
647
|
|
|
552
648
|
class _Gang(observer.Observer, _StateFull, _StateFullMembersList):
|
|
@@ -580,10 +676,11 @@ class _Gang(observer.Observer, _StateFull, _StateFullMembersList):
|
|
|
580
676
|
def nickname(self):
|
|
581
677
|
"""A fancy representation of the Gang's motive."""
|
|
582
678
|
if not self.info:
|
|
583
|
-
return
|
|
679
|
+
return "Anonymous"
|
|
584
680
|
else:
|
|
585
|
-
return ", ".join(
|
|
586
|
-
|
|
681
|
+
return ", ".join(
|
|
682
|
+
["{:s}={!s}".format(k, v) for k, v in self.info.items()]
|
|
683
|
+
)
|
|
587
684
|
|
|
588
685
|
def add_member(self, *members):
|
|
589
686
|
"""Introduce one or several members to the Gang."""
|
|
@@ -603,8 +700,8 @@ class _Gang(observer.Observer, _StateFull, _StateFullMembersList):
|
|
|
603
700
|
with self._t_lock:
|
|
604
701
|
observer.Observer.updobsitem(self, item, info)
|
|
605
702
|
# Move the item around
|
|
606
|
-
self._members[info[
|
|
607
|
-
self._members[info[
|
|
703
|
+
self._members[info["previous_state"]].remove(item)
|
|
704
|
+
self._members[info["state"]].add(item)
|
|
608
705
|
# Update my own state
|
|
609
706
|
self._refresh_state()
|
|
610
707
|
|
|
@@ -665,20 +762,29 @@ class BasicGang(_Gang):
|
|
|
665
762
|
# Remove the waitlimit timer
|
|
666
763
|
if self._waitlimit_timer is not None and not self._ufo_members:
|
|
667
764
|
self._waitlimit_timer.cancel()
|
|
668
|
-
logger.debug(
|
|
669
|
-
|
|
765
|
+
logger.debug(
|
|
766
|
+
"Waitlimit Timer thread canceled: %s (Gang: %s)",
|
|
767
|
+
self._waitlimit_timer,
|
|
768
|
+
self.nickname,
|
|
769
|
+
)
|
|
670
770
|
self._waitlimit_timer = None
|
|
671
771
|
# Print some diagnosis data
|
|
672
772
|
if self.info and new != self._mystates.ufo:
|
|
673
|
-
msg =
|
|
674
|
-
|
|
773
|
+
msg = "State changed from {:s} to {:s} for Gang: {:s}".format(
|
|
774
|
+
previous, new, self.nickname
|
|
775
|
+
)
|
|
675
776
|
if new == self._mystates.pcollectable:
|
|
676
777
|
if self._ufo_members:
|
|
677
|
-
logger.warning(
|
|
678
|
-
|
|
679
|
-
|
|
778
|
+
logger.warning(
|
|
779
|
+
"%s\nSome of the Gang's members are still expected "
|
|
780
|
+
+ "but the %d seconds waitlimit is exhausted.",
|
|
781
|
+
msg,
|
|
782
|
+
self.waitlimit,
|
|
783
|
+
)
|
|
680
784
|
else:
|
|
681
|
-
logger.warning(
|
|
785
|
+
logger.warning(
|
|
786
|
+
"%s\nSome of the Gang's members have failed.", msg
|
|
787
|
+
)
|
|
682
788
|
else:
|
|
683
789
|
logger.info(msg)
|
|
684
790
|
|
|
@@ -688,30 +794,40 @@ class BasicGang(_Gang):
|
|
|
688
794
|
def _waitlimit_check():
|
|
689
795
|
with self._t_lock:
|
|
690
796
|
self._refresh_state()
|
|
691
|
-
logger.debug(
|
|
692
|
-
|
|
797
|
+
logger.debug(
|
|
798
|
+
"Waitlimit Timer thread done: %s (Gang: %s)",
|
|
799
|
+
self._waitlimit_timer,
|
|
800
|
+
self.nickname,
|
|
801
|
+
)
|
|
693
802
|
self._waitlimit_timer = None
|
|
694
803
|
|
|
695
|
-
self._waitlimit_timer = threading.Timer(
|
|
696
|
-
|
|
804
|
+
self._waitlimit_timer = threading.Timer(
|
|
805
|
+
self.waitlimit + 1, _waitlimit_check
|
|
806
|
+
)
|
|
697
807
|
self._waitlimit_timer.daemon = True
|
|
698
808
|
self._waitlimit_timer.start()
|
|
699
|
-
logger.debug(
|
|
700
|
-
|
|
809
|
+
logger.debug(
|
|
810
|
+
"Waitlimit Timer thread started: %s (Gang: %s)",
|
|
811
|
+
self._waitlimit_timer,
|
|
812
|
+
self.nickname,
|
|
813
|
+
)
|
|
701
814
|
|
|
702
815
|
def add_member(self, *members):
|
|
703
816
|
with self._t_lock:
|
|
704
817
|
super().add_member(*members)
|
|
705
|
-
if self._firstseen is None and any(
|
|
706
|
-
|
|
818
|
+
if self._firstseen is None and any(
|
|
819
|
+
[m.state == self._mstates.available for m in members]
|
|
820
|
+
):
|
|
707
821
|
self._firstseen = time.time()
|
|
708
822
|
self._set_waitlimit_timer()
|
|
709
823
|
|
|
710
824
|
def updobsitem(self, item, info):
|
|
711
825
|
with self._t_lock:
|
|
712
826
|
super().updobsitem(item, info)
|
|
713
|
-
if (
|
|
714
|
-
|
|
827
|
+
if (
|
|
828
|
+
self._firstseen is None
|
|
829
|
+
and info["state"] == self._mstates.available
|
|
830
|
+
):
|
|
715
831
|
self._firstseen = time.time()
|
|
716
832
|
self._set_waitlimit_timer()
|
|
717
833
|
|
|
@@ -723,21 +839,29 @@ class BasicGang(_Gang):
|
|
|
723
839
|
@property
|
|
724
840
|
def _ufo_members(self):
|
|
725
841
|
"""The number of ufo members (from a Gang point of view)."""
|
|
726
|
-
return len(self._members[self._mstates.ufo]) + len(
|
|
842
|
+
return len(self._members[self._mstates.ufo]) + len(
|
|
843
|
+
self._members[self._mstates.expected]
|
|
844
|
+
)
|
|
727
845
|
|
|
728
846
|
def _is_collectable(self):
|
|
729
847
|
return len(self._members[self._mstates.available]) == len(self)
|
|
730
848
|
|
|
731
849
|
def _is_pcollectable(self):
|
|
732
|
-
return
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
850
|
+
return len(
|
|
851
|
+
self._members[self._mstates.available]
|
|
852
|
+
) >= self._eff_minsize and (
|
|
853
|
+
self._ufo_members == 0
|
|
854
|
+
or (
|
|
855
|
+
self._firstseen is not None
|
|
856
|
+
and time.time() - self._firstseen > self.waitlimit > 0
|
|
857
|
+
)
|
|
858
|
+
)
|
|
738
859
|
|
|
739
860
|
def _is_undecided(self):
|
|
740
|
-
return
|
|
861
|
+
return (
|
|
862
|
+
len(self._members[self._mstates.available]) + self._ufo_members
|
|
863
|
+
>= self._eff_minsize
|
|
864
|
+
)
|
|
741
865
|
|
|
742
866
|
|
|
743
867
|
class MetaGang(_Gang):
|
|
@@ -764,19 +888,24 @@ class MetaGang(_Gang):
|
|
|
764
888
|
|
|
765
889
|
def has_pcollectable(self):
|
|
766
890
|
"""Is there at least one collectable or collectable_partial member ?"""
|
|
767
|
-
return
|
|
768
|
-
|
|
891
|
+
return len(self._members[self._mstates.pcollectable]) + len(
|
|
892
|
+
self._members[self._mstates.collectable]
|
|
893
|
+
)
|
|
769
894
|
|
|
770
895
|
def pop_collectable(self):
|
|
771
896
|
"""Retrieve a collectable member."""
|
|
772
|
-
return self._unregister_i(
|
|
897
|
+
return self._unregister_i(
|
|
898
|
+
self._members[self._mstates.collectable].pop()
|
|
899
|
+
)
|
|
773
900
|
|
|
774
901
|
def pop_pcollectable(self):
|
|
775
902
|
"""Retrieve a collectable or a collectable_partial member."""
|
|
776
903
|
if self.has_collectable():
|
|
777
904
|
return self.pop_collectable()
|
|
778
905
|
else:
|
|
779
|
-
return self._unregister_i(
|
|
906
|
+
return self._unregister_i(
|
|
907
|
+
self._members[self._mstates.pcollectable].pop()
|
|
908
|
+
)
|
|
780
909
|
|
|
781
910
|
def consume_colectable(self):
|
|
782
911
|
"""Retriece all collectable members (as a generator)."""
|
|
@@ -792,8 +921,10 @@ class MetaGang(_Gang):
|
|
|
792
921
|
return len(self._members[self._mstates.collectable]) == len(self)
|
|
793
922
|
|
|
794
923
|
def _is_pcollectable(self):
|
|
795
|
-
return (
|
|
796
|
-
|
|
924
|
+
return (
|
|
925
|
+
len(self._members[self._mstates.collectable])
|
|
926
|
+
+ len(self._members[self._mstates.pcollectable])
|
|
927
|
+
) == len(self)
|
|
797
928
|
|
|
798
929
|
def _is_undecided(self):
|
|
799
930
|
return len(self._members[self._mstates.failed]) == 0
|
|
@@ -821,13 +952,18 @@ class AutoMetaGang(MetaGang):
|
|
|
821
952
|
# Initialise the gangs
|
|
822
953
|
mdict = defaultdict(list)
|
|
823
954
|
for entry in bm.memberslist:
|
|
824
|
-
entryid = tuple(
|
|
825
|
-
|
|
955
|
+
entryid = tuple(
|
|
956
|
+
[
|
|
957
|
+
entry.section.rh.wide_key_lookup(key)
|
|
958
|
+
for key in grouping_keys
|
|
959
|
+
]
|
|
960
|
+
)
|
|
826
961
|
mdict[entryid].append(entry)
|
|
827
962
|
# Finalise the Gangs setup and use them...
|
|
828
963
|
for entryid, members in mdict.items():
|
|
829
|
-
gang = BasicGang(
|
|
830
|
-
|
|
831
|
-
|
|
964
|
+
gang = BasicGang(
|
|
965
|
+
waitlimit=waitlimit, minsize=len(members) - allowmissing
|
|
966
|
+
)
|
|
967
|
+
gang.add_member(*members)
|
|
832
968
|
gang.info = {k: v for k, v in zip(grouping_keys, entryid)}
|
|
833
969
|
self.add_member(gang)
|