1use crate::task::{TaskId, TaskState};
4use crate::workflow::{WorkflowId, WorkflowState};
5use chrono::{DateTime, Utc};
6use dashmap::DashMap;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::Duration;
12
13fn serialize_atomic<S: Serializer>(val: &Arc<AtomicU64>, ser: S) -> Result<S::Ok, S::Error> {
18 ser.serialize_u64(val.load(Ordering::Relaxed))
19}
20
21fn deserialize_atomic<'de, D: Deserializer<'de>>(de: D) -> Result<Arc<AtomicU64>, D::Error> {
22 let n = u64::deserialize(de)?;
23 Ok(Arc::new(AtomicU64::new(n)))
24}
25
26fn default_atomic() -> Arc<AtomicU64> {
27 Arc::new(AtomicU64::new(0))
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct TaskMetrics {
33 pub task_id: TaskId,
35 pub task_name: String,
37 pub state: TaskState,
39 pub start_time: Option<DateTime<Utc>>,
41 pub end_time: Option<DateTime<Utc>>,
43 pub duration: Option<Duration>,
45 pub retry_count: u32,
47 pub error: Option<String>,
49}
50
51impl TaskMetrics {
52 #[must_use]
54 pub fn new(task_id: TaskId, task_name: String) -> Self {
55 Self {
56 task_id,
57 task_name,
58 state: TaskState::Pending,
59 start_time: None,
60 end_time: None,
61 duration: None,
62 retry_count: 0,
63 error: None,
64 }
65 }
66
67 pub fn mark_started(&mut self) {
69 self.state = TaskState::Running;
70 self.start_time = Some(Utc::now());
71 }
72
73 pub fn mark_completed(&mut self) {
75 self.state = TaskState::Completed;
76 self.end_time = Some(Utc::now());
77 if let Some(start) = self.start_time {
78 self.duration = Some(Duration::from_millis(
79 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
80 ));
81 }
82 }
83
84 pub fn mark_failed(&mut self, error: String) {
86 self.state = TaskState::Failed;
87 self.end_time = Some(Utc::now());
88 self.error = Some(error);
89 if let Some(start) = self.start_time {
90 self.duration = Some(Duration::from_millis(
91 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
92 ));
93 }
94 }
95
96 pub fn increment_retry(&mut self) {
98 self.retry_count += 1;
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct WorkflowMetrics {
110 pub workflow_id: WorkflowId,
112 pub workflow_name: String,
114 pub state: WorkflowState,
116 pub start_time: Option<DateTime<Utc>>,
118 pub end_time: Option<DateTime<Utc>>,
120 pub duration: Option<Duration>,
122 pub tasks: HashMap<TaskId, TaskMetrics>,
124 pub total_tasks: usize,
126 #[serde(
128 serialize_with = "serialize_atomic",
129 deserialize_with = "deserialize_atomic",
130 default = "default_atomic"
131 )]
132 pub completed_tasks: Arc<AtomicU64>,
133 #[serde(
135 serialize_with = "serialize_atomic",
136 deserialize_with = "deserialize_atomic",
137 default = "default_atomic"
138 )]
139 pub failed_tasks: Arc<AtomicU64>,
140 #[serde(
142 serialize_with = "serialize_atomic",
143 deserialize_with = "deserialize_atomic",
144 default = "default_atomic"
145 )]
146 pub running_tasks: Arc<AtomicU64>,
147}
148
149impl WorkflowMetrics {
150 #[must_use]
152 pub fn new(workflow_id: WorkflowId, workflow_name: String, total_tasks: usize) -> Self {
153 Self {
154 workflow_id,
155 workflow_name,
156 state: WorkflowState::Created,
157 start_time: None,
158 end_time: None,
159 duration: None,
160 tasks: HashMap::new(),
161 total_tasks,
162 completed_tasks: Arc::new(AtomicU64::new(0)),
163 failed_tasks: Arc::new(AtomicU64::new(0)),
164 running_tasks: Arc::new(AtomicU64::new(0)),
165 }
166 }
167
168 #[must_use]
170 pub fn completed_tasks_count(&self) -> u64 {
171 self.completed_tasks.load(Ordering::Relaxed)
172 }
173
174 #[must_use]
176 pub fn failed_tasks_count(&self) -> u64 {
177 self.failed_tasks.load(Ordering::Relaxed)
178 }
179
180 #[must_use]
182 pub fn running_tasks_count(&self) -> u64 {
183 self.running_tasks.load(Ordering::Relaxed)
184 }
185
186 pub fn mark_started(&mut self) {
188 self.state = WorkflowState::Running;
189 self.start_time = Some(Utc::now());
190 }
191
192 pub fn mark_completed(&mut self) {
194 self.state = WorkflowState::Completed;
195 self.end_time = Some(Utc::now());
196 if let Some(start) = self.start_time {
197 self.duration = Some(Duration::from_millis(
198 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
199 ));
200 }
201 }
202
203 pub fn mark_failed(&mut self) {
205 self.state = WorkflowState::Failed;
206 self.end_time = Some(Utc::now());
207 if let Some(start) = self.start_time {
208 self.duration = Some(Duration::from_millis(
209 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
210 ));
211 }
212 }
213
214 pub fn update_task(&mut self, task_metrics: TaskMetrics) {
216 if let Some(old_metrics) = self.tasks.get(&task_metrics.task_id) {
218 self.update_counters(old_metrics.state, false);
219 }
220 self.update_counters(task_metrics.state, true);
221
222 self.tasks.insert(task_metrics.task_id, task_metrics);
223 }
224
225 fn update_counters(&self, state: TaskState, increment: bool) {
226 match state {
227 TaskState::Completed => {
228 if increment {
229 self.completed_tasks.fetch_add(1, Ordering::Relaxed);
230 } else {
231 let _ = self.completed_tasks.fetch_update(
233 Ordering::Relaxed,
234 Ordering::Relaxed,
235 |v| if v > 0 { Some(v - 1) } else { Some(0) },
236 );
237 }
238 }
239 TaskState::Failed => {
240 if increment {
241 self.failed_tasks.fetch_add(1, Ordering::Relaxed);
242 } else {
243 let _ =
244 self.failed_tasks
245 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
246 if v > 0 {
247 Some(v - 1)
248 } else {
249 Some(0)
250 }
251 });
252 }
253 }
254 TaskState::Running | TaskState::Retrying => {
255 if increment {
256 self.running_tasks.fetch_add(1, Ordering::Relaxed);
257 } else {
258 let _ = self.running_tasks.fetch_update(
259 Ordering::Relaxed,
260 Ordering::Relaxed,
261 |v| if v > 0 { Some(v - 1) } else { Some(0) },
262 );
263 }
264 }
265 _ => {}
266 }
267 }
268
269 #[must_use]
271 pub fn progress_percentage(&self) -> f64 {
272 if self.total_tasks == 0 {
273 return 100.0;
274 }
275 self.completed_tasks.load(Ordering::Relaxed) as f64 / self.total_tasks as f64 * 100.0
276 }
277
278 #[must_use]
280 pub fn average_task_duration(&self) -> Option<Duration> {
281 let durations: Vec<_> = self.tasks.values().filter_map(|m| m.duration).collect();
282
283 if durations.is_empty() {
284 return None;
285 }
286
287 let total_ms: u64 = durations.iter().map(|d| d.as_millis() as u64).sum();
288 Some(Duration::from_millis(total_ms / durations.len() as u64))
289 }
290
291 #[must_use]
293 pub fn throughput(&self) -> f64 {
294 let completed = self.completed_tasks.load(Ordering::Relaxed) as f64;
295 if let (Some(start), Some(end)) = (self.start_time, self.end_time) {
296 let duration_secs = (end - start).num_seconds().max(1);
297 completed / duration_secs as f64
298 } else if let Some(start) = self.start_time {
299 let duration_secs = (Utc::now() - start).num_seconds().max(1);
300 completed / duration_secs as f64
301 } else {
302 0.0
303 }
304 }
305}
306
307pub struct MonitoringService {
309 workflows: Arc<DashMap<WorkflowId, WorkflowMetrics>>,
311 history: Arc<DashMap<WorkflowId, WorkflowMetrics>>,
313}
314
315impl MonitoringService {
316 #[must_use]
318 pub fn new() -> Self {
319 Self {
320 workflows: Arc::new(DashMap::new()),
321 history: Arc::new(DashMap::new()),
322 }
323 }
324
325 pub fn start_workflow(
327 &self,
328 workflow_id: WorkflowId,
329 workflow_name: String,
330 total_tasks: usize,
331 ) {
332 let mut metrics = WorkflowMetrics::new(workflow_id, workflow_name, total_tasks);
333 metrics.mark_started();
334 self.workflows.insert(workflow_id, metrics);
335 }
336
337 pub fn update_task(
339 &self,
340 workflow_id: WorkflowId,
341 task_id: TaskId,
342 task_name: String,
343 state: TaskState,
344 error: Option<String>,
345 ) {
346 if let Some(mut workflow_metrics) = self.workflows.get_mut(&workflow_id) {
347 let mut task_metrics = workflow_metrics
348 .tasks
349 .get(&task_id)
350 .cloned()
351 .unwrap_or_else(|| TaskMetrics::new(task_id, task_name.clone()));
352
353 match state {
354 TaskState::Running => task_metrics.mark_started(),
355 TaskState::Completed => task_metrics.mark_completed(),
356 TaskState::Failed => {
357 if let Some(err) = error {
358 task_metrics.mark_failed(err);
359 } else {
360 task_metrics.mark_failed("Unknown error".to_string());
361 }
362 }
363 TaskState::Retrying => task_metrics.increment_retry(),
364 _ => task_metrics.state = state,
365 }
366
367 workflow_metrics.update_task(task_metrics);
368 }
369 }
370
371 pub fn complete_workflow(&self, workflow_id: WorkflowId, success: bool) {
373 if let Some((_, mut metrics)) = self.workflows.remove(&workflow_id) {
374 if success {
375 metrics.mark_completed();
376 } else {
377 metrics.mark_failed();
378 }
379 self.history.insert(workflow_id, metrics);
380 }
381 }
382
383 #[must_use]
385 pub fn get_workflow_metrics(&self, workflow_id: &WorkflowId) -> Option<WorkflowMetrics> {
386 self.workflows
387 .get(workflow_id)
388 .map(|m| m.clone())
389 .or_else(|| self.history.get(workflow_id).map(|m| m.clone()))
390 }
391
392 #[must_use]
394 pub fn get_active_workflows(&self) -> Vec<WorkflowMetrics> {
395 self.workflows
396 .iter()
397 .map(|entry| entry.value().clone())
398 .collect()
399 }
400
401 #[must_use]
403 pub fn get_history(&self, limit: Option<usize>) -> Vec<WorkflowMetrics> {
404 let mut history: Vec<_> = self
405 .history
406 .iter()
407 .map(|entry| entry.value().clone())
408 .collect();
409
410 history.sort_by(|a, b| b.start_time.cmp(&a.start_time));
412
413 if let Some(limit) = limit {
414 history.truncate(limit);
415 }
416
417 history
418 }
419
420 pub fn clear_history(&self, older_than: DateTime<Utc>) {
422 self.history.retain(|_, metrics| {
423 if let Some(end_time) = metrics.end_time {
424 end_time > older_than
425 } else {
426 true
427 }
428 });
429 }
430
431 #[must_use]
433 pub fn get_statistics(&self) -> SystemStatistics {
434 let active_workflows = self.workflows.len();
435 let total_workflows = self.history.len() + active_workflows;
436
437 let completed_workflows = self
438 .history
439 .iter()
440 .filter(|entry| matches!(entry.value().state, WorkflowState::Completed))
441 .count();
442
443 let failed_workflows = self
444 .history
445 .iter()
446 .filter(|entry| matches!(entry.value().state, WorkflowState::Failed))
447 .count();
448
449 let total_tasks_completed: usize = (self
450 .workflows
451 .iter()
452 .map(|entry| entry.value().completed_tasks.load(Ordering::Relaxed))
453 .sum::<u64>()
454 + self
455 .history
456 .iter()
457 .map(|entry| entry.value().completed_tasks.load(Ordering::Relaxed))
458 .sum::<u64>()) as usize;
459
460 let total_tasks_failed: usize = (self
461 .workflows
462 .iter()
463 .map(|entry| entry.value().failed_tasks.load(Ordering::Relaxed))
464 .sum::<u64>()
465 + self
466 .history
467 .iter()
468 .map(|entry| entry.value().failed_tasks.load(Ordering::Relaxed))
469 .sum::<u64>()) as usize;
470
471 SystemStatistics {
472 active_workflows,
473 total_workflows,
474 completed_workflows,
475 failed_workflows,
476 total_tasks_completed,
477 total_tasks_failed,
478 }
479 }
480}
481
482impl Default for MonitoringService {
483 fn default() -> Self {
484 Self::new()
485 }
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct SystemStatistics {
491 pub active_workflows: usize,
493 pub total_workflows: usize,
495 pub completed_workflows: usize,
497 pub failed_workflows: usize,
499 pub total_tasks_completed: usize,
501 pub total_tasks_failed: usize,
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_task_metrics_creation() {
511 let task_id = TaskId::new();
512 let metrics = TaskMetrics::new(task_id, "test-task".to_string());
513 assert_eq!(metrics.task_id, task_id);
514 assert_eq!(metrics.task_name, "test-task");
515 assert_eq!(metrics.state, TaskState::Pending);
516 }
517
518 #[test]
519 fn test_task_metrics_lifecycle() {
520 let task_id = TaskId::new();
521 let mut metrics = TaskMetrics::new(task_id, "test-task".to_string());
522
523 metrics.mark_started();
524 assert_eq!(metrics.state, TaskState::Running);
525 assert!(metrics.start_time.is_some());
526
527 metrics.mark_completed();
528 assert_eq!(metrics.state, TaskState::Completed);
529 assert!(metrics.end_time.is_some());
530 assert!(metrics.duration.is_some());
531 }
532
533 #[test]
534 fn test_task_metrics_failure() {
535 let task_id = TaskId::new();
536 let mut metrics = TaskMetrics::new(task_id, "test-task".to_string());
537
538 metrics.mark_started();
539 metrics.mark_failed("Test error".to_string());
540
541 assert_eq!(metrics.state, TaskState::Failed);
542 assert_eq!(metrics.error, Some("Test error".to_string()));
543 }
544
545 #[test]
546 fn test_workflow_metrics_creation() {
547 let workflow_id = WorkflowId::new();
548 let metrics = WorkflowMetrics::new(workflow_id, "test-workflow".to_string(), 5);
549
550 assert_eq!(metrics.workflow_id, workflow_id);
551 assert_eq!(metrics.total_tasks, 5);
552 assert_eq!(metrics.completed_tasks_count(), 0);
553 }
554
555 #[test]
556 fn test_workflow_metrics_progress() {
557 let workflow_id = WorkflowId::new();
558 let metrics = WorkflowMetrics::new(workflow_id, "test-workflow".to_string(), 10);
559
560 assert_eq!(metrics.progress_percentage(), 0.0);
561
562 metrics.completed_tasks.store(5, Ordering::Relaxed);
563 assert_eq!(metrics.progress_percentage(), 50.0);
564
565 metrics.completed_tasks.store(10, Ordering::Relaxed);
566 assert_eq!(metrics.progress_percentage(), 100.0);
567 }
568
569 #[test]
570 fn test_monitoring_service_creation() {
571 let service = MonitoringService::new();
572 assert_eq!(service.get_active_workflows().len(), 0);
573 }
574
575 #[test]
576 fn test_monitoring_service_workflow_tracking() {
577 let service = MonitoringService::new();
578 let workflow_id = WorkflowId::new();
579
580 service.start_workflow(workflow_id, "test-workflow".to_string(), 3);
581
582 let metrics = service.get_workflow_metrics(&workflow_id);
583 assert!(metrics.is_some());
584 assert_eq!(
585 metrics.expect("should succeed in test").state,
586 WorkflowState::Running
587 );
588 }
589
590 #[test]
591 fn test_monitoring_service_task_updates() {
592 let service = MonitoringService::new();
593 let workflow_id = WorkflowId::new();
594 let task_id = TaskId::new();
595
596 service.start_workflow(workflow_id, "test-workflow".to_string(), 1);
597
598 service.update_task(
599 workflow_id,
600 task_id,
601 "task-1".to_string(),
602 TaskState::Running,
603 None,
604 );
605
606 let metrics = service
607 .get_workflow_metrics(&workflow_id)
608 .expect("should succeed in test");
609 assert_eq!(metrics.running_tasks_count(), 1);
610
611 service.update_task(
612 workflow_id,
613 task_id,
614 "task-1".to_string(),
615 TaskState::Completed,
616 None,
617 );
618
619 let metrics = service
620 .get_workflow_metrics(&workflow_id)
621 .expect("should succeed in test");
622 assert_eq!(metrics.completed_tasks_count(), 1);
623 assert_eq!(metrics.running_tasks_count(), 0);
624 }
625
626 #[test]
627 fn test_monitoring_service_completion() {
628 let service = MonitoringService::new();
629 let workflow_id = WorkflowId::new();
630
631 service.start_workflow(workflow_id, "test-workflow".to_string(), 1);
632 assert_eq!(service.get_active_workflows().len(), 1);
633
634 service.complete_workflow(workflow_id, true);
635 assert_eq!(service.get_active_workflows().len(), 0);
636
637 let history = service.get_history(None);
638 assert_eq!(history.len(), 1);
639 assert_eq!(history[0].state, WorkflowState::Completed);
640 }
641
642 #[test]
643 fn test_statistics() {
644 let service = MonitoringService::new();
645
646 let wf1 = WorkflowId::new();
647 let wf2 = WorkflowId::new();
648
649 service.start_workflow(wf1, "workflow1".to_string(), 2);
650 service.start_workflow(wf2, "workflow2".to_string(), 3);
651
652 service.complete_workflow(wf1, true);
653 service.complete_workflow(wf2, false);
654
655 let stats = service.get_statistics();
656 assert_eq!(stats.active_workflows, 0);
657 assert_eq!(stats.total_workflows, 2);
658 assert_eq!(stats.completed_workflows, 1);
659 assert_eq!(stats.failed_workflows, 1);
660 }
661}