use std::sync::atomic::Ordering;
use axum::{Json, extract::State, response::IntoResponse};
use fraiseql_core::db::traits::DatabaseAdapter;
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 rate_5m: f64,
pub rate_1h: f64,
pub rate_24h: 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: 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: PoolStats,
pub cache: CacheStats,
pub subscriptions: SubscriptionStats,
}
impl MetricsSummary {
#[must_use]
pub const fn zero() -> Self {
Self {
latency: LatencyStats {
p50_ms: 0,
p95_ms: 0,
p99_ms: 0,
},
errors: ErrorRates {
rate_5m: 0.0,
rate_1h: 0.0,
rate_24h: 0.0,
},
pool: PoolStats {
active: 0,
idle: 0,
max: 0,
utilization: 0.0,
},
cache: CacheStats {
hit_rate: 0.0,
entries: 0,
},
subscriptions: SubscriptionStats { active: 0 },
}
}
}
pub async fn summary_handler<A>(State(state): State<AppState<A>>) -> impl IntoResponse
where
A: DatabaseAdapter + Clone + Send + Sync + 'static,
{
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 {
rate_5m: error_rate,
rate_1h: error_rate,
rate_24h: error_rate,
};
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: 0, };
let pool = PoolStats {
active: 0,
idle: 0,
max: 0,
utilization: 0.0,
};
let subscriptions = SubscriptionStats { active: 0 };
MetricsSummary {
latency,
errors: errors_stats,
pool,
cache,
subscriptions,
}
}