hpcflow-new2 0.2.0a149__py3-none-any.whl → 0.2.0a153__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.
@@ -25,6 +25,7 @@ from hpcflow.sdk.core import (
25
25
  ABORT_EXIT_CODE,
26
26
  )
27
27
  from hpcflow.sdk.core.actions import EARStatus
28
+ from hpcflow.sdk.log import TimeIt
28
29
  from hpcflow.sdk.persistence import store_cls_from_str, DEFAULT_STORE_FORMAT
29
30
  from hpcflow.sdk.persistence.base import TEMPLATE_COMP_TYPES, AnySEAR
30
31
  from hpcflow.sdk.persistence.utils import ask_pw_on_auth_exc, infer_store
@@ -42,7 +43,7 @@ from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
42
43
  from .utils import (
43
44
  read_JSON_file,
44
45
  read_JSON_string,
45
- read_YAML,
46
+ read_YAML_str,
46
47
  read_YAML_file,
47
48
  replace_items,
48
49
  )
@@ -152,6 +153,7 @@ class WorkflowTemplate(JSONLike):
152
153
  self.doc = [self.doc]
153
154
 
154
155
  @classmethod
156
+ @TimeIt.decorator
155
157
  def _from_data(cls, data: Dict) -> app.WorkflowTemplate:
156
158
  # use element_sets if not already:
157
159
  for task_idx, task_dat in enumerate(data["tasks"]):
@@ -201,16 +203,22 @@ class WorkflowTemplate(JSONLike):
201
203
  return cls.from_json_like(data, shared_data=cls.app.template_components)
202
204
 
203
205
  @classmethod
204
- def from_YAML_string(cls, string: str) -> app.WorkflowTemplate:
206
+ @TimeIt.decorator
207
+ def from_YAML_string(
208
+ cls,
209
+ string: str,
210
+ variables: Optional[Dict[str, str]] = None,
211
+ ) -> app.WorkflowTemplate:
205
212
  """Load from a YAML string.
206
213
 
207
214
  Parameters
208
215
  ----------
209
216
  string
210
217
  The YAML string containing the workflow template parametrisation.
211
-
218
+ variables
219
+ String variables to substitute in `string`.
212
220
  """
213
- return cls._from_data(read_YAML(string))
221
+ return cls._from_data(read_YAML_str(string, variables=variables))
214
222
 
215
223
  @classmethod
216
224
  def _check_name(cls, data: Dict, path: PathLike) -> str:
@@ -228,54 +236,75 @@ class WorkflowTemplate(JSONLike):
228
236
  data["name"] = name
229
237
 
230
238
  @classmethod
231
- def from_YAML_file(cls, path: PathLike) -> app.WorkflowTemplate:
239
+ @TimeIt.decorator
240
+ def from_YAML_file(
241
+ cls,
242
+ path: PathLike,
243
+ variables: Optional[Dict[str, str]] = None,
244
+ ) -> app.WorkflowTemplate:
232
245
  """Load from a YAML file.
233
246
 
234
247
  Parameters
235
248
  ----------
236
249
  path
237
250
  The path to the YAML file containing the workflow template parametrisation.
251
+ variables
252
+ String variables to substitute in the file given by `path`.
238
253
 
239
254
  """
240
255
  cls.app.logger.debug("parsing workflow template from a YAML file")
241
- data = read_YAML_file(path)
256
+ data = read_YAML_file(path, variables=variables)
242
257
  cls._check_name(data, path)
243
258
  data["source_file"] = str(path)
244
259
  return cls._from_data(data)
245
260
 
246
261
  @classmethod
247
- def from_JSON_string(cls, string: str) -> app.WorkflowTemplate:
262
+ @TimeIt.decorator
263
+ def from_JSON_string(
264
+ cls,
265
+ string: str,
266
+ variables: Optional[Dict[str, str]] = None,
267
+ ) -> app.WorkflowTemplate:
248
268
  """Load from a JSON string.
249
269
 
250
270
  Parameters
251
271
  ----------
252
272
  string
253
273
  The JSON string containing the workflow template parametrisation.
254
-
274
+ variables
275
+ String variables to substitute in `string`.
255
276
  """
