//! SLO monitoring and 99.95% availability module //! Implements comprehensive Service Level Objective tracking with real-time monitoring, //! error budget management, burn rate calculation, and automated alerting use crate::{PlatformConfig, PlatformResult, error::SloError}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime}; use tokio::sync::{Mutex, RwLock as AsyncRwLock}; use tokio::time::Interval; use tracing::info; use uuid::Uuid; const MAX_DATAPOINTS_PER_WINDOW: usize = 10_000; const HISTORY_RETENTION_HOURS: u64 = 7 * 24; /// SLA targets configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SlaTargets { pub availability_percentage: f64, pub max_latency_p50_ms: u64, pub max_latency_p99_ms: u64, pub max_latency_p999_ms: u64, pub max_error_rate_percentage: f64, pub error_budget_burn_rate_threshold: f64, pub downtime_budget_minutes_per_month: f64, } impl Default for SlaTargets { fn default() -> Self { Self { availability_percentage: 99.95, max_latency_p50_ms: 100, max_latency_p99_ms: 500, max_latency_p999_ms: 2000, max_error_rate_percentage: 0.1, error_budget_burn_rate_threshold: 2.0, downtime_budget_minutes_per_month: 43.8, // (100 - 99.95) / 100 * SECONDS_PER_MONTH / 60 } } } /// Availability metrics for a region or global #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AvailabilityMetrics { pub current_percentage: f64, pub total_requests: u64, pub successful_requests: u64, pub failed_requests: u64, pub sla_violated: bool, pub last_updated: DateTime, } /// Latency percentile metrics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LatencyMetrics { pub p50_ms: u64, pub p99_ms: u64, pub p999_ms: u64, pub sla_violated_p50: bool, pub sla_violated_p99: bool, pub sla_violated_p999: bool, pub sample_count: u64, pub last_updated: DateTime, } /// Error budget tracking #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorBudget { pub total_budget_minutes: f64, pub consumed_minutes: f64, pub remaining_percentage: f64, pub burn_rate_1h: f64, pub burn_rate_24h: f64, pub last_updated: DateTime, } /// Multi-window error rate tracking #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorRateMetrics { pub window_1m: f64, pub window_5m: f64, pub window_1h: f64, pub sla_violated_1m: bool, pub sla_violated_5m: bool, pub sla_violated_1h: bool, pub last_updated: DateTime, } /// Alert severity levels #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AlertSeverity { Info, Warning, Critical, } /// Alert action types #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AlertAction { pub action_type: String, pub target: String, pub executed_at: DateTime, } /// SLO violation alert #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SloViolation { pub id: Uuid, pub alert_type: String, pub severity: AlertSeverity, pub region: Option, pub metric: String, pub current_value: f64, pub threshold: f64, pub message: String, pub escalated: bool, pub notification_attempts: u32, pub actions_taken: Vec, pub created_at: DateTime, pub resolved_at: Option>, } /// Alert configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AlertConfiguration { pub warning_threshold_percentage: f64, pub critical_threshold_percentage: f64, pub escalation_delay_minutes: u64, pub notification_channels: Vec, } /// Burn rate configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BurnRateConfig { pub short_window_minutes: u64, pub long_window_minutes: u64, pub alert_threshold: f64, } /// Service level objective configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceLevelObjective { pub metric_name: String, pub target_value: f64, pub comparison: String, pub window_duration: Duration, } /// SLO configuration for the monitor #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SloConfiguration { pub availability_slo: ServiceLevelObjective, pub latency_slos: Vec, pub error_rate_slo: ServiceLevelObjective, pub burn_rate_config: BurnRateConfig, pub alert_config: AlertConfiguration, } /// Monitoring window for time-series data #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MonitoringWindow { OneMinute, FiveMinutes, OneHour, TwentyFourHours, } /// Percentile metrics calculation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PercentileMetrics { pub p50: f64, pub p99: f64, pub p999: f64, } /// Regional SLO metrics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RegionalSlo { pub region_id: String, pub availability_percentage: f64, pub latency_p99_ms: u64, pub error_rate_percentage: f64, pub sla_violated: bool, pub last_updated: DateTime, } /// Global SLO metrics (aggregate) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GlobalSlo { pub availability_percentage: f64, pub latency_p99_ms: u64, pub error_rate_percentage: f64, pub sla_violated: bool, pub regions_violating: Vec, pub last_updated: DateTime, } /// SLO breach prediction result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PredictionModel { pub breach_probability: f64, pub estimated_time_to_breach: Duration, pub predicted_metric: String, pub confidence_interval: (f64, f64), pub model_accuracy: f64, } /// Alert rule configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AlertRule { pub id: Uuid, pub name: String, pub condition: String, pub threshold: f64, pub duration: Duration, pub severity: AlertSeverity, pub enabled: bool, } /// Time-stamped data point for metrics #[derive(Debug, Clone)] struct DataPoint { timestamp: SystemTime, value: f64, success: bool, latency_ms: u64, } /// Time-series data storage for a region #[derive(Debug)] struct RegionMetrics { datapoints: VecDeque, total_requests: u64, total_successes: u64, total_failures: u64, downtime_minutes: f64, latency_samples: VecDeque, } impl RegionMetrics { fn new() -> Self { Self { datapoints: VecDeque::with_capacity(MAX_DATAPOINTS_PER_WINDOW), total_requests: 0, total_successes: 0, total_failures: 0, downtime_minutes: 0.0, latency_samples: VecDeque::with_capacity(MAX_DATAPOINTS_PER_WINDOW), } } fn add_request(&mut self, success: bool, latency: Duration, timestamp: SystemTime) { let latency_ms = latency.as_millis() as u64; let datapoint = DataPoint { timestamp, value: if success { 1.0 } else { 0.0 }, success, latency_ms, }; // Add to time series with capacity management if self.datapoints.len() >= MAX_DATAPOINTS_PER_WINDOW { self.datapoints.pop_front(); } self.datapoints.push_back(datapoint); // Update counters self.total_requests += 1; if success { self.total_successes += 1; } else { self.total_failures += 1; } // Add latency sample if self.latency_samples.len() >= MAX_DATAPOINTS_PER_WINDOW { self.latency_samples.pop_front(); } self.latency_samples.push_back(latency_ms); } fn add_downtime(&mut self, duration: Duration) { self.downtime_minutes += duration.as_secs_f64() / 60.0; } fn cleanup_old_data(&mut self, retention_duration: Duration) { let cutoff_time = SystemTime::now() - retention_duration; while let Some(front) = self.datapoints.front() { if front.timestamp < cutoff_time { self.datapoints.pop_front(); } else { break; } } } } /// Metrics collector for gathering system metrics #[derive(Debug, Clone)] pub struct MetricsCollector { region_metrics: Arc>>, global_metrics: Arc>, } impl Default for MetricsCollector { fn default() -> Self { Self::new() } } impl MetricsCollector { pub fn new() -> Self { Self { region_metrics: Arc::new(RwLock::new(HashMap::new())), global_metrics: Arc::new(RwLock::new(RegionMetrics::new())), } } } /// Main SLO monitor implementation #[derive(Debug)] pub struct SloMonitor { config: PlatformConfig, sla_targets: SlaTargets, metrics_collector: MetricsCollector, active_alerts: Arc>>, alert_config: Arc>>, running: Arc, cleanup_interval: Arc>>, } impl SloMonitor { /// Helper to get region metrics with error handling fn get_region_metrics( &self, _region: &str, ) -> PlatformResult>> { self.metrics_collector .region_metrics .read() .map_err(|_| crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), }) } /// Helper to get mutable region metrics fn get_region_metrics_mut( &self, _region: &str, ) -> PlatformResult>> { self.metrics_collector .region_metrics .write() .map_err(|_| crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), }) } /// Create new SLO monitor instance pub async fn new(config: &PlatformConfig) -> PlatformResult { info!("Initializing SLO Monitor with 99.95% availability target"); let sla_targets = config.sla_targets.clone(); let metrics_collector = MetricsCollector::new(); let active_alerts = Arc::new(AsyncRwLock::new(HashMap::new())); let alert_config = Arc::new(AsyncRwLock::new(None)); let running = Arc::new(std::sync::atomic::AtomicBool::new(false)); let cleanup_interval = Arc::new(Mutex::new(None)); Ok(Self { config: config.clone(), sla_targets, metrics_collector, active_alerts, alert_config, running, cleanup_interval, }) } /// Start the SLO monitoring service pub async fn start(&mut self) -> PlatformResult<()> { info!("Starting SLO Monitor"); self.running .store(true, std::sync::atomic::Ordering::SeqCst); // Start periodic cleanup task let cleanup_task = tokio::time::interval(Duration::from_secs(3600)); // Cleanup every hour *self.cleanup_interval.lock().await = Some(cleanup_task); let metrics_collector = self.metrics_collector.clone(); let running = self.running.clone(); // Spawn background cleanup task tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(3600)); while running.load(std::sync::atomic::Ordering::SeqCst) { interval.tick().await; // Cleanup old data let retention_duration = Duration::from_secs(HISTORY_RETENTION_HOURS * 3600); if let Ok(mut regions) = metrics_collector.region_metrics.write() { for (_, metrics) in regions.iter_mut() { metrics.cleanup_old_data(retention_duration); } } if let Ok(mut global) = metrics_collector.global_metrics.write() { global.cleanup_old_data(retention_duration); } } }); info!("SLO Monitor started successfully"); Ok(()) } /// Shutdown the SLO monitoring service pub async fn shutdown(&mut self) -> PlatformResult<()> { info!("Shutting down SLO Monitor"); self.running .store(false, std::sync::atomic::Ordering::SeqCst); // Clear cleanup interval *self.cleanup_interval.lock().await = None; info!("SLO Monitor shutdown complete"); Ok(()) } /// Get configured SLA targets pub fn get_sla_targets(&self) -> &SlaTargets { &self.sla_targets } /// Record a successful request pub async fn record_request_success( &self, region: &str, latency: Duration, ) -> PlatformResult<()> { self.record_request_success_with_timestamp(region, latency, SystemTime::now()) .await } /// Record a failed request pub async fn record_request_failure( &self, region: &str, latency: Duration, ) -> PlatformResult<()> { self.record_request_failure_with_timestamp(region, latency, SystemTime::now()) .await } /// Record a successful request with custom timestamp pub async fn record_request_success_with_timestamp( &self, region: &str, latency: Duration, timestamp: SystemTime, ) -> PlatformResult<()> { if !self.running.load(std::sync::atomic::Ordering::SeqCst) { return Err(crate::PlatformError::Slo(SloError::MonitoringFailure { component: "SloMonitor not running".to_string(), })); } // Update regional and global metrics { let mut regions = self.get_region_metrics_mut(region)?; regions .entry(region.to_string()) .or_insert_with(RegionMetrics::new) .add_request(true, latency, timestamp); } { let mut global = self.metrics_collector.global_metrics.write().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire global metrics lock".to_string(), } })?; global.add_request(true, latency, timestamp); } Ok(()) } /// Record a failed request with custom timestamp pub async fn record_request_failure_with_timestamp( &self, region: &str, latency: Duration, timestamp: SystemTime, ) -> PlatformResult<()> { if !self.running.load(std::sync::atomic::Ordering::SeqCst) { return Err(crate::PlatformError::Slo(SloError::MonitoringFailure { component: "SloMonitor not running".to_string(), })); } // Update regional and global metrics { let mut regions = self.get_region_metrics_mut(region)?; regions .entry(region.to_string()) .or_insert_with(RegionMetrics::new) .add_request(false, latency, timestamp); } { let mut global = self.metrics_collector.global_metrics.write().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire global metrics lock".to_string(), } })?; global.add_request(false, latency, timestamp); } Ok(()) } /// Record downtime for a region pub async fn record_downtime(&self, region: &str, duration: Duration) -> PlatformResult<()> { // Update regional and global metrics { let mut regions = self.get_region_metrics_mut(region)?; regions .entry(region.to_string()) .or_insert_with(RegionMetrics::new) .add_downtime(duration); } { let mut global = self.metrics_collector.global_metrics.write().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire global metrics lock".to_string(), } })?; global.add_downtime(duration); } Ok(()) } /// Get availability metrics for a region pub async fn get_availability_metrics( &self, region: &str, ) -> PlatformResult { let regions = self.get_region_metrics(region)?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let current_percentage = if region_metrics.total_requests > 0 { (region_metrics.total_successes as f64 / region_metrics.total_requests as f64) * 100.0 } else { 100.0 }; Ok(AvailabilityMetrics { current_percentage, total_requests: region_metrics.total_requests, successful_requests: region_metrics.total_successes, failed_requests: region_metrics.total_failures, sla_violated: current_percentage < self.sla_targets.availability_percentage, last_updated: Utc::now(), }) } /// Get availability metrics for a specific time period pub async fn get_availability_metrics_for_period( &self, region: &str, period: Duration, ) -> PlatformResult { let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let cutoff_time = SystemTime::now() - period; let mut total_requests = 0u64; let mut successful_requests = 0u64; for datapoint in ®ion_metrics.datapoints { if datapoint.timestamp >= cutoff_time { total_requests += 1; if datapoint.success { successful_requests += 1; } } } let failed_requests = total_requests - successful_requests; let current_percentage = if total_requests > 0 { (successful_requests as f64 / total_requests as f64) * 100.0 } else { 100.0 }; let sla_violated = current_percentage < self.sla_targets.availability_percentage; Ok(AvailabilityMetrics { current_percentage, total_requests, successful_requests, failed_requests, sla_violated, last_updated: Utc::now(), }) } /// Get latency metrics for a region pub async fn get_latency_metrics(&self, region: &str) -> PlatformResult { let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let sample_count = region_metrics.latency_samples.len() as u64; if sample_count == 0 { return Ok(LatencyMetrics { p50_ms: 0, p99_ms: 0, p999_ms: 0, sla_violated_p50: false, sla_violated_p99: false, sla_violated_p999: false, sample_count: 0, last_updated: Utc::now(), }); } let mut samples: Vec = region_metrics.latency_samples.iter().copied().collect(); samples.sort_unstable(); let p50_idx = (sample_count as f64 * 0.5).ceil() as usize - 1; let p99_idx = (sample_count as f64 * 0.99).ceil() as usize - 1; let p999_idx = (sample_count as f64 * 0.999).ceil() as usize - 1; let p50_ms = samples.get(p50_idx).copied().unwrap_or(0); let p99_ms = samples.get(p99_idx).copied().unwrap_or(0); let p999_ms = samples.get(p999_idx).copied().unwrap_or(0); let sla_violated_p50 = p50_ms > self.sla_targets.max_latency_p50_ms; let sla_violated_p99 = p99_ms > self.sla_targets.max_latency_p99_ms; let sla_violated_p999 = p999_ms > self.sla_targets.max_latency_p999_ms; Ok(LatencyMetrics { p50_ms, p99_ms, p999_ms, sla_violated_p50, sla_violated_p99, sla_violated_p999, sample_count, last_updated: Utc::now(), }) } /// Get error budget for a region pub async fn get_error_budget(&self, region: &str) -> PlatformResult { let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let total_budget_minutes = self.sla_targets.downtime_budget_minutes_per_month; let consumed_minutes = region_metrics.downtime_minutes; let remaining_percentage = ((total_budget_minutes - consumed_minutes) / total_budget_minutes * 100.0).max(0.0); // Calculate burn rates (simplified) let burn_rate_1h = if consumed_minutes > 0.0 { consumed_minutes / (30.0 * 24.0 * 60.0) * 100.0 } else { 0.0 }; let burn_rate_24h = burn_rate_1h; Ok(ErrorBudget { total_budget_minutes, consumed_minutes, remaining_percentage, burn_rate_1h, burn_rate_24h, last_updated: Utc::now(), }) } /// Calculate burn rate for a region over a time window pub async fn calculate_burn_rate(&self, region: &str, window: Duration) -> PlatformResult { let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let cutoff_time = SystemTime::now() - window; let mut total_requests = 0u64; let mut failed_requests = 0u64; for datapoint in ®ion_metrics.datapoints { if datapoint.timestamp >= cutoff_time { total_requests += 1; if !datapoint.success { failed_requests += 1; } } } let error_rate = if total_requests > 0 { (failed_requests as f64 / total_requests as f64) * 100.0 } else { 0.0 }; // Burn rate = actual error rate / SLA error rate allowance let burn_rate = error_rate / self.sla_targets.max_error_rate_percentage; // Generate alert if burn rate exceeds threshold if burn_rate > self.sla_targets.error_budget_burn_rate_threshold { let alert = SloViolation { id: Uuid::new_v4(), alert_type: "burn_rate_high".to_string(), severity: AlertSeverity::Warning, region: Some(region.to_string()), metric: "error_rate".to_string(), current_value: error_rate, threshold: self.sla_targets.max_error_rate_percentage, message: format!("High burn rate detected: {burn_rate:.2}x"), escalated: false, notification_attempts: 1, actions_taken: vec![], created_at: Utc::now(), resolved_at: None, }; let mut alerts = self.active_alerts.write().await; alerts.insert(alert.id, alert); } Ok(burn_rate) } /// Get error rates for multiple time windows pub async fn get_error_rates(&self, region: &str) -> PlatformResult { let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let now = SystemTime::now(); let windows = [ (Duration::from_secs(60), "1m"), (Duration::from_secs(300), "5m"), (Duration::from_secs(3600), "1h"), ]; let mut error_rates = vec![]; for (window_duration, _name) in &windows { let cutoff_time = now - *window_duration; let mut total = 0u64; let mut failures = 0u64; for datapoint in ®ion_metrics.datapoints { if datapoint.timestamp >= cutoff_time { total += 1; if !datapoint.success { failures += 1; } } } let error_rate = if total > 0 { (failures as f64 / total as f64) * 100.0 } else { 0.0 }; error_rates.push(error_rate); } let window_1m = error_rates.first().copied().unwrap_or(0.0); let window_5m = error_rates.get(1).copied().unwrap_or(0.0); let window_1h = error_rates.get(2).copied().unwrap_or(0.0); let sla_violated_1m = window_1m > self.sla_targets.max_error_rate_percentage; let sla_violated_5m = window_5m > self.sla_targets.max_error_rate_percentage; let sla_violated_1h = window_1h > self.sla_targets.max_error_rate_percentage; Ok(ErrorRateMetrics { window_1m, window_5m, window_1h, sla_violated_1m, sla_violated_5m, sla_violated_1h, last_updated: Utc::now(), }) } /// Configure alerting pub async fn configure_alerts(&mut self, config: AlertConfiguration) -> PlatformResult<()> { let mut alert_config = self.alert_config.write().await; *alert_config = Some(config); Ok(()) } /// Get active alerts pub async fn get_active_alerts(&self) -> PlatformResult> { let alerts = self.active_alerts.read().await; Ok(alerts.values().cloned().collect()) } /// Predict SLO breach pub async fn predict_slo_breach( &self, region: &str, horizon: Duration, ) -> PlatformResult { // Simplified prediction based on recent trend let regions = self.metrics_collector.region_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire region metrics lock".to_string(), } })?; let region_metrics = regions.get(region).ok_or_else(|| { crate::PlatformError::Slo(SloError::MonitoringFailure { component: format!("Region {region} not found"), }) })?; let recent_window = Duration::from_secs(3600); // Last hour let cutoff_time = SystemTime::now() - recent_window; let mut recent_error_rate = 0.0; let mut recent_total = 0u64; let mut recent_failures = 0u64; for datapoint in ®ion_metrics.datapoints { if datapoint.timestamp >= cutoff_time { recent_total += 1; if !datapoint.success { recent_failures += 1; } } } if recent_total > 0 { recent_error_rate = (recent_failures as f64 / recent_total as f64) * 100.0; } let breach_probability = if recent_error_rate > self.sla_targets.max_error_rate_percentage { 0.8 // High probability if already violating } else { recent_error_rate / self.sla_targets.max_error_rate_percentage * 0.5 }; let estimated_time_to_breach = if breach_probability > 0.7 { Duration::from_secs(3600) // 1 hour } else { horizon }; // Generate predictive alert if high probability if breach_probability > 0.7 { let alert = SloViolation { id: Uuid::new_v4(), alert_type: "slo_breach_predicted".to_string(), severity: AlertSeverity::Warning, region: Some(region.to_string()), metric: "availability".to_string(), current_value: recent_error_rate, threshold: self.sla_targets.max_error_rate_percentage, message: format!( "SLO breach predicted with {:.1}% probability", breach_probability * 100.0 ), escalated: false, notification_attempts: 1, actions_taken: vec![], created_at: Utc::now(), resolved_at: None, }; let mut alerts = self.active_alerts.write().await; alerts.insert(alert.id, alert); } Ok(PredictionModel { breach_probability, estimated_time_to_breach, predicted_metric: "availability".to_string(), confidence_interval: (breach_probability - 0.1, breach_probability + 0.1), model_accuracy: 0.75, }) } /// Get regional SLO metrics pub async fn get_regional_slo(&self, region: &str) -> PlatformResult { let availability = self.get_availability_metrics(region).await?; let latency = self.get_latency_metrics(region).await?; let error_rates = self.get_error_rates(region).await?; let sla_violated = availability.sla_violated || latency.sla_violated_p99 || error_rates.sla_violated_1h; Ok(RegionalSlo { region_id: region.to_string(), availability_percentage: availability.current_percentage, latency_p99_ms: latency.p99_ms, error_rate_percentage: error_rates.window_1h, sla_violated, last_updated: Utc::now(), }) } /// Get global SLO metrics (aggregate across all regions) pub async fn get_global_slo(&self) -> PlatformResult { let global = self.metrics_collector.global_metrics.read().map_err(|_| { crate::PlatformError::Internal { message: "Failed to acquire global metrics lock".to_string(), } })?; let total_requests = global.total_requests; let successful_requests = global.total_successes; let availability_percentage = if total_requests > 0 { (successful_requests as f64 / total_requests as f64) * 100.0 } else { 100.0 }; // Calculate global latency P99 (simplified) let mut samples: Vec = global.latency_samples.iter().copied().collect(); samples.sort_unstable(); let sample_count = samples.len(); let latency_p99_ms = if sample_count > 0 { let p99_idx = (sample_count as f64 * 0.99).ceil() as usize - 1; samples.get(p99_idx).copied().unwrap_or(0) } else { 0 }; // Global error rate calculation let error_rate_percentage = if total_requests > 0 { ((total_requests - successful_requests) as f64 / total_requests as f64) * 100.0 } else { 0.0 }; let sla_violated = availability_percentage < self.sla_targets.availability_percentage || latency_p99_ms > self.sla_targets.max_latency_p99_ms || error_rate_percentage > self.sla_targets.max_error_rate_percentage; Ok(GlobalSlo { availability_percentage, latency_p99_ms, error_rate_percentage, sla_violated, regions_violating: vec![], // Would need to check each region last_updated: Utc::now(), }) } /// Export metrics in Prometheus format pub async fn export_prometheus_metrics(&self) -> PlatformResult { let mut output = String::new(); let global_slo = self.get_global_slo().await?; let error_budget = self .get_error_budget("global") .await .unwrap_or(ErrorBudget { total_budget_minutes: self.sla_targets.downtime_budget_minutes_per_month, consumed_minutes: 0.0, remaining_percentage: 100.0, burn_rate_1h: 0.0, burn_rate_24h: 0.0, last_updated: Utc::now(), }); output.push_str(&format!( "slo_availability_percentage{{region=\"global\"}} {}\nslo_latency_p99_seconds{{region=\"global\"}} {}\nslo_latency_p50_seconds{{region=\"global\"}} {}\nslo_error_budget_remaining_percentage{{region=\"global\"}} {}\nslo_burn_rate{{region=\"global\"}} {}\n", global_slo.availability_percentage, global_slo.latency_p99_ms as f64 / 1000.0, 0.0, error_budget.remaining_percentage, error_budget.burn_rate_1h )); let regions = self.get_region_metrics("global")?; for (region_name, _) in regions.iter() { if let Ok(regional_slo) = self.get_regional_slo(region_name).await { output.push_str(&format!( "slo_availability_percentage{{region=\"{}\"}} {}\nslo_latency_p99_seconds{{region=\"{}\"}} {}\n", region_name, regional_slo.availability_percentage, region_name, regional_slo.latency_p99_ms as f64 / 1000.0 )); } } Ok(output) } }