use axum::{extract::State, response::IntoResponse}; use metrics::{counter, gauge, histogram}; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; pub fn init() -> PrometheusHandle { PrometheusBuilder::new() .install_recorder() .expect("install prometheus recorder") } // --- Emission --------------------------------------------------------------- // The recorder was installed and `/metrics` served, but nothing was ever // measured. These are the emit sites the runner calls so an operator scraping // `/metrics` sees real build activity, not an empty page. /// A build was accepted and fanned out. pub fn build_started() { counter!("bento_builds_total").increment(1); } /// A target run reached a terminal state. `status` is `ok` / `failed` / /// `aborted`; `secs` is its wall-clock duration. pub fn target_finished(target: &str, status: &str, secs: f64) { counter!("bento_target_runs_total", "target" => target.to_owned(), "status" => status.to_owned()) .increment(1); histogram!("bento_target_run_seconds", "target" => target.to_owned()).record(secs); } /// Set the gauge of target tasks currently in flight (the live `active` count). pub fn set_in_flight(n: usize) { gauge!("bento_targets_in_flight").set(n as f64); } pub async fn render(State(handle): State) -> impl IntoResponse { handle.render() } /// A render handle that does NOT install the global recorder — the global is a /// process-wide singleton, so tests (many `AppState`s per process) must not /// call [`init`]. Metrics emitted via the macros are dropped under this handle, /// which is fine for tests that only exercise routes. #[cfg(test)] pub fn test_handle() -> PrometheusHandle { PrometheusBuilder::new().build_recorder().handle() }