stepup 3.2.1__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stepup-3.2.1/stepup.egg-info → stepup-3.2.2}/PKG-INFO +1 -1
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/director.py +24 -1
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/file.py +1 -1
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/step.py +1 -1
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/tui.py +11 -1
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/worker.py +193 -153
- {stepup-3.2.1 → stepup-3.2.2/stepup.egg-info}/PKG-INFO +1 -1
- {stepup-3.2.1 → stepup-3.2.2}/LICENSE +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/MANIFEST.in +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/README.md +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/pyproject.toml +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/setup.cfg +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/__init__.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/__main__.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/actions.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/api.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/asyncio.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/browse.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/call.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/cascade.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/clean.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/deferred_glob.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/enums.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/exceptions.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/hash.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/interact.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/job.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/logo.svg +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/nglob.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/pytest.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/render_jinja.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/reporter.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/rpc.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/runner.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/scheduler.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/script.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/startup.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/stepinfo.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/utils.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/watcher.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup/core/workflow.py +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/SOURCES.txt +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/dependency_links.txt +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/entry_points.txt +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/requires.txt +0 -0
- {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/top_level.txt +0 -0
|
@@ -34,7 +34,10 @@ from importlib.metadata import version as get_version
|
|
|
34
34
|
import attrs
|
|
35
35
|
from path import Path
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
try:
|
|
38
|
+
import yappi
|
|
39
|
+
except ImportError:
|
|
40
|
+
yappi = None
|
|
38
41
|
|
|
39
42
|
from .asyncio import wait_for_events
|
|
40
43
|
from .enums import ReturnCode, StepState
|
|
@@ -46,6 +49,7 @@ from .rpc import allow_rpc, serve_socket_rpc
|
|
|
46
49
|
from .runner import Runner
|
|
47
50
|
from .scheduler import Scheduler
|
|
48
51
|
from .startup import startup_from_db
|
|
52
|
+
from .step import Step
|
|
49
53
|
from .stepinfo import StepInfo
|
|
50
54
|
from .utils import DBLock, check_plan, mynormpath
|
|
51
55
|
from .watcher import WATCHER_AVAILABLE, Watcher
|
|
@@ -71,6 +75,15 @@ async def async_main():
|
|
|
71
75
|
print(f"SOCKET {args.director_socket}", file=sys.stderr)
|
|
72
76
|
print(f"PID {os.getpid()}", file=sys.stderr)
|
|
73
77
|
print(f"LOG_LEVEL {args.log_level}", file=sys.stderr)
|
|
78
|
+
if args.yappi:
|
|
79
|
+
if yappi is None:
|
|
80
|
+
print(
|
|
81
|
+
"Yappi profiling requested, but the yappi module is not installed.",
|
|
82
|
+
file=sys.stderr,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
yappi.set_clock_type("cpu")
|
|
86
|
+
yappi.start(builtins=True, profile_threads=True)
|
|
74
87
|
async with ReporterClient.socket(args.reporter_socket) as reporter:
|
|
75
88
|
num_workers = interpret_num_workers(args.num_workers)
|
|
76
89
|
await reporter.set_num_workers(num_workers)
|
|
@@ -95,6 +108,10 @@ async def async_main():
|
|
|
95
108
|
finally:
|
|
96
109
|
await reporter("DIRECTOR", "See you!")
|
|
97
110
|
await reporter.shutdown()
|
|
111
|
+
if args.yappi and yappi is not None:
|
|
112
|
+
yappi.stop()
|
|
113
|
+
stats = yappi.get_func_stats()
|
|
114
|
+
stats.save(".stepup/director.prof", type="pstat")
|
|
98
115
|
sys.exit(returncode.value)
|
|
99
116
|
|
|
100
117
|
|
|
@@ -170,6 +187,12 @@ def parse_args() -> argparse.Namespace:
|
|
|
170
187
|
action=argparse.BooleanOptionalAction,
|
|
171
188
|
help="Do not remove outdated output files.",
|
|
172
189
|
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--yappi",
|
|
192
|
+
default=False,
|
|
193
|
+
action=argparse.BooleanOptionalAction,
|
|
194
|
+
help="Profile the director with Yappi (must be installed).",
|
|
195
|
+
)
|
|
173
196
|
args = parser.parse_args()
|
|
174
197
|
if WATCHER_AVAILABLE:
|
|
175
198
|
if args.watch_first:
|
|
@@ -299,7 +299,7 @@ class File(Node):
|
|
|
299
299
|
# Local import to avoid cyclic imports.
|
|
300
300
|
from .step import Step # noqa: PLC0415
|
|
301
301
|
|
|
302
|
-
for step in self.consumers(Step):
|
|
302
|
+
for step in self.consumers(Step, include_orphans=True):
|
|
303
303
|
step.mark_pending()
|
|
304
304
|
elif state != FileState.OUTDATED:
|
|
305
305
|
raise ValueError(f"Cannot make file oudated when its state is {state}")
|
|
@@ -987,6 +987,6 @@ class Step(Node):
|
|
|
987
987
|
logger.info("Mark %s step PENDING: %s", state.name, self.label)
|
|
988
988
|
self.set_state(StepState.PENDING)
|
|
989
989
|
# First make all consumers (output files) pending
|
|
990
|
-
for file in self.consumers(File):
|
|
990
|
+
for file in self.consumers(File, include_orphans=True):
|
|
991
991
|
if file.get_state() == FileState.BUILT:
|
|
992
992
|
file.mark_outdated()
|
|
@@ -123,6 +123,8 @@ async def async_boot(args: argparse.Namespace):
|
|
|
123
123
|
argv.append("--watch-first")
|
|
124
124
|
if not args.clean:
|
|
125
125
|
argv.append("--no-clean")
|
|
126
|
+
if args.yappi:
|
|
127
|
+
argv.append("--yappi")
|
|
126
128
|
returncode = 1 # Internal error unless it is overriden later by the director subprocess
|
|
127
129
|
try:
|
|
128
130
|
with open(".stepup/director.log", "w") as log_file:
|
|
@@ -299,7 +301,15 @@ def boot_subcommand(subparsers) -> callable:
|
|
|
299
301
|
default=os.getenv("STEPUP_PERF", None),
|
|
300
302
|
nargs="?",
|
|
301
303
|
const="500",
|
|
302
|
-
help="
|
|
304
|
+
help="Profile the director with perf, by default at a frequency of %(const)s Hz. "
|
|
303
305
|
"(Only supported on Linux with perf installed.)",
|
|
304
306
|
)
|
|
307
|
+
parser.add_argument(
|
|
308
|
+
"--yappi",
|
|
309
|
+
default=string_to_bool(os.getenv("STEPUP_YAPPI", "0")),
|
|
310
|
+
action=argparse.BooleanOptionalAction,
|
|
311
|
+
help="Profile the director with Yappi (must be installed). "
|
|
312
|
+
"This produces a .stepup/director.prof file that can be analyzed with "
|
|
313
|
+
"tools like SnakeViz.",
|
|
314
|
+
)
|
|
305
315
|
return boot_tool
|
|
@@ -25,6 +25,7 @@ import contextlib
|
|
|
25
25
|
import ctypes
|
|
26
26
|
import inspect
|
|
27
27
|
import io
|
|
28
|
+
import json
|
|
28
29
|
import logging
|
|
29
30
|
import os
|
|
30
31
|
import queue
|
|
@@ -228,8 +229,6 @@ class WorkerClient:
|
|
|
228
229
|
return True
|
|
229
230
|
|
|
230
231
|
# Delegate the calculation of the output part of the step hash to the worker.
|
|
231
|
-
# With skipping=True, the worker knows the outputs should not have changed
|
|
232
|
-
# and will report it on screen.
|
|
233
232
|
new_step_hash, new_out_hashes = await self.compute_out_step_hash(step, new_step_hash)
|
|
234
233
|
|
|
235
234
|
if step_hash.out_digest != new_step_hash.out_digest:
|
|
@@ -707,6 +706,38 @@ def check_executable(executable: Path, shebang: str | None = None) -> bool:
|
|
|
707
706
|
return True
|
|
708
707
|
|
|
709
708
|
|
|
709
|
+
@attrs.define
|
|
710
|
+
class Timer:
|
|
711
|
+
"""Context managers to construct a JSON string with times spent in typical worker tasks."""
|
|
712
|
+
|
|
713
|
+
start: float = attrs.field(init=False, factory=perf_counter)
|
|
714
|
+
sections: dict[str, float] = attrs.field(init=False, factory=dict)
|
|
715
|
+
|
|
716
|
+
@contextlib.contextmanager
|
|
717
|
+
def section(self, section: str):
|
|
718
|
+
"""Context manager for a timed section.
|
|
719
|
+
|
|
720
|
+
Parameters
|
|
721
|
+
----------
|
|
722
|
+
section
|
|
723
|
+
The name of the section.
|
|
724
|
+
"""
|
|
725
|
+
time = perf_counter()
|
|
726
|
+
try:
|
|
727
|
+
yield
|
|
728
|
+
finally:
|
|
729
|
+
time = perf_counter() - time
|
|
730
|
+
self.sections[section] = self.sections.get(section, 0.0) + time
|
|
731
|
+
|
|
732
|
+
def report(self):
|
|
733
|
+
"""Write the timings line to standard error."""
|
|
734
|
+
time = perf_counter() - self.start
|
|
735
|
+
sections = self.sections.copy()
|
|
736
|
+
sections["idle/other"] = time - sum(sections.values())
|
|
737
|
+
sections["total"] = time
|
|
738
|
+
print(f"TIMINGS: {json.dumps(sections)}", file=sys.stderr)
|
|
739
|
+
|
|
740
|
+
|
|
710
741
|
@attrs.define
|
|
711
742
|
class WorkerHandler:
|
|
712
743
|
"""RPC Handler in the worker process to respond to requests from the WorkerClient."""
|
|
@@ -716,6 +747,7 @@ class WorkerHandler:
|
|
|
716
747
|
explain_rerun: bool = attrs.field()
|
|
717
748
|
stop_event: asyncio.Event = attrs.field(factory=asyncio.Event)
|
|
718
749
|
step: WorkerStep | None = attrs.field(init=False, default=None)
|
|
750
|
+
timer: Timer = attrs.field(init=False, factory=Timer)
|
|
719
751
|
|
|
720
752
|
@allow_rpc
|
|
721
753
|
def shutdown(self):
|
|
@@ -777,18 +809,19 @@ class WorkerHandler:
|
|
|
777
809
|
The new hash of the step, with the input part already computed, if available.
|
|
778
810
|
`None` is yielded if, unexpectedly, some inputs are missing or have changed.
|
|
779
811
|
"""
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
812
|
+
with self.timer.section("new_step"):
|
|
813
|
+
if self.step is not None:
|
|
814
|
+
raise RPCError(
|
|
815
|
+
"Worker cannot initiate two steps at the same time. "
|
|
816
|
+
f"Still working on {self.step.action}"
|
|
817
|
+
)
|
|
785
818
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
819
|
+
# Create the step
|
|
820
|
+
action, workdir = split_step_label(label)
|
|
821
|
+
self.step = WorkerStep(i, action, workdir)
|
|
789
822
|
|
|
790
|
-
|
|
791
|
-
|
|
823
|
+
# Create initial StepHash
|
|
824
|
+
return self.compute_inp_step_hash(inp_hashes, env_vars, check_hash)[0]
|
|
792
825
|
|
|
793
826
|
def compute_inp_step_hash(
|
|
794
827
|
self,
|
|
@@ -819,41 +852,44 @@ class WorkerHandler:
|
|
|
819
852
|
so they can be updated in StepUp's workflow database if desired.
|
|
820
853
|
Unchanged ones are not included.
|
|
821
854
|
"""
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if new_file_hash
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
855
|
+
with self.timer.section("compute_inp_step_hash"):
|
|
856
|
+
# Check the input hashes
|
|
857
|
+
messages = []
|
|
858
|
+
new_inp_hashes = []
|
|
859
|
+
all_inp_hashes = []
|
|
860
|
+
for path, old_file_hash in old_inp_hashes:
|
|
861
|
+
new_file_hash = old_file_hash.regen(path)
|
|
862
|
+
all_inp_hashes.append((path, new_file_hash))
|
|
863
|
+
if check_hash and new_file_hash != old_file_hash:
|
|
864
|
+
if new_file_hash.is_unknown:
|
|
865
|
+
messages.append(f"Input vanished unexpectedly: {path} ")
|
|
866
|
+
else:
|
|
867
|
+
messages.append(
|
|
868
|
+
f"Input changed unexpectedly: {path} "
|
|
869
|
+
+ fmt_file_hash_diff(old_file_hash, new_file_hash)
|
|
870
|
+
)
|
|
871
|
+
new_inp_hashes.append((path, new_file_hash))
|
|
872
|
+
|
|
873
|
+
# If there are unexpected issues with inputs, bail out.
|
|
874
|
+
if len(messages) > 0:
|
|
875
|
+
self.step.inp_messages = messages
|
|
876
|
+
self.step.success = False
|
|
877
|
+
return None, new_inp_hashes
|
|
844
878
|
|
|
845
|
-
|
|
846
|
-
|
|
879
|
+
# Add the environment variables to the hash
|
|
880
|
+
env_var_values = [(env_var, os.environ.get(env_var)) for env_var in env_vars]
|
|
847
881
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
882
|
+
# Create the StepHash
|
|
883
|
+
label = self.step.action
|
|
884
|
+
if self.step.workdir != "./":
|
|
885
|
+
label += f" # wd={self.step.workdir}"
|
|
886
|
+
result = StepHash.from_inp(
|
|
887
|
+
f"{label}", self.explain_rerun, all_inp_hashes, env_var_values
|
|
888
|
+
)
|
|
853
889
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
890
|
+
# Copy the inp_digest, because it can be useful for some actions.
|
|
891
|
+
self.step.inp_digest = result.inp_digest
|
|
892
|
+
return result, []
|
|
857
893
|
|
|
858
894
|
@allow_rpc
|
|
859
895
|
def compute_out_step_hash(
|
|
@@ -881,22 +917,23 @@ class WorkerHandler:
|
|
|
881
917
|
so they can be updated in StepUp's workflow database if desired.
|
|
882
918
|
Unchanged ones are not included.
|
|
883
919
|
"""
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
920
|
+
with self.timer.section("compute_out_step_hash"):
|
|
921
|
+
# Check the output hashes
|
|
922
|
+
new_out_hashes = []
|
|
923
|
+
all_out_hashes = []
|
|
924
|
+
for path, old_file_hash in sorted(old_out_hashes):
|
|
925
|
+
new_file_hash = old_file_hash.regen(path)
|
|
926
|
+
all_out_hashes.append((path, new_file_hash))
|
|
927
|
+
if new_file_hash != old_file_hash:
|
|
928
|
+
new_out_hashes.append((path, new_file_hash))
|
|
929
|
+
if new_file_hash.is_unknown:
|
|
930
|
+
self.step.out_missing.append(path)
|
|
931
|
+
self.step.success = False
|
|
895
932
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
933
|
+
# Update the step hash
|
|
934
|
+
if step_hash is not None:
|
|
935
|
+
step_hash = step_hash.evolve_out(all_out_hashes)
|
|
936
|
+
return step_hash, new_out_hashes
|
|
900
937
|
|
|
901
938
|
@allow_rpc
|
|
902
939
|
def compute_full_step_hash(
|
|
@@ -956,57 +993,58 @@ class WorkerHandler:
|
|
|
956
993
|
|
|
957
994
|
@allow_rpc
|
|
958
995
|
async def run(self):
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
996
|
+
with self.timer.section("run"):
|
|
997
|
+
await self.reporter("START", self.step.description)
|
|
998
|
+
await self.reporter.start_step(self.step.description, self.step.i)
|
|
999
|
+
|
|
1000
|
+
# For internal use in actions:
|
|
1001
|
+
os.environ["STEPUP_STEP_I"] = str(self.step.i)
|
|
1002
|
+
# Client code may use the following:
|
|
1003
|
+
os.environ["STEPUP_STEP_INP_DIGEST"] = self.step.inp_digest.hex()
|
|
1004
|
+
os.environ["ROOT"] = str(Path.cwd().relpath(self.step.workdir))
|
|
1005
|
+
os.environ["HERE"] = str(self.step.workdir.relpath())
|
|
1006
|
+
# Note: the variables defined here should be listed in stepup.core.api.getenv
|
|
1007
|
+
|
|
1008
|
+
# Create IO redirection for stdout and stderr
|
|
1009
|
+
step_err = io.StringIO()
|
|
1010
|
+
step_out = io.StringIO()
|
|
1011
|
+
with (
|
|
1012
|
+
contextlib.chdir(self.step.workdir),
|
|
1013
|
+
contextlib.redirect_stderr(step_err),
|
|
1014
|
+
contextlib.redirect_stdout(step_out),
|
|
1015
|
+
):
|
|
1016
|
+
if self.show_perf:
|
|
1017
|
+
ru_initial = resource.getrusage(resource.RUSAGE_CHILDREN)
|
|
1018
|
+
pt_initial = perf_counter()
|
|
1019
|
+
self.step.thread = WorkThread(self.step.action)
|
|
1020
|
+
self.step.thread.start()
|
|
1021
|
+
await self.step.thread.done.wait()
|
|
1022
|
+
self.step.thread.join()
|
|
1023
|
+
self.step.returncode = self.step.thread.returncode
|
|
1024
|
+
self.step.thread = None
|
|
1025
|
+
self.step.stdout = step_out.getvalue()
|
|
1026
|
+
self.step.stderr = step_err.getvalue()
|
|
1027
|
+
|
|
1028
|
+
# Clean up environment variables (to avoid potential confusion)
|
|
1029
|
+
del os.environ["STEPUP_STEP_I"]
|
|
1030
|
+
del os.environ["STEPUP_STEP_INP_DIGEST"]
|
|
1031
|
+
del os.environ["ROOT"]
|
|
1032
|
+
del os.environ["HERE"]
|
|
1033
|
+
|
|
1034
|
+
# Process results of the step.
|
|
978
1035
|
if self.show_perf:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
del os.environ["STEPUP_STEP_I"]
|
|
992
|
-
del os.environ["STEPUP_STEP_INP_DIGEST"]
|
|
993
|
-
del os.environ["ROOT"]
|
|
994
|
-
del os.environ["HERE"]
|
|
995
|
-
|
|
996
|
-
# Process results of the step.
|
|
997
|
-
if self.show_perf:
|
|
998
|
-
ru_final = resource.getrusage(resource.RUSAGE_CHILDREN)
|
|
999
|
-
utime = ru_final.ru_utime - ru_initial.ru_utime
|
|
1000
|
-
stime = ru_final.ru_stime - ru_initial.ru_stime
|
|
1001
|
-
wtime = perf_counter() - pt_initial
|
|
1002
|
-
ru_lines = [
|
|
1003
|
-
f"User CPU time [s]: {utime:9.4f}",
|
|
1004
|
-
f"System CPU time [s]: {stime:9.4f}",
|
|
1005
|
-
f"Total CPU time [s]: {utime + stime:9.4f}",
|
|
1006
|
-
f"Wall time [s]: {wtime:9.4f}",
|
|
1007
|
-
]
|
|
1008
|
-
self.step.perf_info = "\n".join(ru_lines)
|
|
1009
|
-
self.step.success = self.step.returncode == 0
|
|
1036
|
+
ru_final = resource.getrusage(resource.RUSAGE_CHILDREN)
|
|
1037
|
+
utime = ru_final.ru_utime - ru_initial.ru_utime
|
|
1038
|
+
stime = ru_final.ru_stime - ru_initial.ru_stime
|
|
1039
|
+
wtime = perf_counter() - pt_initial
|
|
1040
|
+
ru_lines = [
|
|
1041
|
+
f"User CPU time [s]: {utime:9.4f}",
|
|
1042
|
+
f"System CPU time [s]: {stime:9.4f}",
|
|
1043
|
+
f"Total CPU time [s]: {utime + stime:9.4f}",
|
|
1044
|
+
f"Wall time [s]: {wtime:9.4f}",
|
|
1045
|
+
]
|
|
1046
|
+
self.step.perf_info = "\n".join(ru_lines)
|
|
1047
|
+
self.step.success = self.step.returncode == 0
|
|
1010
1048
|
|
|
1011
1049
|
@allow_rpc
|
|
1012
1050
|
def get_success(self) -> bool:
|
|
@@ -1021,51 +1059,52 @@ class WorkerHandler:
|
|
|
1021
1059
|
|
|
1022
1060
|
@allow_rpc
|
|
1023
1061
|
async def report(self):
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
(
|
|
1043
|
-
|
|
1044
|
-
|
|
1062
|
+
with self.timer.section("report"):
|
|
1063
|
+
pages = []
|
|
1064
|
+
if not self.step.success:
|
|
1065
|
+
# Format command such that it can be copied and pasted into a shell.
|
|
1066
|
+
command = "stepup act "
|
|
1067
|
+
if any(word.startswith("-") for word in shlex.split(self.step.action)):
|
|
1068
|
+
command += "-- "
|
|
1069
|
+
command += self.step.action
|
|
1070
|
+
if self.step.workdir != "./":
|
|
1071
|
+
command = f"(cd {self.step.workdir} && {command})"
|
|
1072
|
+
lines = [f"Command {command}"]
|
|
1073
|
+
# Other info on the execution of the step
|
|
1074
|
+
if self.step.returncode is not None:
|
|
1075
|
+
lines.append(f"Return code {self.step.returncode}")
|
|
1076
|
+
pages.append(("Step info", "\n".join(lines)))
|
|
1077
|
+
if len(self.step.perf_info) > 0:
|
|
1078
|
+
pages.append(("Performance details", self.step.perf_info))
|
|
1079
|
+
if self.step.rescheduled_info != "":
|
|
1080
|
+
pages.append(
|
|
1081
|
+
(
|
|
1082
|
+
"Rescheduling due to unavailable amended inputs",
|
|
1083
|
+
self.step.rescheduled_info,
|
|
1084
|
+
)
|
|
1045
1085
|
)
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
self.step = None
|
|
1086
|
+
else:
|
|
1087
|
+
if len(self.step.inp_messages) > 0:
|
|
1088
|
+
self.step.inp_messages.sort()
|
|
1089
|
+
pages.append(("Invalid inputs", "\n".join(self.step.inp_messages)))
|
|
1090
|
+
if len(self.step.out_missing) > 0:
|
|
1091
|
+
self.step.out_missing.sort()
|
|
1092
|
+
pages.append(("Expected outputs not created", "\n".join(self.step.out_missing)))
|
|
1093
|
+
stdout = self.step.stdout.rstrip()
|
|
1094
|
+
if len(stdout) > 0:
|
|
1095
|
+
pages.append(("Standard output", stdout))
|
|
1096
|
+
stderr = self.step.stderr.rstrip()
|
|
1097
|
+
if len(stderr) > 0:
|
|
1098
|
+
pages.append(("Standard error", stderr))
|
|
1099
|
+
if self.step.rescheduled_info != "":
|
|
1100
|
+
action = "RESCHEDULE"
|
|
1101
|
+
elif self.step.success:
|
|
1102
|
+
action = "SUCCESS"
|
|
1103
|
+
else:
|
|
1104
|
+
action = "FAIL"
|
|
1105
|
+
await self.reporter.stop_step(self.step.i)
|
|
1106
|
+
await self.reporter(action, self.step.description, pages)
|
|
1107
|
+
self.step = None
|
|
1069
1108
|
|
|
1070
1109
|
@allow_rpc
|
|
1071
1110
|
async def skip(self, step_hash: StepHash):
|
|
@@ -1155,6 +1194,7 @@ async def async_main():
|
|
|
1155
1194
|
# Create the worker handler for the RPC server.
|
|
1156
1195
|
handler = WorkerHandler(reporter, args.show_perf, args.explain_rerun)
|
|
1157
1196
|
await serve_socket_rpc(handler, args.worker_socket, handler.stop_event)
|
|
1197
|
+
handler.timer.report()
|
|
1158
1198
|
|
|
1159
1199
|
|
|
1160
1200
|
def main():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|