turbovault_core/
metrics.rs1use parking_lot::RwLock;
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::time::Instant;
18
19#[derive(Debug, Clone)]
21pub struct Counter {
22 value: Arc<AtomicU64>,
23 name: String,
24}
25
26impl Counter {
27 pub fn new(name: impl Into<String>) -> Self {
29 Self {
30 value: Arc::new(AtomicU64::new(0)),
31 name: name.into(),
32 }
33 }
34
35 pub fn increment(&self) {
37 self.add(1);
38 }
39
40 pub fn add(&self, value: u64) {
42 let mut current = self.value.load(Ordering::Relaxed);
43 loop {
44 let new_value = current.saturating_add(value);
45 match self.value.compare_exchange_weak(
46 current,
47 new_value,
48 Ordering::Release,
49 Ordering::Relaxed,
50 ) {
51 Ok(_) => break,
52 Err(actual) => current = actual,
53 }
54 }
55 }
56
57 pub fn value(&self) -> u64 {
59 self.value.load(Ordering::Acquire)
60 }
61
62 pub fn name(&self) -> &str {
64 &self.name
65 }
66
67 pub fn reset(&self) {
69 self.value.store(0, Ordering::Release);
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct Histogram {
76 values: Arc<RwLock<Vec<f64>>>,
77 name: String,
78}
79
80impl Histogram {
81 pub fn new(name: impl Into<String>) -> Self {
83 Self {
84 values: Arc::new(RwLock::new(Vec::new())),
85 name: name.into(),
86 }
87 }
88
89 pub fn record(&self, value: f64) {
91 let mut v = self.values.write();
92 v.push(value);
93 }
94
95 pub fn timer(&self) -> HistogramTimer {
97 HistogramTimer {
98 histogram: self.clone(),
99 start: Instant::now(),
100 }
101 }
102
103 pub fn stats(&self) -> HistogramStats {
105 let v = self.values.read();
106 if v.is_empty() {
107 return HistogramStats {
108 count: 0,
109 sum: 0.0,
110 min: 0.0,
111 max: 0.0,
112 mean: 0.0,
113 };
114 }
115
116 let sum: f64 = v.iter().sum();
117 let count = v.len();
118 let mean = sum / count as f64;
119 let min = v.iter().copied().fold(f64::INFINITY, f64::min);
120 let max = v.iter().copied().fold(f64::NEG_INFINITY, f64::max);
121
122 HistogramStats {
123 count,
124 sum,
125 min,
126 max,
127 mean,
128 }
129 }
130
131 pub fn name(&self) -> &str {
133 &self.name
134 }
135
136 pub fn reset(&self) {
138 let mut v = self.values.write();
139 v.clear();
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct HistogramStats {
146 pub count: usize,
148 pub sum: f64,
150 pub min: f64,
152 pub max: f64,
154 pub mean: f64,
156}
157
158#[derive(Debug)]
160pub struct HistogramTimer {
161 histogram: Histogram,
162 start: Instant,
163}
164
165impl Drop for HistogramTimer {
166 fn drop(&mut self) {
167 let duration_ms = self.start.elapsed().as_secs_f64() * 1000.0;
168 self.histogram.record(duration_ms);
169 }
170}
171
172#[derive(Debug)]
177pub struct MetricsContext {
178 enabled: bool,
179 counters: Arc<RwLock<HashMap<String, Counter>>>,
180 histograms: Arc<RwLock<HashMap<String, Histogram>>>,
181}
182
183impl MetricsContext {
184 pub fn new(enabled: bool) -> Self {
186 Self {
187 enabled,
188 counters: Arc::new(RwLock::new(HashMap::new())),
189 histograms: Arc::new(RwLock::new(HashMap::new())),
190 }
191 }
192
193 pub fn counter(&self, name: &str) -> Counter {
195 if !self.enabled {
196 return Counter::new(name);
197 }
198
199 let mut counters = self.counters.write();
200 if let Some(counter) = counters.get(name) {
201 counter.clone()
202 } else {
203 let counter = Counter::new(name);
204 counters.insert(name.to_string(), counter.clone());
205 counter
206 }
207 }
208
209 pub fn histogram(&self, name: &str) -> Histogram {
211 if !self.enabled {
212 return Histogram::new(name);
213 }
214
215 let mut histograms = self.histograms.write();
216 if let Some(histogram) = histograms.get(name) {
217 histogram.clone()
218 } else {
219 let histogram = Histogram::new(name);
220 histograms.insert(name.to_string(), histogram.clone());
221 histogram
222 }
223 }
224
225 pub fn get_counters(&self) -> HashMap<String, u64> {
227 self.counters
228 .read()
229 .iter()
230 .map(|(k, v)| (k.clone(), v.value()))
231 .collect()
232 }
233
234 pub fn get_histograms(&self) -> HashMap<String, HistogramStats> {
236 self.histograms
237 .read()
238 .iter()
239 .map(|(k, v)| (k.clone(), v.stats()))
240 .collect()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_counter_increment() {
250 let counter = Counter::new("test");
251 assert_eq!(counter.value(), 0);
252 counter.increment();
253 assert_eq!(counter.value(), 1);
254 counter.add(5);
255 assert_eq!(counter.value(), 6);
256 }
257
258 #[test]
259 fn test_counter_saturation() {
260 let counter = Counter::new("test");
261 counter.value.store(u64::MAX, Ordering::Release);
262 counter.add(100); assert_eq!(counter.value(), u64::MAX);
264 }
265
266 #[test]
267 fn test_histogram_stats() {
268 let histogram = Histogram::new("test");
269 histogram.record(1.0);
270 histogram.record(2.0);
271 histogram.record(3.0);
272
273 let stats = histogram.stats();
274 assert_eq!(stats.count, 3);
275 assert_eq!(stats.sum, 6.0);
276 assert_eq!(stats.min, 1.0);
277 assert_eq!(stats.max, 3.0);
278 assert_eq!(stats.mean, 2.0);
279 }
280
281 #[test]
282 fn test_histogram_timer() {
283 let histogram = Histogram::new("test");
284 {
285 let _timer = histogram.timer();
286 std::thread::sleep(std::time::Duration::from_millis(10));
287 }
288
289 let stats = histogram.stats();
290 assert_eq!(stats.count, 1);
291 assert!(stats.sum > 10.0); }
293
294 #[test]
295 fn test_metrics_context() {
296 let ctx = MetricsContext::new(true);
297 let counter = ctx.counter("requests");
298 counter.add(5);
299
300 let counters = ctx.get_counters();
301 assert_eq!(counters.get("requests"), Some(&5));
302 }
303
304 #[test]
305 fn test_metrics_context_disabled() {
306 let ctx = MetricsContext::new(false);
307 let counter = ctx.counter("requests");
308 counter.add(100);
309 let counters = ctx.get_counters();
311 assert!(counters.is_empty());
312 }
313}