Skip to content

Commit cbe599e

Browse files
committed
adding calculation time per processing step
1 parent 1d60dc2 commit cbe599e

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

src/modacor/dataclasses/trace_event.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from hashlib import sha256
1818
from typing import Any
1919

20-
from attrs import define, field
20+
from attrs import define, field, validators
2121

2222

2323
def _to_jsonable(value: Any) -> Any:
@@ -93,6 +93,9 @@ class TraceEvent:
9393
# reserved for later (MessageHandler, timing, etc.)
9494
messages: list[dict[str, Any]] = field(factory=list)
9595

96+
# wall-clock runtime for this step execution (seconds)
97+
duration_s: float | None = field(default=None, validator=validators.optional(validators.instance_of(float)))
98+
9699
def __attrs_post_init__(self) -> None:
97100
object.__setattr__(self, "config_hash", _stable_hash_dict(self.config))
98101

@@ -109,6 +112,7 @@ def to_dict(self) -> dict[str, Any]:
109112
"requires_steps": list(self.requires_steps),
110113
"config": _to_jsonable(self.config),
111114
"config_hash": self.config_hash,
115+
"duration_s": self.duration_s,
112116
"datasets": _to_jsonable(self.datasets),
113117
"messages": _to_jsonable(self.messages),
114118
}

src/modacor/debug/pipeline_tracer.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,13 @@ def _diff_kinds(self, prev: BaseDataProbe, now: BaseDataProbe) -> set[str]:
305305

306306
return kinds
307307

308-
def after_step(self, step: ProcessStep, data: ProcessingData) -> None: # noqa: C901 # too complex, resolve later
308+
def after_step( # noqa: C901 # too complex, resolve later
309+
self,
310+
step: ProcessStep,
311+
data: ProcessingData,
312+
*,
313+
duration_s: float | None = None,
314+
) -> None:
309315
step_id = getattr(step, "step_id", "<??>")
310316
module = getattr(step.documentation, "calling_id", None) or step.__class__.__name__
311317
name = getattr(step.documentation, "calling_name", "")
@@ -411,6 +417,7 @@ def after_step(self, step: ProcessStep, data: ProcessingData) -> None: # noqa:
411417
"module": module,
412418
"name": name,
413419
"changed": changed,
420+
"duration_s": duration_s,
414421
}
415422
)
416423

@@ -488,7 +495,11 @@ def fmt_kv(label: str, prev: object | None, now: object, is_changed: bool) -> st
488495
step_id = tracer_event.get("step_id", "<??>")
489496
module = tracer_event.get("module", "")
490497
name = tracer_event.get("name", "")
491-
lines.append(r.header(f"Step {step_id}{module}{name}"))
498+
dur = tracer_event.get("duration_s", None)
499+
dur_txt = ""
500+
if isinstance(dur, (int, float)):
501+
dur_txt = f" {r.dim(f'⏱ {dur * 1e3:.2f} ms')}" # noqa: E231
502+
lines.append(r.header(f"Step {step_id}{module}{name}") + dur_txt)
492503

493504
changed_map: dict[tuple[str, str], dict[str, Any]] = tracer_event.get("changed", {}) or {}
494505
items = sorted(changed_map.items(), key=lambda kv: (kv[0][0], kv[0][1]))

src/modacor/runner/pipeline.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,12 @@ def _html_escape(s: str) -> str:
642642
datasets = tracer_event_to_datasets_payload(ev)
643643
break
644644

645+
duration_s: float | None = None
646+
if matched_ev is not None:
647+
d = matched_ev.get("duration_s", None)
648+
if isinstance(d, (int, float)):
649+
duration_s = float(d)
650+
645651
messages: list[dict[str, Any]] = []
646652

647653
# --- Rendered trace (STRICTLY step-local) ---
@@ -721,6 +727,7 @@ def _html_escape(s: str) -> str:
721727
requires_steps=prereqs,
722728
config=cfg,
723729
datasets=datasets,
730+
duration_s=duration_s,
724731
messages=messages,
725732
)
726733

src/modacor/tests/debug/test_tracing_integration.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,14 @@ def test_attach_tracer_event_embeds_rendered_trace_and_config():
177177
trace_blocks = [m for m in ev.messages if m.get("kind") == "rendered_trace"]
178178
if trace_blocks:
179179
assert "Step A" in trace_blocks[0].get("content", "")
180+
181+
182+
def test_attach_tracer_event_copies_duration():
183+
step = DummyStep(io_sources=None, step_id="A")
184+
pipeline = Pipeline.from_dict({step: []}, name="t")
185+
186+
tracer = PipelineTracer(watch={"sample": ["signal"]}, record_only_on_change=False)
187+
tracer.after_step(step, ProcessingData(), duration_s=0.0123)
188+
189+
ev = pipeline.attach_tracer_event(step, tracer)
190+
assert ev.duration_s == 0.0123

0 commit comments

Comments
 (0)