1use std::collections::HashMap;
10use std::sync::RwLock;
11use std::time::{Duration, Instant};
12
13pub trait MetricsProvider: Send + Sync {
19 fn record_operation(&self, operation: &str, duration: Duration);
21
22 fn increment_counter(&self, counter: &str, value: u64);
24
25 fn record_gauge(&self, gauge: &str, value: f64);
27
28 fn timer<'a>(&'a self, operation: &'a str) -> OperationTimer<'a>;
31
32 fn summary(&self) -> String;
34}
35
36pub struct OperationTimer<'a> {
38 provider: &'a dyn MetricsProvider,
39 operation: &'a str,
40 start: Instant,
41}
42
43impl<'a> OperationTimer<'a> {
44 fn new(provider: &'a dyn MetricsProvider, operation: &'a str) -> Self {
45 OperationTimer {
46 provider,
47 operation,
48 start: Instant::now(),
49 }
50 }
51}
52
53impl<'a> Drop for OperationTimer<'a> {
54 fn drop(&mut self) {
55 let duration = self.start.elapsed();
56 self.provider.record_operation(self.operation, duration);
57 }
58}
59
60#[derive(Debug)]
62pub struct SimpleMetrics {
63 counters: RwLock<HashMap<String, u64>>,
64 gauges: RwLock<HashMap<String, f64>>,
65 timers: RwLock<HashMap<String, Vec<Duration>>>,
66}
67
68impl SimpleMetrics {
69 pub fn new() -> Self {
71 SimpleMetrics {
72 counters: RwLock::new(HashMap::new()),
73 gauges: RwLock::new(HashMap::new()),
74 timers: RwLock::new(HashMap::new()),
75 }
76 }
77
78 pub fn get_counter(&self, counter: &str) -> Option<u64> {
80 self.counters.read().unwrap_or_else(|e| e.into_inner()).get(counter).cloned()
81 }
82
83 pub fn get_gauge(&self, gauge: &str) -> Option<f64> {
85 self.gauges.read().unwrap_or_else(|e| e.into_inner()).get(gauge).cloned()
86 }
87
88 pub fn get_average_duration(&self, operation: &str) -> Option<Duration> {
90 let timers = self.timers.read().unwrap_or_else(|e| e.into_inner());
91 let durations = timers.get(operation)?;
92
93 if durations.is_empty() {
94 return None;
95 }
96
97 let total_nanos: u128 = durations.iter().map(|d| d.as_nanos()).sum();
98 let avg_nanos = total_nanos / durations.len() as u128;
99
100 Some(Duration::from_nanos(avg_nanos as u64))
101 }
102}
103
104impl MetricsProvider for SimpleMetrics {
105 fn record_operation(&self, operation: &str, duration: Duration) {
106 let mut timers = self.timers.write().unwrap_or_else(|e| e.into_inner());
107 timers.entry(operation.to_string())
108 .or_insert_with(Vec::new)
109 .push(duration);
110 }
111
112 fn increment_counter(&self, counter: &str, value: u64) {
113 let mut counters = self.counters.write().unwrap_or_else(|e| e.into_inner());
114 *counters.entry(counter.to_string()).or_insert(0) += value;
115 }
116
117 fn record_gauge(&self, gauge: &str, value: f64) {
118 let mut gauges = self.gauges.write().unwrap_or_else(|e| e.into_inner());
119 gauges.insert(gauge.to_string(), value);
120 }
121
122 fn timer<'a>(&'a self, operation: &'a str) -> OperationTimer<'a> {
123 OperationTimer::new(self, operation)
124 }
125
126 fn summary(&self) -> String {
127 let mut result = String::new();
128
129 result.push_str("Counters:\n");
131 for (name, value) in self.counters.read().unwrap_or_else(|e| e.into_inner()).iter() {
132 result.push_str(&format!(" {}: {}\n", name, value));
133 }
134
135 result.push_str("\nGauges:\n");
137 for (name, value) in self.gauges.read().unwrap_or_else(|e| e.into_inner()).iter() {
138 result.push_str(&format!(" {}: {:.6}\n", name, value));
139 }
140
141 result.push_str("\nOperations:\n");
143 for (name, durations) in self.timers.read().unwrap_or_else(|e| e.into_inner()).iter() {
144 if durations.is_empty() {
145 continue;
146 }
147
148 let total_nanos: u128 = durations.iter().map(|d| d.as_nanos()).sum();
149 let avg_nanos = total_nanos / durations.len() as u128;
150 let avg_duration = Duration::from_nanos(avg_nanos as u64);
151 let min_duration = durations.iter().min().unwrap();
152 let max_duration = durations.iter().max().unwrap();
153
154 result.push_str(&format!(
155 " {}: count={}, avg={:?}, min={:?}, max={:?}\n",
156 name, durations.len(), avg_duration, min_duration, max_duration
157 ));
158 }
159
160 result
161 }
162}
163
164impl Default for SimpleMetrics {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use std::thread::sleep;
174
175 #[test]
176 fn test_counter_operations() {
177 let metrics = SimpleMetrics::new();
178
179 metrics.increment_counter("test_counter", 1);
181 metrics.increment_counter("test_counter", 2);
182 metrics.increment_counter("another_counter", 5);
183
184 assert_eq!(metrics.get_counter("test_counter"), Some(3));
186 assert_eq!(metrics.get_counter("another_counter"), Some(5));
187 assert_eq!(metrics.get_counter("nonexistent_counter"), None);
188 }
189
190 #[test]
191 fn test_gauge_operations() {
192 let metrics = SimpleMetrics::new();
193
194 metrics.record_gauge("test_gauge", 3.14);
196 metrics.record_gauge("another_gauge", 2.71);
197
198 assert!((metrics.get_gauge("test_gauge").unwrap() - 3.14).abs() < 0.0001);
200 assert!((metrics.get_gauge("another_gauge").unwrap() - 2.71).abs() < 0.0001);
201 assert_eq!(metrics.get_gauge("nonexistent_gauge"), None);
202 }
203
204 #[test]
205 fn test_timer_operations() {
206 let metrics = SimpleMetrics::new();
207
208 metrics.record_operation("op1", Duration::from_millis(100));
210 metrics.record_operation("op1", Duration::from_millis(200));
211
212 {
214 let _timer = metrics.timer("op2");
215 sleep(Duration::from_millis(10)); }
217
218 let avg_op1 = metrics.get_average_duration("op1").unwrap();
220 assert_eq!(avg_op1, Duration::from_millis(150));
221
222 let avg_op2 = metrics.get_average_duration("op2").unwrap();
223 assert!(avg_op2.as_millis() >= 10); }
225
226 #[test]
227 fn test_summary() {
228 let metrics = SimpleMetrics::new();
229
230 metrics.increment_counter("requests", 42);
232 metrics.record_gauge("memory_usage", 123.456);
233 metrics.record_operation("fetch", Duration::from_millis(50));
234
235 let summary = metrics.summary();
237
238 assert!(summary.contains("requests: 42"));
240 assert!(summary.contains("memory_usage:"));
241 assert!(summary.contains("fetch: count=1"));
242 }
243}