1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum MetricType {
16 Counter,
18 Gauge,
20 Histogram,
22 Timer,
24 Summary,
26}
27
28#[derive(Debug, Clone)]
30pub enum MetricValue {
31 Integer(i64),
33 Float(f64),
35 Duration(Duration),
37 Boolean(bool),
39 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#[derive(Debug, Clone)]
57pub struct MetricPoint {
58 pub name: String,
60 pub metric_type: MetricType,
62 pub value: MetricValue,
64 pub timestamp: SystemTime,
66 pub labels: HashMap<String, String>,
68 pub help: Option<String>,
70}
71
72pub struct Counter {
74 value: AtomicU64,
75 name: String,
76 labels: HashMap<String, String>,
77}
78
79impl Counter {
80 pub fn new(name: String) -> Self {
82 Self {
83 value: AtomicU64::new(0),
84 name,
85 labels: HashMap::new(),
86 }
87 }
88
89 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 pub fn inc(&self) {
100 self.value.fetch_add(1, Ordering::Relaxed);
101 }
102
103 pub fn add(&self, amount: u64) {
105 self.value.fetch_add(amount, Ordering::Relaxed);
106 }
107
108 pub fn get(&self) -> u64 {
110 self.value.load(Ordering::Relaxed)
111 }
112
113 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
126pub struct Gauge {
128 value: AtomicU64, name: String,
130 labels: HashMap<String, String>,
131}
132
133impl Gauge {
134 pub fn new(name: String) -> Self {
136 Self {
137 value: AtomicU64::new(0),
138 name,
139 labels: HashMap::new(),
140 }
141 }
142
143 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 pub fn set(&self, value: f64) {
154 self.value.store(value.to_bits(), Ordering::Relaxed);
155 }
156
157 pub fn inc(&self) {
159 let current = f64::from_bits(self.value.load(Ordering::Relaxed));
160 self.set(current + 1.0);
161 }
162
163 pub fn dec(&self) {
165 let current = f64::from_bits(self.value.load(Ordering::Relaxed));
166 self.set(current - 1.0);
167 }
168
169 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 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 pub fn get(&self) -> f64 {
183 f64::from_bits(self.value.load(Ordering::Relaxed))
184 }
185
186 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
199pub struct Histogram {
201 buckets: Vec<(f64, AtomicU64)>, sum: AtomicU64, count: AtomicU64,
204 name: String,
205 labels: HashMap<String, String>,
206}
207
208impl Histogram {
209 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 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 pub fn observe(&self, value: f64) {
246 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 for (upper_bound, count) in &self.buckets {
254 if value <= *upper_bound {
255 count.fetch_add(1, Ordering::Relaxed);
256 }
257 }
258 }
259
260 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#[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
290pub struct Timer {
292 histogram: Histogram,
293}
294
295impl Timer {
296 pub fn new(name: String) -> Self {
298 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 pub fn start(&self) -> TimerGuard {
321 TimerGuard {
322 timer: self,
323 start_time: Instant::now(),
324 }
325 }
326
327 pub fn observe(&self, duration: Duration) {
329 self.histogram.observe(duration.as_secs_f64());
330 }
331
332 pub fn get_stats(&self) -> HistogramStats {
334 self.histogram.get_stats()
335 }
336}
337
338pub 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
351pub struct MetricsRegistry {
353 metrics: RwLock<HashMap<String, Box<dyn MetricProvider + Send + Sync>>>,
354}
355
356pub trait MetricProvider {
358 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 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 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 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 pub fn new() -> Self {
439 Self {
440 metrics: RwLock::new(HashMap::new()),
441 }
442 }
443
444 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 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 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 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 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 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 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#[derive(Debug, Clone, PartialEq)]
537pub enum HealthStatus {
538 Healthy,
540 Warning,
542 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#[derive(Debug, Clone)]
558pub struct HealthCheck {
559 pub name: String,
561 pub status: HealthStatus,
563 pub message: String,
565 pub timestamp: SystemTime,
567 pub duration: Duration,
569}
570
571pub 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
579pub trait HealthChecker {
581 fn check(&self) -> CoreResult<HealthCheck>;
583
584 fn name(&self) -> &str;
586}
587
588impl HealthMonitor {
589 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), }
596 }
597
598 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 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 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 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
664pub struct MemoryHealthCheck {
667 warning_threshold: f64,
668 criticalthreshold: f64,
669}
670
671impl MemoryHealthCheck {
672 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 #[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; 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
726static GLOBAL_METRICS_REGISTRY: std::sync::LazyLock<MetricsRegistry> =
728 std::sync::LazyLock::new(MetricsRegistry::new);
729
730static GLOBAL_HEALTH_MONITOR: std::sync::LazyLock<HealthMonitor> = std::sync::LazyLock::new(|| {
732 let monitor = HealthMonitor::new();
733
734 let _ = monitor.register_check(MemoryHealthCheck::new(0.8, 0.95));
736
737 monitor
738});
739
740#[allow(dead_code)]
742pub fn global_metrics_registry() -> &'static MetricsRegistry {
743 &GLOBAL_METRICS_REGISTRY
744}
745
746#[allow(dead_code)]
748pub fn global_healthmonitor() -> &'static HealthMonitor {
749 &GLOBAL_HEALTH_MONITOR
750}
751
752#[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#[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#[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#[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 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}