use std::sync::atomic::{AtomicU64, Ordering};
pub static SUCCESSFUL_RESPONSES_TOTAL: AtomicU64 = AtomicU64::new(0);
pub static OK_RESPONSES_TOTAL: AtomicU64 = AtomicU64::new(0);
pub static HTTP_ACCEPTS_TOTAL: AtomicU64 = AtomicU64::new(0);
pub static HTTP_CONNECTIONS_OPEN: AtomicU64 = AtomicU64::new(0);
pub static HTTP_CONNECTIONS_CLOSED_TOTAL: AtomicU64 = AtomicU64::new(0);
#[inline]
pub fn record_response(status_code: u16) {
if (200..=399).contains(&status_code) {
SUCCESSFUL_RESPONSES_TOTAL.fetch_add(1, Ordering::Relaxed);
}
if status_code == 200 {
OK_RESPONSES_TOTAL.fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
pub fn record_accept() {
HTTP_ACCEPTS_TOTAL.fetch_add(1, Ordering::Relaxed);
HTTP_CONNECTIONS_OPEN.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_close() {
let prev = HTTP_CONNECTIONS_OPEN.load(Ordering::Relaxed);
if prev > 0 {
HTTP_CONNECTIONS_OPEN.fetch_sub(1, Ordering::Relaxed);
}
HTTP_CONNECTIONS_CLOSED_TOTAL.fetch_add(1, Ordering::Relaxed);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CounterSnapshot {
pub successful: u64,
pub ok: u64,
pub accepts: u64,
pub connections_open: u64,
pub connections_closed: u64,
}
pub fn snapshot() -> CounterSnapshot {
CounterSnapshot {
successful: SUCCESSFUL_RESPONSES_TOTAL.load(Ordering::Relaxed),
ok: OK_RESPONSES_TOTAL.load(Ordering::Relaxed),
accepts: HTTP_ACCEPTS_TOTAL.load(Ordering::Relaxed),
connections_open: HTTP_CONNECTIONS_OPEN.load(Ordering::Relaxed),
connections_closed: HTTP_CONNECTIONS_CLOSED_TOTAL.load(Ordering::Relaxed),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn reset_counters() {
SUCCESSFUL_RESPONSES_TOTAL.store(0, Ordering::Relaxed);
OK_RESPONSES_TOTAL.store(0, Ordering::Relaxed);
HTTP_ACCEPTS_TOTAL.store(0, Ordering::Relaxed);
HTTP_CONNECTIONS_OPEN.store(0, Ordering::Relaxed);
HTTP_CONNECTIONS_CLOSED_TOTAL.store(0, Ordering::Relaxed);
}
#[test]
fn record_response_classifies_2xx_as_successful() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_response(200);
record_response(204);
record_response(301);
let s = snapshot();
assert_eq!(s.successful, 3, "200, 204, 301 are all successful");
assert_eq!(s.ok, 1, "only one 200");
}
#[test]
fn record_response_excludes_4xx_5xx_from_successful() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_response(404);
record_response(429);
record_response(500);
record_response(503);
let s = snapshot();
assert_eq!(s.successful, 0);
assert_eq!(s.ok, 0);
}
#[test]
fn record_accept_increments() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_accept();
record_accept();
record_accept();
let s = snapshot();
assert_eq!(s.accepts, 3);
}
#[test]
fn snapshot_returns_current_values() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_response(200);
record_response(200);
record_accept();
let s = snapshot();
assert_eq!(s.successful, 2);
assert_eq!(s.ok, 2);
assert_eq!(s.accepts, 1);
}
#[test]
fn record_accept_bumps_open_gauge() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_accept();
record_accept();
let s = snapshot();
assert_eq!(s.accepts, 2, "lifetime accepts");
assert_eq!(s.connections_open, 2, "currently open");
assert_eq!(s.connections_closed, 0);
}
#[test]
fn record_close_decrements_open_and_bumps_closed() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_accept();
record_accept();
record_close();
let s = snapshot();
assert_eq!(s.accepts, 2);
assert_eq!(s.connections_open, 1, "one still open");
assert_eq!(s.connections_closed, 1);
}
#[test]
fn record_close_without_matching_accept_does_not_underflow() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_close();
record_close();
let s = snapshot();
assert_eq!(s.connections_open, 0, "saturating decrement");
assert_eq!(s.connections_closed, 2, "closed still tallied");
}
#[test]
fn ok_counter_only_for_status_200() {
let _g = TEST_LOCK.lock().unwrap();
reset_counters();
record_response(200);
record_response(201);
record_response(204);
let s = snapshot();
assert_eq!(s.ok, 1, "only 200 increments ok counter");
assert_eq!(s.successful, 3, "all three are successful");
}
}