use std::fmt::Write as _;
use axum::extract::State;
use axum::http::header::CONTENT_TYPE;
use axum::response::IntoResponse;
use crate::routes::http::AppState;
use crate::routines::{svc_list_all_runs, FleetRunSummary, RunStatus};
use crate::utils::time::now_secs;
const PROMETHEUS_CONTENT_TYPE: &str = "text/plain; version=0.0.4; charset=utf-8";
const DURATION_BUCKETS_SECS: [u64; 9] = [5, 15, 30, 60, 120, 300, 600, 1800, 3600];
struct MetricsSnapshot<'input> {
uptime_secs: u64,
version: &'input str,
git_sha: &'input str,
machine: &'input str,
active_sessions: usize,
workbench_bytes: u64,
runs: &'input [FleetRunSummary],
cleanup_removed_total: u64,
cleanup_freed_bytes_total: u64,
}
#[utoipa::path(get, path = "/metrics",
responses((status = 200, description = "Prometheus text exposition format (version 0.0.4)", body = str)))]
pub async fn metrics(State(state): State<AppState>) -> impl IntoResponse {
let runs = svc_list_all_runs(&state.routines, Some(usize::MAX));
let machine = crate::machine::current_machine();
let (cleanup_removed_total, cleanup_freed_bytes_total) =
crate::routines::cleanup_sweep_totals();
let snapshot = MetricsSnapshot {
uptime_secs: now_secs().saturating_sub(state.uptime_start),
version: crate::build_info::VERSION,
git_sha: crate::build_info::GIT_SHA,
machine: machine.as_str(),
active_sessions: crate::routines::tmux_session_count(crate::routines::TMUX_SESSION_PREFIX),
workbench_bytes: crate::routines::workbenches_total_bytes(),
runs: &runs,
cleanup_removed_total,
cleanup_freed_bytes_total,
};
([(CONTENT_TYPE, PROMETHEUS_CONTENT_TYPE)], render(&snapshot))
}
#[derive(Default)]
struct RunStatusCounts {
success: u64,
failed: u64,
running: u64,
unknown: u64,
}
fn escape_label_value(value: &str) -> String {
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
}
fn render(snapshot: &MetricsSnapshot<'_>) -> String {
let mut out = String::new();
let _ = writeln!(
out,
"# HELP moadim_uptime_seconds Seconds since the daemon started."
);
let _ = writeln!(out, "# TYPE moadim_uptime_seconds gauge");
let _ = writeln!(out, "moadim_uptime_seconds {}", snapshot.uptime_secs);
let _ = writeln!(
out,
"# HELP moadim_build_info Daemon build metadata; the sample value is always 1."
);
let _ = writeln!(out, "# TYPE moadim_build_info gauge");
let _ = writeln!(
out,
r#"moadim_build_info{{version="{}",git_sha="{}",machine="{}"}} 1"#,
snapshot.version,
snapshot.git_sha,
escape_label_value(snapshot.machine)
);
let _ = writeln!(
out,
"# HELP moadim_active_sessions Number of live tmux agent sessions right now."
);
let _ = writeln!(out, "# TYPE moadim_active_sessions gauge");
let _ = writeln!(out, "moadim_active_sessions {}", snapshot.active_sessions);
let _ = writeln!(
out,
"# HELP moadim_workbench_bytes Total size in bytes of the workbench tree on disk."
);
let _ = writeln!(out, "# TYPE moadim_workbench_bytes gauge");
let _ = writeln!(out, "moadim_workbench_bytes {}", snapshot.workbench_bytes);
render_runs_total(&mut out, snapshot.runs);
render_run_duration_histogram(&mut out, snapshot.runs);
let _ = writeln!(
out,
"# HELP moadim_cleanup_removed_total Workbenches removed by cleanup sweeps since the daemon started."
);
let _ = writeln!(out, "# TYPE moadim_cleanup_removed_total counter");
let _ = writeln!(
out,
"moadim_cleanup_removed_total {}",
snapshot.cleanup_removed_total
);
let _ = writeln!(
out,
"# HELP moadim_cleanup_freed_bytes_total Bytes freed by cleanup sweeps since the daemon started."
);
let _ = writeln!(out, "# TYPE moadim_cleanup_freed_bytes_total counter");
let _ = writeln!(
out,
"moadim_cleanup_freed_bytes_total {}",
snapshot.cleanup_freed_bytes_total
);
out
}
fn render_runs_total(out: &mut String, runs: &[FleetRunSummary]) {
let mut counts = RunStatusCounts::default();
for run in runs {
match run.status {
RunStatus::Success => counts.success += 1,
RunStatus::Failed => counts.failed += 1,
RunStatus::Running => counts.running += 1,
RunStatus::Unknown => counts.unknown += 1,
}
}
let _ = writeln!(
out,
"# HELP moadim_runs_total Total routine runs observed, by outcome."
);
let _ = writeln!(out, "# TYPE moadim_runs_total counter");
let _ = writeln!(
out,
r#"moadim_runs_total{{status="success"}} {}"#,
counts.success
);
let _ = writeln!(
out,
r#"moadim_runs_total{{status="failed"}} {}"#,
counts.failed
);
let _ = writeln!(
out,
r#"moadim_runs_total{{status="running"}} {}"#,
counts.running
);
let _ = writeln!(
out,
r#"moadim_runs_total{{status="unknown"}} {}"#,
counts.unknown
);
}
fn render_run_duration_histogram(out: &mut String, runs: &[FleetRunSummary]) {
let durations: Vec<u64> = runs
.iter()
.filter_map(|run| {
run.finished_at
.map(|finished| finished.saturating_sub(run.started_at))
})
.collect();
let _ = writeln!(
out,
"# HELP moadim_run_duration_seconds Duration of finished routine runs, in seconds."
);
let _ = writeln!(out, "# TYPE moadim_run_duration_seconds histogram");
for bound in DURATION_BUCKETS_SECS {
let cumulative = durations.iter().filter(|&&secs| secs <= bound).count();
let _ = writeln!(
out,
r#"moadim_run_duration_seconds_bucket{{le="{bound}"}} {cumulative}"#
);
}
let _ = writeln!(
out,
r#"moadim_run_duration_seconds_bucket{{le="+Inf"}} {}"#,
durations.len()
);
let sum: u64 = durations.iter().sum();
let _ = writeln!(out, "moadim_run_duration_seconds_sum {sum}");
let _ = writeln!(out, "moadim_run_duration_seconds_count {}", durations.len());
}
#[cfg(test)]
#[path = "metrics_tests.rs"]
mod metrics_tests;