1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! # Execution Observer Module
//!
//! An always-on, per-task callback for aggregation — counters, histograms,
//! spans — as distinct from [`crate::ExecutionTrace`], which is a per-request
//! allocation you persist.
//!
//! This exists because the eight sync built-ins (`map`, `validation`/`validate`,
//! `parse_json`, `parse_xml`, `publish_json`, `publish_xml`, `filter`, `log`)
//! are dispatched inside a private method on the workflow executor and never
//! reach the function registry. A host can time its own registered handlers by
//! wrapping their bodies, but it cannot time those eight at any price, and so
//! cannot tell how much of a message's wall clock was spent inside the engine
//! versus inside its own handlers.
use Duration;
/// One finished task.
///
/// Borrowed for the duration of the callback; an observer must copy out anything
/// it needs to keep.
/// A message run beginning.
/// A message run finishing, whether it completed or stopped early.
/// A workflow beginning to run.
///
/// Fires only for a workflow that a rollout gate **and** its condition both
/// admitted — a skipped workflow never starts, mirroring how a skipped task is
/// not reported today.
/// A workflow finishing.
/// Receives one callback per dispatched task.
///
/// Object-safe by construction — no generic methods, no associated types — so
/// this is unrelated to the [`crate::AsyncFunctionHandler`] /
/// `DynAsyncFunctionHandler` split and needs no `Dyn` sibling.
/// `Arc<dyn ExecutionObserver>` works directly.
///
/// # Contract
///
/// `task_finished` is called **synchronously**, on the executor's thread,
/// immediately after the task body returns and *before* the audit trail is
/// written. On the sync-built-in path it runs inside the arena scope while the
/// `!Send` arena borrow is live.
///
/// So an implementation must not block, must not re-enter the engine, and must
/// not panic — a panic unwinds through the arena scope and out of
/// `process_message`. It cannot `await`, since the method is synchronous. Push to
/// a channel or bump an atomic and return.
///
/// A task whose condition evaluated false is **not** reported: it was never
/// dispatched, so there is nothing to time. Tasks that fail *are* reported, with
/// `status: Some(500)` — the event is emitted before the error propagates,
/// because those are the tasks a host most wants timed.
///
/// # Example
///
/// ```
/// use dataflow_rs::{ExecutionObserver, TaskEvent};
/// use std::sync::atomic::{AtomicU64, Ordering};
///
/// #[derive(Default)]
/// struct TotalMicros(AtomicU64);
///
/// impl ExecutionObserver for TotalMicros {
/// fn task_finished(&self, event: &TaskEvent<'_>) {
/// // Cheap and non-blocking, as the contract requires.
/// self.0
/// .fetch_add(event.duration.as_micros() as u64, Ordering::Relaxed);
/// }
/// }
/// ```