1use 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#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum MetricType {
19 Counter,
21 Gauge,
23 Histogram,
25 Timer,
27 Summary,
29 Throughput,
31 Latency,
33 Cpu,
35 Memory,
37}
38
39#[derive(Debug, Clone)]
41pub enum MetricValue {
42 Integer(i64),
44 Float(f64),
46 Duration(Duration),
48 Boolean(bool),
50 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#[derive(Debug, Clone)]
68pub struct MetricPoint {
69 pub name: String,
71 pub metric_type: MetricType,
73 pub value: MetricValue,
75 pub timestamp: SystemTime,
77 pub labels: HashMap<String, String>,
79 pub help: Option<String>,
81}
82
83pub struct Counter {
85 value: AtomicU64,
86 name: String,
87 labels: HashMap<String, String>,
88}
89
90impl Counter {
91 pub fn new(name: String) -> Self {
93 Self {
94 value: AtomicU64::new(0),
95 name,
96 labels: HashMap::new(),
97 }
98 }
99
100 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 pub fn inc(&self) {
111 self.value.fetch_add(1, Ordering::Relaxed);
112 }
113
114 pub fn add(&self, amount: u64) {
116 self.value.fetch_add(amount, Ordering::Relaxed);
117 }
118
119 pub fn get(&self) -> u64 {
121 self.value.load(Ordering::Relaxed)
122 }
123
124 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
137pub struct Gauge {
139 value: AtomicU64, name: String,
141 labels: HashMap<String, String>,
142}
143
144impl Gauge {
145 pub fn new(name: String) -> Self {
147 Self {
148 value: AtomicU64::new(0),
149 name,
150 labels: HashMap::new(),
151 }
152 }
153
154 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 pub fn set(&self, value: f64) {
165 self.value.store(value.to_bits(), Ordering::Relaxed);
166 }
167
168 pub fn inc(&self) {
170 let current = f64::from_bits(self.value.load(Ordering::Relaxed));
171 self.set(current + 1.0);
172 }
173
174 pub fn dec(&self) {
176 let current = f64::from_bits(self.value.load(Ordering::Relaxed));
177 self.set(current - 1.0);
178 }
179
180 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 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 pub fn get(&self) -> f64 {
194 f64::from_bits(self.value.load(Ordering::Relaxed))
195 }
196
197 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
210pub struct Histogram {
212 buckets: Vec<(f64, AtomicU64)>, sum: AtomicU64, count: AtomicU64,
215 name: String,
216 labels: HashMap<String, String>,
217}
218
219impl Histogram {
220 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 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 pub fn observe(&self, value: f64) {
257 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 for (upper_bound, count) in &self.buckets {
265 if value <= *upper_bound {
266 count.fetch_add(1, Ordering::Relaxed);
267 }
268 }
269 }
270
271 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#[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
301pub struct Timer {
303 histogram: Histogram,
304}
305
306impl Timer {
307 pub fn new(name: String) -> Self {
309 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 pub fn start(&self) -> TimerGuard {
332 TimerGuard {
333 timer: self,
334 start_time: Instant::now(),
335 }
336 }
337
338 pub fn observe(&self, duration: Duration) {
340 self.histogram.observe(duration.as_secs_f64());
341 }
342
343 pub fn get_stats(&self) -> HistogramStats {
345 self.histogram.get_stats()
346 }
347}
348
349pub 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
362pub struct MetricsRegistry {
364 metrics: RwLock<HashMap<String, Box<dyn MetricProvider + Send + Sync>>>,
365}
366
367pub trait MetricProvider {
369 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 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 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 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 pub fn new() -> Self {
450 Self {
451 metrics: RwLock::new(HashMap::new()),
452 }
453 }
454
455 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 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 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 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 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 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 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#[derive(Debug, Clone, PartialEq)]
552pub enum HealthStatus {
553 Healthy,
555 Warning,
557 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#[derive(Debug, Clone)]
573pub struct HealthCheck {
574 pub name: String,
576 pub status: HealthStatus,
578 pub message: String,
580 pub timestamp: SystemTime,
582 pub duration: Duration,
584}
585
586pub 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
594pub trait HealthChecker {
596 fn check(&self) -> CoreResult<HealthCheck>;
598
599 fn name(&self) -> &str;
601}
602
603impl HealthMonitor {
604 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), }
611 }
612
613 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 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 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 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
679pub struct MemoryHealthCheck {
682 warning_threshold: f64,
683 criticalthreshold: f64,
684}
685
686impl MemoryHealthCheck {
687 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 #[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; 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
741static GLOBAL_METRICS_REGISTRY: std::sync::LazyLock<MetricsRegistry> =
743 std::sync::LazyLock::new(MetricsRegistry::new);
744
745static GLOBAL_HEALTH_MONITOR: std::sync::LazyLock<HealthMonitor> = std::sync::LazyLock::new(|| {
747 let monitor = HealthMonitor::new();
748
749 let _ = monitor.register_check(MemoryHealthCheck::new(0.8, 0.95));
751
752 monitor
753});
754
755#[allow(dead_code)]
757pub fn global_metrics_registry() -> &'static MetricsRegistry {
758 &GLOBAL_METRICS_REGISTRY
759}
760
761#[allow(dead_code)]
763pub fn global_healthmonitor() -> &'static HealthMonitor {
764 &GLOBAL_HEALTH_MONITOR
765}
766
767#[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#[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#[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#[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 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}