use serde::Serialize; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio::sync::RwLock; /// Métrique individuelle avec historique #[derive(Debug, Clone, Serialize)] pub struct Metric { pub name: String, pub value: f64, pub timestamp: u64, pub labels: HashMap, } /// Agrégation de métriques par type #[derive(Debug, Clone, Serialize)] pub struct MetricSummary { pub name: String, pub count: u64, pub avg: f64, pub min: f64, pub max: f64, pub sum: f64, pub labels: HashMap, } /// Types de métriques supportées #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MetricType { Counter, Gauge, Histogram, Timer, } /// Gestionnaire de métriques en temps réel #[derive(Debug)] pub struct MetricsCollector { metrics: Arc>>>, counters: Arc>>, gauges: Arc>>, histograms: Arc>>>, retention_duration: Duration, } impl MetricsCollector { pub fn new(retention_duration: Duration) -> Self { Self { metrics: Arc::new(RwLock::new(HashMap::new())), counters: Arc::new(RwLock::new(HashMap::new())), gauges: Arc::new(RwLock::new(HashMap::new())), histograms: Arc::new(RwLock::new(HashMap::new())), retention_duration, } } /// Incrémente un compteur pub async fn increment_counter(&self, name: &str, labels: HashMap) { let key = self.create_key(name, &labels); let mut counters = self.counters.write().await; *counters.entry(key.clone()).or_insert(0) += 1; self.record_metric(name, *counters.get(&key).unwrap_or(&0) as f64, labels) .await; tracing::debug!(metric_name = %name, key = %key, "📊 Counter incrémenté"); } /// Met à jour une jauge pub async fn set_gauge(&self, name: &str, value: f64, labels: HashMap) { let key = self.create_key(name, &labels); let mut gauges = self.gauges.write().await; gauges.insert(key, value); self.record_metric(name, value, labels).await; tracing::debug!(metric_name = %name, value = %value, "📊 Gauge mise à jour"); } /// Ajoute une valeur à un histogramme pub async fn record_histogram(&self, name: &str, value: f64, labels: HashMap) { let key = self.create_key(name, &labels); let mut histograms = self.histograms.write().await; histograms.entry(key).or_default().push(value); self.record_metric(name, value, labels).await; tracing::debug!(metric_name = %name, value = %value, "📊 Valeur ajoutée à l'histogramme"); } /// Mesure le temps d'exécution d'une opération pub async fn time_operation( &self, name: &str, labels: HashMap, operation: F, ) -> T where F: std::future::Future, { let start = Instant::now(); let result = operation.await; let duration = start.elapsed().as_secs_f64(); self.record_histogram(name, duration, labels).await; result } /// Enregistre une métrique brute async fn record_metric(&self, name: &str, value: f64, labels: HashMap) { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let metric = Metric { name: name.to_string(), value, timestamp, labels, }; let mut metrics = self.metrics.write().await; metrics.entry(name.to_string()).or_default().push(metric); } /// Crée une clé unique pour une métrique avec ses labels fn create_key(&self, name: &str, labels: &HashMap) -> String { let mut key = name.to_string(); let mut label_pairs: Vec<_> = labels.iter().collect(); label_pairs.sort_by_key(|(k, _)| *k); for (k, v) in label_pairs { key.push_str(&format!("{}={}", k, v)); } key } /// Obtient un résumé d'une métrique pub async fn get_metric_summary(&self, name: &str) -> Option { let metrics = self.metrics.read().await; let metric_values = metrics.get(name)?; if metric_values.is_empty() { return None; } let values: Vec = metric_values.iter().map(|m| m.value).collect(); let count = values.len() as u64; let sum: f64 = values.iter().sum(); let avg = sum / count as f64; let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b)); let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); // Prendre les labels de la dernière métrique let labels = metric_values.last()?.labels.clone(); Some(MetricSummary { name: name.to_string(), count, avg, min, max, sum, labels, }) } /// Obtient toutes les métriques actives pub async fn get_all_metrics(&self) -> HashMap> { let metrics = self.metrics.read().await; metrics.clone() } /// Nettoie les métriques anciennes pub async fn cleanup_old_metrics(&self) { let cutoff_time = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() - self.retention_duration.as_secs(); let mut metrics = self.metrics.write().await; for values in metrics.values_mut() { values.retain(|m| m.timestamp > cutoff_time); } // Supprimer les entrées vides metrics.retain(|_, values| !values.is_empty()); tracing::debug!("🧹 Nettoyage des métriques anciennes effectué"); } } use sysinfo::{Pid, ProcessesToUpdate, System}; /// Métriques spécifiques au chat #[derive(Debug)] pub struct ChatMetrics { collector: MetricsCollector, system: Arc>, } impl Default for ChatMetrics { fn default() -> Self { Self::new() } } impl ChatMetrics { pub fn new() -> Self { let mut sys = System::new_all(); sys.refresh_all(); Self { collector: MetricsCollector::new(Duration::from_secs(24 * 3600)), system: Arc::new(RwLock::new(sys)), } } /// Connexion WebSocket établie pub async fn websocket_connected(&self, user_id: String) { let labels = HashMap::from([("user_id".to_string(), user_id)]); self.collector .increment_counter("websocket_connections_total", labels) .await; } /// Connexion WebSocket fermée pub async fn websocket_disconnected(&self, user_id: String) { let labels = HashMap::from([("user_id".to_string(), user_id)]); self.collector .increment_counter("websocket_disconnections_total", labels) .await; } /// Message envoyé (salon ou DM) pub async fn message_sent(&self, message_type: &str, room: Option<&str>) { let labels = HashMap::from([ ("message_type".to_string(), message_type.to_string()), ("room".to_string(), room.unwrap_or("dm").to_string()), ]); self.collector .increment_counter("messages_sent_total", labels) .await; } /// Erreur survenue pub async fn error_occurred(&self, error_type: &str, context: &str) { let labels = HashMap::from([ ("error_type".to_string(), error_type.to_string()), ("context".to_string(), context.to_string()), ]); self.collector .increment_counter("errors_total", labels) .await; } /// Rate limit déclenché pub async fn rate_limit_triggered(&self, user_id: String) { let labels = HashMap::from([("user_id".to_string(), user_id)]); self.collector .increment_counter("rate_limits_triggered_total", labels) .await; } /// Utilisateurs actifs pub async fn active_users(&self, count: u64) { let labels = HashMap::new(); self.collector .set_gauge("active_users", count as f64, labels) .await; } /// Salons actifs pub async fn active_rooms(&self, count: u64) { let labels = HashMap::new(); self.collector .set_gauge("active_rooms", count as f64, labels) .await; } /// Temps de traitement d'un message pub async fn message_processing_time(&self, duration: Duration, message_type: &str) { let labels = HashMap::from([("message_type".to_string(), message_type.to_string())]); self.collector .record_histogram( "message_processing_duration", duration.as_secs_f64(), labels, ) .await; } /// Taille d'un message pub async fn message_size(&self, size_bytes: usize, message_type: &str) { let labels = HashMap::from([("message_type".to_string(), message_type.to_string())]); self.collector .record_histogram("message_size_bytes", size_bytes as f64, labels) .await; } /// Obtient toutes les métriques pour l'API de monitoring pub async fn get_all_metrics(&self) -> HashMap> { self.collector.get_all_metrics().await } /// Nettoie les anciennes métriques pub async fn cleanup(&self) { self.collector.cleanup_old_metrics().await; } /// Mesure le temps d'une opération de base de données pub async fn time_db_operation( &self, operation_type: &str, future: impl std::future::Future, ) -> T { let labels = HashMap::from([("operation".to_string(), operation_type.to_string())]); self.collector .time_operation("database_operation_duration_seconds", labels, future) .await } /// Mesure le temps d'authentification pub async fn time_auth_operation(&self, future: impl std::future::Future) -> T { let labels = HashMap::new(); self.collector .time_operation("auth_operation_duration_seconds", labels, future) .await } /// Rafraîchit et retourne les métriques système (CPU, RAM) pub async fn get_system_metrics(&self) -> (u64, f64) { let mut sys = self.system.write().await; // Refresh specific info sys.refresh_cpu_usage(); sys.refresh_memory(); // Refresh specific process let pid = Pid::from(std::process::id() as usize); sys.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); // Mémoire utilisée en MB let memory = if let Some(process) = sys.process(pid) { process.memory() / 1024 / 1024 } else { sys.used_memory() / 1024 / 1024 }; // CPU global usage let cpu = sys.global_cpu_usage() as f64; (memory, cpu) } } /// Point d'API pour exposer les métriques (format Prometheus ou JSON) #[derive(Serialize)] pub struct MetricsExport { pub timestamp: u64, pub metrics: HashMap>, pub system_info: SystemInfo, } #[derive(Serialize)] pub struct SystemInfo { pub uptime_seconds: u64, pub memory_usage_mb: u64, pub cpu_usage_percent: f64, } impl MetricsExport { pub async fn new(metrics: &ChatMetrics, start_time: Instant) -> Self { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let metrics_data = metrics.get_all_metrics().await; // Récupérer les vraies métriques système let (memory_mb, cpu_percent) = metrics.get_system_metrics().await; let system_info = SystemInfo { uptime_seconds: start_time.elapsed().as_secs(), memory_usage_mb: memory_mb, cpu_usage_percent: cpu_percent, }; Self { timestamp, metrics: metrics_data, system_info, } } /// Exporte au format Prometheus pub fn to_prometheus_format(&self) -> String { let mut output = String::new(); for (name, metrics) in &self.metrics { if !metrics.is_empty() { output.push_str(&format!("# HELP {} Auto-generated metric\n", name)); output.push_str(&format!("# TYPE {} gauge\n", name)); // Calculs basiques sur les métriques let count = metrics.len(); let sum: f64 = metrics.iter().map(|m| m.value).sum(); let avg = sum / count as f64; output.push_str(&format!("{}_count {}\n", name, count)); output.push_str(&format!("{}_sum {}\n", name, sum)); output.push_str(&format!("{}_avg {}\n", name, avg)); } } // Métriques système output.push_str(&format!( "chat_server_uptime_seconds {}\n", self.system_info.uptime_seconds )); output.push_str(&format!( "chat_server_memory_usage_mb {}\n", self.system_info.memory_usage_mb )); output.push_str(&format!( "chat_server_cpu_usage_percent {}\n", self.system_info.cpu_usage_percent )); output } }