1use crate::task::{TaskId, TaskState};
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct EventId(u64);
13
14impl EventId {
15 #[must_use]
17 pub fn new(id: u64) -> Self {
18 Self(id)
19 }
20
21 #[must_use]
23 pub fn as_u64(&self) -> u64 {
24 self.0
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub enum EventKind {
31 TaskSpawned {
33 name: String,
35 parent: Option<TaskId>,
37 location: Option<String>,
39 },
40
41 PollStarted,
43
44 PollEnded {
46 duration: Duration,
48 },
49
50 AwaitStarted {
52 await_point: String,
54 location: Option<String>,
56 },
57
58 AwaitEnded {
60 await_point: String,
62 duration: Duration,
64 },
65
66 TaskCompleted {
68 duration: Duration,
70 },
71
72 TaskFailed {
74 error: Option<String>,
76 },
77
78 InspectionPoint {
80 label: String,
82 message: Option<String>,
84 },
85
86 StateChanged {
88 old_state: TaskState,
90 new_state: TaskState,
92 },
93}
94
95impl fmt::Display for EventKind {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::TaskSpawned { name, .. } => write!(f, "Spawned: {name}"),
99 Self::PollStarted => write!(f, "Poll started"),
100 Self::PollEnded { duration } => {
101 write!(f, "Poll ended ({:.2}ms)", duration.as_secs_f64() * 1000.0)
102 }
103 Self::AwaitStarted { await_point, .. } => write!(f, "Await started: {await_point}"),
104 Self::AwaitEnded {
105 await_point,
106 duration,
107 } => {
108 write!(
109 f,
110 "Await ended: {} ({:.2}ms)",
111 await_point,
112 duration.as_secs_f64() * 1000.0
113 )
114 }
115 Self::TaskCompleted { duration } => {
116 write!(f, "Completed ({:.2}s)", duration.as_secs_f64())
117 }
118 Self::TaskFailed { error } => {
119 if let Some(err) = error {
120 write!(f, "Failed: {err}")
121 } else {
122 write!(f, "Failed")
123 }
124 }
125 Self::InspectionPoint { label, message } => {
126 if let Some(msg) = message {
127 write!(f, "Inspection[{label}]: {msg}")
128 } else {
129 write!(f, "Inspection[{label}]")
130 }
131 }
132 Self::StateChanged {
133 old_state,
134 new_state,
135 } => {
136 write!(f, "State: {old_state} → {new_state}")
137 }
138 }
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct Event {
145 pub id: EventId,
147
148 pub task_id: TaskId,
150
151 pub timestamp: Instant,
153
154 pub kind: EventKind,
156}
157
158impl Event {
159 #[must_use]
161 pub fn new(id: u64, task_id: TaskId, kind: EventKind) -> Self {
162 Self {
163 id: EventId::new(id),
164 task_id,
165 timestamp: Instant::now(),
166 kind,
167 }
168 }
169
170 #[must_use]
172 pub fn age(&self) -> Duration {
173 self.timestamp.elapsed()
174 }
175}
176
177impl fmt::Display for Event {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(
180 f,
181 "[{:.3}s] Task {}: {}",
182 self.age().as_secs_f64(),
183 self.task_id,
184 self.kind
185 )
186 }
187}
188
189#[derive(Debug, Default)]
191pub struct Timeline {
192 events: Vec<Event>,
194
195 start_time: Option<Instant>,
197}
198
199impl Timeline {
200 #[must_use]
202 pub fn new() -> Self {
203 Self {
204 events: Vec::new(),
205 start_time: None,
206 }
207 }
208
209 pub fn add_event(&mut self, event: Event) {
211 if self.start_time.is_none() {
212 self.start_time = Some(event.timestamp);
213 }
214 self.events.push(event);
215 }
216
217 #[must_use]
219 pub fn events(&self) -> &[Event] {
220 &self.events
221 }
222
223 #[must_use]
225 pub fn events_for_task(&self, task_id: TaskId) -> Vec<&Event> {
226 self.events
227 .iter()
228 .filter(|e| e.task_id == task_id)
229 .collect()
230 }
231
232 #[must_use]
234 pub fn duration(&self) -> Duration {
235 self.start_time
236 .map_or(Duration::ZERO, |start| start.elapsed())
237 }
238
239 #[must_use]
241 pub fn len(&self) -> usize {
242 self.events.len()
243 }
244
245 #[must_use]
247 pub fn is_empty(&self) -> bool {
248 self.events.is_empty()
249 }
250
251 pub fn clear(&mut self) {
253 self.events.clear();
254 self.start_time = None;
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::task::TaskId;
262
263 #[test]
264 fn test_timeline_creation() {
265 let timeline = Timeline::new();
266 assert!(timeline.is_empty());
267 assert_eq!(timeline.len(), 0);
268 }
269
270 #[test]
271 fn test_add_event() {
272 let mut timeline = Timeline::new();
273 let task_id = TaskId::new();
274 let event = Event::new(
275 1,
276 task_id,
277 EventKind::TaskSpawned {
278 name: "test".to_string(),
279 parent: None,
280 location: None,
281 },
282 );
283
284 timeline.add_event(event);
285 assert_eq!(timeline.len(), 1);
286 }
287
288 #[test]
289 fn test_events_for_task() {
290 let mut timeline = Timeline::new();
291 let task1 = TaskId::new();
292 let task2 = TaskId::new();
293
294 timeline.add_event(Event::new(1, task1, EventKind::PollStarted));
295 timeline.add_event(Event::new(2, task2, EventKind::PollStarted));
296 timeline.add_event(Event::new(
297 3,
298 task1,
299 EventKind::PollEnded {
300 duration: Duration::from_millis(10),
301 },
302 ));
303
304 let task1_events = timeline.events_for_task(task1);
305 assert_eq!(task1_events.len(), 2);
306 }
307}