use serde::Serialize;
use std::collections::BTreeMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Default, Debug)]
pub struct MetricsCounters {
pub accepted_connections: AtomicU64,
pub dispatched_requests: AtomicU64,
pub dispatch_errors_by_code: Mutex<BTreeMap<u16, u64>>,
pub audit_events_total: AtomicU64,
pub audit_drops_total: AtomicU64,
}
impl MetricsCounters {
pub fn record_error(&self, code: u16) {
if let Ok(mut map) = self.dispatch_errors_by_code.lock() {
*map.entry(code).or_insert(0) += 1;
}
}
pub fn snapshot(&self) -> MetricsSnapshot {
let errors_by_code = self
.dispatch_errors_by_code
.lock()
.map(|m| m.clone())
.unwrap_or_default();
MetricsSnapshot {
accepted_connections: self.accepted_connections.load(Ordering::Relaxed),
dispatched_requests: self.dispatched_requests.load(Ordering::Relaxed),
dispatch_errors_by_code: errors_by_code,
audit_events_total: self.audit_events_total.load(Ordering::Relaxed),
audit_drops_total: self.audit_drops_total.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct MetricsSnapshot {
pub accepted_connections: u64,
pub dispatched_requests: u64,
pub dispatch_errors_by_code: BTreeMap<u16, u64>,
pub audit_events_total: u64,
pub audit_drops_total: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_zero() {
let m = MetricsCounters::default();
let s = m.snapshot();
assert_eq!(s.accepted_connections, 0);
assert_eq!(s.dispatched_requests, 0);
assert_eq!(s.audit_events_total, 0);
assert_eq!(s.audit_drops_total, 0);
assert!(s.dispatch_errors_by_code.is_empty());
}
#[test]
fn atomic_increments_visible_in_snapshot() {
let m = MetricsCounters::default();
m.accepted_connections.fetch_add(3, Ordering::Relaxed);
m.dispatched_requests.fetch_add(7, Ordering::Relaxed);
m.audit_events_total.fetch_add(11, Ordering::Relaxed);
m.audit_drops_total.fetch_add(1, Ordering::Relaxed);
let s = m.snapshot();
assert_eq!(s.accepted_connections, 3);
assert_eq!(s.dispatched_requests, 7);
assert_eq!(s.audit_events_total, 11);
assert_eq!(s.audit_drops_total, 1);
}
#[test]
fn record_error_accumulates_per_code() {
let m = MetricsCounters::default();
m.record_error(1001); m.record_error(1001);
m.record_error(1002); let s = m.snapshot();
assert_eq!(s.dispatch_errors_by_code.get(&1001), Some(&2));
assert_eq!(s.dispatch_errors_by_code.get(&1002), Some(&1));
assert_eq!(s.dispatch_errors_by_code.get(&9999), None);
}
#[test]
fn snapshot_is_serialisable_to_json() {
let m = MetricsCounters::default();
m.accepted_connections.fetch_add(5, Ordering::Relaxed);
m.record_error(1010);
let s = m.snapshot();
let j = serde_json::to_value(&s).expect("serialize snapshot");
assert_eq!(j["accepted_connections"], 5);
assert_eq!(j["dispatch_errors_by_code"]["1010"], 1);
}
}