1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Arc;
4use std::time::{Duration, Instant};
5
6pub struct MetricsCollector {
8 metrics: HashMap<String, Metric>,
10 start_time: Instant,
12 enabled: bool,
14 sample_rate: f64,
16}
17
18#[derive(Debug, Clone)]
20pub struct Metric {
21 pub name: String,
23 pub metric_type: MetricType,
25 pub value: MetricValue,
27 pub unit: String,
29 pub description: String,
31 pub last_updated: Instant,
33 pub update_count: u64,
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub enum MetricType {
40 Counter,
42 Gauge,
44 Histogram,
46 Timer,
48 Rate,
50}
51
52#[derive(Debug, Clone)]
54pub enum MetricValue {
55 Counter(Arc<AtomicU64>),
57 Gauge(f64),
59 Histogram(HistogramData),
61 Timer(TimerData),
63 Rate(RateData),
65}
66
67#[derive(Debug, Clone)]
69pub struct HistogramData {
70 pub buckets: Vec<(f64, u64)>,
72 pub count: u64,
74 pub sum: f64,
76 pub min: f64,
78 pub max: f64,
80}
81
82#[derive(Debug, Clone)]
84pub struct TimerData {
85 pub total_duration: Duration,
87 pub count: u64,
89 pub min_duration: Duration,
91 pub max_duration: Duration,
93 pub recent_durations: Vec<Duration>,
95}
96
97#[derive(Debug, Clone)]
99pub struct RateData {
100 pub total_events: u64,
102 pub window_duration: Duration,
104 pub event_times: Vec<Instant>,
106 pub current_rate: f64,
108}
109
110impl MetricsCollector {
111 pub fn new() -> Self {
113 Self {
114 metrics: HashMap::new(),
115 start_time: Instant::now(),
116 enabled: true,
117 sample_rate: 1.0,
118 }
119 }
120
121 pub fn with_sample_rate(sample_rate: f64) -> Self {
123 Self {
124 metrics: HashMap::new(),
125 start_time: Instant::now(),
126 enabled: true,
127 sample_rate: sample_rate.clamp(0.0, 1.0),
128 }
129 }
130
131 pub fn set_enabled(&mut self, enabled: bool) {
133 self.enabled = enabled;
134 }
135
136 pub fn is_enabled(&self) -> bool {
138 self.enabled
139 }
140
141 pub fn increment_counter(&mut self, name: &str, value: u64) {
143 if !self.should_sample() {
144 return;
145 }
146
147 let metric = self
148 .metrics
149 .entry(name.to_string())
150 .or_insert_with(|| Metric::new_counter(name, "Number of events"));
151
152 if let MetricValue::Counter(counter) = &metric.value {
153 counter.fetch_add(value, Ordering::Relaxed);
154 metric.last_updated = Instant::now();
155 metric.update_count += 1;
156 }
157 }
158
159 pub fn set_gauge(&mut self, name: &str, value: f64, unit: &str) {
161 if !self.should_sample() {
162 return;
163 }
164
165 let metric = self
166 .metrics
167 .entry(name.to_string())
168 .or_insert_with(|| Metric::new_gauge(name, unit, "Current value measurement"));
169
170 if let MetricValue::Gauge(ref mut gauge_value) = metric.value {
171 *gauge_value = value;
172 metric.last_updated = Instant::now();
173 metric.update_count += 1;
174 }
175 }
176
177 pub fn record_histogram(&mut self, name: &str, value: f64) {
179 if !self.should_sample() {
180 return;
181 }
182
183 let metric = self
184 .metrics
185 .entry(name.to_string())
186 .or_insert_with(|| Metric::new_histogram(name, "Distribution of values"));
187
188 if let MetricValue::Histogram(ref mut hist) = metric.value {
189 hist.observe(value);
190 metric.last_updated = Instant::now();
191 metric.update_count += 1;
192 }
193 }
194
195 pub fn record_timer(&mut self, name: &str, duration: Duration) {
197 if !self.should_sample() {
198 return;
199 }
200
201 let metric = self
202 .metrics
203 .entry(name.to_string())
204 .or_insert_with(|| Metric::new_timer(name, "Duration measurements"));
205
206 if let MetricValue::Timer(ref mut timer) = metric.value {
207 timer.record(duration);
208 metric.last_updated = Instant::now();
209 metric.update_count += 1;
210 }
211 }
212
213 pub fn record_rate_event(&mut self, name: &str) {
215 if !self.should_sample() {
216 return;
217 }
218
219 let metric = self
220 .metrics
221 .entry(name.to_string())
222 .or_insert_with(|| Metric::new_rate(name, "Events per second"));
223
224 if let MetricValue::Rate(ref mut rate) = metric.value {
225 rate.record_event();
226 metric.last_updated = Instant::now();
227 metric.update_count += 1;
228 }
229 }
230
231 pub fn get_metric(&self, name: &str) -> Option<&Metric> {
233 self.metrics.get(name)
234 }
235
236 pub fn get_all_metrics(&self) -> &HashMap<String, Metric> {
238 &self.metrics
239 }
240
241 pub fn get_summary(&self) -> MetricsSummary {
243 let total_metrics = self.metrics.len();
244 let active_metrics = self
245 .metrics
246 .values()
247 .filter(|m| m.last_updated.elapsed() < Duration::from_secs(60))
248 .count();
249
250 let total_updates: u64 = self.metrics.values().map(|m| m.update_count).sum();
251
252 let uptime = self.start_time.elapsed();
253 let update_rate = if uptime.as_secs() > 0 {
254 total_updates as f64 / uptime.as_secs_f64()
255 } else {
256 0.0
257 };
258
259 MetricsSummary {
260 total_metrics,
261 active_metrics,
262 total_updates,
263 update_rate,
264 uptime,
265 sample_rate: self.sample_rate,
266 }
267 }
268
269 pub fn clear_metrics(&mut self) {
271 self.metrics.clear();
272 }
273
274 pub fn cleanup_old_metrics(&mut self, max_age: Duration) {
276 let cutoff_time = Instant::now() - max_age;
277 self.metrics
278 .retain(|_, metric| metric.last_updated > cutoff_time);
279 }
280
281 fn should_sample(&self) -> bool {
282 if !self.enabled {
283 return false;
284 }
285
286 if self.sample_rate >= 1.0 {
287 return true;
288 }
289
290 let sample_decision = (Instant::now().elapsed().as_nanos() % 1000) as f64 / 1000.0;
292 sample_decision < self.sample_rate
293 }
294}
295
296impl Metric {
297 pub fn new_counter(name: &str, description: &str) -> Self {
299 Self {
300 name: name.to_string(),
301 metric_type: MetricType::Counter,
302 value: MetricValue::Counter(Arc::new(AtomicU64::new(0))),
303 unit: "count".to_string(),
304 description: description.to_string(),
305 last_updated: Instant::now(),
306 update_count: 0,
307 }
308 }
309
310 pub fn new_gauge(name: &str, unit: &str, description: &str) -> Self {
312 Self {
313 name: name.to_string(),
314 metric_type: MetricType::Gauge,
315 value: MetricValue::Gauge(0.0),
316 unit: unit.to_string(),
317 description: description.to_string(),
318 last_updated: Instant::now(),
319 update_count: 0,
320 }
321 }
322
323 pub fn new_histogram(name: &str, description: &str) -> Self {
325 Self {
326 name: name.to_string(),
327 metric_type: MetricType::Histogram,
328 value: MetricValue::Histogram(HistogramData::new()),
329 unit: "distribution".to_string(),
330 description: description.to_string(),
331 last_updated: Instant::now(),
332 update_count: 0,
333 }
334 }
335
336 pub fn new_timer(name: &str, description: &str) -> Self {
338 Self {
339 name: name.to_string(),
340 metric_type: MetricType::Timer,
341 value: MetricValue::Timer(TimerData::new()),
342 unit: "duration".to_string(),
343 description: description.to_string(),
344 last_updated: Instant::now(),
345 update_count: 0,
346 }
347 }
348
349 pub fn new_rate(name: &str, description: &str) -> Self {
351 Self {
352 name: name.to_string(),
353 metric_type: MetricType::Rate,
354 value: MetricValue::Rate(RateData::new()),
355 unit: "events/sec".to_string(),
356 description: description.to_string(),
357 last_updated: Instant::now(),
358 update_count: 0,
359 }
360 }
361
362 pub fn value_string(&self) -> String {
364 match &self.value {
365 MetricValue::Counter(counter) => counter.load(Ordering::Relaxed).to_string(),
366 MetricValue::Gauge(value) => format!("{:.2}", value),
367 MetricValue::Histogram(hist) => {
368 format!("count={}, avg={:.2}", hist.count, hist.average())
369 }
370 MetricValue::Timer(timer) => {
371 format!("avg={:.2}ms", timer.average_duration().as_millis())
372 }
373 MetricValue::Rate(rate) => format!("{:.2}/sec", rate.current_rate),
374 }
375 }
376}
377
378impl HistogramData {
379 pub fn new() -> Self {
381 let buckets = vec![
382 (0.001, 0),
383 (0.005, 0),
384 (0.01, 0),
385 (0.025, 0),
386 (0.05, 0),
387 (0.1, 0),
388 (0.25, 0),
389 (0.5, 0),
390 (1.0, 0),
391 (2.5, 0),
392 (5.0, 0),
393 (10.0, 0),
394 (f64::INFINITY, 0),
395 ];
396
397 Self {
398 buckets,
399 count: 0,
400 sum: 0.0,
401 min: f64::INFINITY,
402 max: f64::NEG_INFINITY,
403 }
404 }
405
406 pub fn observe(&mut self, value: f64) {
408 self.count += 1;
409 self.sum += value;
410 self.min = self.min.min(value);
411 self.max = self.max.max(value);
412
413 for (upper_bound, count) in &mut self.buckets {
415 if value <= *upper_bound {
416 *count += 1;
417 }
418 }
419 }
420
421 pub fn average(&self) -> f64 {
423 if self.count > 0 {
424 self.sum / self.count as f64
425 } else {
426 0.0
427 }
428 }
429}
430
431impl Default for HistogramData {
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437impl TimerData {
438 pub fn new() -> Self {
440 Self {
441 total_duration: Duration::ZERO,
442 count: 0,
443 min_duration: Duration::from_secs(u64::MAX),
444 max_duration: Duration::ZERO,
445 recent_durations: Vec::new(),
446 }
447 }
448
449 pub fn record(&mut self, duration: Duration) {
451 self.total_duration += duration;
452 self.count += 1;
453 self.min_duration = self.min_duration.min(duration);
454 self.max_duration = self.max_duration.max(duration);
455
456 self.recent_durations.push(duration);
458 if self.recent_durations.len() > 1000 {
459 self.recent_durations.drain(0..500); }
461 }
462
463 pub fn average_duration(&self) -> Duration {
465 if self.count > 0 {
466 self.total_duration / self.count as u32
467 } else {
468 Duration::ZERO
469 }
470 }
471}
472impl Default for TimerData {
473 fn default() -> Self {
474 Self::new()
475 }
476}
477
478impl RateData {
479 pub fn new() -> Self {
481 Self {
482 total_events: 0,
483 window_duration: Duration::from_secs(60),
484 event_times: Vec::new(),
485 current_rate: 0.0,
486 }
487 }
488
489 pub fn record_event(&mut self) {
491 let now = Instant::now();
492 self.total_events += 1;
493 self.event_times.push(now);
494
495 let cutoff = now - self.window_duration;
497 self.event_times.retain(|&time| time > cutoff);
498
499 self.current_rate = self.event_times.len() as f64 / self.window_duration.as_secs_f64();
501 }
502}
503impl Default for RateData {
504 fn default() -> Self {
505 Self::new()
506 }
507}
508
509#[derive(Debug, Clone)]
511pub struct MetricsSummary {
512 pub total_metrics: usize,
514 pub active_metrics: usize,
516 pub total_updates: u64,
518 pub update_rate: f64,
520 pub uptime: Duration,
522 pub sample_rate: f64,
524}
525
526impl Default for MetricsCollector {
527 fn default() -> Self {
528 Self::new()
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535
536 #[test]
537 fn test_counter_metric() {
538 let mut collector = MetricsCollector::new();
539
540 collector.increment_counter("test_counter", 5);
541 collector.increment_counter("test_counter", 3);
542
543 let metric = collector
544 .get_metric("test_counter")
545 .expect("Metric should exist");
546 if let MetricValue::Counter(counter) = &metric.value {
547 assert_eq!(counter.load(Ordering::Relaxed), 8);
548 } else {
549 panic!("Expected counter metric");
550 }
551 }
552
553 #[test]
554 fn test_gauge_metric() {
555 let mut collector = MetricsCollector::new();
556
557 collector.set_gauge("test_gauge", 42.5, "units");
558
559 let metric = collector
560 .get_metric("test_gauge")
561 .expect("Metric should exist");
562 if let MetricValue::Gauge(value) = &metric.value {
563 assert_eq!(*value, 42.5);
564 } else {
565 panic!("Expected gauge metric");
566 }
567 }
568
569 #[test]
570 fn test_histogram_metric() {
571 let mut collector = MetricsCollector::new();
572
573 collector.record_histogram("test_histogram", 1.0);
574 collector.record_histogram("test_histogram", 2.0);
575 collector.record_histogram("test_histogram", 3.0);
576
577 let metric = collector
578 .get_metric("test_histogram")
579 .expect("Metric should exist");
580 if let MetricValue::Histogram(hist) = &metric.value {
581 assert_eq!(hist.count, 3);
582 assert_eq!(hist.average(), 2.0);
583 } else {
584 panic!("Expected histogram metric");
585 }
586 }
587
588 #[test]
589 fn test_timer_metric() {
590 let mut collector = MetricsCollector::new();
591
592 collector.record_timer("test_timer", Duration::from_millis(100));
593 collector.record_timer("test_timer", Duration::from_millis(200));
594
595 let metric = collector
596 .get_metric("test_timer")
597 .expect("Metric should exist");
598 if let MetricValue::Timer(timer) = &metric.value {
599 assert_eq!(timer.count, 2);
600 assert_eq!(timer.average_duration(), Duration::from_millis(150));
601 } else {
602 panic!("Expected timer metric");
603 }
604 }
605
606 #[test]
607 fn test_metrics_summary() {
608 let mut collector = MetricsCollector::new();
609
610 collector.increment_counter("counter1", 1);
611 collector.set_gauge("gauge1", 10.0, "units");
612 collector.record_histogram("hist1", 5.0);
613
614 let summary = collector.get_summary();
615 assert_eq!(summary.total_metrics, 3);
616 assert!(summary.total_updates >= 3);
617 assert_eq!(summary.sample_rate, 1.0);
618 }
619
620 #[test]
621 fn test_sample_rate() {
622 let collector = MetricsCollector::with_sample_rate(0.5);
623 assert_eq!(collector.sample_rate, 0.5);
624
625 let collector = MetricsCollector::with_sample_rate(1.5);
626 assert_eq!(collector.sample_rate, 1.0); }
628}