memscope_rs/metrics/
collector.rs

1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Arc;
4use std::time::{Duration, Instant};
5
6/// High-performance metrics collection system
7pub struct MetricsCollector {
8    /// Current metric values
9    metrics: HashMap<String, Metric>,
10    /// Collection start time
11    start_time: Instant,
12    /// Whether collection is enabled
13    enabled: bool,
14    /// Sample rate for performance metrics (0.0 to 1.0)
15    sample_rate: f64,
16}
17
18/// Individual metric with metadata
19#[derive(Debug, Clone)]
20pub struct Metric {
21    /// Metric name identifier
22    pub name: String,
23    /// Type of metric
24    pub metric_type: MetricType,
25    /// Current value
26    pub value: MetricValue,
27    /// Unit of measurement
28    pub unit: String,
29    /// Description of what this metric measures
30    pub description: String,
31    /// When this metric was last updated
32    pub last_updated: Instant,
33    /// Number of times this metric was updated
34    pub update_count: u64,
35}
36
37/// Types of metrics supported
38#[derive(Debug, Clone, PartialEq)]
39pub enum MetricType {
40    /// Simple counter that only increases
41    Counter,
42    /// Value that can go up or down
43    Gauge,
44    /// Histogram for distribution analysis
45    Histogram,
46    /// Timer for duration measurements
47    Timer,
48    /// Rate measurements (events per second)
49    Rate,
50}
51
52/// Metric value storage
53#[derive(Debug, Clone)]
54pub enum MetricValue {
55    /// Integer counter value
56    Counter(Arc<AtomicU64>),
57    /// Floating point gauge value
58    Gauge(f64),
59    /// Histogram buckets and statistics
60    Histogram(HistogramData),
61    /// Timer duration measurements
62    Timer(TimerData),
63    /// Rate calculation data
64    Rate(RateData),
65}
66
67/// Histogram data structure
68#[derive(Debug, Clone)]
69pub struct HistogramData {
70    /// Histogram buckets with upper bounds and counts
71    pub buckets: Vec<(f64, u64)>,
72    /// Total number of observations
73    pub count: u64,
74    /// Sum of all observed values
75    pub sum: f64,
76    /// Minimum observed value
77    pub min: f64,
78    /// Maximum observed value
79    pub max: f64,
80}
81
82/// Timer measurement data
83#[derive(Debug, Clone)]
84pub struct TimerData {
85    /// Total duration accumulated
86    pub total_duration: Duration,
87    /// Number of timing measurements
88    pub count: u64,
89    /// Minimum duration observed
90    pub min_duration: Duration,
91    /// Maximum duration observed
92    pub max_duration: Duration,
93    /// Recent durations for percentile calculation
94    pub recent_durations: Vec<Duration>,
95}
96
97/// Rate calculation data
98#[derive(Debug, Clone)]
99pub struct RateData {
100    /// Total events counted
101    pub total_events: u64,
102    /// Time window for rate calculation
103    pub window_duration: Duration,
104    /// Event timestamps within current window
105    pub event_times: Vec<Instant>,
106    /// Current calculated rate (events per second)
107    pub current_rate: f64,
108}
109
110impl MetricsCollector {
111    /// Create new metrics collector
112    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    /// Create collector with custom sample rate
122    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    /// Enable or disable metrics collection
132    pub fn set_enabled(&mut self, enabled: bool) {
133        self.enabled = enabled;
134    }
135
136    /// Check if metrics collection is enabled
137    pub fn is_enabled(&self) -> bool {
138        self.enabled
139    }
140
141    /// Increment a counter metric
142    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    /// Set a gauge metric value
160    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    /// Record histogram observation
178    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    /// Record timer measurement
196    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    /// Record rate event
214    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    /// Get current value of a metric
232    pub fn get_metric(&self, name: &str) -> Option<&Metric> {
233        self.metrics.get(name)
234    }
235
236    /// Get all metrics
237    pub fn get_all_metrics(&self) -> &HashMap<String, Metric> {
238        &self.metrics
239    }
240
241    /// Get metrics summary
242    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    /// Clear all metrics
270    pub fn clear_metrics(&mut self) {
271        self.metrics.clear();
272    }
273
274    /// Clean up old metrics
275    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        // Simple sampling based on system time
291        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    /// Create new counter metric
298    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    /// Create new gauge metric
311    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    /// Create new histogram metric
324    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    /// Create new timer metric
337    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    /// Create new rate metric
350    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    /// Get current metric value as string
363    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    /// Create new histogram with default buckets
380    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    /// Record an observation
407    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        // Update buckets
414        for (upper_bound, count) in &mut self.buckets {
415            if value <= *upper_bound {
416                *count += 1;
417            }
418        }
419    }
420
421    /// Calculate average value
422    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    /// Create new timer data
439    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    /// Record a duration measurement
450    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        // Keep recent durations for percentile calculations
457        self.recent_durations.push(duration);
458        if self.recent_durations.len() > 1000 {
459            self.recent_durations.drain(0..500); // Keep last 500
460        }
461    }
462
463    /// Calculate average duration
464    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    /// Create new rate data
480    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    /// Record an event occurrence
490    pub fn record_event(&mut self) {
491        let now = Instant::now();
492        self.total_events += 1;
493        self.event_times.push(now);
494
495        // Clean old events outside window
496        let cutoff = now - self.window_duration;
497        self.event_times.retain(|&time| time > cutoff);
498
499        // Calculate current rate
500        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/// Summary of metrics collection performance
510#[derive(Debug, Clone)]
511pub struct MetricsSummary {
512    /// Total number of metrics tracked
513    pub total_metrics: usize,
514    /// Number of recently active metrics
515    pub active_metrics: usize,
516    /// Total metric updates performed
517    pub total_updates: u64,
518    /// Rate of metric updates per second
519    pub update_rate: f64,
520    /// How long collector has been running
521    pub uptime: Duration,
522    /// Current sampling rate
523    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); // Clamped
627    }
628}