Add grafana
This commit is contained in:
parent
69de6d75af
commit
ba79df5966
3 changed files with 435 additions and 13 deletions
99
server-rs/src/metrics.rs
Normal file
99
server-rs/src/metrics.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
use axum::http::StatusCode;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use metrics::{counter, gauge, histogram};
|
||||
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
|
||||
use std::time::Instant;
|
||||
|
||||
/// Initialize the Prometheus metrics exporter and return a handle for rendering metrics.
|
||||
pub fn init_metrics() -> PrometheusHandle {
|
||||
PrometheusBuilder::new()
|
||||
.install_recorder()
|
||||
.expect("Failed to install Prometheus recorder")
|
||||
}
|
||||
|
||||
/// Middleware to track HTTP request metrics.
|
||||
pub async fn track_metrics(request: Request<Body>, next: Next) -> Response {
|
||||
let path = request.uri().path().to_string();
|
||||
let method = request.method().to_string();
|
||||
|
||||
// Skip metrics endpoint itself to avoid recursion
|
||||
if path == "/metrics" {
|
||||
return next.run(request).await;
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let response = next.run(request).await;
|
||||
let duration = start.elapsed();
|
||||
|
||||
let status = response.status().as_u16().to_string();
|
||||
|
||||
// Normalize path for metrics (avoid high cardinality from dynamic segments)
|
||||
let normalized_path = normalize_path(&path);
|
||||
|
||||
// Record metrics
|
||||
histogram!("http_request_duration_seconds", "method" => method.clone(), "path" => normalized_path.clone(), "status" => status.clone())
|
||||
.record(duration.as_secs_f64());
|
||||
|
||||
counter!("http_requests_total", "method" => method, "path" => normalized_path, "status" => status)
|
||||
.increment(1);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Normalize paths to avoid high cardinality from dynamic segments.
|
||||
fn normalize_path(path: &str) -> String {
|
||||
if path.starts_with("/api/tiles/") && !path.ends_with("style.json") {
|
||||
return "/api/tiles/:z/:x/:y".to_string();
|
||||
}
|
||||
path.to_string()
|
||||
}
|
||||
|
||||
/// Handler for the /metrics endpoint.
|
||||
pub async fn metrics_handler(handle: PrometheusHandle) -> impl IntoResponse {
|
||||
// Update process metrics before rendering
|
||||
update_process_metrics();
|
||||
|
||||
match handle.render() {
|
||||
output if !output.is_empty() => (StatusCode::OK, output),
|
||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to render metrics".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update process-level metrics (memory, etc.)
|
||||
fn update_process_metrics() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
|
||||
for line in status.lines() {
|
||||
if let Some(value) = line.strip_prefix("VmRSS:") {
|
||||
if let Some(kb) = parse_kb(value) {
|
||||
gauge!("process_resident_memory_bytes").set((kb * 1024) as f64);
|
||||
}
|
||||
} else if let Some(value) = line.strip_prefix("VmSize:") {
|
||||
if let Some(kb) = parse_kb(value) {
|
||||
gauge!("process_virtual_memory_bytes").set((kb * 1024) as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn parse_kb(value: &str) -> Option<u64> {
|
||||
value
|
||||
.trim()
|
||||
.strip_suffix(" kB")
|
||||
.or_else(|| value.trim().strip_suffix("kB"))
|
||||
.and_then(|num| num.trim().parse().ok())
|
||||
}
|
||||
|
||||
/// Record a custom gauge for data loading (call at startup).
|
||||
pub fn record_data_stats(property_count: usize, poi_count: usize, postcode_count: usize) {
|
||||
gauge!("data_properties_loaded").set(property_count as f64);
|
||||
gauge!("data_pois_loaded").set(poi_count as f64);
|
||||
gauge!("data_postcodes_loaded").set(postcode_count as f64);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue