1use crate::task::{TaskId, TaskState};
4use crate::workflow::{WorkflowId, WorkflowState};
5use chrono::{DateTime, Utc};
6use dashmap::DashMap;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::Arc;
10use std::time::Duration;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TaskMetrics {
15 pub task_id: TaskId,
17 pub task_name: String,
19 pub state: TaskState,
21 pub start_time: Option<DateTime<Utc>>,
23 pub end_time: Option<DateTime<Utc>>,
25 pub duration: Option<Duration>,
27 pub retry_count: u32,
29 pub error: Option<String>,
31}
32
33impl TaskMetrics {
34 #[must_use]
36 pub fn new(task_id: TaskId, task_name: String) -> Self {
37 Self {
38 task_id,
39 task_name,
40 state: TaskState::Pending,
41 start_time: None,
42 end_time: None,
43 duration: None,
44 retry_count: 0,
45 error: None,
46 }
47 }
48
49 pub fn mark_started(&mut self) {
51 self.state = TaskState::Running;
52 self.start_time = Some(Utc::now());
53 }
54
55 pub fn mark_completed(&mut self) {
57 self.state = TaskState::Completed;
58 self.end_time = Some(Utc::now());
59 if let Some(start) = self.start_time {
60 self.duration = Some(Duration::from_millis(
61 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
62 ));
63 }
64 }
65
66 pub fn mark_failed(&mut self, error: String) {
68 self.state = TaskState::Failed;
69 self.end_time = Some(Utc::now());
70 self.error = Some(error);
71 if let Some(start) = self.start_time {
72 self.duration = Some(Duration::from_millis(
73 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
74 ));
75 }
76 }
77
78 pub fn increment_retry(&mut self) {
80 self.retry_count += 1;
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct WorkflowMetrics {
87 pub workflow_id: WorkflowId,
89 pub workflow_name: String,
91 pub state: WorkflowState,
93 pub start_time: Option<DateTime<Utc>>,
95 pub end_time: Option<DateTime<Utc>>,
97 pub duration: Option<Duration>,
99 pub tasks: HashMap<TaskId, TaskMetrics>,
101 pub total_tasks: usize,
103 pub completed_tasks: usize,
105 pub failed_tasks: usize,
107 pub running_tasks: usize,
109}
110
111impl WorkflowMetrics {
112 #[must_use]
114 pub fn new(workflow_id: WorkflowId, workflow_name: String, total_tasks: usize) -> Self {
115 Self {
116 workflow_id,
117 workflow_name,
118 state: WorkflowState::Created,
119 start_time: None,
120 end_time: None,
121 duration: None,
122 tasks: HashMap::new(),
123 total_tasks,
124 completed_tasks: 0,
125 failed_tasks: 0,
126 running_tasks: 0,
127 }
128 }
129
130 pub fn mark_started(&mut self) {
132 self.state = WorkflowState::Running;
133 self.start_time = Some(Utc::now());
134 }
135
136 pub fn mark_completed(&mut self) {
138 self.state = WorkflowState::Completed;
139 self.end_time = Some(Utc::now());
140 if let Some(start) = self.start_time {
141 self.duration = Some(Duration::from_millis(
142 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
143 ));
144 }
145 }
146
147 pub fn mark_failed(&mut self) {
149 self.state = WorkflowState::Failed;
150 self.end_time = Some(Utc::now());
151 if let Some(start) = self.start_time {
152 self.duration = Some(Duration::from_millis(
153 u64::try_from((Utc::now() - start).num_milliseconds()).unwrap_or(0),
154 ));
155 }
156 }
157
158 pub fn update_task(&mut self, task_metrics: TaskMetrics) {
160 if let Some(old_metrics) = self.tasks.get(&task_metrics.task_id) {
162 self.update_counters(old_metrics.state, false);
163 }
164 self.update_counters(task_metrics.state, true);
165
166 self.tasks.insert(task_metrics.task_id, task_metrics);
167 }
168
169 fn update_counters(&mut self, state: TaskState, increment: bool) {
170 let delta = if increment { 1_isize } else { -1_isize };
171
172 match state {
173 TaskState::Completed => {
174 self.completed_tasks = (self.completed_tasks as isize + delta).max(0) as usize;
175 }
176 TaskState::Failed => {
177 self.failed_tasks = (self.failed_tasks as isize + delta).max(0) as usize;
178 }
179 TaskState::Running | TaskState::Retrying => {
180 self.running_tasks = (self.running_tasks as isize + delta).max(0) as usize;
181 }
182 _ => {}
183 }
184 }
185
186 #[must_use]
188 pub fn progress_percentage(&self) -> f64 {
189 if self.total_tasks == 0 {
190 return 100.0;
191 }
192 (self.completed_tasks as f64 / self.total_tasks as f64) * 100.0
193 }
194
195 #[must_use]
197 pub fn average_task_duration(&self) -> Option<Duration> {
198 let durations: Vec<_> = self.tasks.values().filter_map(|m| m.duration).collect();
199
200 if durations.is_empty() {
201 return None;
202 }
203
204 let total_ms: u64 = durations.iter().map(|d| d.as_millis() as u64).sum();
205 Some(Duration::from_millis(total_ms / durations.len() as u64))
206 }
207
208 #[must_use]
210 pub fn throughput(&self) -> f64 {
211 if let (Some(start), Some(end)) = (self.start_time, self.end_time) {
212 let duration_secs = (end - start).num_seconds().max(1);
213 self.completed_tasks as f64 / duration_secs as f64
214 } else if let Some(start) = self.start_time {
215 let duration_secs = (Utc::now() - start).num_seconds().max(1);
216 self.completed_tasks as f64 / duration_secs as f64
217 } else {
218 0.0
219 }
220 }
221}
222
223pub struct MonitoringService {
225 workflows: Arc<DashMap<WorkflowId, WorkflowMetrics>>,
227 history: Arc<DashMap<WorkflowId, WorkflowMetrics>>,
229}
230
231impl MonitoringService {
232 #[must_use]
234 pub fn new() -> Self {
235 Self {
236 workflows: Arc::new(DashMap::new()),
237 history: Arc::new(DashMap::new()),
238 }
239 }
240
241 pub fn start_workflow(
243 &self,
244 workflow_id: WorkflowId,
245 workflow_name: String,
246 total_tasks: usize,
247 ) {
248 let mut metrics = WorkflowMetrics::new(workflow_id, workflow_name, total_tasks);
249 metrics.mark_started();
250 self.workflows.insert(workflow_id, metrics);
251 }
252
253 pub fn update_task(
255 &self,
256 workflow_id: WorkflowId,
257 task_id: TaskId,
258 task_name: String,
259 state: TaskState,
260 error: Option<String>,
261 ) {
262 if let Some(mut workflow_metrics) = self.workflows.get_mut(&workflow_id) {
263 let mut task_metrics = workflow_metrics
264 .tasks
265 .get(&task_id)
266 .cloned()
267 .unwrap_or_else(|| TaskMetrics::new(task_id, task_name.clone()));
268
269 match state {
270 TaskState::Running => task_metrics.mark_started(),
271 TaskState::Completed => task_metrics.mark_completed(),
272 TaskState::Failed => {
273 if let Some(err) = error {
274 task_metrics.mark_failed(err);
275 } else {
276 task_metrics.mark_failed("Unknown error".to_string());
277 }
278 }
279 TaskState::Retrying => task_metrics.increment_retry(),
280 _ => task_metrics.state = state,
281 }
282
283 workflow_metrics.update_task(task_metrics);
284 }
285 }
286
287 pub fn complete_workflow(&self, workflow_id: WorkflowId, success: bool) {
289 if let Some((_, mut metrics)) = self.workflows.remove(&workflow_id) {
290 if success {
291 metrics.mark_completed();
292 } else {
293 metrics.mark_failed();
294 }
295 self.history.insert(workflow_id, metrics);
296 }
297 }
298
299 #[must_use]
301 pub fn get_workflow_metrics(&self, workflow_id: &WorkflowId) -> Option<WorkflowMetrics> {
302 self.workflows
303 .get(workflow_id)
304 .map(|m| m.clone())
305 .or_else(|| self.history.get(workflow_id).map(|m| m.clone()))
306 }
307
308 #[must_use]
310 pub fn get_active_workflows(&self) -> Vec<WorkflowMetrics> {
311 self.workflows
312 .iter()
313 .map(|entry| entry.value().clone())
314 .collect()
315 }
316
317 #[must_use]
319 pub fn get_history(&self, limit: Option<usize>) -> Vec<WorkflowMetrics> {
320 let mut history: Vec<_> = self
321 .history
322 .iter()
323 .map(|entry| entry.value().clone())
324 .collect();
325
326 history.sort_by(|a, b| b.start_time.cmp(&a.start_time));
328
329 if let Some(limit) = limit {
330 history.truncate(limit);
331 }
332
333 history
334 }
335
336 pub fn clear_history(&self, older_than: DateTime<Utc>) {
338 self.history.retain(|_, metrics| {
339 if let Some(end_time) = metrics.end_time {
340 end_time > older_than
341 } else {
342 true
343 }
344 });
345 }
346
347 #[must_use]
349 pub fn get_statistics(&self) -> SystemStatistics {
350 let active_workflows = self.workflows.len();
351 let total_workflows = self.history.len() + active_workflows;
352
353 let completed_workflows = self
354 .history
355 .iter()
356 .filter(|entry| matches!(entry.value().state, WorkflowState::Completed))
357 .count();
358
359 let failed_workflows = self
360 .history
361 .iter()
362 .filter(|entry| matches!(entry.value().state, WorkflowState::Failed))
363 .count();
364
365 let total_tasks_completed: usize = self
366 .workflows
367 .iter()
368 .map(|entry| entry.value().completed_tasks)
369 .sum::<usize>()
370 + self
371 .history
372 .iter()
373 .map(|entry| entry.value().completed_tasks)
374 .sum::<usize>();
375
376 let total_tasks_failed: usize = self
377 .workflows
378 .iter()
379 .map(|entry| entry.value().failed_tasks)
380 .sum::<usize>()
381 + self
382 .history
383 .iter()
384 .map(|entry| entry.value().failed_tasks)
385 .sum::<usize>();
386
387 SystemStatistics {
388 active_workflows,
389 total_workflows,
390 completed_workflows,
391 failed_workflows,
392 total_tasks_completed,
393 total_tasks_failed,
394 }
395 }
396}
397
398impl Default for MonitoringService {
399 fn default() -> Self {
400 Self::new()
401 }
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct SystemStatistics {
407 pub active_workflows: usize,
409 pub total_workflows: usize,
411 pub completed_workflows: usize,
413 pub failed_workflows: usize,
415 pub total_tasks_completed: usize,
417 pub total_tasks_failed: usize,
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_task_metrics_creation() {
427 let task_id = TaskId::new();
428 let metrics = TaskMetrics::new(task_id, "test-task".to_string());
429 assert_eq!(metrics.task_id, task_id);
430 assert_eq!(metrics.task_name, "test-task");
431 assert_eq!(metrics.state, TaskState::Pending);
432 }
433
434 #[test]
435 fn test_task_metrics_lifecycle() {
436 let task_id = TaskId::new();
437 let mut metrics = TaskMetrics::new(task_id, "test-task".to_string());
438
439 metrics.mark_started();
440 assert_eq!(metrics.state, TaskState::Running);
441 assert!(metrics.start_time.is_some());
442
443 metrics.mark_completed();
444 assert_eq!(metrics.state, TaskState::Completed);
445 assert!(metrics.end_time.is_some());
446 assert!(metrics.duration.is_some());
447 }
448
449 #[test]
450 fn test_task_metrics_failure() {
451 let task_id = TaskId::new();
452 let mut metrics = TaskMetrics::new(task_id, "test-task".to_string());
453
454 metrics.mark_started();
455 metrics.mark_failed("Test error".to_string());
456
457 assert_eq!(metrics.state, TaskState::Failed);
458 assert_eq!(metrics.error, Some("Test error".to_string()));
459 }
460
461 #[test]
462 fn test_workflow_metrics_creation() {
463 let workflow_id = WorkflowId::new();
464 let metrics = WorkflowMetrics::new(workflow_id, "test-workflow".to_string(), 5);
465
466 assert_eq!(metrics.workflow_id, workflow_id);
467 assert_eq!(metrics.total_tasks, 5);
468 assert_eq!(metrics.completed_tasks, 0);
469 }
470
471 #[test]
472 fn test_workflow_metrics_progress() {
473 let workflow_id = WorkflowId::new();
474 let mut metrics = WorkflowMetrics::new(workflow_id, "test-workflow".to_string(), 10);
475
476 assert_eq!(metrics.progress_percentage(), 0.0);
477
478 metrics.completed_tasks = 5;
479 assert_eq!(metrics.progress_percentage(), 50.0);
480
481 metrics.completed_tasks = 10;
482 assert_eq!(metrics.progress_percentage(), 100.0);
483 }
484
485 #[test]
486 fn test_monitoring_service_creation() {
487 let service = MonitoringService::new();
488 assert_eq!(service.get_active_workflows().len(), 0);
489 }
490
491 #[test]
492 fn test_monitoring_service_workflow_tracking() {
493 let service = MonitoringService::new();
494 let workflow_id = WorkflowId::new();
495
496 service.start_workflow(workflow_id, "test-workflow".to_string(), 3);
497
498 let metrics = service.get_workflow_metrics(&workflow_id);
499 assert!(metrics.is_some());
500 assert_eq!(
501 metrics.expect("should succeed in test").state,
502 WorkflowState::Running
503 );
504 }
505
506 #[test]
507 fn test_monitoring_service_task_updates() {
508 let service = MonitoringService::new();
509 let workflow_id = WorkflowId::new();
510 let task_id = TaskId::new();
511
512 service.start_workflow(workflow_id, "test-workflow".to_string(), 1);
513
514 service.update_task(
515 workflow_id,
516 task_id,
517 "task-1".to_string(),
518 TaskState::Running,
519 None,
520 );
521
522 let metrics = service
523 .get_workflow_metrics(&workflow_id)
524 .expect("should succeed in test");
525 assert_eq!(metrics.running_tasks, 1);
526
527 service.update_task(
528 workflow_id,
529 task_id,
530 "task-1".to_string(),
531 TaskState::Completed,
532 None,
533 );
534
535 let metrics = service
536 .get_workflow_metrics(&workflow_id)
537 .expect("should succeed in test");
538 assert_eq!(metrics.completed_tasks, 1);
539 assert_eq!(metrics.running_tasks, 0);
540 }
541
542 #[test]
543 fn test_monitoring_service_completion() {
544 let service = MonitoringService::new();
545 let workflow_id = WorkflowId::new();
546
547 service.start_workflow(workflow_id, "test-workflow".to_string(), 1);
548 assert_eq!(service.get_active_workflows().len(), 1);
549
550 service.complete_workflow(workflow_id, true);
551 assert_eq!(service.get_active_workflows().len(), 0);
552
553 let history = service.get_history(None);
554 assert_eq!(history.len(), 1);
555 assert_eq!(history[0].state, WorkflowState::Completed);
556 }
557
558 #[test]
559 fn test_statistics() {
560 let service = MonitoringService::new();
561
562 let wf1 = WorkflowId::new();
563 let wf2 = WorkflowId::new();
564
565 service.start_workflow(wf1, "workflow1".to_string(), 2);
566 service.start_workflow(wf2, "workflow2".to_string(), 3);
567
568 service.complete_workflow(wf1, true);
569 service.complete_workflow(wf2, false);
570
571 let stats = service.get_statistics();
572 assert_eq!(stats.active_workflows, 0);
573 assert_eq!(stats.total_workflows, 2);
574 assert_eq!(stats.completed_workflows, 1);
575 assert_eq!(stats.failed_workflows, 1);
576 }
577}