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