1use crate::traced::{PollEvent, PollResult};
4use std::time::Duration;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum StepOutcome {
24 Completed,
26 Pending,
28 Cancelled,
30}
31
32#[derive(Debug, Clone)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct StepNode {
50 pub id: usize,
52 pub label: String,
54 pub duration_us: u64,
56 pub outcome: StepOutcome,
58}
59
60#[derive(Debug, Clone)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80pub struct AsyncStepGraph {
81 pub steps: Vec<StepNode>,
83 pub edges: Vec<(usize, usize)>,
85}
86
87fn outcome_for(result: &PollResult) -> StepOutcome {
88 match result {
89 PollResult::Ready => StepOutcome::Completed,
90 PollResult::Pending => StepOutcome::Pending,
91 PollResult::Cancelled => StepOutcome::Cancelled,
92 }
93}
94
95pub fn reify_execution(events: Vec<PollEvent>) -> AsyncStepGraph {
119 if events.is_empty() {
120 return AsyncStepGraph {
121 steps: vec![],
122 edges: vec![],
123 };
124 }
125
126 let mut steps = Vec::new();
127 let mut edges = Vec::new();
128
129 let push_step = |steps: &mut Vec<StepNode>,
130 edges: &mut Vec<(usize, usize)>,
131 label: Option<String>,
132 start_offset: Duration,
133 end_offset: Duration,
134 last_result: &PollResult| {
135 let step_id = steps.len();
136 let duration_us = end_offset
137 .saturating_sub(start_offset)
138 .as_micros()
139 .min(u64::MAX as u128) as u64;
140 steps.push(StepNode {
141 id: step_id,
142 label: label.unwrap_or_else(|| format!("step_{step_id}")),
143 duration_us,
144 outcome: outcome_for(last_result),
145 });
146 if step_id > 0 {
147 edges.push((step_id - 1, step_id));
148 }
149 };
150
151 let mut current_label = events[0].label.clone();
152 let mut group_start_offset = events[0].offset;
153 let mut group_last_result = events[0].result.clone();
154 let mut group_last_offset = events[0].offset;
155
156 for event in events.iter().skip(1) {
157 if event.label != current_label {
158 push_step(
159 &mut steps,
160 &mut edges,
161 current_label.clone(),
162 group_start_offset,
163 group_last_offset,
164 &group_last_result,
165 );
166 current_label = event.label.clone();
167 group_start_offset = event.offset;
168 }
169 group_last_result = event.result.clone();
170 group_last_offset = event.offset;
171 }
172
173 push_step(
174 &mut steps,
175 &mut edges,
176 current_label,
177 group_start_offset,
178 group_last_offset,
179 &group_last_result,
180 );
181
182 AsyncStepGraph { steps, edges }
183}
184
185pub fn to_dot(graph: &AsyncStepGraph) -> String {
205 let mut out = String::from("digraph async_trace {\n rankdir=TB;\n node [shape=box];\n\n");
206
207 for step in &graph.steps {
208 let color = match step.outcome {
209 StepOutcome::Completed => "green",
210 StepOutcome::Pending => "yellow",
211 StepOutcome::Cancelled => "red",
212 };
213 out.push_str(&format!(
214 " n{} [label=\"{}\\n({}us)\" style=filled fillcolor={}];\n",
215 step.id, step.label, step.duration_us, color
216 ));
217 }
218
219 out.push('\n');
220
221 for (from, to) in &graph.edges {
222 out.push_str(&format!(" n{from} -> n{to};\n"));
223 }
224
225 out.push_str("}\n");
226 out
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 fn make_event(step: usize, result: PollResult, label: Option<&str>) -> PollEvent {
234 PollEvent {
235 step,
236 offset: Duration::from_micros(step as u64 * 10),
237 result,
238 label: label.map(String::from),
239 }
240 }
241
242 #[test]
243 fn empty_trace() {
244 let graph = reify_execution(vec![]);
245 assert!(graph.steps.is_empty());
246 assert!(graph.edges.is_empty());
247 }
248
249 #[test]
250 fn single_event() {
251 let graph = reify_execution(vec![make_event(0, PollResult::Ready, Some("only"))]);
252 assert_eq!(graph.steps.len(), 1);
253 assert_eq!(graph.steps[0].label, "only");
254 assert_eq!(graph.steps[0].outcome, StepOutcome::Completed);
255 assert!(graph.edges.is_empty());
256 }
257
258 #[test]
259 fn two_steps() {
260 let graph = reify_execution(vec![
261 make_event(0, PollResult::Pending, Some("a")),
262 make_event(1, PollResult::Ready, Some("a")),
263 make_event(2, PollResult::Ready, Some("b")),
264 ]);
265 assert_eq!(graph.steps.len(), 2);
266 assert_eq!(graph.steps[0].label, "a");
267 assert_eq!(graph.steps[0].outcome, StepOutcome::Completed);
268 assert_eq!(graph.steps[1].label, "b");
269 assert_eq!(graph.edges, vec![(0, 1)]);
270 }
271
272 #[test]
273 fn three_steps_chain() {
274 let graph = reify_execution(vec![
275 make_event(0, PollResult::Ready, Some("x")),
276 make_event(1, PollResult::Pending, Some("y")),
277 make_event(2, PollResult::Ready, Some("y")),
278 make_event(3, PollResult::Ready, Some("z")),
279 ]);
280 assert_eq!(graph.steps.len(), 3);
281 assert_eq!(graph.edges, vec![(0, 1), (1, 2)]);
282 }
283
284 #[test]
285 fn unlabeled_steps() {
286 let graph = reify_execution(vec![
287 make_event(0, PollResult::Ready, None),
288 make_event(1, PollResult::Ready, Some("b")),
289 ]);
290 assert_eq!(graph.steps.len(), 2);
291 assert_eq!(graph.steps[0].label, "step_0");
292 assert_eq!(graph.steps[1].label, "b");
293 }
294
295 #[test]
296 fn cancelled_outcome_propagates() {
297 let graph = reify_execution(vec![
298 make_event(0, PollResult::Pending, Some("dropped_step")),
299 make_event(1, PollResult::Cancelled, Some("dropped_step")),
300 ]);
301 assert_eq!(graph.steps.len(), 1);
302 assert_eq!(graph.steps[0].outcome, StepOutcome::Cancelled);
303 }
304
305 #[test]
306 fn dot_output() {
307 let graph = AsyncStepGraph {
308 steps: vec![
309 StepNode {
310 id: 0,
311 label: "start".into(),
312 duration_us: 100,
313 outcome: StepOutcome::Completed,
314 },
315 StepNode {
316 id: 1,
317 label: "end".into(),
318 duration_us: 50,
319 outcome: StepOutcome::Pending,
320 },
321 ],
322 edges: vec![(0, 1)],
323 };
324 let dot = to_dot(&graph);
325 assert!(dot.contains("digraph async_trace"));
326 assert!(dot.contains("start"));
327 assert!(dot.contains("end"));
328 assert!(dot.contains("n0 -> n1"));
329 assert!(dot.contains("green")); assert!(dot.contains("yellow")); }
332}