hpcflow-new2 0.2.0a159__py3-none-any.whl → 0.2.0a160__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.
- hpcflow/_version.py +1 -1
- hpcflow/sdk/__init__.py +2 -0
- hpcflow/sdk/app.py +89 -15
- hpcflow/sdk/cli.py +18 -0
- hpcflow/sdk/cli_common.py +16 -0
- hpcflow/sdk/core/actions.py +14 -6
- hpcflow/sdk/core/command_files.py +4 -4
- hpcflow/sdk/core/element.py +15 -16
- hpcflow/sdk/core/run_dir_files.py +63 -0
- hpcflow/sdk/core/task.py +34 -35
- hpcflow/sdk/core/utils.py +37 -0
- hpcflow/sdk/core/workflow.py +144 -49
- hpcflow/sdk/demo/cli.py +12 -0
- hpcflow/sdk/log.py +2 -2
- hpcflow/sdk/persistence/base.py +140 -12
- hpcflow/sdk/persistence/json.py +84 -63
- hpcflow/sdk/persistence/pending.py +21 -7
- hpcflow/sdk/persistence/zarr.py +143 -108
- hpcflow/sdk/submission/jobscript.py +22 -4
- hpcflow/sdk/submission/shells/bash.py +2 -2
- hpcflow/sdk/submission/shells/powershell.py +2 -2
- hpcflow/sdk/submission/submission.py +20 -7
- hpcflow/tests/scripts/test_main_scripts.py +40 -0
- hpcflow/tests/unit/test_utils.py +28 -0
- {hpcflow_new2-0.2.0a159.dist-info → hpcflow_new2-0.2.0a160.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a159.dist-info → hpcflow_new2-0.2.0a160.dist-info}/RECORD +28 -27
- {hpcflow_new2-0.2.0a159.dist-info → hpcflow_new2-0.2.0a160.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a159.dist-info → hpcflow_new2-0.2.0a160.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/persistence/zarr.py
CHANGED
@@ -42,6 +42,7 @@ from hpcflow.sdk.log import TimeIt
|
|
42
42
|
blosc.use_threads = False # hpcflow is a multiprocess program in general
|
43
43
|
|
44
44
|
|
45
|
+
@TimeIt.decorator
|
45
46
|
def _zarr_get_coord_selection(arr, selection, logger):
|
46
47
|
@retry(
|
47
48
|
RuntimeError,
|
@@ -51,6 +52,7 @@ def _zarr_get_coord_selection(arr, selection, logger):
|
|
51
52
|
jitter=(0, 5),
|
52
53
|
logger=logger,
|
53
54
|
)
|
55
|
+
@TimeIt.decorator
|
54
56
|
def _inner(arr, selection):
|
55
57
|
return arr.get_coordinate_selection(selection)
|
56
58
|
|
@@ -594,14 +596,21 @@ class ZarrPersistentStore(PersistentStore):
|
|
594
596
|
if attrs != attrs_orig:
|
595
597
|
arr.attrs.put(attrs)
|
596
598
|
|
597
|
-
|
599
|
+
@TimeIt.decorator
|
600
|
+
def _update_EAR_submission_indices(self, sub_indices: Dict[int:int]):
|
601
|
+
EAR_IDs = list(sub_indices.keys())
|
602
|
+
EARs = self._get_persistent_EARs(EAR_IDs)
|
603
|
+
|
598
604
|
arr = self._get_EARs_arr(mode="r+")
|
599
605
|
attrs_orig = arr.attrs.asdict()
|
600
606
|
attrs = copy.deepcopy(attrs_orig)
|
601
607
|
|
602
|
-
|
603
|
-
|
604
|
-
|
608
|
+
encoded_EARs = []
|
609
|
+
for EAR_ID_i, sub_idx_i in sub_indices.items():
|
610
|
+
new_EAR_i = EARs[EAR_ID_i].update(submission_idx=sub_idx_i)
|
611
|
+
# seems to be a Zarr bug that prevents `set_coordinate_selection` with an
|
612
|
+
# object array, so set one-by-one:
|
613
|
+
arr[EAR_ID_i] = new_EAR_i.encode(attrs, self.ts_fmt)
|
605
614
|
|
606
615
|
if attrs != attrs_orig:
|
607
616
|
arr.attrs.put(attrs)
|
@@ -686,24 +695,6 @@ class ZarrPersistentStore(PersistentStore):
|
|
686
695
|
f"PersistentStore._append_parameters: finished adding {len(params)} parameters."
|
687
696
|
)
|
688
697
|
|
689
|
-
def _set_parameter_value(self, param_id: int, value: Any, is_file: bool):
|
690
|
-
"""Set an unset persistent parameter."""
|
691
|
-
|
692
|
-
# the `decode` call in `_get_persistent_parameters` should be quick:
|
693
|
-
param_i = self._get_persistent_parameters([param_id])[param_id]
|
694
|
-
if is_file:
|
695
|
-
param_i = param_i.set_file(value)
|
696
|
-
else:
|
697
|
-
param_i = param_i.set_data(value)
|
698
|
-
dat_i = param_i.encode(
|
699
|
-
root_group=self._get_parameter_user_array_group(mode="r+"),
|
700
|
-
arr_path=self._param_data_arr_grp_name(param_i.id_),
|
701
|
-
)
|
702
|
-
|
703
|
-
# no need to update sources array:
|
704
|
-
base_arr = self._get_parameter_base_array(mode="r+")
|
705
|
-
base_arr[param_id] = dat_i
|
706
|
-
|
707
698
|
def _set_parameter_values(self, set_parameters: Dict[int, Tuple[Any, bool]]):
|
708
699
|
"""Set multiple unset persistent parameters."""
|
709
700
|
|
@@ -747,9 +738,16 @@ class ZarrPersistentStore(PersistentStore):
|
|
747
738
|
with self.using_resource("attrs", "update") as md:
|
748
739
|
md["template_components"] = tc
|
749
740
|
|
741
|
+
@TimeIt.decorator
|
750
742
|
def _get_num_persistent_tasks(self) -> int:
|
751
|
-
"""Get the number of persistent
|
752
|
-
|
743
|
+
"""Get the number of persistent tasks."""
|
744
|
+
if self.use_cache and self.num_tasks_cache is not None:
|
745
|
+
num = self.num_tasks_cache
|
746
|
+
else:
|
747
|
+
num = len(self._get_tasks_arr())
|
748
|
+
if self.use_cache and self.num_tasks_cache is None:
|
749
|
+
self.num_tasks_cache = num
|
750
|
+
return num
|
753
751
|
|
754
752
|
def _get_num_persistent_loops(self) -> int:
|
755
753
|
"""Get the number of persistent loops."""
|
@@ -932,32 +930,39 @@ class ZarrPersistentStore(PersistentStore):
|
|
932
930
|
with self.using_resource("attrs", "read") as attrs:
|
933
931
|
return attrs["template"]
|
934
932
|
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
i
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
933
|
+
@TimeIt.decorator
|
934
|
+
def _get_persistent_tasks(self, id_lst: Iterable[int]) -> Dict[int, ZarrStoreTask]:
|
935
|
+
tasks, id_lst = self._get_cached_persistent_tasks(id_lst)
|
936
|
+
if id_lst:
|
937
|
+
with self.using_resource("attrs", action="read") as attrs:
|
938
|
+
task_dat = {}
|
939
|
+
elem_IDs = []
|
940
|
+
for idx, i in enumerate(attrs["tasks"]):
|
941
|
+
i = copy.deepcopy(i)
|
942
|
+
elem_IDs.append(i.pop("element_IDs_idx"))
|
943
|
+
if id_lst is None or i["id_"] in id_lst:
|
944
|
+
task_dat[i["id_"]] = {**i, "index": idx}
|
945
|
+
if task_dat:
|
946
|
+
try:
|
947
|
+
elem_IDs_arr_dat = self._get_tasks_arr().get_coordinate_selection(
|
948
|
+
elem_IDs
|
949
|
+
)
|
950
|
+
except zarr.errors.BoundsCheckError:
|
951
|
+
raise MissingStoreTaskError(
|
952
|
+
elem_IDs
|
953
|
+
) from None # TODO: not an ID list
|
954
|
+
|
955
|
+
new_tasks = {
|
956
|
+
id_: ZarrStoreTask.decode({**i, "element_IDs": elem_IDs_arr_dat[id_]})
|
957
|
+
for idx, (id_, i) in enumerate(task_dat.items())
|
958
|
+
}
|
959
|
+
else:
|
960
|
+
new_tasks = {}
|
961
|
+
self.task_cache.update(new_tasks)
|
962
|
+
tasks.update(new_tasks)
|
963
|
+
return tasks
|
960
964
|
|
965
|
+
@TimeIt.decorator
|
961
966
|
def _get_persistent_loops(self, id_lst: Optional[Iterable[int]] = None):
|
962
967
|
with self.using_resource("attrs", "read") as attrs:
|
963
968
|
loop_dat = {
|
@@ -967,6 +972,7 @@ class ZarrPersistentStore(PersistentStore):
|
|
967
972
|
}
|
968
973
|
return loop_dat
|
969
974
|
|
975
|
+
@TimeIt.decorator
|
970
976
|
def _get_persistent_submissions(self, id_lst: Optional[Iterable[int]] = None):
|
971
977
|
self.logger.debug("loading persistent submissions from the zarr store")
|
972
978
|
with self.using_resource("attrs", "read") as attrs:
|
@@ -989,47 +995,66 @@ class ZarrPersistentStore(PersistentStore):
|
|
989
995
|
|
990
996
|
return subs_dat
|
991
997
|
|
998
|
+
@TimeIt.decorator
|
992
999
|
def _get_persistent_elements(
|
993
1000
|
self, id_lst: Iterable[int]
|
994
1001
|
) -> Dict[int, ZarrStoreElement]:
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1002
|
+
elems, id_lst = self._get_cached_persistent_elements(id_lst)
|
1003
|
+
if id_lst:
|
1004
|
+
arr = self._get_elements_arr()
|
1005
|
+
attrs = arr.attrs.asdict()
|
1006
|
+
try:
|
1007
|
+
elem_arr_dat = arr.get_coordinate_selection(id_lst)
|
1008
|
+
except zarr.errors.BoundsCheckError:
|
1009
|
+
raise MissingStoreElementError(id_lst) from None
|
1010
|
+
elem_dat = dict(zip(id_lst, elem_arr_dat))
|
1011
|
+
new_elems = {
|
1012
|
+
k: ZarrStoreElement.decode(v, attrs) for k, v in elem_dat.items()
|
1013
|
+
}
|
1014
|
+
self.element_cache.update(new_elems)
|
1015
|
+
elems.update(new_elems)
|
1016
|
+
return elems
|
1003
1017
|
|
1018
|
+
@TimeIt.decorator
|
1004
1019
|
def _get_persistent_element_iters(
|
1005
1020
|
self, id_lst: Iterable[int]
|
1006
|
-
) -> Dict[int,
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1021
|
+
) -> Dict[int, ZarrStoreElementIter]:
|
1022
|
+
iters, id_lst = self._get_cached_persistent_element_iters(id_lst)
|
1023
|
+
if id_lst:
|
1024
|
+
arr = self._get_iters_arr()
|
1025
|
+
attrs = arr.attrs.asdict()
|
1026
|
+
try:
|
1027
|
+
iter_arr_dat = arr.get_coordinate_selection(id_lst)
|
1028
|
+
except zarr.errors.BoundsCheckError:
|
1029
|
+
raise MissingStoreElementIterationError(id_lst) from None
|
1030
|
+
iter_dat = dict(zip(id_lst, iter_arr_dat))
|
1031
|
+
new_iters = {
|
1032
|
+
k: ZarrStoreElementIter.decode(v, attrs) for k, v in iter_dat.items()
|
1033
|
+
}
|
1034
|
+
self.element_iter_cache.update(new_iters)
|
1035
|
+
iters.update(new_iters)
|
1036
|
+
return iters
|
1015
1037
|
|
1038
|
+
@TimeIt.decorator
|
1016
1039
|
def _get_persistent_EARs(self, id_lst: Iterable[int]) -> Dict[int, ZarrStoreEAR]:
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1040
|
+
runs, id_lst = self._get_cached_persistent_EARs(id_lst)
|
1041
|
+
if id_lst:
|
1042
|
+
arr = self._get_EARs_arr()
|
1043
|
+
attrs = arr.attrs.asdict()
|
1044
|
+
try:
|
1045
|
+
self.logger.debug(f"_get_persistent_EARs: {id_lst=}")
|
1046
|
+
EAR_arr_dat = _zarr_get_coord_selection(arr, id_lst, self.logger)
|
1047
|
+
except zarr.errors.BoundsCheckError:
|
1048
|
+
raise MissingStoreEARError(id_lst) from None
|
1049
|
+
EAR_dat = dict(zip(id_lst, EAR_arr_dat))
|
1050
|
+
new_runs = {
|
1051
|
+
k: ZarrStoreEAR.decode(EAR_dat=v, attrs=attrs, ts_fmt=self.ts_fmt)
|
1052
|
+
for k, v in EAR_dat.items()
|
1053
|
+
}
|
1054
|
+
self.EAR_cache.update(new_runs)
|
1055
|
+
runs.update(new_runs)
|
1027
1056
|
|
1028
|
-
|
1029
|
-
k: ZarrStoreEAR.decode(EAR_dat=v, attrs=attrs, ts_fmt=self.ts_fmt)
|
1030
|
-
for k, v in EAR_dat.items()
|
1031
|
-
}
|
1032
|
-
return iters
|
1057
|
+
return runs
|
1033
1058
|
|
1034
1059
|
@TimeIt.decorator
|
1035
1060
|
def _get_persistent_parameters(
|
@@ -1037,39 +1062,49 @@ class ZarrPersistentStore(PersistentStore):
|
|
1037
1062
|
id_lst: Iterable[int],
|
1038
1063
|
dataset_copy: Optional[bool] = False,
|
1039
1064
|
) -> Dict[int, ZarrStoreParameter]:
|
1040
|
-
base_arr = self._get_parameter_base_array(mode="r")
|
1041
|
-
src_arr = self._get_parameter_sources_array(mode="r")
|
1042
1065
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
raise MissingParameterData(id_lst) from None
|
1048
|
-
|
1049
|
-
param_dat = dict(zip(id_lst, param_arr_dat))
|
1050
|
-
src_dat = dict(zip(id_lst, src_arr_dat))
|
1066
|
+
params, id_lst = self._get_cached_persistent_parameters(id_lst)
|
1067
|
+
if id_lst:
|
1068
|
+
base_arr = self._get_parameter_base_array(mode="r")
|
1069
|
+
src_arr = self._get_parameter_sources_array(mode="r")
|
1051
1070
|
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
)
|
1060
|
-
|
1061
|
-
|
1071
|
+
try:
|
1072
|
+
param_arr_dat = base_arr.get_coordinate_selection(list(id_lst))
|
1073
|
+
src_arr_dat = src_arr.get_coordinate_selection(list(id_lst))
|
1074
|
+
except zarr.errors.BoundsCheckError:
|
1075
|
+
raise MissingParameterData(id_lst) from None
|
1076
|
+
|
1077
|
+
param_dat = dict(zip(id_lst, param_arr_dat))
|
1078
|
+
src_dat = dict(zip(id_lst, src_arr_dat))
|
1079
|
+
|
1080
|
+
new_params = {
|
1081
|
+
k: ZarrStoreParameter.decode(
|
1082
|
+
id_=k,
|
1083
|
+
data=v,
|
1084
|
+
source=src_dat[k],
|
1085
|
+
arr_group=self._get_parameter_data_array_group(k),
|
1086
|
+
dataset_copy=dataset_copy,
|
1087
|
+
)
|
1088
|
+
for k, v in param_dat.items()
|
1089
|
+
}
|
1090
|
+
self.parameter_cache.update(new_params)
|
1091
|
+
params.update(new_params)
|
1062
1092
|
|
1063
1093
|
return params
|
1064
1094
|
|
1095
|
+
@TimeIt.decorator
|
1065
1096
|
def _get_persistent_param_sources(self, id_lst: Iterable[int]) -> Dict[int, Dict]:
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1097
|
+
sources, id_lst = self._get_cached_persistent_param_sources(id_lst)
|
1098
|
+
if id_lst:
|
1099
|
+
src_arr = self._get_parameter_sources_array(mode="r")
|
1100
|
+
try:
|
1101
|
+
src_arr_dat = src_arr.get_coordinate_selection(list(id_lst))
|
1102
|
+
except zarr.errors.BoundsCheckError:
|
1103
|
+
raise MissingParameterData(id_lst) from None
|
1104
|
+
new_sources = dict(zip(id_lst, src_arr_dat))
|
1105
|
+
self.param_sources_cache.update(new_sources)
|
1106
|
+
sources.update(new_sources)
|
1107
|
+
return sources
|
1073
1108
|
|
1074
1109
|
def _get_persistent_parameter_set_status(
|
1075
1110
|
self, id_lst: Iterable[int]
|
@@ -23,6 +23,7 @@ from hpcflow.sdk.submission.schedulers import Scheduler
|
|
23
23
|
from hpcflow.sdk.submission.shells import get_shell
|
24
24
|
|
25
25
|
|
26
|
+
@TimeIt.decorator
|
26
27
|
def generate_EAR_resource_map(
|
27
28
|
task: app.WorkflowTask,
|
28
29
|
loop_idx: Dict,
|
@@ -77,6 +78,7 @@ def generate_EAR_resource_map(
|
|
77
78
|
)
|
78
79
|
|
79
80
|
|
81
|
+
@TimeIt.decorator
|
80
82
|
def group_resource_map_into_jobscripts(
|
81
83
|
resource_map: Union[List, NDArray],
|
82
84
|
none_val: Any = -1,
|
@@ -148,6 +150,7 @@ def group_resource_map_into_jobscripts(
|
|
148
150
|
return jobscripts, js_map
|
149
151
|
|
150
152
|
|
153
|
+
@TimeIt.decorator
|
151
154
|
def resolve_jobscript_dependencies(jobscripts, element_deps):
|
152
155
|
# first pass is to find the mappings between jobscript elements:
|
153
156
|
jobscript_deps = {}
|
@@ -218,6 +221,7 @@ def resolve_jobscript_dependencies(jobscripts, element_deps):
|
|
218
221
|
return jobscript_deps
|
219
222
|
|
220
223
|
|
224
|
+
@TimeIt.decorator
|
221
225
|
def merge_jobscripts_across_tasks(jobscripts: Dict) -> Dict:
|
222
226
|
"""Try to merge jobscripts between tasks.
|
223
227
|
|
@@ -275,6 +279,7 @@ def merge_jobscripts_across_tasks(jobscripts: Dict) -> Dict:
|
|
275
279
|
return jobscripts
|
276
280
|
|
277
281
|
|
282
|
+
@TimeIt.decorator
|
278
283
|
def jobscripts_to_list(jobscripts: Dict[int, Dict]) -> List[Dict]:
|
279
284
|
"""Convert the jobscripts dict to a list, normalising jobscript indices so they refer
|
280
285
|
to list indices; also remove `resource_hash`."""
|
@@ -397,8 +402,11 @@ class Jobscript(JSONLike):
|
|
397
402
|
return self._workflow_app_alias
|
398
403
|
|
399
404
|
def get_commands_file_name(self, js_action_idx, shell=None):
|
400
|
-
|
401
|
-
|
405
|
+
return self.app.RunDirAppFiles.get_commands_file_name(
|
406
|
+
js_idx=self.index,
|
407
|
+
js_action_idx=js_action_idx,
|
408
|
+
shell=shell or self.shell,
|
409
|
+
)
|
402
410
|
|
403
411
|
@property
|
404
412
|
def task_insert_IDs(self):
|
@@ -421,6 +429,7 @@ class Jobscript(JSONLike):
|
|
421
429
|
return self.EAR_ID.flatten()
|
422
430
|
|
423
431
|
@property
|
432
|
+
@TimeIt.decorator
|
424
433
|
def all_EARs(self) -> List:
|
425
434
|
if not self._all_EARs:
|
426
435
|
self._all_EARs = self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)
|
@@ -439,6 +448,7 @@ class Jobscript(JSONLike):
|
|
439
448
|
return self._dependencies
|
440
449
|
|
441
450
|
@property
|
451
|
+
@TimeIt.decorator
|
442
452
|
def start_time(self):
|
443
453
|
"""Get the first start time from all EARs."""
|
444
454
|
if not self.is_submitted:
|
@@ -450,6 +460,7 @@ class Jobscript(JSONLike):
|
|
450
460
|
return None
|
451
461
|
|
452
462
|
@property
|
463
|
+
@TimeIt.decorator
|
453
464
|
def end_time(self):
|
454
465
|
"""Get the last end time from all EARs."""
|
455
466
|
if not self.is_submitted:
|
@@ -732,6 +743,7 @@ class Jobscript(JSONLike):
|
|
732
743
|
)
|
733
744
|
return loop_idx
|
734
745
|
|
746
|
+
@TimeIt.decorator
|
735
747
|
def write_EAR_ID_file(self):
|
736
748
|
"""Write a text file with `num_elements` lines and `num_actions` delimited tokens
|
737
749
|
per line, representing whether a given EAR must be executed."""
|
@@ -745,6 +757,7 @@ class Jobscript(JSONLike):
|
|
745
757
|
delimiter=self._EAR_files_delimiter,
|
746
758
|
)
|
747
759
|
|
760
|
+
@TimeIt.decorator
|
748
761
|
def write_element_run_dir_file(self, run_dirs: List[List[Path]]):
|
749
762
|
"""Write a text file with `num_elements` lines and `num_actions` delimited tokens
|
750
763
|
per line, representing the working directory for each EAR.
|
@@ -764,6 +777,7 @@ class Jobscript(JSONLike):
|
|
764
777
|
delimiter=self._EAR_files_delimiter,
|
765
778
|
)
|
766
779
|
|
780
|
+
@TimeIt.decorator
|
767
781
|
def compose_jobscript(
|
768
782
|
self,
|
769
783
|
deps: Optional[Dict] = None,
|
@@ -822,7 +836,7 @@ class Jobscript(JSONLike):
|
|
822
836
|
"workflow_app_alias": self.workflow_app_alias,
|
823
837
|
"env_setup": env_setup,
|
824
838
|
"app_invoc": app_invoc,
|
825
|
-
"
|
839
|
+
"run_log_file": self.app.RunDirAppFiles.get_log_file_name(),
|
826
840
|
"config_dir": str(self.app.config.config_directory),
|
827
841
|
"config_invoc_key": self.app.config.config_key,
|
828
842
|
"workflow_path": self.workflow.path,
|
@@ -869,7 +883,7 @@ class Jobscript(JSONLike):
|
|
869
883
|
EAR_files_delimiter=self._EAR_files_delimiter,
|
870
884
|
workflow_app_alias=self.workflow_app_alias,
|
871
885
|
commands_file_name=self.get_commands_file_name(r"${JS_act_idx}", shell=shell),
|
872
|
-
|
886
|
+
run_stream_file=self.app.RunDirAppFiles.get_std_file_name(),
|
873
887
|
)
|
874
888
|
|
875
889
|
out = header
|
@@ -891,6 +905,7 @@ class Jobscript(JSONLike):
|
|
891
905
|
|
892
906
|
return out
|
893
907
|
|
908
|
+
@TimeIt.decorator
|
894
909
|
def write_jobscript(
|
895
910
|
self,
|
896
911
|
os_name: str = None,
|
@@ -953,6 +968,7 @@ class Jobscript(JSONLike):
|
|
953
968
|
|
954
969
|
return run_dirs
|
955
970
|
|
971
|
+
@TimeIt.decorator
|
956
972
|
def _launch_direct_js_win(self):
|
957
973
|
# this is a "trick" to ensure we always get a fully detached new process (with no
|
958
974
|
# parent); the `powershell.exe -Command` process exits after running the inner
|
@@ -996,6 +1012,7 @@ class Jobscript(JSONLike):
|
|
996
1012
|
process_ID = int(self.direct_win_pid_file_path.read_text())
|
997
1013
|
return process_ID
|
998
1014
|
|
1015
|
+
@TimeIt.decorator
|
999
1016
|
def _launch_direct_js_posix(self) -> int:
|
1000
1017
|
# direct submission; submit jobscript asynchronously:
|
1001
1018
|
# detached process, avoid interrupt signals propagating to the subprocess:
|
@@ -1143,6 +1160,7 @@ class Jobscript(JSONLike):
|
|
1143
1160
|
out["num_js_elements"] = self.num_elements
|
1144
1161
|
return out
|
1145
1162
|
|
1163
|
+
@TimeIt.decorator
|
1146
1164
|
def get_active_states(
|
1147
1165
|
self, as_json: bool = False
|
1148
1166
|
) -> Dict[int, JobscriptElementState]:
|
@@ -24,7 +24,7 @@ class Bash(Shell):
|
|
24
24
|
{workflow_app_alias} () {{
|
25
25
|
(
|
26
26
|
{env_setup}{app_invoc}\\
|
27
|
-
--with-config log_file_path "`pwd`/{
|
27
|
+
--with-config log_file_path "`pwd`/{run_log_file}"\\
|
28
28
|
--config-dir "{config_dir}"\\
|
29
29
|
--config-key "{config_invoc_key}"\\
|
30
30
|
"$@"
|
@@ -70,7 +70,7 @@ class Bash(Shell):
|
|
70
70
|
|
71
71
|
run_dir="$(cut -d'{EAR_files_delimiter}' -f $(($JS_act_idx + 1)) <<< $elem_run_dirs)"
|
72
72
|
cd "$WK_PATH/$run_dir"
|
73
|
-
app_stream_file="`pwd`/{
|
73
|
+
app_stream_file="`pwd`/{run_stream_file}"
|
74
74
|
|
75
75
|
skip=`{workflow_app_alias} internal workflow "$WK_PATH_ARG" get-ear-skipped $EAR_ID 2>> "$app_stream_file"`
|
76
76
|
exc_sk=$?
|
@@ -22,7 +22,7 @@ class WindowsPowerShell(Shell):
|
|
22
22
|
function {workflow_app_alias} {{
|
23
23
|
& {{
|
24
24
|
{env_setup}{app_invoc} `
|
25
|
-
--with-config log_file_path "$pwd/{
|
25
|
+
--with-config log_file_path "$pwd/{run_log_file}" `
|
26
26
|
--config-dir "{config_dir}" `
|
27
27
|
--config-key "{config_invoc_key}" `
|
28
28
|
$args
|
@@ -83,7 +83,7 @@ class WindowsPowerShell(Shell):
|
|
83
83
|
$run_dir = ($elem_run_dirs -split "{EAR_files_delimiter}")[$JS_act_idx]
|
84
84
|
$run_dir_abs = "$WK_PATH\\$run_dir"
|
85
85
|
Set-Location $run_dir_abs
|
86
|
-
$app_stream_file = "$pwd/{
|
86
|
+
$app_stream_file = "$pwd/{run_stream_file}"
|
87
87
|
|
88
88
|
$skip = {workflow_app_alias} internal workflow $WK_PATH get-ear-skipped $EAR_ID 2>> $app_stream_file
|
89
89
|
$exc_sk = $LASTEXITCODE
|
@@ -82,6 +82,7 @@ class Submission(JSONLike):
|
|
82
82
|
for js_idx, js in enumerate(self.jobscripts):
|
83
83
|
js._index = js_idx
|
84
84
|
|
85
|
+
@TimeIt.decorator
|
85
86
|
def _set_environments(self):
|
86
87
|
filterable = ElementResources.get_env_instance_filterable_attributes()
|
87
88
|
|
@@ -167,31 +168,36 @@ class Submission(JSONLike):
|
|
167
168
|
]
|
168
169
|
return self._submission_parts_lst
|
169
170
|
|
171
|
+
@TimeIt.decorator
|
170
172
|
def get_start_time(self, submit_time: str) -> Union[datetime, None]:
|
171
173
|
"""Get the start time of a given submission part."""
|
172
174
|
js_idx = self._submission_parts[submit_time]
|
173
175
|
all_part_starts = []
|
174
176
|
for i in js_idx:
|
175
|
-
|
176
|
-
|
177
|
+
start_time = self.jobscripts[i].start_time
|
178
|
+
if start_time:
|
179
|
+
all_part_starts.append(start_time)
|
177
180
|
if all_part_starts:
|
178
181
|
return min(all_part_starts)
|
179
182
|
else:
|
180
183
|
return None
|
181
184
|
|
185
|
+
@TimeIt.decorator
|
182
186
|
def get_end_time(self, submit_time: str) -> Union[datetime, None]:
|
183
187
|
"""Get the end time of a given submission part."""
|
184
188
|
js_idx = self._submission_parts[submit_time]
|
185
189
|
all_part_ends = []
|
186
190
|
for i in js_idx:
|
187
|
-
|
188
|
-
|
191
|
+
end_time = self.jobscripts[i].end_time
|
192
|
+
if end_time:
|
193
|
+
all_part_ends.append(end_time)
|
189
194
|
if all_part_ends:
|
190
195
|
return max(all_part_ends)
|
191
196
|
else:
|
192
197
|
return None
|
193
198
|
|
194
199
|
@property
|
200
|
+
@TimeIt.decorator
|
195
201
|
def start_time(self):
|
196
202
|
"""Get the first non-None start time over all submission parts."""
|
197
203
|
all_start_times = []
|
@@ -205,6 +211,7 @@ class Submission(JSONLike):
|
|
205
211
|
return None
|
206
212
|
|
207
213
|
@property
|
214
|
+
@TimeIt.decorator
|
208
215
|
def end_time(self):
|
209
216
|
"""Get the final non-None end time over all submission parts."""
|
210
217
|
all_end_times = []
|
@@ -278,6 +285,7 @@ class Submission(JSONLike):
|
|
278
285
|
return [i for js in self.jobscripts for i in js.all_EARs]
|
279
286
|
|
280
287
|
@property
|
288
|
+
@TimeIt.decorator
|
281
289
|
def EARs_by_elements(self):
|
282
290
|
task_elem_EARs = defaultdict(lambda: defaultdict(list))
|
283
291
|
for i in self.all_EARs:
|
@@ -292,6 +300,7 @@ class Submission(JSONLike):
|
|
292
300
|
def abort_EARs_file_path(self):
|
293
301
|
return self.path / self.abort_EARs_file_name
|
294
302
|
|
303
|
+
@TimeIt.decorator
|
295
304
|
def get_active_jobscripts(
|
296
305
|
self, as_json: bool = False
|
297
306
|
) -> List[Tuple[int, Dict[int, JobscriptElementState]]]:
|
@@ -429,7 +438,8 @@ class Submission(JSONLike):
|
|
429
438
|
supports_JS_para = self.workflow._store._features.jobscript_parallelism
|
430
439
|
if self.JS_parallelism:
|
431
440
|
if not supports_JS_para:
|
432
|
-
status
|
441
|
+
if status:
|
442
|
+
status.stop()
|
433
443
|
raise ValueError(
|
434
444
|
f"Store type {self.workflow._store!r} does not support jobscript "
|
435
445
|
f"parallelism."
|
@@ -508,7 +518,8 @@ class Submission(JSONLike):
|
|
508
518
|
continue
|
509
519
|
|
510
520
|
try:
|
511
|
-
status
|
521
|
+
if status:
|
522
|
+
status.update(f"Submitting jobscript {js.index}...")
|
512
523
|
js_ref_i = js.submit(scheduler_refs, print_stdout=print_stdout)
|
513
524
|
scheduler_refs[js.index] = (js_ref_i, js.is_array)
|
514
525
|
submitted_js_idx.append(js.index)
|
@@ -533,7 +544,8 @@ class Submission(JSONLike):
|
|
533
544
|
)
|
534
545
|
|
535
546
|
if errs and not ignore_errors:
|
536
|
-
status
|
547
|
+
if status:
|
548
|
+
status.stop()
|
537
549
|
self._raise_failure(submitted_js_idx, errs)
|
538
550
|
|
539
551
|
len_js = len(submitted_js_idx)
|
@@ -541,6 +553,7 @@ class Submission(JSONLike):
|
|
541
553
|
|
542
554
|
return submitted_js_idx
|
543
555
|
|
556
|
+
@TimeIt.decorator
|
544
557
|
def cancel(self):
|
545
558
|
act_js = list(self.get_active_jobscripts())
|
546
559
|
if not act_js:
|