use crate::error::{CoreError, CoreResult, ErrorContext};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::RwLock;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MetricType {
Counter,
Gauge,
Histogram,
Timer,
Summary,
Throughput,
Latency,
Cpu,
Memory,
}
#[derive(Debug, Clone)]
pub enum MetricValue {
Integer(i64),
Float(f64),
Duration(Duration),
Boolean(bool),
String(String),
}
impl fmt::Display for MetricValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MetricValue::Integer(v) => write!(f, "{v}"),
MetricValue::Float(v) => write!(f, "{v}"),
MetricValue::Duration(v) => write!(f, "{v:?}"),
MetricValue::Boolean(v) => write!(f, "{v}"),
MetricValue::String(v) => write!(f, "{v}"),
}
}
}
#[derive(Debug, Clone)]
pub struct MetricPoint {
pub name: String,
pub metric_type: MetricType,
pub value: MetricValue,
pub timestamp: SystemTime,
pub labels: HashMap<String, String>,
pub help: Option<String>,
}
pub struct Counter {
value: AtomicU64,
name: String,
labels: HashMap<String, String>,
}
impl Counter {
pub fn new(name: String) -> Self {
Self {
value: AtomicU64::new(0),
name,
labels: HashMap::new(),
}
}
pub fn with_labels(name: String, labels: HashMap<String, String>) -> Self {
Self {
value: AtomicU64::new(0),
name,
labels,
}
}
pub fn inc(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
pub fn add(&self, amount: u64) {
self.value.fetch_add(amount, Ordering::Relaxed);
}
pub fn get(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn to_metric_point(&self) -> MetricPoint {
MetricPoint {
name: self.name.clone(),
metric_type: MetricType::Counter,
value: MetricValue::Integer(self.get() as i64),
timestamp: SystemTime::now(),
labels: self.labels.clone(),
help: None,
}
}
}
pub struct Gauge {
value: AtomicU64, name: String,
labels: HashMap<String, String>,
}
impl Gauge {
pub fn new(name: String) -> Self {
Self {
value: AtomicU64::new(0),
name,
labels: HashMap::new(),
}
}
pub fn with_labels(name: String, labels: HashMap<String, String>) -> Self {
Self {
value: AtomicU64::new(0),
name,
labels,
}
}
pub fn set(&self, value: f64) {
self.value.store(value.to_bits(), Ordering::Relaxed);
}
pub fn inc(&self) {
let current = f64::from_bits(self.value.load(Ordering::Relaxed));
self.set(current + 1.0);
}
pub fn dec(&self) {
let current = f64::from_bits(self.value.load(Ordering::Relaxed));
self.set(current - 1.0);
}
pub fn add(&self, amount: f64) {
let current = f64::from_bits(self.value.load(Ordering::Relaxed));
self.set(current + amount);
}
pub fn sub(&self, amount: f64) {
let current = f64::from_bits(self.value.load(Ordering::Relaxed));
self.set(current - amount);
}
pub fn get(&self) -> f64 {
f64::from_bits(self.value.load(Ordering::Relaxed))
}
pub fn to_metric_point(&self) -> MetricPoint {
MetricPoint {
name: self.name.clone(),
metric_type: MetricType::Gauge,
value: MetricValue::Float(self.get()),
timestamp: SystemTime::now(),
labels: self.labels.clone(),
help: None,
}
}
}
pub struct Histogram {
buckets: Vec<(f64, AtomicU64)>, sum: AtomicU64, count: AtomicU64,
name: String,
labels: HashMap<String, String>,
}
impl Histogram {
pub fn new(name: String) -> Self {
let default_buckets = vec![
0.005,
0.01,
0.025,
0.05,
0.1,
0.25,
0.5,
1.0,
2.5,
5.0,
10.0,
f64::INFINITY,
];
Self::with_buckets(name, default_buckets)
}
pub fn with_buckets(name: String, buckets: Vec<f64>) -> Self {
let bucket_pairs = buckets
.into_iter()
.map(|b| (b, AtomicU64::new(0)))
.collect();
Self {
buckets: bucket_pairs,
sum: AtomicU64::new(0),
count: AtomicU64::new(0),
name,
labels: HashMap::new(),
}
}
pub fn observe(&self, value: f64) {
self.count.fetch_add(1, Ordering::Relaxed);
let current_sum = f64::from_bits(self.sum.load(Ordering::Relaxed));
self.sum
.store((current_sum + value).to_bits(), Ordering::Relaxed);
for (upper_bound, count) in &self.buckets {
if value <= *upper_bound {
count.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn get_stats(&self) -> HistogramStats {
let count = self.count.load(Ordering::Relaxed);
let sum = f64::from_bits(self.sum.load(Ordering::Relaxed));
let mean = if count > 0 { sum / count as f64 } else { 0.0 };
let bucket_counts: Vec<(f64, u64)> = self
.buckets
.iter()
.map(|(bound, count)| (*bound, count.load(Ordering::Relaxed)))
.collect();
HistogramStats {
count,
sum,
mean,
buckets: bucket_counts,
}
}
}
#[derive(Debug, Clone)]
pub struct HistogramStats {
pub count: u64,
pub sum: f64,
pub mean: f64,
pub buckets: Vec<(f64, u64)>,
}
pub struct Timer {
histogram: Histogram,
}
impl Timer {
pub fn new(name: String) -> Self {
let timing_buckets = vec![
0.001,
0.005,
0.01,
0.025,
0.05,
0.1,
0.25,
0.5,
1.0,
2.5,
5.0,
10.0,
f64::INFINITY,
];
Self {
histogram: Histogram::with_buckets(name, timing_buckets),
}
}
pub fn start(&self) -> TimerGuard {
TimerGuard {
timer: self,
start_time: Instant::now(),
}
}
pub fn observe(&self, duration: Duration) {
self.histogram.observe(duration.as_secs_f64());
}
pub fn get_stats(&self) -> HistogramStats {
self.histogram.get_stats()
}
}
pub struct TimerGuard<'a> {
timer: &'a Timer,
start_time: Instant,
}
impl Drop for TimerGuard<'_> {
fn drop(&mut self) {
let duration = self.start_time.elapsed();
self.timer.observe(duration);
}
}
pub struct MetricsRegistry {
metrics: RwLock<HashMap<String, Box<dyn MetricProvider + Send + Sync>>>,
}
pub trait MetricProvider {
fn get_metric_points(&self) -> Vec<MetricPoint>;
}
impl MetricProvider for Counter {
fn get_metric_points(&self) -> Vec<MetricPoint> {
vec![self.to_metric_point()]
}
}
impl MetricProvider for Gauge {
fn get_metric_points(&self) -> Vec<MetricPoint> {
vec![self.to_metric_point()]
}
}
impl MetricProvider for Histogram {
fn get_metric_points(&self) -> Vec<MetricPoint> {
let stats = self.get_stats();
let mut points = Vec::new();
points.push(MetricPoint {
name: {
let name = &self.name;
format!("{name}_count")
},
metric_type: MetricType::Counter,
value: MetricValue::Integer(stats.count as i64),
timestamp: SystemTime::now(),
labels: self.labels.clone(),
help: Some({
let name = &self.name;
format!("name: {name}")
}),
});
points.push(MetricPoint {
name: {
let name = &self.name;
format!("{name}_sum")
},
metric_type: MetricType::Counter,
value: MetricValue::Float(stats.sum),
timestamp: SystemTime::now(),
labels: self.labels.clone(),
help: Some({
let name = &self.name;
format!("name: {name}")
}),
});
for (bucket, count) in stats.buckets {
let mut bucket_labels = self.labels.clone();
bucket_labels.insert("le".to_string(), bucket.to_string());
points.push(MetricPoint {
name: {
let name = &self.name;
format!("{name}_bucket")
},
metric_type: MetricType::Counter,
value: MetricValue::Integer(count as i64),
timestamp: SystemTime::now(),
labels: bucket_labels,
help: Some({
let name = &self.name;
format!("name: {name}")
}),
});
}
points
}
}
impl MetricsRegistry {
pub fn new() -> Self {
Self {
metrics: RwLock::new(HashMap::new()),
}
}
pub fn register<T>(&self, name: String, metric: T) -> CoreResult<()>
where
T: MetricProvider + Send + Sync + 'static,
{
let mut metrics = self.metrics.write().map_err(|_| {
CoreError::ComputationError(ErrorContext::new("Failed to acquire metrics lock"))
})?;
metrics.insert(name, Box::new(metric));
Ok(())
}
pub fn get_all_metrics(&self) -> CoreResult<Vec<MetricPoint>> {
let metrics = self.metrics.read().map_err(|_| {
CoreError::ComputationError(ErrorContext::new("Failed to acquire metrics lock"))
})?;
let mut all_points = Vec::new();
for provider in metrics.values() {
all_points.extend(provider.get_metric_points());
}
Ok(all_points)
}
pub fn export_prometheus(&self) -> CoreResult<String> {
let metrics = self.get_all_metrics()?;
let mut output = String::new();
for metric in metrics {
if let Some(help) = &metric.help {
output.push_str(&format!(
"# HELP {name} {help}\n",
name = metric.name,
help = help
));
}
let type_str = match metric.metric_type {
MetricType::Counter => "counter",
MetricType::Gauge => "gauge",
MetricType::Histogram => "histogram",
MetricType::Timer => "histogram",
MetricType::Summary => "summary",
MetricType::Throughput => "gauge",
MetricType::Latency => "gauge",
MetricType::Cpu => "gauge",
MetricType::Memory => "gauge",
};
output.push_str(&format!(
"# TYPE {name} {type_str}\n",
name = metric.name,
type_str = type_str
));
let labels_str = if metric.labels.is_empty() {
String::new()
} else {
let label_pairs: Vec<String> = metric
.labels
.iter()
.map(|(k, v)| format!("{k}=\"{v}\""))
.collect();
format!("{{{}}}", label_pairs.join(","))
};
let timestamp = metric
.timestamp
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
output.push_str(&format!(
"{}{} {} {}\n",
metric.name, labels_str, metric.value, timestamp
));
}
Ok(output)
}
}
impl Default for MetricsRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum HealthStatus {
Healthy,
Warning,
Unhealthy,
}
impl fmt::Display for HealthStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "healthy"),
HealthStatus::Warning => write!(f, "warning"),
HealthStatus::Unhealthy => write!(f, "unhealthy"),
}
}
}
#[derive(Debug, Clone)]
pub struct HealthCheck {
pub name: String,
pub status: HealthStatus,
pub message: String,
pub timestamp: SystemTime,
pub duration: Duration,
}
pub struct HealthMonitor {
checks: RwLock<HashMap<String, Box<dyn HealthChecker + Send + Sync>>>,
results_cache: RwLock<HashMap<String, HealthCheck>>,
#[allow(dead_code)]
cache_duration: Duration,
}
pub trait HealthChecker {
fn check(&self) -> CoreResult<HealthCheck>;
fn name(&self) -> &str;
}
impl HealthMonitor {
pub fn new() -> Self {
Self {
checks: RwLock::new(HashMap::new()),
results_cache: RwLock::new(HashMap::new()),
cache_duration: Duration::from_secs(30), }
}
pub fn register_check<T>(&self, checker: T) -> CoreResult<()>
where
T: HealthChecker + Send + Sync + 'static,
{
let mut checks = self.checks.write().map_err(|_| {
CoreError::ComputationError(ErrorContext::new("Failed to acquire health checks lock"))
})?;
checks.insert(checker.name().to_string(), Box::new(checker));
Ok(())
}
pub fn check_all(&self) -> CoreResult<Vec<HealthCheck>> {
let checks = self.checks.read().map_err(|_| {
CoreError::ComputationError(ErrorContext::new("Failed to acquire health checks lock"))
})?;
let mut results = Vec::new();
for checker in checks.values() {
match checker.check() {
Ok(result) => results.push(result),
Err(error) => {
results.push(HealthCheck {
name: checker.name().to_string(),
status: HealthStatus::Unhealthy,
message: format!("error: {error}"),
timestamp: SystemTime::now(),
duration: Duration::ZERO,
});
}
}
}
if let Ok(mut cache) = self.results_cache.write() {
cache.clear();
for result in &results {
cache.insert(result.name.clone(), result.clone());
}
}
Ok(results)
}
pub fn overall_status(&self) -> CoreResult<HealthStatus> {
let results = self.check_all()?;
if results.iter().any(|r| r.status == HealthStatus::Unhealthy) {
Ok(HealthStatus::Unhealthy)
} else if results.iter().any(|r| r.status == HealthStatus::Warning) {
Ok(HealthStatus::Warning)
} else {
Ok(HealthStatus::Healthy)
}
}
}
impl Default for HealthMonitor {
fn default() -> Self {
Self::new()
}
}
pub struct MemoryHealthCheck {
warning_threshold: f64,
criticalthreshold: f64,
}
impl MemoryHealthCheck {
pub fn new(warning_threshold: f64, criticalthreshold: f64) -> Self {
Self {
warning_threshold,
criticalthreshold,
}
}
}
impl HealthChecker for MemoryHealthCheck {
fn check(&self) -> CoreResult<HealthCheck> {
let start_time = Instant::now();
#[cfg(feature = "memory_management")]
let pressure = {
let tracker = crate::memory::safety::global_safety_tracker();
tracker.memory_pressure()
};
#[cfg(not(feature = "memory_management"))]
let pressure = 0.0;
let (status, message) = if pressure >= self.criticalthreshold {
(
HealthStatus::Unhealthy,
format!("Memory usage critical: {:.1}%", pressure * 100.0),
)
} else if pressure >= self.warning_threshold {
(
HealthStatus::Warning,
format!("Memory usage high: {:.1}%", pressure * 100.0),
)
} else {
(
HealthStatus::Healthy,
format!("Memory usage normal: {:.1}%", pressure * 100.0),
)
};
Ok(HealthCheck {
name: "memory".to_string(),
status,
message,
timestamp: SystemTime::now(),
duration: start_time.elapsed(),
})
}
fn name(&self) -> &str {
"memory"
}
}
static GLOBAL_METRICS_REGISTRY: std::sync::LazyLock<MetricsRegistry> =
std::sync::LazyLock::new(MetricsRegistry::new);
static GLOBAL_HEALTH_MONITOR: std::sync::LazyLock<HealthMonitor> = std::sync::LazyLock::new(|| {
let monitor = HealthMonitor::new();
let _ = monitor.register_check(MemoryHealthCheck::new(0.8, 0.95));
monitor
});
#[allow(dead_code)]
pub fn global_metrics_registry() -> &'static MetricsRegistry {
&GLOBAL_METRICS_REGISTRY
}
#[allow(dead_code)]
pub fn global_healthmonitor() -> &'static HealthMonitor {
&GLOBAL_HEALTH_MONITOR
}
#[macro_export]
macro_rules! counter {
($name:expr) => {{
let counter = $crate::metrics::Counter::new($name.to_string());
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), counter);
counter
}};
($name:expr, $labels:expr) => {{
let counter = $crate::metrics::Counter::with_labels($name.to_string(), $labels);
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), counter);
counter
}};
}
#[macro_export]
macro_rules! gauge {
($name:expr) => {{
let gauge = $crate::metrics::Gauge::new($name.to_string());
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), gauge);
gauge
}};
($name:expr, $labels:expr) => {{
let gauge = $crate::metrics::Gauge::with_labels($name.to_string(), $labels);
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), gauge);
gauge
}};
}
#[macro_export]
macro_rules! histogram {
($name:expr) => {{
let histogram = $crate::metrics::Histogram::new($name.to_string());
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), histogram);
histogram
}};
($name:expr, $buckets:expr) => {{
let histogram = $crate::metrics::Histogram::with_buckets($name.to_string(), $buckets);
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), histogram);
histogram
}};
}
#[macro_export]
macro_rules! timer {
($name:expr) => {{
let timer = $crate::metrics::Timer::new($name.to_string());
let _ = $crate::metrics::global_metrics_registry().register($name.to_string(), timer);
timer
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter() {
let counter = Counter::new("test_counter".to_string());
assert_eq!(counter.get(), 0);
counter.inc();
assert_eq!(counter.get(), 1);
counter.add(5);
assert_eq!(counter.get(), 6);
}
#[test]
fn test_gauge() {
let gauge = Gauge::new("test_gauge".to_string());
assert_eq!(gauge.get(), 0.0);
gauge.set(std::f64::consts::PI);
assert!((gauge.get() - std::f64::consts::PI).abs() < f64::EPSILON);
gauge.inc();
assert!((gauge.get() - (std::f64::consts::PI + 1.0)).abs() < 1e-10);
gauge.dec();
assert!((gauge.get() - std::f64::consts::PI).abs() < 1e-10);
}
#[test]
fn test_histogram() {
let histogram = Histogram::new("test_histogram".to_string());
histogram.observe(0.5);
histogram.observe(1.5);
histogram.observe(2.5);
let stats = histogram.get_stats();
assert_eq!(stats.count, 3);
assert!((stats.sum - 4.5).abs() < f64::EPSILON);
assert!((stats.mean - 1.5).abs() < f64::EPSILON);
}
#[test]
fn test_timer() {
let timer = Timer::new("test_timer".to_string());
{
let _guard = timer.start();
std::thread::sleep(Duration::from_millis(10));
}
let stats = timer.get_stats();
assert_eq!(stats.count, 1);
assert!(stats.sum > 0.0);
}
#[test]
fn test_metrics_registry() {
let registry = MetricsRegistry::new();
let counter = Counter::new("test_counter".to_string());
registry
.register("test_counter".to_string(), counter)
.expect("Operation failed");
let metrics = registry.get_all_metrics().expect("Operation failed");
assert_eq!(metrics.len(), 1);
assert_eq!(metrics[0].name, "test_counter");
}
#[test]
fn test_healthmonitor() {
let monitor = HealthMonitor::new();
let memory_check = MemoryHealthCheck::new(0.8, 0.95);
monitor
.register_check(memory_check)
.expect("Operation failed");
let results = monitor.check_all().expect("Operation failed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].name, "memory");
}
#[test]
fn test_prometheus_export() {
let registry = MetricsRegistry::new();
let counter = Counter::new("test_counter".to_string());
counter.inc();
registry
.register("test_counter".to_string(), counter)
.expect("Operation failed");
let prometheus_output = registry.export_prometheus().expect("Operation failed");
assert!(prometheus_output.contains("test_counter"));
assert!(prometheus_output.contains("counter"));
}
}