use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::hash::Hash;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MetricType {
CpuUsage,
MemoryUsage,
HeapMemory,
ExecutionTime,
ConcurrentTasks,
RequestsPerSecond,
CacheHitRate,
GcTime,
V8HeapSize,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricValue {
pub metric_type: MetricType,
pub value: f64,
pub timestamp: u64,
pub tags: HashMap<String, _>,
}
#[derive(Debug, Clone)]
pub struct AggregatedMetric {
pub metric_type: MetricType,
pub avg: f64,
pub min: f64,
pub max: f64,
pub p50: f64,
pub p90: f64,
pub p95: f64,
pub p99: f64,
pub count: u64,
pub window: Duration,
}
#[derive(Debug, Clone)]
pub struct ThresholdConfig {
pub metric_type: MetricType,
pub warning: f64,
pub critical: f64,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub struct MonitorConfig {
pub aggregation_window: Duration,
pub retention_period: Duration,
pub max_metrics: usize,
pub thresholds: Vec<ThresholdConfig>,
}
#[derive(Debug)]
pub struct PerformanceMonitor {
config: MonitorConfig,
raw_metrics: Arc<Mutex<VecDeque<MetricValue>>>,
aggregated_metrics: Arc<Mutex<HashMap<String, _>>>,
stats: Arc<Mutex<CollectionStats>>,
}
#[derive(Debug, Clone)]
pub struct CollectionStats {
pub total_collections: u64,
pub total_metrics: u64,
pub last_collection_time: Option<Instant>,
pub error_count: u64,
}
impl PerformanceMonitor {
pub fn new(config: MonitorConfig) -> Self {
Self {
config,
raw_metrics: Arc::new(Mutex::new(VecDeque::new())),
aggregated_metrics: Arc::new(Mutex::new(HashMap::new())),
stats: Arc::new(Mutex::new(CollectionStats {
total_collections: 0,
total_metrics: 0,
last_collection_time: None,
error_count: 0,
})),
}
}
pub fn with_default_config() -> Self {
let config: _ = MonitorConfig {
aggregation_window: Duration::from_secs(10),
retention_period: Duration::from_secs(3600), max_metrics: 10000,
thresholds: vec![
ThresholdConfig {
metric_type: MetricType::CpuUsage,
warning: 70.0,
critical: 90.0,
enabled: true,
},
ThresholdConfig {
metric_type: MetricType::MemoryUsage,
warning: 80.0,
critical: 95.0,
enabled: true,
},
ThresholdConfig {
metric_type: MetricType::ExecutionTime,
warning: 1000.0, critical: 5000.0, enabled: true,
},
],
};
Self::new(config)
}
pub fn collect_metric(&self, metric: MetricValue) -> Result<(), String> {
let mut raw_metrics = self.raw_metrics.lock().map_err(|e| e.to_string())?;
let mut stats = self.stats.lock().map_err(|e| e.to_string())?;
raw_metrics.push_back(metric.clone());
stats.total_metrics += 1;
while raw_metrics.len() > self.config.max_metrics {
raw_metrics.pop_front();
}
self.cleanup_old_metrics()?;
stats.total_collections += 1;
stats.last_collection_time = Some(Instant::now());
Ok(())
}
pub fn collect_metrics(&self, metrics: Vec<MetricValue>) -> Result<(), String> {
for metric in metrics {
self.collect_metric(metric)?;
}
Ok(())
}
pub fn get_real_time_metrics(&self) -> Result<Vec<MetricValue>, String> {
let raw_metrics: _ = self.raw_metrics.lock().map_err(|e| e.to_string())?;
Ok(raw_metrics.iter().cloned().collect())
}
pub fn get_aggregated_metrics(&self) -> Result<HashMap<String, _>, String> {
let aggregated_metrics: _ = self.aggregated_metrics.lock().map_err(|e| e.to_string())?;
Ok(aggregated_metrics.clone())
}
pub fn aggregate_metrics(&self) -> Result<(), String> {
let raw_metrics: _ = self.raw_metrics.lock().map_err(|e| e.to_string())?;
let mut aggregated_metrics = self.aggregated_metrics.lock().map_err(|e| e.to_string())?;
let mut grouped_metrics: HashMap<String, _> = HashMap::new();
for metric in raw_metrics.iter() {
grouped_metrics
.entry(metric.metric_type.clone())
.or_insert_with(Vec::new)
.push(metric.value);
}
for (metric_type, values) in grouped_metrics {
if values.is_empty() {
continue;
}
let mut sorted_values = values.clone();
sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let count: _ = sorted_values.len() as u64;
let sum: f64 = sorted_values.iter().sum();
let avg: _ = sum / count as f64;
let min: _ = sorted_values[0];
let max: _ = sorted_values[count as usize - 1];
let p50: _ = Self::calculate_percentile(&sorted_values, 0.50);
let p90: _ = Self::calculate_percentile(&sorted_values, 0.90);
let p95: _ = Self::calculate_percentile(&sorted_values, 0.95);
let p99: _ = Self::calculate_percentile(&sorted_values, 0.99);
let aggregated: _ = AggregatedMetric {
metric_type,
avg,
min,
max,
p50,
p90,
p95,
p99,
count,
window: self.config.aggregation_window,
};
aggregated_metrics.insert(aggregated.metric_type.clone(), aggregated);
}
Ok(())
}
fn calculate_percentile(sorted_values: &[f64], percentile: f64) -> f64 {
if sorted_values.is_empty() {
return 0.0;
}
let index: _ = (sorted_values.len() as f64 * percentile).floor() as usize;
let index: _ = std::cmp::min(index, sorted_values.len() - 1);
sorted_values[index]
}
pub fn check_thresholds(&self) -> Result<Vec<ThresholdViolation>, String> {
let aggregated_metrics: _ = self.aggregated_metrics.lock().map_err(|e| e.to_string())?;
let mut violations = Vec::new();
for threshold in &self.config.thresholds {
if !threshold.enabled {
continue;
}
if let Some(metric) = aggregated_metrics.get(&threshold.metric_type) {
let severity: _ = if metric.avg >= threshold.critical {
ThresholdSeverity::Critical
} else if metric.avg >= threshold.warning {
ThresholdSeverity::Warning
} else {
continue;
};
violations.push(ThresholdViolation {
metric_type: threshold.metric_type.clone(),
current_value: metric.avg,
threshold_value: match severity {
ThresholdSeverity::Critical => threshold.critical,
ThresholdSeverity::Warning => threshold.warning,
},
severity,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
});
}
}
Ok(violations)
}
fn cleanup_old_metrics(&self) -> Result<(), String> {
let mut raw_metrics = self.raw_metrics.lock().map_err(|e| e.to_string())?;
let _cutoff_time: _ = Instant::now()
.checked_sub(self.config.retention_period)
.unwrap();
while raw_metrics.len() > self.config.max_metrics / 2 {
raw_metrics.pop_front();
}
Ok(())
}
pub fn get_stats(&self) -> Result<CollectionStats, String> {
let stats: _ = self.stats.lock().map_err(|e| e.to_string())?;
Ok(stats.clone())
}
pub fn get_current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
#[derive(Debug, Clone)]
pub struct ThresholdViolation {
pub metric_type: MetricType,
pub current_value: f64,
pub threshold_value: f64,
pub severity: ThresholdSeverity,
pub timestamp: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ThresholdSeverity {
Warning,
Critical,
}
impl ThresholdSeverity {
pub fn as_str(&self) -> &'static str {
match self {
ThresholdSeverity::Warning => "WARNING",
ThresholdSeverity::Critical => "CRITICAL",
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_performance_monitor_creation() {
let monitor: _ = PerformanceMonitor::with_default_config();
assert!(monitor.get_stats().is_ok());
}
#[test]
fn test_collect_single_metric() {
let monitor: _ = PerformanceMonitor::with_default_config();
let metric: _ = MetricValue {
metric_type: MetricType::CpuUsage,
value: 50.0,
timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
};
assert!(monitor.collect_metric(metric).is_ok());
let stats: _ = monitor.get_stats().unwrap();
assert_eq!(stats.total_metrics, 1);
}
#[test]
fn test_collect_multiple_metrics() {
let monitor: _ = PerformanceMonitor::with_default_config();
let metrics: _ = vec![
MetricValue {
metric_type: MetricType::CpuUsage,
value: 50.0,
timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
},
MetricValue {
metric_type: MetricType::MemoryUsage,
value: 100.0,
timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
},
];
assert!(monitor.collect_metrics(metrics).is_ok());
let stats: _ = monitor.get_stats().unwrap();
assert_eq!(stats.total_metrics, 2);
}
#[test]
fn test_real_time_metrics() {
let monitor: _ = PerformanceMonitor::with_default_config();
let metric: _ = MetricValue {
metric_type: MetricType::CpuUsage,
value: 50.0,
timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
};
monitor.collect_metric(metric).unwrap();
let real_time_metrics: _ = monitor.get_real_time_metrics().unwrap();
assert_eq!(real_time_metrics.len(), 1);
}
#[test]
fn test_aggregate_metrics() {
let monitor: _ = PerformanceMonitor::with_default_config();
for i in 0..10 {
let metric: _ = MetricValue {
metric_type: MetricType::CpuUsage,
value: 50.0 + i as f64,
timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
};
monitor.collect_metric(metric).unwrap();
}
monitor.aggregate_metrics().unwrap();
let aggregated: _ = monitor.get_aggregated_metrics().unwrap();
assert!(aggregated.contains_key(&MetricType::CpuUsage));
let cpu_metric: _ = aggregated.get(&MetricType::CpuUsage).unwrap();
assert_eq!(cpu_metric.count, 10);
assert!(cpu_metric.avg > 0.0);
}
#[test]
fn test_threshold_detection() {
let monitor: _ = PerformanceMonitor::with_default_config();
let metric: _ = MetricValue {
metric_type: MetricType::CpuUsage,
value: 95.0, timestamp: PerformanceMonitor::get_current_timestamp(),
tags: HashMap::new(),
};
monitor.collect_metric(metric).unwrap();
monitor.aggregate_metrics().unwrap();
let violations: _ = monitor.check_thresholds().unwrap();
assert!(!violations.is_empty());
assert_eq!(violations[0].severity, ThresholdSeverity::Critical);
}
#[test]
fn test_percentile_calculation() {
let values: _ = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let p50: _ = PerformanceMonitor::calculate_percentile(&values, 0.50);
let p90: _ = PerformanceMonitor::calculate_percentile(&values, 0.90);
let p95: _ = PerformanceMonitor::calculate_percentile(&values, 0.95);
assert_eq!(p50, 5.0);
assert_eq!(p90, 9.0);
assert_eq!(p95, 9.5);
}
#[test]
fn test_custom_metric_type() {
let custom_type: _ = MetricType::Custom("custom_metric".to_string());
assert_eq!(custom_type, MetricType::Custom("custom_metric".to_string()));
}
}