1use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct TaskId(u64);
14
15impl TaskId {
16 pub fn new() -> Self {
18 static COUNTER: AtomicU64 = AtomicU64::new(1);
19 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
20 }
21
22 #[must_use]
24 pub fn as_u64(&self) -> u64 {
25 self.0
26 }
27
28 #[must_use]
30 pub fn from_u64(id: u64) -> Self {
31 Self(id)
32 }
33}
34
35impl Default for TaskId {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl fmt::Display for TaskId {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 write!(f, "#{}", self.0)
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub enum TaskState {
50 Pending,
52 Running,
54 Blocked {
56 await_point: String,
58 },
59 Completed,
61 Failed,
63}
64
65impl fmt::Display for TaskState {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 Self::Pending => write!(f, "PENDING"),
69 Self::Running => write!(f, "RUNNING"),
70 Self::Blocked { await_point } => write!(f, "BLOCKED({await_point})"),
71 Self::Completed => write!(f, "COMPLETED"),
72 Self::Failed => write!(f, "FAILED"),
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct TaskInfo {
80 pub id: TaskId,
82
83 pub name: String,
85
86 pub state: TaskState,
88
89 pub created_at: Instant,
91
92 pub last_updated: Instant,
94
95 pub poll_count: u64,
97
98 pub total_run_time: Duration,
100
101 pub parent: Option<TaskId>,
103
104 pub location: Option<String>,
106}
107
108impl TaskInfo {
109 #[must_use]
111 pub fn new(name: String) -> Self {
112 let now = Instant::now();
113 Self {
114 id: TaskId::new(),
115 name,
116 state: TaskState::Pending,
117 created_at: now,
118 last_updated: now,
119 poll_count: 0,
120 total_run_time: Duration::ZERO,
121 parent: None,
122 location: None,
123 }
124 }
125
126 pub fn update_state(&mut self, new_state: TaskState) {
128 self.state = new_state;
129 self.last_updated = Instant::now();
130 }
131
132 pub fn record_poll(&mut self, duration: Duration) {
134 self.poll_count += 1;
135 self.total_run_time += duration;
136 self.last_updated = Instant::now();
137 }
138
139 #[must_use]
141 pub fn age(&self) -> Duration {
142 self.created_at.elapsed()
143 }
144
145 #[must_use]
147 pub fn time_since_update(&self) -> Duration {
148 self.last_updated.elapsed()
149 }
150
151 #[must_use]
153 pub fn with_parent(mut self, parent: TaskId) -> Self {
154 self.parent = Some(parent);
155 self
156 }
157
158 #[must_use]
160 pub fn with_location(mut self, location: String) -> Self {
161 self.location = Some(location);
162 self
163 }
164}
165
166impl fmt::Display for TaskInfo {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(
169 f,
170 "Task {} [{}]: {} (polls: {}, runtime: {:.2}s, age: {:.2}s)",
171 self.id,
172 self.name,
173 self.state,
174 self.poll_count,
175 self.total_run_time.as_secs_f64(),
176 self.age().as_secs_f64()
177 )
178 }
179}
180
181#[derive(Debug, Clone, Default)]
195pub struct TaskFilter {
196 pub state: Option<TaskState>,
198 pub name_pattern: Option<String>,
200 pub min_duration: Option<Duration>,
202 pub max_duration: Option<Duration>,
204 pub min_polls: Option<u64>,
206 pub max_polls: Option<u64>,
208 pub parent: Option<TaskId>,
210 pub root_only: bool,
212}
213
214impl TaskFilter {
215 #[must_use]
217 pub fn new() -> Self {
218 Self::default()
219 }
220
221 #[must_use]
223 pub fn with_state(mut self, state: TaskState) -> Self {
224 self.state = Some(state);
225 self
226 }
227
228 #[must_use]
230 pub fn with_name_pattern(mut self, pattern: impl Into<String>) -> Self {
231 self.name_pattern = Some(pattern.into());
232 self
233 }
234
235 #[must_use]
237 pub fn with_min_duration(mut self, duration: Duration) -> Self {
238 self.min_duration = Some(duration);
239 self
240 }
241
242 #[must_use]
244 pub fn with_max_duration(mut self, duration: Duration) -> Self {
245 self.max_duration = Some(duration);
246 self
247 }
248
249 #[must_use]
251 pub fn with_min_polls(mut self, count: u64) -> Self {
252 self.min_polls = Some(count);
253 self
254 }
255
256 #[must_use]
258 pub fn with_max_polls(mut self, count: u64) -> Self {
259 self.max_polls = Some(count);
260 self
261 }
262
263 #[must_use]
265 pub fn with_parent(mut self, parent: TaskId) -> Self {
266 self.parent = Some(parent);
267 self
268 }
269
270 #[must_use]
272 pub fn root_only(mut self) -> Self {
273 self.root_only = true;
274 self
275 }
276
277 #[must_use]
279 pub fn matches(&self, task: &TaskInfo) -> bool {
280 if let Some(ref state) = self.state {
282 if !self.state_matches(&task.state, state) {
283 return false;
284 }
285 }
286
287 if let Some(ref pattern) = self.name_pattern {
289 if !task.name.to_lowercase().contains(&pattern.to_lowercase()) {
290 return false;
291 }
292 }
293
294 if let Some(min) = self.min_duration {
296 if task.age() < min {
297 return false;
298 }
299 }
300
301 if let Some(max) = self.max_duration {
303 if task.age() > max {
304 return false;
305 }
306 }
307
308 if let Some(min) = self.min_polls {
310 if task.poll_count < min {
311 return false;
312 }
313 }
314
315 if let Some(max) = self.max_polls {
317 if task.poll_count > max {
318 return false;
319 }
320 }
321
322 if let Some(parent) = self.parent {
324 if task.parent != Some(parent) {
325 return false;
326 }
327 }
328
329 if self.root_only && task.parent.is_some() {
331 return false;
332 }
333
334 true
335 }
336
337 fn state_matches(&self, task_state: &TaskState, filter_state: &TaskState) -> bool {
339 match (task_state, filter_state) {
340 (TaskState::Pending, TaskState::Pending) => true,
341 (TaskState::Running, TaskState::Running) => true,
342 (TaskState::Blocked { .. }, TaskState::Blocked { .. }) => true,
343 (TaskState::Completed, TaskState::Completed) => true,
344 (TaskState::Failed, TaskState::Failed) => true,
345 _ => false,
346 }
347 }
348
349 pub fn filter<'a>(&self, tasks: impl IntoIterator<Item = &'a TaskInfo>) -> Vec<&'a TaskInfo> {
351 tasks.into_iter().filter(|t| self.matches(t)).collect()
352 }
353
354 pub fn filter_cloned(&self, tasks: impl IntoIterator<Item = TaskInfo>) -> Vec<TaskInfo> {
356 tasks.into_iter().filter(|t| self.matches(t)).collect()
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
362pub enum TaskSortBy {
363 #[default]
365 Id,
366 Name,
368 Age,
370 Polls,
372 RunTime,
374 State,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
380pub enum SortDirection {
381 #[default]
383 Ascending,
384 Descending,
386}
387
388pub fn sort_tasks(tasks: &mut [TaskInfo], sort_by: TaskSortBy, direction: SortDirection) {
390 tasks.sort_by(|a, b| {
391 let cmp = match sort_by {
392 TaskSortBy::Id => a.id.as_u64().cmp(&b.id.as_u64()),
393 TaskSortBy::Name => a.name.cmp(&b.name),
394 TaskSortBy::Age => a.created_at.cmp(&b.created_at),
395 TaskSortBy::Polls => a.poll_count.cmp(&b.poll_count),
396 TaskSortBy::RunTime => a.total_run_time.cmp(&b.total_run_time),
397 TaskSortBy::State => state_order(&a.state).cmp(&state_order(&b.state)),
398 };
399
400 match direction {
401 SortDirection::Ascending => cmp,
402 SortDirection::Descending => cmp.reverse(),
403 }
404 });
405}
406
407fn state_order(state: &TaskState) -> u8 {
409 match state {
410 TaskState::Running => 0,
411 TaskState::Blocked { .. } => 1,
412 TaskState::Pending => 2,
413 TaskState::Completed => 3,
414 TaskState::Failed => 4,
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_task_id_uniqueness() {
424 let id1 = TaskId::new();
425 let id2 = TaskId::new();
426 assert_ne!(id1, id2);
427 }
428
429 #[test]
430 fn test_task_info_creation() {
431 let task = TaskInfo::new("test_task".to_string());
432 assert_eq!(task.name, "test_task");
433 assert_eq!(task.state, TaskState::Pending);
434 assert_eq!(task.poll_count, 0);
435 }
436
437 #[test]
438 fn test_task_state_update() {
439 let mut task = TaskInfo::new("test".to_string());
440 task.update_state(TaskState::Running);
441 assert_eq!(task.state, TaskState::Running);
442 }
443
444 #[test]
445 fn test_task_poll_recording() {
446 let mut task = TaskInfo::new("test".to_string());
447 task.record_poll(Duration::from_millis(100));
448 assert_eq!(task.poll_count, 1);
449 assert_eq!(task.total_run_time, Duration::from_millis(100));
450 }
451}