256
- return cls._from_data(read_JSON_string(string))
277
+ return cls._from_data(read_JSON_string(string, variables=variables))
257
278
 
258
279
  @classmethod
259
- def from_JSON_file(cls, path: PathLike) -> app.WorkflowTemplate:
280
+ @TimeIt.decorator
281
+ def from_JSON_file(
282
+ cls,
283
+ path: PathLike,
284
+ variables: Optional[Dict[str, str]] = None,
285
+ ) -> app.WorkflowTemplate:
260
286
  """Load from a JSON file.
261
287
 
262
288
  Parameters
263
289
  ----------
264
290
  path
265
291
  The path to the JSON file containing the workflow template parametrisation.
266
-
292
+ variables
293
+ String variables to substitute in the file given by `path`.
267
294
  """
268
295
  cls.app.logger.debug("parsing workflow template from a JSON file")
269
- data = read_JSON_file(path)
296
+ data = read_JSON_file(path, variables=variables)
270
297
  cls._check_name(data, path)
271
298
  data["source_file"] = str(path)
272
299
  return cls._from_data(data)
273
300
 
274
301
  @classmethod
302
+ @TimeIt.decorator
275
303
  def from_file(
276
304
  cls,
277
305
  path: PathLike,
278
306
  template_format: Optional[str] = None,
307
+ variables: Optional[Dict[str, str]] = None,
279
308
  ) -> app.WorkflowTemplate:
280
309
  """Load from either a YAML or JSON file, depending on the file extension.
281
310
 
@@ -286,14 +315,16 @@ class WorkflowTemplate(JSONLike):
286
315
  template_format
287
316
  The file format to expect at `path`. One of "json" or "yaml", if specified. By
288
317
  default, "yaml".
318
+ variables
319
+ String variables to substitute in the file given by `path`.
289
320
 
290
321
  """
291
322
  path = Path(path)
292
323
  fmt = template_format.lower() if template_format else None
293
324
  if fmt == "yaml" or path.suffix in (".yaml", ".yml"):
294
- return cls.from_YAML_file(path)
325
+ return cls.from_YAML_file(path, variables=variables)
295
326
  elif fmt == "json" or path.suffix in (".json", ".jsonc"):
296
- return cls.from_JSON_file(path)
327
+ return cls.from_JSON_file(path, variables=variables)
297
328
  else:
