From 7f9184371c898eb1fbe0730694654a9a93a913ab Mon Sep 17 00:00:00 2001 From: "Y.Horie" Date: Fri, 28 Aug 2026 23:14:30 +0900 Subject: [PATCH] feat: expose stub_status connection-state atomics via event::connection_stats Add a safe accessor for the seven `ngx_stat_*` globals nginx populates when built with `--with-http_stub_status_module`. Bindgen already emits the raw externs once that build flag is set, but consumers currently have to read them by hand with several `unsafe` casts; the new `event::connection_stats()` function returns a `ConnectionStats` snapshot in one call. Gated behind `#[cfg(ngx_feature = "stat_stub")]` so nginx builds without stub_status still link cleanly. Signed-off-by: Y.Horie --- src/event.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +++ 2 files changed, 138 insertions(+) create mode 100644 src/event.rs diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 00000000..49110d42 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,132 @@ +//! Helpers around nginx's event-loop globals. + +#[cfg(ngx_feature = "stat_stub")] +use crate::ffi::{ + ngx_atomic_t, ngx_stat_accepted, ngx_stat_active, ngx_stat_handled, ngx_stat_reading, + ngx_stat_requests, ngx_stat_waiting, ngx_stat_writing, +}; + +/// One snapshot of nginx's global connection-state atomics — the +/// same values that the `ngx_http_stub_status_module` exposes. +/// +/// The seven atomics are read independently, so the snapshot is +/// not consistent across counters; for monitoring use cases the +/// drift between reads is sub-microsecond and irrelevant. +#[cfg(ngx_feature = "stat_stub")] +#[derive(Clone, Copy, Debug, Default)] +#[non_exhaustive] +pub struct ConnectionStats { + /// Connections currently in use. + pub active: u64, + /// Connections currently reading a request header. + pub reading: u64, + /// Connections currently writing a response. + pub writing: u64, + /// Connections currently idle in keep-alive. + pub waiting: u64, + /// Total connections accepted since process start. + pub accepted: u64, + /// Total connections handled since process start. + pub handled: u64, + /// Total requests served since process start. + pub requests: u64, +} + +/// Sample the global `ngx_stat_*` connection counters. +/// +/// This is the programmatic equivalent of fetching the +/// `stub_status` response, suitable for plumbing into custom +/// exporters (Prometheus, statsd, etc.) without having to scrape +/// HTTP. Available only on nginx builds where +/// `ngx_http_stub_status_module` is compiled in. +#[cfg(ngx_feature = "stat_stub")] +pub fn connection_stats() -> ConnectionStats { + // SAFETY: the seven atomics are allocated and assigned by + // nginx core in the master process before workers fork, and + // are valid for the lifetime of the process. Each pointer is + // therefore stable to dereference once. + unsafe { + snapshot_from_ptrs( + ngx_stat_active, + ngx_stat_reading, + ngx_stat_writing, + ngx_stat_waiting, + ngx_stat_accepted, + ngx_stat_handled, + ngx_stat_requests, + ) + } +} + +/// Read the seven atomics through the supplied pointers. Exists +/// as its own function so unit tests can exercise the field +/// mapping with stack-allocated atomics, without depending on the +/// global `ngx_stat_*` symbols being resolvable in the test +/// binary. +/// +/// # Safety +/// +/// Every pointer must be valid for a non-volatile read of +/// `ngx_atomic_t`. The caller is responsible for upholding that +/// (in production callers always pass nginx-owned globals, which +/// satisfy the requirement by construction). +#[cfg(ngx_feature = "stat_stub")] +#[allow(clippy::unnecessary_cast)] // `ngx_atomic_t` is `c_ulong`, which is 32-bit on some targets. +unsafe fn snapshot_from_ptrs( + active: *const ngx_atomic_t, + reading: *const ngx_atomic_t, + writing: *const ngx_atomic_t, + waiting: *const ngx_atomic_t, + accepted: *const ngx_atomic_t, + handled: *const ngx_atomic_t, + requests: *const ngx_atomic_t, +) -> ConnectionStats { + unsafe { + ConnectionStats { + active: *active as u64, + reading: *reading as u64, + writing: *writing as u64, + waiting: *waiting as u64, + accepted: *accepted as u64, + handled: *handled as u64, + requests: *requests as u64, + } + } +} + +#[cfg(all(test, ngx_feature = "stat_stub"))] +mod tests { + use super::*; + + #[test] + fn snapshot_from_ptrs_maps_each_field() { + // Use distinct values so a field-to-field swap is caught. + let active: ngx_atomic_t = 7; + let reading: ngx_atomic_t = 1; + let writing: ngx_atomic_t = 2; + let waiting: ngx_atomic_t = 4; + let accepted: ngx_atomic_t = 100; + let handled: ngx_atomic_t = 99; + let requests: ngx_atomic_t = 250; + + let s = unsafe { + snapshot_from_ptrs( + &raw const active, + &raw const reading, + &raw const writing, + &raw const waiting, + &raw const accepted, + &raw const handled, + &raw const requests, + ) + }; + + assert_eq!(s.active, 7); + assert_eq!(s.reading, 1); + assert_eq!(s.writing, 2); + assert_eq!(s.waiting, 4); + assert_eq!(s.accepted, 100); + assert_eq!(s.handled, 99); + assert_eq!(s.requests, 250); + } +} diff --git a/src/lib.rs b/src/lib.rs index b0329b4b..b164b712 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,6 +133,12 @@ pub mod collections; /// utilities will generally align with the NGINX 'core' files and APIs. pub mod core; +/// The event module. +/// +/// Helpers around nginx's event-loop globals, currently the +/// `stub_status` connection-state atomics. +pub mod event; + /// The ffi module. /// /// This module provides scoped FFI bindings for NGINX symbols.