//! 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_server::metrics::{ LOCAL_ROUTES, PEER_TCP, RIB_ATTRIBUTE_SETS, RIB_JOURNAL_ENTRIES, RIB_PREFIXES, RIB_ROUTES, }; use prometheus::{Encoder, TextEncoder}; use crate::AppState; pub async fn metricz(State(state): State) -> impl IntoResponse { // A scrape that cannot read a table fails. Serving the last known // size would hide that the daemon is broken from the one system whose // job is to notice. let gauge = |count: usize| i64::try_from(count).unwrap_or(i64::MAX); match state.local_rib.len().await { Ok(local) => LOCAL_ROUTES.set(gauge(local)), Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(), } RIB_ROUTES.set(gauge(state.rib.len())); RIB_PREFIXES.set(gauge(state.rib.prefix_count())); RIB_ATTRIBUTE_SETS.set(gauge(state.rib.attrs().len())); RIB_JOURNAL_ENTRIES.set(gauge(state.rib.journal_len())); sample_tcp(&state).await; let mut buf = Vec::new(); let encoder = TextEncoder::new(); match encoder.encode(&prometheus::gather(), &mut buf) { Ok(()) => ( StatusCode::OK, [(header::CONTENT_TYPE, encoder.format_type())], buf, ) .into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } /// Asks the kernel about every established session's TCP connection and /// sets the peer's gauges: one netlink round trip a session, at scrape /// time, so that there is history to look back on. A session the kernel /// has no connection for any more loses its series. async fn sample_tcp(state: &AppState) { let tcp = &*PEER_TCP; for session in state.registry.list() { let label = session.metric_label(); let connection = bgp_fib::sockdiag::tcp_connection(session.local_addr, session.peer.addr).await; let Ok(Some(c)) = connection else { tcp.remove(&label); continue; }; let seconds = |us: u64| us as f64 / 1e6; let int = |n: u64| i64::try_from(n).unwrap_or(i64::MAX); let label = [label.as_str()]; tcp.rtt_seconds .with_label_values(&label) .set(seconds(c.rtt.into())); tcp.min_rtt_seconds .with_label_values(&label) .set(seconds(c.min_rtt.into())); tcp.rtt_variance_seconds .with_label_values(&label) .set(seconds(c.rtt_variance.into())); tcp.cwnd_segments .with_label_values(&label) .set(c.cwnd.into()); tcp.mss_bytes.with_label_values(&label).set(c.mss.into()); tcp.send_window_bytes .with_label_values(&label) .set(c.send_window.into()); tcp.receive_window_bytes .with_label_values(&label) .set(c.receive_window.into()); tcp.send_queue_bytes .with_label_values(&label) .set(c.send_queue.into()); tcp.not_sent_bytes .with_label_values(&label) .set(c.not_sent.into()); tcp.receive_queue_bytes .with_label_values(&label) .set(c.recv_queue.into()); tcp.unacked_segments .with_label_values(&label) .set(c.unacked.into()); tcp.retransmitted_segments .with_label_values(&label) .set(c.total_retrans.into()); tcp.lost_segments .with_label_values(&label) .set(c.lost.into()); tcp.busy_seconds .with_label_values(&label) .set(seconds(c.busy)); tcp.receive_window_limited_seconds .with_label_values(&label) .set(seconds(c.limited_by_receive_window)); tcp.send_buffer_limited_seconds .with_label_values(&label) .set(seconds(c.limited_by_send_buffer)); tcp.sent_bytes .with_label_values(&label) .set(int(c.bytes_sent)); tcp.received_bytes .with_label_values(&label) .set(int(c.bytes_received)); tcp.delivery_rate_bytes_per_second .with_label_values(&label) .set(int(c.delivery_rate)); tcp.pacing_rate_bytes_per_second .with_label_values(&label) .set(int(c.pacing_rate)); } }