Files
ferrix/crates/web/src/metrics.rs
T
Rayhaan JaufeerallyandClaude Fable 5.1 41090966e4 Stop treating a dead RIB as an empty one
The RIB handles swallowed every failure of their actor: an UPDATE applied
to a stopped Adj-RIB-In vanished, and a snapshot of it came back empty.
Sessions stayed up, peers' routes went nowhere, and the status page showed
a healthy router with no routes. An actor stops only by panicking, so this
is the state the server would be in after a bug.

- Every operation on AdjRibIn and LocalRib returns Result<_, RibStopped>.
  LocalRibError is replaced by it.
- The server waits on both actors, the advertiser and the dialers. If one
  stops it sends every peer Cease / Administrative Shutdown and returns an
  error, so the process exits and can be restarted. Before, the tasks'
  handles were held and never looked at.
- A session that cannot store an UPDATE ends with the same Cease.
- The HTTP API answers 503 with the reason for anything that needs a table
  it cannot read, including /metricz: a scrape that fails is noticed, last
  known sizes are not.

Also:
- The routes of a session are now removed even if its task is torn down
  without running its cleanup (a panic), instead of staying in the table
  as routes of a peer that is gone.
- Table sizes for the status page and metrics are asked for as a count
  instead of copying the whole table to take its length.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-01 23:18:59 +02:00

39 lines
1.4 KiB
Rust

//! Prometheus scrape endpoint. Counters are updated by the server crate as
//! messages flow; table-size gauges are refreshed at scrape time.
use axum::extract::State;
use axum::http::{StatusCode, header};
use axum::response::IntoResponse;
use bgp_rib::RibStopped;
use bgp_server::metrics::{ADJ_RIB_IN_ROUTES, LOCAL_ROUTES};
use prometheus::{Encoder, TextEncoder};
use crate::AppState;
pub async fn metricz(State(state): State<AppState>) -> impl IntoResponse {
// A scrape that cannot read the tables fails. Serving the last known
// sizes would hide that the daemon is broken from the one system whose
// job is to notice.
let sizes = async {
Ok::<_, RibStopped>((state.local_rib.len().await?, state.adj_rib_in.len().await?))
};
match sizes.await {
Ok((local, received)) => {
LOCAL_ROUTES.set(i64::try_from(local).unwrap_or(i64::MAX));
ADJ_RIB_IN_ROUTES.set(i64::try_from(received).unwrap_or(i64::MAX));
}
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
}
let mut buf = Vec::new();
match TextEncoder::new().encode(&prometheus::gather(), &mut buf) {
Ok(()) => (
StatusCode::OK,
[(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
buf,
)
.into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}