use std::sync::atomic::Ordering;
use axum::{Json, extract::State, response::IntoResponse};
use serde::{Deserialize, Serialize};
use crate::{metrics_server::MetricsCollector, routes::graphql::app_state::AppState};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatencyStats {
pub p50_ms: u64,
pub p95_ms: u64,
pub p99_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorRates {
pub lifetime: f64,
pub rate_5m: Option<f64>,
pub rate_1h: Option<f64>,
pub rate_24h: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolStats {
pub active: u32,
pub idle: u32,
pub max: u32,
pub utilization: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStats {
pub hit_rate: f64,
pub entries: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionStats {
pub active: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsSummary {
pub latency: LatencyStats,
pub errors: ErrorRates,
pub pool: Option<PoolStats>,
pub cache: CacheStats,
pub subscriptions: Option<SubscriptionStats>,
}
impl MetricsSummary {
#[must_use]
pub const fn zero() -> Self {
Self {
latency: LatencyStats {
p50_ms: 0,
p95_ms: 0,
p99_ms: 0,
},
errors: ErrorRates {
lifetime: 0.0,
rate_5m: None,
rate_1h: None,
rate_24h: None,
},
pool: None,
cache: CacheStats {
hit_rate: 0.0,
entries: None,
},
subscriptions: None,
}
}
}
pub async fn summary_handler(State(state): State<AppState>) -> impl IntoResponse {
Json(build_summary(&state.metrics))
}
pub(crate) fn build_summary(m: &MetricsCollector) -> MetricsSummary {
let latency = LatencyStats {
p50_ms: m.http_request_duration.estimate_quantile_us(0.5) / 1_000,
p95_ms: m.http_request_duration.estimate_quantile_us(0.95) / 1_000,
p99_ms: m.http_request_duration.estimate_quantile_us(0.99) / 1_000,
};
let total = m.queries_total.load(Ordering::Relaxed);
let errors = m.queries_error.load(Ordering::Relaxed);
#[allow(clippy::cast_precision_loss)] let error_rate = if total > 0 {
errors as f64 / total as f64
} else {
0.0
};
let errors_stats = ErrorRates {
lifetime: error_rate,
rate_5m: None,
rate_1h: None,
rate_24h: None,
};
let hits = m.cache_hits.load(Ordering::Relaxed);
let misses = m.cache_misses.load(Ordering::Relaxed);
#[allow(clippy::cast_precision_loss)] let hit_rate = if hits + misses > 0 {
hits as f64 / (hits + misses) as f64
} else {
0.0
};
let cache = CacheStats {
hit_rate,
entries: None, };
let pool = None;
let subscriptions = None;
MetricsSummary {
latency,
errors: errors_stats,
pool,
cache,
subscriptions,
}
}