use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
pub struct FaultStats {
checks: AtomicU64,
activations: AtomicU64,
affected_requests: AtomicU64,
enabled: AtomicBool,
last_activation_ms: AtomicU64,
}
impl FaultStats {
pub fn new() -> Self {
Self {
checks: AtomicU64::new(0),
activations: AtomicU64::new(0),
affected_requests: AtomicU64::new(0),
enabled: AtomicBool::new(true),
last_activation_ms: AtomicU64::new(0),
}
}
#[inline]
pub fn record_check(&self) {
self.checks.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_activation(&self) {
self.activations.fetch_add(1, Ordering::Relaxed);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
self.last_activation_ms.store(now, Ordering::Relaxed);
}
#[inline]
pub fn record_affected(&self) {
self.affected_requests.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.enabled.load(Ordering::Acquire)
}
#[inline]
pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Release);
}
pub fn snapshot(&self) -> FaultStatsSnapshot {
FaultStatsSnapshot {
checks: self.checks.load(Ordering::Acquire),
activations: self.activations.load(Ordering::Acquire),
affected_requests: self.affected_requests.load(Ordering::Acquire),
enabled: self.enabled.load(Ordering::Acquire),
last_activation_ms: self.last_activation_ms.load(Ordering::Acquire),
}
}
pub fn reset(&self) {
self.checks.store(0, Ordering::Release);
self.activations.store(0, Ordering::Release);
self.affected_requests.store(0, Ordering::Release);
self.last_activation_ms.store(0, Ordering::Release);
}
}
impl Default for FaultStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FaultStatsSnapshot {
pub checks: u64,
pub activations: u64,
pub affected_requests: u64,
pub enabled: bool,
pub last_activation_ms: u64,
}
impl FaultStatsSnapshot {
pub fn activation_rate(&self) -> f64 {
if self.checks == 0 {
0.0
} else {
self.activations as f64 / self.checks as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_new() {
let stats = FaultStats::new();
let snap = stats.snapshot();
assert_eq!(snap.checks, 0);
assert_eq!(snap.activations, 0);
assert_eq!(snap.affected_requests, 0);
assert!(snap.enabled);
assert_eq!(snap.last_activation_ms, 0);
}
#[test]
fn test_stats_record() {
let stats = FaultStats::new();
stats.record_check();
stats.record_check();
stats.record_activation();
stats.record_affected();
let snap = stats.snapshot();
assert_eq!(snap.checks, 2);
assert_eq!(snap.activations, 1);
assert_eq!(snap.affected_requests, 1);
assert!(snap.last_activation_ms > 0);
}
#[test]
fn test_stats_enable_disable() {
let stats = FaultStats::new();
assert!(stats.is_enabled());
stats.set_enabled(false);
assert!(!stats.is_enabled());
assert!(!stats.snapshot().enabled);
stats.set_enabled(true);
assert!(stats.is_enabled());
}
#[test]
fn test_stats_reset() {
let stats = FaultStats::new();
stats.record_check();
stats.record_activation();
stats.record_affected();
stats.set_enabled(false);
stats.reset();
let snap = stats.snapshot();
assert_eq!(snap.checks, 0);
assert_eq!(snap.activations, 0);
assert_eq!(snap.affected_requests, 0);
assert!(!snap.enabled);
}
#[test]
fn test_activation_rate() {
let snap = FaultStatsSnapshot {
checks: 100,
activations: 25,
affected_requests: 25,
enabled: true,
last_activation_ms: 0,
};
assert!((snap.activation_rate() - 0.25).abs() < f64::EPSILON);
let empty = FaultStatsSnapshot {
checks: 0,
activations: 0,
affected_requests: 0,
enabled: true,
last_activation_ms: 0,
};
assert!((empty.activation_rate() - 0.0).abs() < f64::EPSILON);
}
}