298
329
  raise ValueError(
299
330
  f"Unknown workflow template file extension {path.suffix!r}. Supported "
@@ -450,6 +481,7 @@ class Workflow:
450
481
  return len(self.tasks)
451
482
 
452
483
  @classmethod
484
+ @TimeIt.decorator
453
485
  def from_template(
454
486
  cls,
455
487
  template: WorkflowTemplate,
@@ -508,6 +540,7 @@ class Workflow:
508
540
  return wk
509
541
 
510
542
  @classmethod
543
+ @TimeIt.decorator
511
544
  def from_YAML_file(
512
545
  cls,
513
546
  YAML_path: PathLike,
@@ -518,6 +551,7 @@ class Workflow:
518
551
  ts_fmt: Optional[str] = None,
519
552
  ts_name_fmt: Optional[str] = None,
520
553
  store_kwargs: Optional[Dict] = None,
554
+ variables: Optional[Dict[str, str]] = None,
521
555
  ) -> app.Workflow:
522
556
  """Generate from a YAML file.
523
557
 
@@ -546,8 +580,13 @@ class Workflow:
546
580
  includes a timestamp.
547
581
  store_kwargs
548
582
  Keyword arguments to pass to the store's `write_empty_workflow` method.
583
+ variables
584
+ String variables to substitute in the file given by `YAML_path`.
549
585
  """
550
- template = cls.app.WorkflowTemplate.from_YAML_file(YAML_path)
586
+ template = cls.app.WorkflowTemplate.from_YAML_file(
587
+ path=YAML_path,
588
+ variables=variables,
589
+ )
551
590
  return cls.from_template(
552
591
  template,
553
592
  path,
@@ -570,6 +609,7 @@ class Workflow:
570
609
  ts_fmt: Optional[str] = None,
571
610
  ts_name_fmt: Optional[str] = None,
572
611
  store_kwargs: Optional[Dict] = None,
612
+ variables: Optional[Dict[str, str]] = None,
573
613
  ) -> app.Workflow:
574
614
  """Generate from a YAML string.
575
615
 
@@ -598,8 +638,13 @@ class Workflow:
598
638
  includes a timestamp.
599
639
  store_kwargs
600
640
  Keyword arguments to pass to the store's `write_empty_workflow` method.
641
+ variables
642
+ String variables to substitute in the string `YAML_str`.
601
643
  """
602
- template = cls.app.WorkflowTemplate.from_YAML_string(YAML_str)
644
+ template = cls.app.WorkflowTemplate.from_YAML_string(
645
+ string=YAML_str,
646
+ variables=variables,
647
+ )
603
648
  return cls.from_template(
604
649
  template,
605
650
  path,
@@ -622,6 +667,7 @@ class Workflow:
622
667
  ts_fmt: Optional[str] = None,
623
668
  ts_name_fmt: Optional[str] = None,
624
669
  store_kwargs: Optional[Dict] = None,
670
+ variables: Optional[Dict[str, str]] = None,
625
671
  ) -> app.Workflow:
626
672
  """Generate from a JSON file.
627
673
 
@@ -650,8 +696,13 @@ class Workflow:
650
696
  includes a timestamp.
651
697
  store_kwargs
652
698
  Keyword arguments to pass to the store's `write_empty_workflow` method.
699
+ variables
700
+ String variables to substitute in the file given by `JSON_path`.
653
701
  """
654
- template = cls.app.WorkflowTemplate.from_JSON_file(JSON_path)
702
+ template = cls.app.WorkflowTemplate.from_JSON_file(
703
+ path=JSON_path,
704
+ variables=variables,
705
+ )
655
706
  return cls.from_template(
656
707
  template,
657
708
  path,
@@ -674,6 +725,7 @@ class Workflow:
674
725
  ts_fmt: Optional[str] = None,
675
726
  ts_name_fmt: Optional[str] = None,
676
727
  store_kwargs: Optional[Dict] = None,
728
+ variables: Optional[Dict[str, str]] = None,
677
729
  ) -> app.Workflow:
678
730
  """Generate from a JSON string.
679
731
 
@@ -702,8 +754,13 @@ class Workflow:
702
754
  includes a timestamp.
703
755
  store_kwargs
704
756
  Keyword arguments to pass to the store's `write_empty_workflow` method.
757
+ variables
758
+ String variables to substitute in the string `JSON_str`.
705
759
  """
706
- template = cls.app.WorkflowTemplate.from_JSON_string(JSON_str)
760
+ template = cls.app.WorkflowTemplate.from_JSON_string(
761
+ string=JSON_str,
762
+ variables=variables,
763
+ )
707
764
  return cls.from_template(
708
765
  template,
709
766
  path,
@@ -716,6 +773,7 @@ class Workflow:
716
773
  )
717
774
 
718
775
  @classmethod
776
+ @TimeIt.decorator
719
777
  def from_file(
720
778
  cls,
721
779
  template_path: PathLike,
@@ -727,6 +785,7 @@ class Workflow:
727
785
  ts_fmt: Optional[str] = None,
728
786
  ts_name_fmt: Optional[str] = None,
729
787
  store_kwargs: Optional[Dict] = None,
788
+ variables: Optional[Dict[str, str]] = None,
730
789
  ) -> app.Workflow:
731
790
  """Generate from either a YAML or JSON file, depending on the file extension.
732
791
 
@@ -759,8 +818,14 @@ class Workflow:
759
818
  includes a timestamp.
760
819
  store_kwargs
761
820
  Keyword arguments to pass to the store's `write_empty_workflow` method.
821
+ variables
822
+ String variables to substitute in the file given by `template_path`.
762
823
  """
763
- template = cls.app.WorkflowTemplate.from_file(template_path, template_format)
824
+ template = cls.app.WorkflowTemplate.from_file(
825
+ template_path,
826
+ template_format,
827
+ variables=variables,
828
+ )
764
829
  return cls.from_template(
765
830
  template,
766
831
  path,
@@ -773,6 +838,7 @@ class Workflow:
773
838
  )
774
839
 
775
840
  @classmethod
841
+ @TimeIt.decorator
776
842
  def from_template_data(
777
843
  cls,
778
844
  template_name: str,
@@ -841,6 +907,7 @@ class Workflow:
841
907
  store_kwargs,
842
908
  )
843
909
 
910
+ @TimeIt.decorator
844
911
  def _add_empty_task(
845
912
  self,
846
913
  task: app.Task,
@@ -878,9 +945,9 @@ class Workflow:
878
945
  self._pending["template_components"][comp_type].append(idx)
879
946
 
880
947
  self._pending["tasks"].append(new_index)
881
-
882
948
  return self.tasks[new_index]
883
949
 
950
+ @TimeIt.decorator
884
951
  def _add_task(self, task: app.Task, new_index: Optional[int] = None) -> None:
885
952
  new_wk_task = self._add_empty_task(task=task, new_index=new_index)
886
953
  new_wk_task._add_elements(element_sets=task.element_sets)
@@ -916,6 +983,7 @@ class Workflow:
916
983
  self.add_task(new_task, new_index)
917
984
  # TODO: add new downstream elements?
918
985
 
986
+ @TimeIt.decorator
919
987
  def _add_empty_loop(self, loop: app.Loop) -> app.WorkflowLoop:
920
988
  """Add a new loop (zeroth iterations only) to the workflow."""
921
989
 
@@ -955,6 +1023,7 @@ class Workflow:
955
1023
 
956
1024
  return wk_loop
957
1025
 
1026
+ @TimeIt.decorator
958
1027
  def _add_loop(self, loop: app.Loop, parent_loop_indices: Dict = None) -> None:
959
1028
  new_wk_loop = self._add_empty_loop(loop)
960
1029
  if loop.num_iterations is not None:
@@ -1075,17 +1144,21 @@ class Workflow:
1075
1144
  def num_added_tasks(self) -> int:
1076
1145
  return self._store._get_num_total_added_tasks()
1077
1146
 
1147
+ @TimeIt.decorator
1078
1148
  def get_store_EARs(self, id_lst: Iterable[int]) -> List[AnySEAR]:
1079
1149
  return self._store.get_EARs(id_lst)
1080
1150
 
1151
+ @TimeIt.decorator
1081
1152
  def get_store_element_iterations(
1082
1153
  self, id_lst: Iterable[int]
1083
1154
  ) -> List[AnySElementIter]:
1084
1155
  return self._store.get_element_iterations(id_lst)
1085
1156
 
1157
+ @TimeIt.decorator
1086
1158
  def get_store_elements(self, id_lst: Iterable[int]) -> List[AnySElement]:
1087
1159
  return self._store.get_elements(id_lst)
1088
1160
 
1161
+ @TimeIt.decorator
1089
1162
  def get_store_tasks(self, id_lst: Iterable[int]) -> List[AnySTask]:
1090
1163
  return self._store.get_tasks_by_IDs(id_lst)
1091
1164
 
@@ -1186,6 +1259,7 @@ class Workflow:
1186
1259
 
1187
1260
  return objs
1188
1261
 
1262
+ @TimeIt.decorator
1189
1263
  def get_EARs_from_IDs(self, id_lst: Iterable[int]) -> List[app.ElementActionRun]:
1190
1264
  """Return element action run objects from a list of IDs."""
1191
1265
 
@@ -1228,12 +1302,15 @@ class Workflow:
1228
1302
 
1229
1303
  return objs
1230
1304
 
1305
+ @TimeIt.decorator
1231
1306
  def get_all_elements(self) -> List[app.Element]:
1232
1307
  return self.get_elements_from_IDs(range(self.num_elements))
1233
1308
 
1309
+ @TimeIt.decorator
1234
1310
  def get_all_element_iterations(self) -> List[app.ElementIteration]:
1235
1311
  return self.get_element_iterations_from_IDs(range(self.num_element_iterations))
1236
1312
 
1313
+ @TimeIt.decorator
1237
1314
  def get_all_EARs(self) -> List[app.ElementActionRun]:
1238
1315
  return self.get_EARs_from_IDs(range(self.num_EARs))
1239
1316
 
@@ -1339,6 +1416,7 @@ class Workflow:
1339
1416
  return all_replaced[-1]
1340
1417
 
1341
1418
  @classmethod
1419
+ @TimeIt.decorator
1342
1420
  def _write_empty_workflow(
1343
1421
  cls,
1344
1422
  template: app.WorkflowTemplate,
@@ -1467,15 +1545,19 @@ class Workflow:
1467
1545
  ) -> List[AnySParameter]:
1468
1546
  return self._store.get_parameters(id_lst, **kwargs)
1469
1547
 
1548
+ @TimeIt.decorator
1470
1549
  def get_parameter_sources(self, id_lst: Iterable[int]) -> List[Dict]:
1471
1550
  return self._store.get_parameter_sources(id_lst)
1472
1551
 
1552
+ @TimeIt.decorator
1473
1553
  def get_parameter_set_statuses(self, id_lst: Iterable[int]) -> List[bool]:
1474
1554
  return self._store.get_parameter_set_statuses(id_lst)
1475
1555
 
1556
+ @TimeIt.decorator
1476
1557
  def get_parameter(self, index: int, **kwargs: Dict) -> AnySParameter:
1477
1558
  return self.get_parameters([index], **kwargs)[0]
1478
1559
 
1560
+ @TimeIt.decorator
1479
1561
  def get_parameter_data(self, index: int, **kwargs: Dict) -> Any:
1480
1562
  param = self.get_parameter(index, **kwargs)
1481
1563
  if param.data is not None:
@@ -1483,18 +1565,22 @@ class Workflow:
1483
1565
  else:
1484
1566
  return param.file
1485
1567
 
1568
+ @TimeIt.decorator
1486
1569
  def get_parameter_source(self, index: int) -> Dict:
1487
1570
  return self.get_parameter_sources([index])[0]
1488
1571
 
1572
+ @TimeIt.decorator
1489
1573
  def is_parameter_set(self, index: int) -> bool:
1490
1574
  return self.get_parameter_set_statuses([index])[0]
1491
1575
 
1576
+ @TimeIt.decorator
1492
1577
  def get_all_parameters(self, **kwargs: Dict) -> List[AnySParameter]:
1493
1578
  """Retrieve all store parameters."""
1494
1579
  num_params = self._store._get_num_total_parameters()
1495
1580
  id_lst = list(range(num_params))
1496
1581
  return self._store.get_parameters(id_lst, **kwargs)
1497
1582
 
1583
+ @TimeIt.decorator
1498
1584
  def get_all_parameter_data(self, **kwargs: Dict) -> Dict[int, Any]:
1499
1585
  """Retrieve all workflow parameter data."""
1500
1586
  params = self.get_all_parameters(**kwargs)
@@ -1671,6 +1757,7 @@ class Workflow:
1671
1757
  def execution_path(self):
1672
1758
  return Path(self.path) / self._exec_dir_name
1673
1759
 
1760
+ @TimeIt.decorator
1674
1761
  def get_task_elements(self, task: app.Task, selection: slice) -> List[app.Element]:
1675
1762
  return [
1676
1763
  self.app.Element(task=task, **{k: v for k, v in i.items() if k != "task_ID"})
@@ -1818,6 +1905,7 @@ class Workflow:
1818
1905
  with self._store.cached_load():
1819
1906
  return self._store.get_EAR_skipped(EAR_ID)
1820
1907
 
1908
+ @TimeIt.decorator
1821
1909
  def set_parameter_value(
1822
1910
  self, param_id: int, value: Any, commit: bool = False
1823
1911
  ) -> None:
hpcflow/sdk/demo/cli.py CHANGED
@@ -11,6 +11,7 @@ from hpcflow.sdk.cli_common import (
11
11
  store_option,
12
12
  ts_fmt_option,
13
13
  ts_name_fmt_option,
14
+ variables_option,
14
15
  js_parallelism_option,
15
16
  wait_option,
16
17
  add_to_known_opt,
@@ -98,6 +99,7 @@ def get_demo_workflow_CLI(app):
98
99
  @store_option
99
100
  @ts_fmt_option
100
101
  @ts_name_fmt_option
102
+ @variables_option
101
103
  def make_demo_workflow(
102
104
  workflow_name,
103
105
  format,
@@ -107,6 +109,7 @@ def get_demo_workflow_CLI(app):
107
109
  store,
108
110
  ts_fmt=None,
109
111
  ts_name_fmt=None,
112
+ variables=None,
110
113
  ):
111
114
  wk = app.make_demo_workflow(
112
115
  workflow_name=workflow_name,
@@ -117,6 +120,7 @@ def get_demo_workflow_CLI(app):
117
120
  store=store,
118
121
  ts_fmt=ts_fmt,
119
122
  ts_name_fmt=ts_name_fmt,
123
+ variables=variables,
120
124
  )
121
125
  click.echo(wk.path)
122
126
 
@@ -129,6 +133,7 @@ def get_demo_workflow_CLI(app):
129
133
  @store_option
130
134
  @ts_fmt_option
131
135
  @ts_name_fmt_option
136
+ @variables_option
132
137
  @js_parallelism_option
133
138
  @wait_option
134
139
  @add_to_known_opt
@@ -143,6 +148,7 @@ def get_demo_workflow_CLI(app):
143
148
  store,
144
149
  ts_fmt=None,
145
150
  ts_name_fmt=None,
151
+ variables=None,
146
152
  js_parallelism=None,
147
153
  wait=False,
148
154
  add_to_known=True,
@@ -158,6 +164,7 @@ def get_demo_workflow_CLI(app):
158
164
  store=store,
159
165
  ts_fmt=ts_fmt,
160
166
  ts_name_fmt=ts_name_fmt,
167
+ variables=variables,
161
168
  JS_parallelism=js_parallelism,
162
169
  wait=wait,
163
170
  add_to_known=add_to_known,
hpcflow/sdk/log.py CHANGED
@@ -1,4 +1,128 @@
1
+ from functools import wraps
1
2
  import logging
3
+ from pathlib import Path
4
+ import time
5
+ from collections import defaultdict
6
+ import statistics
7
+
8
+
9
+ class TimeIt:
10
+
11
+ active = False
12
+ file_path = None
13
+ timers = defaultdict(list)
14
+ trace = []
15
+ trace_idx = []
16
+ trace_prev = []
17
+ trace_idx_prev = []
18
+
19
+ @classmethod
20
+ def decorator(cls, func):
21
+ @wraps(func)
22
+ def wrapper(*args, **kwargs):
23
+
24
+ if not cls.active:
25
+ return func(*args, **kwargs)
26
+
27
+ cls.trace.append(func.__qualname__)
28
+
29
+ if cls.trace_prev == cls.trace:
30
+ new_trace_idx = cls.trace_idx_prev[-1] + 1
31
+ else:
32
+ new_trace_idx = 0
33
+ cls.trace_idx.append(new_trace_idx)
34
+
35
+ tic = time.perf_counter()
36
+ out = func(*args, **kwargs)
37
+ toc = time.perf_counter()
38
+ elapsed = toc - tic
39
+
40
+ cls.timers[tuple(cls.trace)].append(elapsed)
41
+
42
+ cls.trace_prev = list(cls.trace)
43
+ cls.trace_idx_prev = list(cls.trace_idx)
44
+
45
+ cls.trace.pop()
46
+ cls.trace_idx.pop()
47
+
48
+ return out
49
+
50
+ return wrapper
51
+
52
+ @classmethod
53
+ def summarise(cls):
54
+ stats = {
55
+ k: {
56
+ "number": len(v),
57
+ "mean": statistics.mean(v),
58
+ "stddev": statistics.pstdev(v),
59
+ "min": min(v),
60
+ "max": max(v),
61
+ "sum": sum(v),
62
+ }
63
+ for k, v in cls.timers.items()
64
+ }
65
+
66
+ # make a graph
67
+ for k in stats:
68
+ stats[k]["children"] = {}
69
+
70
+ for key in sorted(stats.keys(), key=lambda x: len(x), reverse=True):
71
+ if len(key) == 1:
72
+ continue
73
+ value = stats.pop(key)
74
+ parent = key[:-1]
75
+ for other_key in stats.keys():
76
+ if other_key == parent:
77
+ stats[other_key]["children"][key] = value
78
+ break
79
+
80
+ return stats
81
+
82
+ @classmethod
83
+ def summarise_string(cls):
84
+ def _format_nodes(node, depth=0, depth_final=None):
85
+ if depth_final is None:
86
+ depth_final = []
87
+ out = []
88
+ for idx, (k, v) in enumerate(node.items()):
89
+ is_final_child = idx == len(node) - 1
90
+ angle = "└ " if is_final_child else "├ "
91
+ bars = ""
92
+ if depth > 0:
93
+ bars = "".join(f"{'│ ' if not i else ' '}" for i in depth_final)
94
+ k_str = bars + (angle if depth > 0 else "") + f"{k[depth]}"
95
+ number = v["number"]
96
+ min_str = f"{v['min']:10.6f}" if number > 1 else f"{f'-':^12s}"
97
+ max_str = f"{v['max']:10.6f}" if number > 1 else f"{f'-':^12s}"
98
+ stddev_str = f"({v['stddev']:8.6f})" if number > 1 else f"{f' ':^10s}"
99
+ out.append(
100
+ f"{k_str:.<60s} {v['sum']:12.6f} "
101
+ f"{v['mean']:10.6f} {stddev_str} {number:8d} "
102
+ f"{min_str} {max_str} "
103
+ )
104
+ depth_final_next = list(depth_final) + (
105
+ [is_final_child] if depth > 0 else []
106
+ )
107
+ out.extend(
108
+ _format_nodes(
109
+ v["children"], depth=depth + 1, depth_final=depth_final_next
110
+ )
111
+ )
112
+ return out
113
+
114
+ summary = cls.summarise()
115
+
116
+ out = [
117
+ f"{'function':^60s} {'sum /s':^12s} {'mean (stddev) /s':^20s} {'N':^8s} "
118
+ f"{'min /s':^12s} {'max /s':^12s}"
119
+ ]
120
+ out += _format_nodes(summary)
121
+ out_str = "\n".join(out)
122
+ if cls.file_path:
123
+ Path(cls.file_path).write_text(out_str, encoding="utf-8")
124
+ else:
125
+ print(out_str)
2
126
 
3
127
 
4
128
  class AppLog:
@@ -22,6 +22,7 @@ from hpcflow.sdk.core.utils import (
22
22
  set_in_container,
23
23
  JSONLikeDirSnapShot,
24
24
  )
25
+ from hpcflow.sdk.log import TimeIt
25
26
  from hpcflow.sdk.persistence.pending import PendingChanges
26
27
 
27
28
  AnySTask = TypeVar("AnySTask", bound="StoreTask")
@@ -925,6 +926,7 @@ class PersistentStore(ABC):
925
926
  self.save()
926
927
  return new_ID
927
928
 
929
+ @TimeIt.decorator
928
930
  def add_EAR(
929
931
  self,
930
932
  elem_iter_ID: int,
@@ -1054,7 +1056,7 @@ class PersistentStore(ABC):
1054
1056
  file: Dict = None,
1055
1057
  save: bool = True,
1056
1058
  ) -> int:
1057
- self.logger.debug(f"Adding store parameter{f' (unset)' if data is None else ''}.")
1059
+ self.logger.debug(f"Adding store parameter{f' (unset)' if not is_set else ''}.")
1058
1060
  new_idx = self._get_num_total_parameters()
1059
1061
  self._pending.add_parameters[new_idx] = self._store_param_cls(
1060
1062
  id_=new_idx,
@@ -1203,6 +1205,7 @@ class PersistentStore(ABC):
1203
1205
  if save:
1204
1206
  self.save()
1205
1207
 
1208
+ @TimeIt.decorator
1206
1209
  def update_param_source(self, param_id: int, source: Dict, save: bool = True) -> None:
1207
1210
  self.logger.debug(f"Updating parameter ID {param_id!r} source to {source!r}.")
1208
1211
  self._pending.update_param_sources[param_id] = source