#![allow(dead_code)]
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
};
use std::fmt::Write;
use std::sync::Arc;
use crate::AppState;
fn formatting_error_response() -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
[("content-type", "text/plain; version=0.0.4; charset=utf-8")],
"Failed to format metrics".to_string(),
)
.into_response()
}
#[utoipa::path(
get,
path = "/metrics",
responses(
(status = 200, description = "Prometheus metrics", content_type = "text/plain"),
(status = 500, description = "Internal server error")
),
tag = "metrics"
)]
pub async fn prometheus_metrics(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let mut output = String::new();
if write_metrics(&mut output, &state).is_err() {
return formatting_error_response();
}
(
StatusCode::OK,
[("content-type", "text/plain; version=0.0.4; charset=utf-8")],
output,
)
.into_response()
}
fn write_metrics(output: &mut String, state: &AppState) -> std::fmt::Result {
writeln!(output, "# VelesDB Prometheus Metrics")?;
writeln!(output)?;
write_server_info(output)?;
write_uptime_gauge(output)?;
let cache_metrics = state.db.plan_cache().metrics();
let cache_stats = state.db.plan_cache().stats();
write_cache_metrics(output, cache_metrics, &cache_stats)?;
output.push_str(&state.operational_metrics.export_prometheus());
output.push_str(&velesdb_core::metrics::global_guardrails_metrics().export_prometheus());
output.push_str(&state.traversal_metrics.export_prometheus());
output.push_str(&state.db.graph_metrics_prometheus());
output.push_str(&state.db.match_metrics_prometheus());
output.push_str(&state.query_duration_histogram.export_prometheus(
"velesdb_query_duration_seconds",
"Query execution duration in seconds",
));
Ok(())
}
fn write_server_info(output: &mut String) -> std::fmt::Result {
writeln!(output, "# HELP velesdb_info VelesDB server information")?;
writeln!(output, "# TYPE velesdb_info gauge")?;
writeln!(
output,
"velesdb_info{{version=\"{}\"}} 1",
env!("CARGO_PKG_VERSION")
)?;
writeln!(output)
}
fn write_uptime_gauge(output: &mut String) -> std::fmt::Result {
writeln!(output, "# HELP velesdb_up VelesDB server is up and running")?;
writeln!(output, "# TYPE velesdb_up gauge")?;
writeln!(output, "velesdb_up 1")?;
writeln!(output)
}
fn write_cache_metrics(
output: &mut String,
metrics: &velesdb_core::cache::PlanCacheMetrics,
stats: &velesdb_core::cache::LockFreeCacheStats,
) -> std::fmt::Result {
write_cache_hits(output, metrics)?;
write_cache_misses(output, metrics)?;
write_cache_size(output, stats)?;
write_cache_hit_rate(output, metrics)
}
fn write_cache_hits(
output: &mut String,
metrics: &velesdb_core::cache::PlanCacheMetrics,
) -> std::fmt::Result {
writeln!(
output,
"# HELP velesdb_plan_cache_hits_total Plan cache hits"
)?;
writeln!(output, "# TYPE velesdb_plan_cache_hits_total counter")?;
writeln!(output, "velesdb_plan_cache_hits_total {}", metrics.hits())?;
writeln!(output)
}
fn write_cache_misses(
output: &mut String,
metrics: &velesdb_core::cache::PlanCacheMetrics,
) -> std::fmt::Result {
writeln!(
output,
"# HELP velesdb_plan_cache_misses_total Plan cache misses"
)?;
writeln!(output, "# TYPE velesdb_plan_cache_misses_total counter")?;
writeln!(
output,
"velesdb_plan_cache_misses_total {}",
metrics.misses()
)?;
writeln!(output)
}
fn write_cache_size(
output: &mut String,
stats: &velesdb_core::cache::LockFreeCacheStats,
) -> std::fmt::Result {
writeln!(
output,
"# HELP velesdb_plan_cache_size Current number of cached plans"
)?;
writeln!(output, "# TYPE velesdb_plan_cache_size gauge")?;
writeln!(
output,
"velesdb_plan_cache_size {}",
stats.l1_size + stats.l2_size
)?;
writeln!(output)
}
fn write_cache_hit_rate(
output: &mut String,
metrics: &velesdb_core::cache::PlanCacheMetrics,
) -> std::fmt::Result {
writeln!(
output,
"# HELP velesdb_plan_cache_hit_rate Plan cache hit rate"
)?;
writeln!(output, "# TYPE velesdb_plan_cache_hit_rate gauge")?;
writeln!(
output,
"velesdb_plan_cache_hit_rate {:.4}",
metrics.hit_rate()
)?;
writeln!(output)
}
pub async fn health_metrics() -> impl IntoResponse {
let mut output = String::new();
if writeln!(output, "# HELP velesdb_up VelesDB server is up").is_err()
|| writeln!(output, "# TYPE velesdb_up gauge").is_err()
|| writeln!(output, "velesdb_up 1").is_err()
|| writeln!(output).is_err()
{
return formatting_error_response();
}
(
StatusCode::OK,
[("content-type", "text/plain; version=0.0.4; charset=utf-8")],
output,
)
.into_response()
}
#[cfg(test)]
#[path = "metrics_tests.rs"]
mod tests;