//! Prometheus metrics for GuruConnect server //! //! This module exposes metrics for monitoring server health, performance, and usage. //! Metrics are exposed at the `/metrics` endpoint in Prometheus format. use prometheus_client::encoding::EncodeLabelSet; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; use prometheus_client::metrics::gauge::Gauge; use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; use prometheus_client::registry::Registry; use std::sync::Arc; /// Metrics labels for HTTP requests #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct RequestLabels { pub method: String, pub path: String, pub status: u16, } /// Metrics labels for session events #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct SessionLabels { pub status: String, // created, closed, failed, expired } /// Metrics labels for connection events #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct ConnectionLabels { pub conn_type: String, // agent, viewer, dashboard } /// Metrics labels for error tracking #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct ErrorLabels { pub error_type: String, // auth, database, websocket, protocol, internal } /// Metrics labels for database operations #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct DatabaseLabels { pub operation: String, // select, insert, update, delete pub status: String, // success, error } /// GuruConnect server metrics #[derive(Clone)] pub struct Metrics { // Request metrics pub requests_total: Family, pub request_duration_seconds: Family, // Session metrics pub sessions_total: Family, pub active_sessions: Gauge, pub session_duration_seconds: Histogram, // Connection metrics pub connections_total: Family, pub active_connections: Family, // Error metrics pub errors_total: Family, // Database metrics pub db_operations_total: Family, pub db_query_duration_seconds: Family, // System metrics pub uptime_seconds: Gauge, } impl Metrics { /// Create a new metrics instance and register all metrics pub fn new(registry: &mut Registry) -> Self { // Request metrics let requests_total = Family::::default(); registry.register( "guruconnect_requests_total", "Total number of HTTP requests", requests_total.clone(), ); let request_duration_seconds = Family::::new_with_constructor(|| { Histogram::new(exponential_buckets(0.001, 2.0, 10)) // 1ms to ~1s }); registry.register( "guruconnect_request_duration_seconds", "HTTP request duration in seconds", request_duration_seconds.clone(), ); // Session metrics let sessions_total = Family::::default(); registry.register( "guruconnect_sessions_total", "Total number of sessions", sessions_total.clone(), ); let active_sessions = Gauge::default(); registry.register( "guruconnect_active_sessions", "Number of currently active sessions", active_sessions.clone(), ); let session_duration_seconds = Histogram::new(exponential_buckets(1.0, 2.0, 15)); // 1s to ~9 hours registry.register( "guruconnect_session_duration_seconds", "Session duration in seconds", session_duration_seconds.clone(), ); // Connection metrics let connections_total = Family::::default(); registry.register( "guruconnect_connections_total", "Total number of WebSocket connections", connections_total.clone(), ); let active_connections = Family::::default(); registry.register( "guruconnect_active_connections", "Number of active WebSocket connections by type", active_connections.clone(), ); // Error metrics let errors_total = Family::::default(); registry.register( "guruconnect_errors_total", "Total number of errors by type", errors_total.clone(), ); // Database metrics let db_operations_total = Family::::default(); registry.register( "guruconnect_db_operations_total", "Total number of database operations", db_operations_total.clone(), ); let db_query_duration_seconds = Family::::new_with_constructor(|| { Histogram::new(exponential_buckets(0.0001, 2.0, 12)) // 0.1ms to ~400ms }); registry.register( "guruconnect_db_query_duration_seconds", "Database query duration in seconds", db_query_duration_seconds.clone(), ); // System metrics let uptime_seconds = Gauge::default(); registry.register( "guruconnect_uptime_seconds", "Server uptime in seconds", uptime_seconds.clone(), ); Self { requests_total, request_duration_seconds, sessions_total, active_sessions, session_duration_seconds, connections_total, active_connections, errors_total, db_operations_total, db_query_duration_seconds, uptime_seconds, } } /// Increment request counter pub fn record_request(&self, method: &str, path: &str, status: u16) { self.requests_total .get_or_create(&RequestLabels { method: method.to_string(), path: path.to_string(), status, }) .inc(); } /// Record request duration pub fn record_request_duration(&self, method: &str, path: &str, status: u16, duration_secs: f64) { self.request_duration_seconds .get_or_create(&RequestLabels { method: method.to_string(), path: path.to_string(), status, }) .observe(duration_secs); } /// Record session creation pub fn record_session_created(&self) { self.sessions_total .get_or_create(&SessionLabels { status: "created".to_string(), }) .inc(); self.active_sessions.inc(); } /// Record session closure pub fn record_session_closed(&self) { self.sessions_total .get_or_create(&SessionLabels { status: "closed".to_string(), }) .inc(); self.active_sessions.dec(); } /// Record session failure pub fn record_session_failed(&self) { self.sessions_total .get_or_create(&SessionLabels { status: "failed".to_string(), }) .inc(); } /// Record session duration pub fn record_session_duration(&self, duration_secs: f64) { self.session_duration_seconds.observe(duration_secs); } /// Record connection created pub fn record_connection_created(&self, conn_type: &str) { self.connections_total .get_or_create(&ConnectionLabels { conn_type: conn_type.to_string(), }) .inc(); self.active_connections .get_or_create(&ConnectionLabels { conn_type: conn_type.to_string(), }) .inc(); } /// Record connection closed pub fn record_connection_closed(&self, conn_type: &str) { self.active_connections .get_or_create(&ConnectionLabels { conn_type: conn_type.to_string(), }) .dec(); } /// Record an error pub fn record_error(&self, error_type: &str) { self.errors_total .get_or_create(&ErrorLabels { error_type: error_type.to_string(), }) .inc(); } /// Record database operation pub fn record_db_operation(&self, operation: &str, status: &str, duration_secs: f64) { let labels = DatabaseLabels { operation: operation.to_string(), status: status.to_string(), }; self.db_operations_total .get_or_create(&labels.clone()) .inc(); self.db_query_duration_seconds .get_or_create(&labels) .observe(duration_secs); } /// Update uptime metric pub fn update_uptime(&self, uptime_secs: i64) { self.uptime_seconds.set(uptime_secs); } } /// Global metrics state wrapped in Arc for sharing across threads pub type SharedMetrics = Arc;