use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Default)]
pub struct PermissionCheckStats {
pub total_checks: AtomicU64,
pub allowed_checks: AtomicU64,
pub denied_checks: AtomicU64,
pub rate_limited_checks: AtomicU64,
pub cache_hits: AtomicU64,
pub cache_misses: AtomicU64,
pub stampede_events: AtomicU64,
}
impl PermissionCheckStats {
pub fn new() -> Self {
Self {
total_checks: AtomicU64::new(0),
allowed_checks: AtomicU64::new(0),
denied_checks: AtomicU64::new(0),
rate_limited_checks: AtomicU64::new(0),
cache_hits: AtomicU64::new(0),
cache_misses: AtomicU64::new(0),
stampede_events: AtomicU64::new(0),
}
}
pub fn record_allowed(&self) {
self.total_checks.fetch_add(1, Ordering::SeqCst);
self.allowed_checks.fetch_add(1, Ordering::SeqCst);
}
pub fn record_denied(&self) {
self.total_checks.fetch_add(1, Ordering::SeqCst);
self.denied_checks.fetch_add(1, Ordering::SeqCst);
}
pub fn record_rate_limited(&self) {
self.total_checks.fetch_add(1, Ordering::SeqCst);
self.rate_limited_checks.fetch_add(1, Ordering::SeqCst);
}
pub fn record_cache_hit(&self) {
self.cache_hits.fetch_add(1, Ordering::SeqCst);
}
pub fn record_cache_miss(&self) {
self.cache_misses.fetch_add(1, Ordering::SeqCst);
}
pub fn record_stampede(&self) {
self.stampede_events.fetch_add(1, Ordering::SeqCst);
}
pub fn snapshot(&self) -> PermissionCheckStatsSnapshot {
PermissionCheckStatsSnapshot {
total_checks: self.total_checks.load(Ordering::SeqCst),
allowed_checks: self.allowed_checks.load(Ordering::SeqCst),
denied_checks: self.denied_checks.load(Ordering::SeqCst),
rate_limited_checks: self.rate_limited_checks.load(Ordering::SeqCst),
cache_hits: self.cache_hits.load(Ordering::SeqCst),
cache_misses: self.cache_misses.load(Ordering::SeqCst),
stampede_events: self.stampede_events.load(Ordering::SeqCst),
}
}
}
#[derive(Debug, Clone)]
pub struct PermissionCheckStatsSnapshot {
pub total_checks: u64,
pub allowed_checks: u64,
pub denied_checks: u64,
pub rate_limited_checks: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub stampede_events: u64,
}
impl PermissionCheckStatsSnapshot {
pub fn cache_hit_rate(&self) -> f64 {
let total = self.cache_hits + self.cache_misses;
if total == 0 {
0.0
} else {
self.cache_hits as f64 / total as f64
}
}
pub fn denial_rate(&self) -> f64 {
let total = self.total_checks;
if total == 0 {
0.0
} else {
self.denied_checks as f64 / total as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_stats_all_zero() {
let s = PermissionCheckStats::new();
let snap = s.snapshot();
assert_eq!(snap.total_checks, 0);
assert_eq!(snap.allowed_checks, 0);
assert_eq!(snap.denied_checks, 0);
assert_eq!(snap.rate_limited_checks, 0);
assert_eq!(snap.cache_hits, 0);
assert_eq!(snap.cache_misses, 0);
assert_eq!(snap.stampede_events, 0);
}
#[test]
fn record_allowed_increments() {
let s = PermissionCheckStats::new();
s.record_allowed();
let snap = s.snapshot();
assert_eq!(snap.total_checks, 1);
assert_eq!(snap.allowed_checks, 1);
assert_eq!(snap.denied_checks, 0);
}
#[test]
fn record_denied_increments() {
let s = PermissionCheckStats::new();
s.record_denied();
let snap = s.snapshot();
assert_eq!(snap.total_checks, 1);
assert_eq!(snap.denied_checks, 1);
}
#[test]
fn record_rate_limited_increments() {
let s = PermissionCheckStats::new();
s.record_rate_limited();
let snap = s.snapshot();
assert_eq!(snap.total_checks, 1);
assert_eq!(snap.rate_limited_checks, 1);
}
#[test]
fn record_cache_hit() {
let s = PermissionCheckStats::new();
s.record_cache_hit();
assert_eq!(s.snapshot().cache_hits, 1);
}
#[test]
fn record_cache_miss() {
let s = PermissionCheckStats::new();
s.record_cache_miss();
assert_eq!(s.snapshot().cache_misses, 1);
}
#[test]
fn record_stampede() {
let s = PermissionCheckStats::new();
s.record_stampede();
assert_eq!(s.snapshot().stampede_events, 1);
}
#[test]
fn snapshot_is_independent_copy() {
let s = PermissionCheckStats::new();
s.record_allowed();
let snap1 = s.snapshot();
s.record_allowed();
let snap2 = s.snapshot();
assert_eq!(snap1.total_checks, 1);
assert_eq!(snap2.total_checks, 2);
}
#[test]
fn cache_hit_rate_zero_when_no_data() {
let snap = PermissionCheckStatsSnapshot {
total_checks: 0,
allowed_checks: 0,
denied_checks: 0,
rate_limited_checks: 0,
cache_hits: 0,
cache_misses: 0,
stampede_events: 0,
};
assert_eq!(snap.cache_hit_rate(), 0.0);
}
#[test]
fn cache_hit_rate_partial() {
let snap = PermissionCheckStatsSnapshot {
total_checks: 0,
allowed_checks: 0,
denied_checks: 0,
rate_limited_checks: 0,
cache_hits: 3,
cache_misses: 1,
stampede_events: 0,
};
assert!((snap.cache_hit_rate() - 0.75).abs() < f64::EPSILON);
}
#[test]
fn denial_rate_zero_when_no_data() {
let snap = PermissionCheckStatsSnapshot {
total_checks: 0,
allowed_checks: 0,
denied_checks: 0,
rate_limited_checks: 0,
cache_hits: 0,
cache_misses: 0,
stampede_events: 0,
};
assert_eq!(snap.denial_rate(), 0.0);
}
#[test]
fn denial_rate_correct() {
let snap = PermissionCheckStatsSnapshot {
total_checks: 10,
allowed_checks: 7,
denied_checks: 3,
rate_limited_checks: 0,
cache_hits: 0,
cache_misses: 0,
stampede_events: 0,
};
assert!((snap.denial_rate() - 0.3).abs() < f64::EPSILON);
}
#[test]
fn cache_stats_is_copy() {
let c = CacheStats {
cached_roles: 5,
capacity: 100,
};
assert_eq!(c.cached_roles, 5);
assert_eq!(c.capacity, 100);
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub cached_roles: usize,
pub capacity: usize,
}