Skip to main content

aspect_std/
metrics.rs

1//! Metrics collection aspect (counters, gauges, histograms).
2
3use 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/// Metrics aspect for collecting function call statistics.
11///
12/// # Example
13///
14/// ```rust,ignore
15/// use aspect_std::MetricsAspect;
16/// use aspect_macros::aspect;
17///
18/// let metrics = MetricsAspect::new();
19///
20/// #[aspect(metrics.clone())]
21/// fn api_handler() -> Result<(), String> {
22///     Ok(())
23/// }
24///
25/// // Print metrics
26/// metrics.print();
27/// ```
28#[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    /// Create a new metrics aspect.
36    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    /// Get call count for a function.
44    pub fn get_count(&self, function_name: &str) -> u64 {
45        self.counters.lock().get(function_name).copied().unwrap_or(0)
46    }
47
48    /// Get duration histogram for a function.
49    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    /// Print all metrics.
58    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    /// Clear all metrics.
82    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        // Increment counter
100        *self.counters.lock().entry(function_name.clone()).or_insert(0) += 1;
101
102        let result = pjp.proceed();
103
104        // Record duration
105        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}