1use std::collections::HashMap;
4use std::sync::{Arc, Mutex};
5use std::time::Instant;
6use tracing::{debug, info};
7
8#[derive(Debug, Clone)]
10pub struct ExecutionMetrics {
11 pub agent_id: String,
13 pub start_time: Instant,
15 pub end_time: Option<Instant>,
17 pub findings_count: usize,
19 pub severity_distribution: HashMap<String, usize>,
21 pub success: bool,
23 pub error: Option<String>,
25}
26
27impl ExecutionMetrics {
28 pub fn new(agent_id: String) -> Self {
30 debug!(agent_id = %agent_id, "Creating execution metrics");
31 Self {
32 agent_id,
33 start_time: Instant::now(),
34 end_time: None,
35 findings_count: 0,
36 severity_distribution: HashMap::new(),
37 success: false,
38 error: None,
39 }
40 }
41
42 pub fn complete(&mut self, success: bool, error: Option<String>) {
44 self.end_time = Some(Instant::now());
45 self.success = success;
46 self.error = error;
47
48 let duration_ms = self.duration_ms();
49 debug!(
50 agent_id = %self.agent_id,
51 duration_ms = duration_ms,
52 success = success,
53 "Execution metrics completed"
54 );
55 }
56
57 pub fn duration_ms(&self) -> u64 {
59 let end = self.end_time.unwrap_or_else(Instant::now);
60 end.duration_since(self.start_time).as_millis() as u64
61 }
62
63 pub fn record_findings(&mut self, count: usize, severity_dist: HashMap<String, usize>) {
65 self.findings_count = count;
66 self.severity_distribution = severity_dist;
67 debug!(
68 agent_id = %self.agent_id,
69 findings_count = count,
70 "Findings recorded"
71 );
72 }
73}
74
75pub struct MetricsCollector {
77 executions: Arc<Mutex<Vec<ExecutionMetrics>>>,
78 agent_stats: Arc<Mutex<HashMap<String, AgentStats>>>,
79}
80
81#[derive(Debug, Clone)]
83pub struct AgentStats {
84 pub agent_id: String,
86 pub total_executions: u64,
88 pub successful_executions: u64,
90 pub failed_executions: u64,
92 pub total_findings: u64,
94 pub avg_execution_time_ms: f64,
96 pub min_execution_time_ms: u64,
98 pub max_execution_time_ms: u64,
100}
101
102impl MetricsCollector {
103 pub fn new() -> Self {
105 info!("Creating metrics collector");
106 Self {
107 executions: Arc::new(Mutex::new(Vec::new())),
108 agent_stats: Arc::new(Mutex::new(HashMap::new())),
109 }
110 }
111
112 pub fn record_execution(&self, metrics: ExecutionMetrics) {
114 let agent_id = metrics.agent_id.clone();
115 let duration_ms = metrics.duration_ms();
116 let findings_count = metrics.findings_count;
117 let success = metrics.success;
118
119 {
121 let mut executions = self.executions.lock().unwrap();
122 executions.push(metrics);
123 }
124
125 {
127 let mut stats = self.agent_stats.lock().unwrap();
128 let agent_stat = stats.entry(agent_id.clone()).or_insert_with(|| AgentStats {
129 agent_id: agent_id.clone(),
130 total_executions: 0,
131 successful_executions: 0,
132 failed_executions: 0,
133 total_findings: 0,
134 avg_execution_time_ms: 0.0,
135 min_execution_time_ms: u64::MAX,
136 max_execution_time_ms: 0,
137 });
138
139 agent_stat.total_executions += 1;
140 if success {
141 agent_stat.successful_executions += 1;
142 } else {
143 agent_stat.failed_executions += 1;
144 }
145 agent_stat.total_findings += findings_count as u64;
146
147 let total_time = agent_stat.avg_execution_time_ms
149 * (agent_stat.total_executions - 1) as f64
150 + duration_ms as f64;
151 agent_stat.avg_execution_time_ms = total_time / agent_stat.total_executions as f64;
152
153 agent_stat.min_execution_time_ms = agent_stat.min_execution_time_ms.min(duration_ms);
155 agent_stat.max_execution_time_ms = agent_stat.max_execution_time_ms.max(duration_ms);
156
157 debug!(
158 agent_id = %agent_id,
159 total_executions = agent_stat.total_executions,
160 avg_execution_time_ms = agent_stat.avg_execution_time_ms,
161 "Agent statistics updated"
162 );
163 }
164 }
165
166 pub fn get_agent_stats(&self, agent_id: &str) -> Option<AgentStats> {
168 let stats = self.agent_stats.lock().unwrap();
169 stats.get(agent_id).cloned()
170 }
171
172 pub fn all_agent_stats(&self) -> Vec<AgentStats> {
174 let stats = self.agent_stats.lock().unwrap();
175 stats.values().cloned().collect()
176 }
177
178 pub fn all_executions(&self) -> Vec<ExecutionMetrics> {
180 let executions = self.executions.lock().unwrap();
181 executions.clone()
182 }
183
184 pub fn get_agent_executions(&self, agent_id: &str) -> Vec<ExecutionMetrics> {
186 let executions = self.executions.lock().unwrap();
187 executions
188 .iter()
189 .filter(|e| e.agent_id == agent_id)
190 .cloned()
191 .collect()
192 }
193
194 pub fn clear(&self) {
196 let mut executions = self.executions.lock().unwrap();
197 let mut stats = self.agent_stats.lock().unwrap();
198 executions.clear();
199 stats.clear();
200 info!("Metrics collector cleared");
201 }
202
203 pub fn total_execution_count(&self) -> usize {
205 let executions = self.executions.lock().unwrap();
206 executions.len()
207 }
208
209 pub fn total_findings_count(&self) -> u64 {
211 let stats = self.agent_stats.lock().unwrap();
212 stats.values().map(|s| s.total_findings).sum()
213 }
214
215 pub fn average_execution_time_ms(&self) -> f64 {
217 let stats = self.agent_stats.lock().unwrap();
218 if stats.is_empty() {
219 return 0.0;
220 }
221
222 let total_time: f64 = stats.values().map(|s| s.avg_execution_time_ms).sum();
223 total_time / stats.len() as f64
224 }
225}
226
227impl Default for MetricsCollector {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_execution_metrics_new() {
239 let metrics = ExecutionMetrics::new("test-agent".to_string());
240 assert_eq!(metrics.agent_id, "test-agent");
241 assert!(metrics.end_time.is_none());
242 assert_eq!(metrics.findings_count, 0);
243 assert!(!metrics.success);
244 assert!(metrics.error.is_none());
245 }
246
247 #[test]
248 fn test_execution_metrics_complete_success() {
249 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
250 metrics.complete(true, None);
251
252 assert!(metrics.end_time.is_some());
253 assert!(metrics.success);
254 assert!(metrics.error.is_none());
255 let _ = metrics.duration_ms();
257 }
258
259 #[test]
260 fn test_execution_metrics_complete_failure() {
261 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
262 metrics.complete(false, Some("Test error".to_string()));
263
264 assert!(metrics.end_time.is_some());
265 assert!(!metrics.success);
266 assert_eq!(metrics.error, Some("Test error".to_string()));
267 }
268
269 #[test]
270 fn test_execution_metrics_record_findings() {
271 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
272 let mut severity_dist = HashMap::new();
273 severity_dist.insert("Critical".to_string(), 2);
274 severity_dist.insert("Warning".to_string(), 5);
275
276 metrics.record_findings(7, severity_dist.clone());
277
278 assert_eq!(metrics.findings_count, 7);
279 assert_eq!(metrics.severity_distribution, severity_dist);
280 }
281
282 #[test]
283 fn test_metrics_collector_new() {
284 let collector = MetricsCollector::new();
285 assert_eq!(collector.total_execution_count(), 0);
286 assert_eq!(collector.total_findings_count(), 0);
287 }
288
289 #[test]
290 fn test_metrics_collector_record_execution() {
291 let collector = MetricsCollector::new();
292 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
293 metrics.record_findings(5, HashMap::new());
294 metrics.complete(true, None);
295
296 collector.record_execution(metrics);
297
298 assert_eq!(collector.total_execution_count(), 1);
299 assert_eq!(collector.total_findings_count(), 5);
300 }
301
302 #[test]
303 fn test_metrics_collector_get_agent_stats() {
304 let collector = MetricsCollector::new();
305 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
306 metrics.record_findings(3, HashMap::new());
307 metrics.complete(true, None);
308
309 collector.record_execution(metrics);
310
311 let stats = collector.get_agent_stats("test-agent");
312 assert!(stats.is_some());
313
314 let stat = stats.unwrap();
315 assert_eq!(stat.agent_id, "test-agent");
316 assert_eq!(stat.total_executions, 1);
317 assert_eq!(stat.successful_executions, 1);
318 assert_eq!(stat.failed_executions, 0);
319 assert_eq!(stat.total_findings, 3);
320 }
321
322 #[test]
323 fn test_metrics_collector_multiple_executions() {
324 let collector = MetricsCollector::new();
325
326 for i in 0..3 {
327 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
328 metrics.record_findings(i + 1, HashMap::new());
329 metrics.complete(true, None);
330 collector.record_execution(metrics);
331 }
332
333 assert_eq!(collector.total_execution_count(), 3);
334 assert_eq!(collector.total_findings_count(), 6); let stats = collector.get_agent_stats("test-agent").unwrap();
337 assert_eq!(stats.total_executions, 3);
338 assert_eq!(stats.successful_executions, 3);
339 assert_eq!(stats.total_findings, 6);
340 }
341
342 #[test]
343 fn test_metrics_collector_mixed_success_failure() {
344 let collector = MetricsCollector::new();
345
346 let mut metrics1 = ExecutionMetrics::new("test-agent".to_string());
348 metrics1.record_findings(5, HashMap::new());
349 metrics1.complete(true, None);
350 collector.record_execution(metrics1);
351
352 let mut metrics2 = ExecutionMetrics::new("test-agent".to_string());
354 metrics2.record_findings(0, HashMap::new());
355 metrics2.complete(false, Some("Error".to_string()));
356 collector.record_execution(metrics2);
357
358 let stats = collector.get_agent_stats("test-agent").unwrap();
359 assert_eq!(stats.total_executions, 2);
360 assert_eq!(stats.successful_executions, 1);
361 assert_eq!(stats.failed_executions, 1);
362 assert_eq!(stats.total_findings, 5);
363 }
364
365 #[test]
366 fn test_metrics_collector_get_agent_executions() {
367 let collector = MetricsCollector::new();
368
369 let mut metrics1 = ExecutionMetrics::new("agent-1".to_string());
370 metrics1.complete(true, None);
371 collector.record_execution(metrics1);
372
373 let mut metrics2 = ExecutionMetrics::new("agent-2".to_string());
374 metrics2.complete(true, None);
375 collector.record_execution(metrics2);
376
377 let mut metrics3 = ExecutionMetrics::new("agent-1".to_string());
378 metrics3.complete(true, None);
379 collector.record_execution(metrics3);
380
381 let agent1_executions = collector.get_agent_executions("agent-1");
382 assert_eq!(agent1_executions.len(), 2);
383
384 let agent2_executions = collector.get_agent_executions("agent-2");
385 assert_eq!(agent2_executions.len(), 1);
386 }
387
388 #[test]
389 fn test_metrics_collector_clear() {
390 let collector = MetricsCollector::new();
391
392 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
393 metrics.complete(true, None);
394 collector.record_execution(metrics);
395
396 assert_eq!(collector.total_execution_count(), 1);
397
398 collector.clear();
399
400 assert_eq!(collector.total_execution_count(), 0);
401 assert_eq!(collector.total_findings_count(), 0);
402 }
403
404 #[test]
405 fn test_metrics_collector_average_execution_time() {
406 let collector = MetricsCollector::new();
407
408 for _ in 0..3 {
409 let mut metrics = ExecutionMetrics::new("test-agent".to_string());
410 metrics.complete(true, None);
411 collector.record_execution(metrics);
412 }
413
414 let avg = collector.average_execution_time_ms();
415 assert!(avg >= 0.0);
416 }
417
418 #[test]
419 fn test_metrics_collector_all_agent_stats() {
420 let collector = MetricsCollector::new();
421
422 let mut metrics1 = ExecutionMetrics::new("agent-1".to_string());
423 metrics1.complete(true, None);
424 collector.record_execution(metrics1);
425
426 let mut metrics2 = ExecutionMetrics::new("agent-2".to_string());
427 metrics2.complete(true, None);
428 collector.record_execution(metrics2);
429
430 let all_stats = collector.all_agent_stats();
431 assert_eq!(all_stats.len(), 2);
432 }
433
434 #[test]
435 fn test_metrics_collector_all_executions() {
436 let collector = MetricsCollector::new();
437
438 let mut metrics1 = ExecutionMetrics::new("agent-1".to_string());
439 metrics1.complete(true, None);
440 collector.record_execution(metrics1);
441
442 let mut metrics2 = ExecutionMetrics::new("agent-2".to_string());
443 metrics2.complete(true, None);
444 collector.record_execution(metrics2);
445
446 let all_executions = collector.all_executions();
447 assert_eq!(all_executions.len(), 2);
448 }
449
450 #[test]
451 fn test_agent_stats_min_max_execution_time() {
452 let collector = MetricsCollector::new();
453
454 let mut metrics1 = ExecutionMetrics::new("test-agent".to_string());
456 metrics1.complete(true, None);
457 let duration1 = metrics1.duration_ms();
458 collector.record_execution(metrics1);
459
460 let mut metrics2 = ExecutionMetrics::new("test-agent".to_string());
462 metrics2.complete(true, None);
463 let duration2 = metrics2.duration_ms();
464 collector.record_execution(metrics2);
465
466 let stats = collector.get_agent_stats("test-agent").unwrap();
467 assert!(stats.min_execution_time_ms <= stats.max_execution_time_ms);
468 assert!(stats.min_execution_time_ms <= duration1.max(duration2));
469 assert!(stats.max_execution_time_ms >= duration1.min(duration2));
470 }
471}