use crate::task::{TaskId, TaskState};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EventId(u64);
impl EventId {
#[must_use]
pub fn new(id: u64) -> Self {
Self(id)
}
#[must_use]
pub fn as_u64(&self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventKind {
TaskSpawned {
name: String,
parent: Option<TaskId>,
location: Option<String>,
},
PollStarted,
PollEnded {
duration: Duration,
},
AwaitStarted {
await_point: String,
location: Option<String>,
},
AwaitEnded {
await_point: String,
duration: Duration,
},
TaskCompleted {
duration: Duration,
},
TaskFailed {
error: Option<String>,
},
InspectionPoint {
label: String,
message: Option<String>,
},
StateChanged {
old_state: TaskState,
new_state: TaskState,
},
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TaskSpawned { name, .. } => write!(f, "Spawned: {name}"),
Self::PollStarted => write!(f, "Poll started"),
Self::PollEnded { duration } => {
write!(f, "Poll ended ({:.2}ms)", duration.as_secs_f64() * 1000.0)
}
Self::AwaitStarted { await_point, .. } => write!(f, "Await started: {await_point}"),
Self::AwaitEnded {
await_point,
duration,
} => {
write!(
f,
"Await ended: {} ({:.2}ms)",
await_point,
duration.as_secs_f64() * 1000.0
)
}
Self::TaskCompleted { duration } => {
write!(f, "Completed ({:.2}s)", duration.as_secs_f64())
}
Self::TaskFailed { error } => {
if let Some(err) = error {
write!(f, "Failed: {err}")
} else {
write!(f, "Failed")
}
}
Self::InspectionPoint { label, message } => {
if let Some(msg) = message {
write!(f, "Inspection[{label}]: {msg}")
} else {
write!(f, "Inspection[{label}]")
}
}
Self::StateChanged {
old_state,
new_state,
} => {
write!(f, "State: {old_state} → {new_state}")
}
}
}
}
#[derive(Debug, Clone)]
pub struct Event {
pub id: EventId,
pub task_id: TaskId,
pub timestamp: Instant,
pub kind: EventKind,
}
impl Event {
#[must_use]
pub fn new(id: u64, task_id: TaskId, kind: EventKind) -> Self {
Self {
id: EventId::new(id),
task_id,
timestamp: Instant::now(),
kind,
}
}
#[must_use]
pub fn age(&self) -> Duration {
self.timestamp.elapsed()
}
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{:.3}s] Task {}: {}",
self.age().as_secs_f64(),
self.task_id,
self.kind
)
}
}
#[derive(Debug, Default)]
pub struct Timeline {
events: Vec<Event>,
start_time: Option<Instant>,
}
impl Timeline {
#[must_use]
pub fn new() -> Self {
Self {
events: Vec::new(),
start_time: None,
}
}
pub fn add_event(&mut self, event: Event) {
if self.start_time.is_none() {
self.start_time = Some(event.timestamp);
}
self.events.push(event);
}
#[must_use]
pub fn events(&self) -> &[Event] {
&self.events
}
#[must_use]
pub fn events_for_task(&self, task_id: TaskId) -> Vec<&Event> {
self.events
.iter()
.filter(|e| e.task_id == task_id)
.collect()
}
#[must_use]
pub fn duration(&self) -> Duration {
self.start_time
.map_or(Duration::ZERO, |start| start.elapsed())
}
#[must_use]
pub fn len(&self) -> usize {
self.events.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn clear(&mut self) {
self.events.clear();
self.start_time = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::task::TaskId;
#[test]
fn test_timeline_creation() {
let timeline = Timeline::new();
assert!(timeline.is_empty());
assert_eq!(timeline.len(), 0);
}
#[test]
fn test_add_event() {
let mut timeline = Timeline::new();
let task_id = TaskId::new();
let event = Event::new(
1,
task_id,
EventKind::TaskSpawned {
name: "test".to_string(),
parent: None,
location: None,
},
);
timeline.add_event(event);
assert_eq!(timeline.len(), 1);
}
#[test]
fn test_events_for_task() {
let mut timeline = Timeline::new();
let task1 = TaskId::new();
let task2 = TaskId::new();
timeline.add_event(Event::new(1, task1, EventKind::PollStarted));
timeline.add_event(Event::new(2, task2, EventKind::PollStarted));
timeline.add_event(Event::new(
3,
task1,
EventKind::PollEnded {
duration: Duration::from_millis(10),
},
));
let task1_events = timeline.events_for_task(task1);
assert_eq!(task1_events.len(), 2);
}
}