scirs2_core/
metrics.rs

1//! # Production-Level Metrics Collection and Monitoring
2//!
3//! This module provides comprehensive metrics collection, health checks, and monitoring
4//! capabilities for production deployments of ``SciRS2`` Core.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7#[cfg(feature = "serialization")]
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fmt;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::RwLock;
13use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
14
15/// Metric types supported by the system
16#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum MetricType {
19    /// Counter metric (monotonically increasing)
20    Counter,
21    /// Gauge metric (can go up or down)
22    Gauge,
23    /// Histogram metric (distribution of values)
24    Histogram,
25    /// Timer metric (duration measurements)
26    Timer,
27    /// Summary metric (quantiles over sliding time window)
28    Summary,
29    /// Throughput metric (operations per second)
30    Throughput,
31    /// Latency metric (response time)
32    Latency,
33    /// CPU utilization metric
34    Cpu,
35    /// Memory usage metric
36    Memory,
37}
38
39/// Metric value representation
40#[derive(Debug, Clone)]
41pub enum MetricValue {
42    /// Integer value
43    Integer(i64),
44    /// Floating point value
45    Float(f64),
46    /// Duration value
47    Duration(Duration),
48    /// Boolean value
49    Boolean(bool),
50    /// String value
51    String(String),
52}
53
54impl fmt::Display for MetricValue {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            MetricValue::Integer(v) => write!(f, "{v}"),
58            MetricValue::Float(v) => write!(f, "{v}"),
59            MetricValue::Duration(v) => write!(f, "{v:?}"),
60            MetricValue::Boolean(v) => write!(f, "{v}"),
61            MetricValue::String(v) => write!(f, "{v}"),
62        }
63    }
64}
65
66/// A metric data point with timestamp and labels
67#[derive(Debug, Clone)]
68pub struct MetricPoint {
69    /// Metric name
70    pub name: String,
71    /// Metric type
72    pub metric_type: MetricType,
73    /// Metric value
74    pub value: MetricValue,
75    /// Timestamp when metric was recorded
76    pub timestamp: SystemTime,
77    /// Labels/tags for the metric
78    pub labels: HashMap<String, String>,
79    /// Help text describing the metric
80    pub help: Option<String>,
81}
82
83/// High-performance counter metric
84pub struct Counter {
85    value: AtomicU64,
86    name: String,
87    labels: HashMap<String, String>,
88}
89
90impl Counter {
91    /// Create a new counter
92    pub fn new(name: String) -> Self {
93        Self {
94            value: AtomicU64::new(0),
95            name,
96            labels: HashMap::new(),
97        }
98    }
99
100    /// Create a counter with labels
101    pub fn with_labels(name: String, labels: HashMap<String, String>) -> Self {
102        Self {
103            value: AtomicU64::new(0),
104            name,
105            labels,
106        }
107    }
108
109    /// Increment the counter by 1
110    pub fn inc(&self) {
111        self.value.fetch_add(1, Ordering::Relaxed);
112    }
113
114    /// Increment the counter by a specific amount
115    pub fn add(&self, amount: u64) {
116        self.value.fetch_add(amount, Ordering::Relaxed);
117    }
118
119    /// Get the current value
120    pub fn get(&self) -> u64 {
121        self.value.load(Ordering::Relaxed)
122    }
123
124    /// Get metric point
125    pub fn to_metric_point(&self) -> MetricPoint {
126        MetricPoint {
127            name: self.name.clone(),
128            metric_type: MetricType::Counter,
129            value: MetricValue::Integer(self.get() as i64),
130            timestamp: SystemTime::now(),
131            labels: self.labels.clone(),
132            help: None,
133        }
134    }
135}
136
137/// High-performance gauge metric
138pub struct Gauge {
139    value: AtomicU64, // Store as bits of f64
140    name: String,
141    labels: HashMap<String, String>,
142}
143
144impl Gauge {
145    /// Create a new gauge
146    pub fn new(name: String) -> Self {
147        Self {
148            value: AtomicU64::new(0),
149            name,
150            labels: HashMap::new(),
151        }
152    }
153
154    /// Create a gauge with labels
155    pub fn with_labels(name: String, labels: HashMap<String, String>) -> Self {
156        Self {
157            value: AtomicU64::new(0),
158            name,
159            labels,
160        }
161    }
162
163    /// Set the gauge value
164    pub fn set(&self, value: f64) {
165        self.value.store(value.to_bits(), Ordering::Relaxed);
166    }
167
168    /// Increment the gauge
169    pub fn inc(&self) {
170        let current = f64::from_bits(self.value.load(Ordering::Relaxed));
171        self.set(current + 1.0);
172    }
173
174    /// Decrement the gauge
175    pub fn dec(&self) {
176        let current = f64::from_bits(self.value.load(Ordering::Relaxed));
177        self.set(current - 1.0);
178    }
179
180    /// Add to the gauge
181    pub fn add(&self, amount: f64) {
182        let current = f64::from_bits(self.value.load(Ordering::Relaxed));
183        self.set(current + amount);
184    }
185
186    /// Subtract from the gauge
187    pub fn sub(&self, amount: f64) {
188        let current = f64::from_bits(self.value.load(Ordering::Relaxed));
189        self.set(current - amount);
190    }
191
192    /// Get the current value
193    pub fn get(&self) -> f64 {
194        f64::from_bits(self.value.load(Ordering::Relaxed))
195    }
196
197    /// Get metric point
198    pub fn to_metric_point(&self) -> MetricPoint {
199        MetricPoint {
200            name: self.name.clone(),
201            metric_type: MetricType::Gauge,
202            value: MetricValue::Float(self.get()),
203            timestamp: SystemTime::now(),
204            labels: self.labels.clone(),
205            help: None,
206        }
207    }
208}
209
210/// Histogram metric for tracking distributions
211pub struct Histogram {
212    buckets: Vec<(f64, AtomicU64)>, // (upper_bound, count)
213    sum: AtomicU64,                 // Store as bits of f64
214    count: AtomicU64,
215    name: String,
216    labels: HashMap<String, String>,
217}
218
219impl Histogram {
220    /// Create a new histogram with default buckets
221    pub fn new(name: String) -> Self {
222        let default_buckets = vec![
223            0.005,
224            0.01,
225            0.025,
226            0.05,
227            0.1,
228            0.25,
229            0.5,
230            1.0,
231            2.5,
232            5.0,
233            10.0,
234            f64::INFINITY,
235        ];
236        Self::with_buckets(name, default_buckets)
237    }
238
239    /// Create a histogram with custom buckets
240    pub fn with_buckets(name: String, buckets: Vec<f64>) -> Self {
241        let bucket_pairs = buckets
242            .into_iter()
243            .map(|b| (b, AtomicU64::new(0)))
244            .collect();
245
246        Self {
247            buckets: bucket_pairs,
248            sum: AtomicU64::new(0),
249            count: AtomicU64::new(0),
250            name,
251            labels: HashMap::new(),
252        }
253    }
254
255    /// Observe a value
256    pub fn observe(&self, value: f64) {
257        // Update count and sum
258        self.count.fetch_add(1, Ordering::Relaxed);
259        let current_sum = f64::from_bits(self.sum.load(Ordering::Relaxed));
260        self.sum
261            .store((current_sum + value).to_bits(), Ordering::Relaxed);
262
263        // Update buckets
264        for (upper_bound, count) in &self.buckets {
265            if value <= *upper_bound {
266                count.fetch_add(1, Ordering::Relaxed);
267            }
268        }
269    }
270
271    /// Get histogram statistics
272    pub fn get_stats(&self) -> HistogramStats {
273        let count = self.count.load(Ordering::Relaxed);
274        let sum = f64::from_bits(self.sum.load(Ordering::Relaxed));
275        let mean = if count > 0 { sum / count as f64 } else { 0.0 };
276
277        let bucket_counts: Vec<(f64, u64)> = self
278            .buckets
279            .iter()
280            .map(|(bound, count)| (*bound, count.load(Ordering::Relaxed)))
281            .collect();
282
283        HistogramStats {
284            count,
285            sum,
286            mean,
287            buckets: bucket_counts,
288        }
289    }
290}
291
292/// Histogram statistics
293#[derive(Debug, Clone)]
294pub struct HistogramStats {
295    pub count: u64,
296    pub sum: f64,
297    pub mean: f64,
298    pub buckets: Vec<(f64, u64)>,
299}
300
301/// Timer metric for measuring durations
302pub struct Timer {
303    histogram: Histogram,
304}
305
306impl Timer {
307    /// Create a new timer
308    pub fn new(name: String) -> Self {
309        // Use smaller buckets for timing measurements (in seconds)
310        let timing_buckets = vec![
311            0.001,
312            0.005,
313            0.01,
314            0.025,
315            0.05,
316            0.1,
317            0.25,
318            0.5,
319            1.0,
320            2.5,
321            5.0,
322            10.0,
323            f64::INFINITY,
324        ];
325        Self {
326            histogram: Histogram::with_buckets(name, timing_buckets),
327        }
328    }
329
330    /// Start timing an operation
331    pub fn start(&self) -> TimerGuard {
332        TimerGuard {
333            timer: self,
334            start_time: Instant::now(),
335        }
336    }
337
338    /// Observe a duration
339    pub fn observe(&self, duration: Duration) {
340        self.histogram.observe(duration.as_secs_f64());
341    }
342
343    /// Get timing statistics
344    pub fn get_stats(&self) -> HistogramStats {
345        self.histogram.get_stats()
346    }
347}
348
349/// Guard for automatic timing
350pub struct TimerGuard<'a> {
351    timer: &'a Timer,
352    start_time: Instant,
353}
354
355impl Drop for TimerGuard<'_> {
356    fn drop(&mut self) {
357        let duration = self.start_time.elapsed();
358        self.timer.observe(duration);
359    }
360}
361
362/// Metrics registry for managing all metrics
363pub struct MetricsRegistry {
364    metrics: RwLock<HashMap<String, Box<dyn MetricProvider + Send + Sync>>>,
365}
366
367/// Trait for types that can provide metric points
368pub trait MetricProvider {
369    /// Get all metric points from this provider
370    fn get_metric_points(&self) -> Vec<MetricPoint>;
371}
372
373impl MetricProvider for Counter {
374    fn get_metric_points(&self) -> Vec<MetricPoint> {
375        vec![self.to_metric_point()]
376    }
377}
378
379impl MetricProvider for Gauge {
380    fn get_metric_points(&self) -> Vec<MetricPoint> {
381        vec![self.to_metric_point()]
382    }
383}
384
385impl MetricProvider for Histogram {
386    fn get_metric_points(&self) -> Vec<MetricPoint> {
387        let stats = self.get_stats();
388        let mut points = Vec::new();
389
390        // Count metric
391        points.push(MetricPoint {
392            name: {
393                let name = &self.name;
394                format!("{name}_count")
395            },
396            metric_type: MetricType::Counter,
397            value: MetricValue::Integer(stats.count as i64),
398            timestamp: SystemTime::now(),
399            labels: self.labels.clone(),
400            help: Some({
401                let name = &self.name;
402                format!("name: {name}")
403            }),
404        });
405
406        // Sum metric
407        points.push(MetricPoint {
408            name: {
409                let name = &self.name;
410                format!("{name}_sum")
411            },
412            metric_type: MetricType::Counter,
413            value: MetricValue::Float(stats.sum),
414            timestamp: SystemTime::now(),
415            labels: self.labels.clone(),
416            help: Some({
417                let name = &self.name;
418                format!("name: {name}")
419            }),
420        });
421
422        // Bucket metrics
423        for (bucket, count) in stats.buckets {
424            let mut bucket_labels = self.labels.clone();
425            bucket_labels.insert("le".to_string(), bucket.to_string());
426
427            points.push(MetricPoint {
428                name: {
429                    let name = &self.name;
430                    format!("{name}_bucket")
431                },
432                metric_type: MetricType::Counter,
433                value: MetricValue::Integer(count as i64),
434                timestamp: SystemTime::now(),
435                labels: bucket_labels,
436                help: Some({
437                    let name = &self.name;
438                    format!("name: {name}")
439                }),
440            });
441        }
442
443        points
444    }
445}
446
447impl MetricsRegistry {
448    /// Create a new metrics registry
449    pub fn new() -> Self {
450        Self {
451            metrics: RwLock::new(HashMap::new()),
452        }
453    }
454
455    /// Register a metric
456    pub fn register<T>(&self, name: String, metric: T) -> CoreResult<()>
457    where
458        T: MetricProvider + Send + Sync + 'static,
459    {
460        let mut metrics = self.metrics.write().map_err(|_| {
461            CoreError::ComputationError(ErrorContext::new("Failed to acquire metrics lock"))
462        })?;
463
464        metrics.insert(name, Box::new(metric));
465        Ok(())
466    }
467
468    /// Get all metric points
469    pub fn get_all_metrics(&self) -> CoreResult<Vec<MetricPoint>> {
470        let metrics = self.metrics.read().map_err(|_| {
471            CoreError::ComputationError(ErrorContext::new("Failed to acquire metrics lock"))
472        })?;
473
474        let mut all_points = Vec::new();
475        for provider in metrics.values() {
476            all_points.extend(provider.get_metric_points());
477        }
478
479        Ok(all_points)
480    }
481
482    /// Export metrics in Prometheus format
483    pub fn export_prometheus(&self) -> CoreResult<String> {
484        let metrics = self.get_all_metrics()?;
485        let mut output = String::new();
486
487        for metric in metrics {
488            // Add help text if available
489            if let Some(help) = &metric.help {
490                output.push_str(&format!(
491                    "# HELP {name} {help}\n",
492                    name = metric.name,
493                    help = help
494                ));
495            }
496
497            // Add type information
498            let type_str = match metric.metric_type {
499                MetricType::Counter => "counter",
500                MetricType::Gauge => "gauge",
501                MetricType::Histogram => "histogram",
502                MetricType::Timer => "histogram",
503                MetricType::Summary => "summary",
504                MetricType::Throughput => "gauge",
505                MetricType::Latency => "gauge",
506                MetricType::Cpu => "gauge",
507                MetricType::Memory => "gauge",
508            };
509            output.push_str(&format!(
510                "# TYPE {name} {type_str}\n",
511                name = metric.name,
512                type_str = type_str
513            ));
514
515            // Format labels
516            let labels_str = if metric.labels.is_empty() {
517                String::new()
518            } else {
519                let label_pairs: Vec<String> = metric
520                    .labels
521                    .iter()
522                    .map(|(k, v)| format!("{k}=\"{v}\""))
523                    .collect();
524                format!("{{{}}}", label_pairs.join(","))
525            };
526
527            // Add metric line
528            let timestamp = metric
529                .timestamp
530                .duration_since(UNIX_EPOCH)
531                .unwrap_or_default()
532                .as_millis();
533
534            output.push_str(&format!(
535                "{}{} {} {}\n",
536                metric.name, labels_str, metric.value, timestamp
537            ));
538        }
539
540        Ok(output)
541    }
542}
543
544impl Default for MetricsRegistry {
545    fn default() -> Self {
546        Self::new()
547    }
548}
549
550/// Health check status
551#[derive(Debug, Clone, PartialEq)]
552pub enum HealthStatus {
553    /// System is healthy
554    Healthy,
555    /// System has warnings but is operational
556    Warning,
557    /// System is unhealthy
558    Unhealthy,
559}
560
561impl fmt::Display for HealthStatus {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        match self {
564            HealthStatus::Healthy => write!(f, "healthy"),
565            HealthStatus::Warning => write!(f, "warning"),
566            HealthStatus::Unhealthy => write!(f, "unhealthy"),
567        }
568    }
569}
570
571/// Health check result
572#[derive(Debug, Clone)]
573pub struct HealthCheck {
574    /// Name of the health check
575    pub name: String,
576    /// Status of the health check
577    pub status: HealthStatus,
578    /// Message describing the status
579    pub message: String,
580    /// Timestamp of the check
581    pub timestamp: SystemTime,
582    /// Duration of the check
583    pub duration: Duration,
584}
585
586/// Health monitoring system
587pub struct HealthMonitor {
588    checks: RwLock<HashMap<String, Box<dyn HealthChecker + Send + Sync>>>,
589    results_cache: RwLock<HashMap<String, HealthCheck>>,
590    #[allow(dead_code)]
591    cache_duration: Duration,
592}
593
594/// Trait for health check implementations
595pub trait HealthChecker {
596    /// Perform the health check
597    fn check(&self) -> CoreResult<HealthCheck>;
598
599    /// Get the name of this health check
600    fn name(&self) -> &str;
601}
602
603impl HealthMonitor {
604    /// Create a new health monitor
605    pub fn new() -> Self {
606        Self {
607            checks: RwLock::new(HashMap::new()),
608            results_cache: RwLock::new(HashMap::new()),
609            cache_duration: Duration::from_secs(30), // 30 second cache
610        }
611    }
612
613    /// Register a health check
614    pub fn register_check<T>(&self, checker: T) -> CoreResult<()>
615    where
616        T: HealthChecker + Send + Sync + 'static,
617    {
618        let mut checks = self.checks.write().map_err(|_| {
619            CoreError::ComputationError(ErrorContext::new("Failed to acquire health checks lock"))
620        })?;
621
622        checks.insert(checker.name().to_string(), Box::new(checker));
623        Ok(())
624    }
625
626    /// Run all health checks
627    pub fn check_all(&self) -> CoreResult<Vec<HealthCheck>> {
628        let checks = self.checks.read().map_err(|_| {
629            CoreError::ComputationError(ErrorContext::new("Failed to acquire health checks lock"))
630        })?;
631
632        let mut results = Vec::new();
633        for checker in checks.values() {
634            match checker.check() {
635                Ok(result) => results.push(result),
636                Err(error) => {
637                    results.push(HealthCheck {
638                        name: checker.name().to_string(),
639                        status: HealthStatus::Unhealthy,
640                        message: format!("error: {error}"),
641                        timestamp: SystemTime::now(),
642                        duration: Duration::ZERO,
643                    });
644                }
645            }
646        }
647
648        // Update cache
649        if let Ok(mut cache) = self.results_cache.write() {
650            cache.clear();
651            for result in &results {
652                cache.insert(result.name.clone(), result.clone());
653            }
654        }
655
656        Ok(results)
657    }
658
659    /// Get overall health status
660    pub fn overall_status(&self) -> CoreResult<HealthStatus> {
661        let results = self.check_all()?;
662
663        if results.iter().any(|r| r.status == HealthStatus::Unhealthy) {
664            Ok(HealthStatus::Unhealthy)
665        } else if results.iter().any(|r| r.status == HealthStatus::Warning) {
666            Ok(HealthStatus::Warning)
667        } else {
668            Ok(HealthStatus::Healthy)
669        }
670    }
671}
672
673impl Default for HealthMonitor {
674    fn default() -> Self {
675        Self::new()
676    }
677}
678
679/// Built-in health checks
680/// Memory usage health check
681pub struct MemoryHealthCheck {
682    warning_threshold: f64,
683    criticalthreshold: f64,
684}
685
686impl MemoryHealthCheck {
687    /// Create a new memory health check
688    pub fn new(warning_threshold: f64, criticalthreshold: f64) -> Self {
689        Self {
690            warning_threshold,
691            criticalthreshold,
692        }
693    }
694}
695
696impl HealthChecker for MemoryHealthCheck {
697    fn check(&self) -> CoreResult<HealthCheck> {
698        let start_time = Instant::now();
699
700        // Get memory usage from safety tracker
701        #[cfg(feature = "memory_management")]
702        let pressure = {
703            let tracker = crate::memory::safety::global_safety_tracker();
704            tracker.memory_pressure()
705        };
706
707        #[cfg(not(feature = "memory_management"))]
708        let pressure = 0.0; // Fallback when memory management is not available
709
710        let (status, message) = if pressure >= self.criticalthreshold {
711            (
712                HealthStatus::Unhealthy,
713                format!("Memory usage critical: {:.1}%", pressure * 100.0),
714            )
715        } else if pressure >= self.warning_threshold {
716            (
717                HealthStatus::Warning,
718                format!("Memory usage high: {:.1}%", pressure * 100.0),
719            )
720        } else {
721            (
722                HealthStatus::Healthy,
723                format!("Memory usage normal: {:.1}%", pressure * 100.0),
724            )
725        };
726
727        Ok(HealthCheck {
728            name: "memory".to_string(),
729            status,
730            message,
731            timestamp: SystemTime::now(),
732            duration: start_time.elapsed(),
733        })
734    }
735
736    fn name(&self) -> &str {
737        "memory"
738    }
739}
740
741/// Global metrics registry instance
742static GLOBAL_METRICS_REGISTRY: std::sync::LazyLock<MetricsRegistry> =
743    std::sync::LazyLock::new(MetricsRegistry::new);
744
745/// Global health monitor instance
746static GLOBAL_HEALTH_MONITOR: std::sync::LazyLock<HealthMonitor> = std::sync::LazyLock::new(|| {
747    let monitor = HealthMonitor::new();
748
749    // Register built-in health checks
750    let _ = monitor.register_check(MemoryHealthCheck::new(0.8, 0.95));
751
752    monitor
753});
754
755/// Get the global metrics registry
756#[allow(dead_code)]
757pub fn global_metrics_registry() -> &'static MetricsRegistry {
758    &GLOBAL_METRICS_REGISTRY
759}
760
761/// Get the global health monitor
762#[allow(dead_code)]
763pub fn global_healthmonitor() -> &'static HealthMonitor {
764    &GLOBAL_HEALTH_MONITOR
765}
766
767/// Convenience macros for metrics
768/// Create and register a counter metric
769#[macro_export]
770macro_rules! counter {
771    ($name:expr) => {{
772        let counter = $crate::metrics::Counter::new($name.to_string());
773        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), counter);
774        counter
775    }};
776    ($name:expr, $labels:expr) => {{
777        let counter = $crate::metrics::Counter::with_labels($name.to_string(), $labels);
778        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), counter);
779        counter
780    }};
781}
782
783/// Create and register a gauge metric
784#[macro_export]
785macro_rules! gauge {
786    ($name:expr) => {{
787        let gauge = $crate::metrics::Gauge::new($name.to_string());
788        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), gauge);
789        gauge
790    }};
791    ($name:expr, $labels:expr) => {{
792        let gauge = $crate::metrics::Gauge::with_labels($name.to_string(), $labels);
793        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), gauge);
794        gauge
795    }};
796}
797
798/// Create and register a histogram metric
799#[macro_export]
800macro_rules! histogram {
801    ($name:expr) => {{
802        let histogram = $crate::metrics::Histogram::new($name.to_string());
803        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), histogram);
804        histogram
805    }};
806    ($name:expr, $buckets:expr) => {{
807        let histogram = $crate::metrics::Histogram::with_buckets($name.to_string(), $buckets);
808        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), histogram);
809        histogram
810    }};
811}
812
813/// Create and register a timer metric
814#[macro_export]
815macro_rules! timer {
816    ($name:expr) => {{
817        let timer = $crate::metrics::Timer::new($name.to_string());
818        let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), timer);
819        timer
820    }};
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826
827    #[test]
828    fn test_counter() {
829        let counter = Counter::new("test_counter".to_string());
830        assert_eq!(counter.get(), 0);
831
832        counter.inc();
833        assert_eq!(counter.get(), 1);
834
835        counter.add(5);
836        assert_eq!(counter.get(), 6);
837    }
838
839    #[test]
840    fn test_gauge() {
841        let gauge = Gauge::new("test_gauge".to_string());
842        assert_eq!(gauge.get(), 0.0);
843
844        gauge.set(std::f64::consts::PI);
845        assert!((gauge.get() - std::f64::consts::PI).abs() < f64::EPSILON);
846
847        gauge.inc();
848        assert!((gauge.get() - (std::f64::consts::PI + 1.0)).abs() < 1e-10);
849
850        gauge.dec();
851        assert!((gauge.get() - std::f64::consts::PI).abs() < 1e-10);
852    }
853
854    #[test]
855    fn test_histogram() {
856        let histogram = Histogram::new("test_histogram".to_string());
857
858        histogram.observe(0.5);
859        histogram.observe(1.5);
860        histogram.observe(2.5);
861
862        let stats = histogram.get_stats();
863        assert_eq!(stats.count, 3);
864        assert!((stats.sum - 4.5).abs() < f64::EPSILON);
865        assert!((stats.mean - 1.5).abs() < f64::EPSILON);
866    }
867
868    #[test]
869    fn test_timer() {
870        let timer = Timer::new("test_timer".to_string());
871
872        {
873            let _guard = timer.start();
874            std::thread::sleep(Duration::from_millis(10));
875        }
876
877        let stats = timer.get_stats();
878        assert_eq!(stats.count, 1);
879        assert!(stats.sum > 0.0);
880    }
881
882    #[test]
883    fn test_metrics_registry() {
884        let registry = MetricsRegistry::new();
885        let counter = Counter::new("test_counter".to_string());
886
887        registry
888            .register("test_counter".to_string(), counter)
889            .unwrap();
890
891        let metrics = registry.get_all_metrics().unwrap();
892        assert_eq!(metrics.len(), 1);
893        assert_eq!(metrics[0].name, "test_counter");
894    }
895
896    #[test]
897    fn test_healthmonitor() {
898        let monitor = HealthMonitor::new();
899
900        // Register memory health check
901        let memory_check = MemoryHealthCheck::new(0.8, 0.95);
902        monitor.register_check(memory_check).unwrap();
903
904        let results = monitor.check_all().unwrap();
905        assert_eq!(results.len(), 1);
906        assert_eq!(results[0].name, "memory");
907    }
908
909    #[test]
910    fn test_prometheus_export() {
911        let registry = MetricsRegistry::new();
912        let counter = Counter::new("test_counter".to_string());
913        counter.inc();
914
915        registry
916            .register("test_counter".to_string(), counter)
917            .unwrap();
918
919        let prometheus_output = registry.export_prometheus().unwrap();
920        assert!(prometheus_output.contains("test_counter"));
921        assert!(prometheus_output.contains("counter"));
922    }
923}