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.
@@ -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
- def _update_EAR_submission_index(self, EAR_id: int, sub_idx: int):
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
- EAR_i = self._get_persistent_EARs([EAR_id])[EAR_id]
603
- EAR_i = EAR_i.update(submission_idx=sub_idx)
604
- arr[EAR_id] = EAR_i.encode(attrs, self.ts_fmt)
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 elements."""
752
- return len(self._get_tasks_arr())
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
- def _get_persistent_tasks(
936
- self, id_lst: Optional[Iterable[int]] = None
937
- ) -> Dict[int, ZarrStoreTask]:
938
- with self.using_resource("attrs", action="read") as attrs:
939
- task_dat = {}
940
- elem_IDs = []
941
- for idx, i in enumerate(attrs["tasks"]):
942
- i = copy.deepcopy(i)
943
- elem_IDs.append(i.pop("element_IDs_idx"))
944
- if id_lst is None or i["id_"] in id_lst:
945
- task_dat[i["id_"]] = {**i, "index": idx}
946
- if task_dat:
947
- try:
948
- elem_IDs_arr_dat = self._get_tasks_arr().get_coordinate_selection(
949
- elem_IDs
950
- )
951
- except zarr.errors.BoundsCheckError:
952
- raise MissingStoreTaskError(elem_IDs) from None # TODO: not an ID list
953
-
954
- return {
955
- id_: ZarrStoreTask.decode({**i, "element_IDs": elem_IDs_arr_dat[id_]})
956
- for idx, (id_, i) in enumerate(task_dat.items())
957
- }
958
- else:
959
- return {}
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
- arr = self._get_elements_arr()
996
- attrs = arr.attrs.asdict()
997
- try:
998
- elem_arr_dat = arr.get_coordinate_selection(list(id_lst))
999
- except zarr.errors.BoundsCheckError:
1000
- raise MissingStoreElementError(id_lst) from None
1001
- elem_dat = dict(zip(id_lst, elem_arr_dat))
1002
- return {k: ZarrStoreElement.decode(v, attrs) for k, v in elem_dat.items()}
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, StoreElementIter]:
1007
- arr = self._get_iters_arr()
1008
- attrs = arr.attrs.asdict()
1009
- try:
1010
- iter_arr_dat = arr.get_coordinate_selection(list(id_lst))
1011
- except zarr.errors.BoundsCheckError:
1012
- raise MissingStoreElementIterationError(id_lst) from None
1013
- iter_dat = dict(zip(id_lst, iter_arr_dat))
1014
- return {k: ZarrStoreElementIter.decode(v, attrs) for k, v in iter_dat.items()}
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
- arr = self._get_EARs_arr()
1018
- attrs = arr.attrs.asdict()
1019
-
1020
- try:
1021
- self.logger.debug(f"_get_persistent_EARs: {list(id_lst)=}")
1022
- EAR_arr_dat = _zarr_get_coord_selection(arr, list(id_lst), self.logger)
1023
- except zarr.errors.BoundsCheckError:
1024
- raise MissingStoreEARError(id_lst) from None
1025
-
1026
- EAR_dat = dict(zip(id_lst, EAR_arr_dat))
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
- iters = {
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
- try:
1044
- param_arr_dat = base_arr.get_coordinate_selection(list(id_lst))
1045
- src_arr_dat = src_arr.get_coordinate_selection(list(id_lst))
1046
- except zarr.errors.BoundsCheckError:
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
- params = {
1053
- k: ZarrStoreParameter.decode(
1054
- id_=k,
1055
- data=v,
1056
- source=src_dat[k],
1057
- arr_group=self._get_parameter_data_array_group(k),
1058
- dataset_copy=dataset_copy,
1059
- )
1060
- for k, v in param_dat.items()
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
- src_arr = self._get_parameter_sources_array(mode="r")
1067
- try:
1068
- src_arr_dat = src_arr.get_coordinate_selection(list(id_lst))
1069
- except zarr.errors.BoundsCheckError:
1070
- raise MissingParameterData(id_lst) from None
1071
-
1072
- return dict(zip(id_lst, src_arr_dat))
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
- shell = shell or self.shell
401
- return f"js_{self.index}_act_{js_action_idx}{shell.JS_EXT}"
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
- "app_package_name": self.app.package_name,
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
- app_package_name=self.app.package_name,
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`/{app_package_name}.log"\\
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`/{app_package_name}_std.txt"
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/{app_package_name}.log" `
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/{app_package_name}_std.txt"
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
- if self.jobscripts[i].start_time:
176
- all_part_starts.append(self.jobscripts[i].start_time)
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
- if self.jobscripts[i].end_time:
188
- all_part_ends.append(self.jobscripts[i].end_time)
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.stop()
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.update(f"Submitting jobscript {js.index}...")
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.stop()
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: