use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Metrics {
inner: Arc<MetricsInner>,
}
#[derive(Debug)]
struct MetricsInner {
events_allowed: AtomicU64,
events_suppressed: AtomicU64,
signatures_evicted: AtomicU64,
}
impl Metrics {
pub fn new() -> Self {
Self {
inner: Arc::new(MetricsInner {
events_allowed: AtomicU64::new(0),
events_suppressed: AtomicU64::new(0),
signatures_evicted: AtomicU64::new(0),
}),
}
}
pub(crate) fn record_allowed(&self) {
self.inner.events_allowed.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_suppressed(&self) {
self.inner.events_suppressed.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_eviction(&self) {
self.inner
.signatures_evicted
.fetch_add(1, Ordering::Relaxed);
}
pub fn events_allowed(&self) -> u64 {
self.inner.events_allowed.load(Ordering::Relaxed)
}
pub fn events_suppressed(&self) -> u64 {
self.inner.events_suppressed.load(Ordering::Relaxed)
}
pub fn signatures_evicted(&self) -> u64 {
self.inner.signatures_evicted.load(Ordering::Relaxed)
}
pub fn snapshot(&self) -> MetricsSnapshot {
MetricsSnapshot {
events_allowed: self.events_allowed(),
events_suppressed: self.events_suppressed(),
signatures_evicted: self.signatures_evicted(),
}
}
pub fn reset(&self) {
self.inner.events_allowed.store(0, Ordering::Relaxed);
self.inner.events_suppressed.store(0, Ordering::Relaxed);
self.inner.signatures_evicted.store(0, Ordering::Relaxed);
}
}
impl Default for Metrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetricsSnapshot {
pub events_allowed: u64,
pub events_suppressed: u64,
pub signatures_evicted: u64,
}
impl MetricsSnapshot {
pub fn suppression_rate(&self) -> f64 {
let total = self.events_allowed.saturating_add(self.events_suppressed);
if total == 0 {
0.0
} else {
self.events_suppressed as f64 / total as f64
}
}
pub fn total_events(&self) -> u64 {
self.events_allowed.saturating_add(self.events_suppressed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_initial_state() {
let metrics = Metrics::new();
assert_eq!(metrics.events_allowed(), 0);
assert_eq!(metrics.events_suppressed(), 0);
assert_eq!(metrics.signatures_evicted(), 0);
}
#[test]
fn test_record_allowed() {
let metrics = Metrics::new();
metrics.record_allowed();
metrics.record_allowed();
metrics.record_allowed();
assert_eq!(metrics.events_allowed(), 3);
assert_eq!(metrics.events_suppressed(), 0);
}
#[test]
fn test_record_suppressed() {
let metrics = Metrics::new();
metrics.record_suppressed();
metrics.record_suppressed();
assert_eq!(metrics.events_allowed(), 0);
assert_eq!(metrics.events_suppressed(), 2);
}
#[test]
fn test_record_eviction() {
let metrics = Metrics::new();
metrics.record_eviction();
assert_eq!(metrics.signatures_evicted(), 1);
}
#[test]
fn test_snapshot() {
let metrics = Metrics::new();
metrics.record_allowed();
metrics.record_allowed();
metrics.record_suppressed();
metrics.record_eviction();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.events_allowed, 2);
assert_eq!(snapshot.events_suppressed, 1);
assert_eq!(snapshot.signatures_evicted, 1);
}
#[test]
fn test_snapshot_suppression_rate() {
let metrics = Metrics::new();
assert_eq!(metrics.snapshot().suppression_rate(), 0.0);
metrics.record_allowed();
assert_eq!(metrics.snapshot().suppression_rate(), 0.0);
metrics.record_suppressed();
assert!((metrics.snapshot().suppression_rate() - 0.5).abs() < f64::EPSILON);
metrics.record_suppressed();
metrics.record_suppressed();
assert!((metrics.snapshot().suppression_rate() - 0.75).abs() < f64::EPSILON);
}
#[test]
fn test_snapshot_total_events() {
let metrics = Metrics::new();
assert_eq!(metrics.snapshot().total_events(), 0);
metrics.record_allowed();
metrics.record_allowed();
metrics.record_suppressed();
assert_eq!(metrics.snapshot().total_events(), 3);
}
#[test]
fn test_reset() {
let metrics = Metrics::new();
metrics.record_allowed();
metrics.record_suppressed();
metrics.record_eviction();
metrics.reset();
assert_eq!(metrics.events_allowed(), 0);
assert_eq!(metrics.events_suppressed(), 0);
assert_eq!(metrics.signatures_evicted(), 0);
}
#[test]
fn test_metrics_clone() {
let metrics1 = Metrics::new();
metrics1.record_allowed();
let metrics2 = metrics1.clone();
metrics2.record_allowed();
assert_eq!(metrics1.events_allowed(), 2);
assert_eq!(metrics2.events_allowed(), 2);
}
#[test]
fn test_concurrent_updates() {
use std::thread;
let metrics = Metrics::new();
let mut handles = vec![];
for _ in 0..10 {
let m = metrics.clone();
handles.push(thread::spawn(move || {
for _ in 0..100 {
m.record_allowed();
m.record_suppressed();
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(metrics.events_allowed(), 1000);
assert_eq!(metrics.events_suppressed(), 1000);
}
}