1use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Default)]
13pub struct Counter {
14 value: AtomicU64,
15}
16
17impl Counter {
18 #[must_use]
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn inc(&self) {
26 self.value.fetch_add(1, Ordering::Relaxed);
27 }
28
29 pub fn add(&self, n: u64) {
31 self.value.fetch_add(n, Ordering::Relaxed);
32 }
33
34 #[must_use]
36 pub fn get(&self) -> u64 {
37 self.value.load(Ordering::Relaxed)
38 }
39
40 pub fn reset(&self) {
42 self.value.store(0, Ordering::Relaxed);
43 }
44}
45
46#[derive(Debug, Default)]
48pub struct Gauge {
49 value: AtomicU64,
50}
51
52impl Gauge {
53 #[must_use]
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 pub fn set(&self, value: u64) {
61 self.value.store(value, Ordering::Relaxed);
62 }
63
64 pub fn inc(&self) {
66 self.value.fetch_add(1, Ordering::Relaxed);
67 }
68
69 pub fn dec(&self) {
71 self.value.fetch_sub(1, Ordering::Relaxed);
72 }
73
74 #[must_use]
76 pub fn get(&self) -> u64 {
77 self.value.load(Ordering::Relaxed)
78 }
79}
80
81#[derive(Debug)]
83pub struct Histogram {
84 buckets: Vec<f64>,
86 counts: Vec<AtomicU64>,
88 sum: AtomicU64,
90 count: AtomicU64,
92}
93
94impl Histogram {
95 #[must_use]
97 pub fn new() -> Self {
98 Self::with_buckets(vec![
99 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
100 ])
101 }
102
103 #[must_use]
105 pub fn with_buckets(buckets: Vec<f64>) -> Self {
106 let counts = (0..=buckets.len()).map(|_| AtomicU64::new(0)).collect();
107 Self {
108 buckets,
109 counts,
110 sum: AtomicU64::new(0),
111 count: AtomicU64::new(0),
112 }
113 }
114
115 pub fn observe(&self, value: f64) {
117 let idx = self
119 .buckets
120 .iter()
121 .position(|&b| value <= b)
122 .unwrap_or(self.buckets.len());
123 self.counts[idx].fetch_add(1, Ordering::Relaxed);
124
125 let bits = value.to_bits();
127 self.sum.fetch_add(bits, Ordering::Relaxed);
128 self.count.fetch_add(1, Ordering::Relaxed);
129 }
130
131 #[must_use]
133 pub fn count(&self) -> u64 {
134 self.count.load(Ordering::Relaxed)
135 }
136
137 #[must_use]
139 pub fn bucket_counts(&self) -> Vec<u64> {
140 self.counts
141 .iter()
142 .map(|c| c.load(Ordering::Relaxed))
143 .collect()
144 }
145}
146
147impl Default for Histogram {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[derive(Debug)]
155pub struct Timer {
156 start: Instant,
157}
158
159impl Timer {
160 #[must_use]
162 pub fn start() -> Self {
163 Self {
164 start: Instant::now(),
165 }
166 }
167
168 #[must_use]
170 pub fn elapsed(&self) -> Duration {
171 self.start.elapsed()
172 }
173
174 #[must_use]
176 pub fn stop(self) -> f64 {
177 self.start.elapsed().as_secs_f64()
178 }
179
180 pub fn record_to(self, histogram: &Histogram) {
182 histogram.observe(self.stop());
183 }
184}
185
186#[derive(Debug, Default)]
188pub struct SessionMetrics {
189 pub bytes_sent: Counter,
191 pub bytes_received: Counter,
193 pub commands_executed: Counter,
195 pub pattern_matches: Counter,
197 pub timeouts: Counter,
199 pub errors: Counter,
201 pub active_sessions: Gauge,
203 pub command_duration: Histogram,
205 pub expect_duration: Histogram,
207}
208
209impl SessionMetrics {
210 #[must_use]
212 pub fn new() -> Self {
213 Self::default()
214 }
215
216 #[must_use]
218 pub fn snapshot(&self) -> MetricsSnapshot {
219 MetricsSnapshot {
220 bytes_sent: self.bytes_sent.get(),
221 bytes_received: self.bytes_received.get(),
222 commands_executed: self.commands_executed.get(),
223 pattern_matches: self.pattern_matches.get(),
224 timeouts: self.timeouts.get(),
225 errors: self.errors.get(),
226 active_sessions: self.active_sessions.get(),
227 }
228 }
229}
230
231#[derive(Debug, Clone)]
233pub struct MetricsSnapshot {
234 pub bytes_sent: u64,
236 pub bytes_received: u64,
238 pub commands_executed: u64,
240 pub pattern_matches: u64,
242 pub timeouts: u64,
244 pub errors: u64,
246 pub active_sessions: u64,
248}
249
250#[derive(Debug, Default)]
252pub struct MetricsRegistry {
253 counters: Arc<Mutex<HashMap<String, Arc<Counter>>>>,
254 gauges: Arc<Mutex<HashMap<String, Arc<Gauge>>>>,
255 histograms: Arc<Mutex<HashMap<String, Arc<Histogram>>>>,
256}
257
258impl MetricsRegistry {
259 #[must_use]
261 pub fn new() -> Self {
262 Self::default()
263 }
264
265 #[must_use]
267 pub fn counter(&self, name: &str) -> Arc<Counter> {
268 let mut counters = self
269 .counters
270 .lock()
271 .unwrap_or_else(std::sync::PoisonError::into_inner);
272 counters
273 .entry(name.to_string())
274 .or_insert_with(|| Arc::new(Counter::new()))
275 .clone()
276 }
277
278 #[must_use]
280 pub fn gauge(&self, name: &str) -> Arc<Gauge> {
281 let mut gauges = self
282 .gauges
283 .lock()
284 .unwrap_or_else(std::sync::PoisonError::into_inner);
285 gauges
286 .entry(name.to_string())
287 .or_insert_with(|| Arc::new(Gauge::new()))
288 .clone()
289 }
290
291 #[must_use]
293 pub fn histogram(&self, name: &str) -> Arc<Histogram> {
294 let mut histograms = self
295 .histograms
296 .lock()
297 .unwrap_or_else(std::sync::PoisonError::into_inner);
298 histograms
299 .entry(name.to_string())
300 .or_insert_with(|| Arc::new(Histogram::new()))
301 .clone()
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn counter_basic() {
311 let counter = Counter::new();
312 assert_eq!(counter.get(), 0);
313
314 counter.inc();
315 assert_eq!(counter.get(), 1);
316
317 counter.add(5);
318 assert_eq!(counter.get(), 6);
319 }
320
321 #[test]
322 fn gauge_basic() {
323 let gauge = Gauge::new();
324 assert_eq!(gauge.get(), 0);
325
326 gauge.set(10);
327 assert_eq!(gauge.get(), 10);
328
329 gauge.inc();
330 assert_eq!(gauge.get(), 11);
331
332 gauge.dec();
333 assert_eq!(gauge.get(), 10);
334 }
335
336 #[test]
337 fn histogram_basic() {
338 let histogram = Histogram::new();
339 histogram.observe(0.1);
340 histogram.observe(0.5);
341 histogram.observe(1.0);
342
343 assert_eq!(histogram.count(), 3);
344 }
345
346 #[test]
347 fn timer_basic() {
348 let timer = Timer::start();
349 std::thread::sleep(Duration::from_millis(10));
350 let elapsed = timer.stop();
351
352 assert!(elapsed >= 0.01);
353 }
354
355 #[test]
356 fn session_metrics() {
357 let metrics = SessionMetrics::new();
358 metrics.bytes_sent.add(100);
359 metrics.commands_executed.inc();
360
361 let snapshot = metrics.snapshot();
362 assert_eq!(snapshot.bytes_sent, 100);
363 assert_eq!(snapshot.commands_executed, 1);
364 }
365}