1use aspect_core::{Aspect, AspectError, ProceedingJoinPoint};
4use parking_lot::Mutex;
5use std::any::Any;
6use std::collections::HashMap;
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9
10#[derive(Clone)]
29pub struct MetricsAspect {
30 counters: Arc<Mutex<HashMap<String, u64>>>,
31 histograms: Arc<Mutex<HashMap<String, Vec<Duration>>>>,
32}
33
34impl MetricsAspect {
35 pub fn new() -> Self {
37 Self {
38 counters: Arc::new(Mutex::new(HashMap::new())),
39 histograms: Arc::new(Mutex::new(HashMap::new())),
40 }
41 }
42
43 pub fn get_count(&self, function_name: &str) -> u64 {
45 self.counters.lock().get(function_name).copied().unwrap_or(0)
46 }
47
48 pub fn get_histogram(&self, function_name: &str) -> Vec<Duration> {
50 self.histograms
51 .lock()
52 .get(function_name)
53 .cloned()
54 .unwrap_or_default()
55 }
56
57 pub fn print(&self) {
59 println!("\n=== Metrics ===");
60
61 let counters = self.counters.lock();
62 println!("\nCall Counts:");
63 for (name, count) in counters.iter() {
64 println!(" {}: {}", name, count);
65 }
66
67 drop(counters);
68
69 let histograms = self.histograms.lock();
70 println!("\nDuration Histograms:");
71 for (name, durations) in histograms.iter() {
72 if !durations.is_empty() {
73 let total: Duration = durations.iter().sum();
74 let avg = total / durations.len() as u32;
75 println!(" {}: avg={:?}, count={}", name, avg, durations.len());
76 }
77 }
78 println!();
79 }
80
81 pub fn clear(&self) {
83 self.counters.lock().clear();
84 self.histograms.lock().clear();
85 }
86}
87
88impl Default for MetricsAspect {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Aspect for MetricsAspect {
95 fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
96 let function_name = pjp.context().function_name.to_string();
97 let start = Instant::now();
98
99 *self.counters.lock().entry(function_name.clone()).or_insert(0) += 1;
101
102 let result = pjp.proceed();
103
104 let duration = start.elapsed();
106 self.histograms
107 .lock()
108 .entry(function_name)
109 .or_insert_with(Vec::new)
110 .push(duration);
111
112 result
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_metrics_aspect() {
122 let metrics = MetricsAspect::new();
123
124 assert_eq!(metrics.get_count("test"), 0);
125 assert!(metrics.get_histogram("test").is_empty());
126 }
127
128 #[test]
129 fn test_metrics_increment() {
130 let metrics = MetricsAspect::new();
131
132 *metrics.counters.lock().entry("test".to_string()).or_insert(0) += 1;
133 *metrics.counters.lock().entry("test".to_string()).or_insert(0) += 1;
134
135 assert_eq!(metrics.get_count("test"), 2);
136 }
137}