use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
#[inline]
pub fn log_state_change(flag: &AtomicBool, new_state: bool) -> bool {
flag.swap(new_state, Ordering::Relaxed) != new_state
}
#[inline]
pub fn log_sampled(counter: &AtomicU64, sample_rate: u64) -> bool {
let count = counter.fetch_add(1, Ordering::Relaxed) + 1;
count == 1 || count.is_multiple_of(sample_rate)
}
#[inline]
pub fn log_debounced(last_epoch_ms: &AtomicU64, min_interval_ms: u64) -> bool {
let now = u64::try_from(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis(),
)
.unwrap_or(u64::MAX);
let last = last_epoch_ms.load(Ordering::Relaxed);
if now.saturating_sub(last) >= min_interval_ms {
last_epoch_ms.store(now, Ordering::Relaxed);
true
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_change_transitions() {
let flag = AtomicBool::new(false);
assert!(log_state_change(&flag, true));
assert!(!log_state_change(&flag, true));
assert!(log_state_change(&flag, false));
assert!(!log_state_change(&flag, false));
}
#[test]
fn test_sampled_first_and_nth() {
let counter = AtomicU64::new(0);
assert!(log_sampled(&counter, 1000));
for _ in 0..998 {
assert!(!log_sampled(&counter, 1000));
}
assert!(log_sampled(&counter, 1000));
assert!(!log_sampled(&counter, 1000));
}
#[test]
fn test_sampled_rate_1() {
let counter = AtomicU64::new(0);
for _ in 0..10 {
assert!(log_sampled(&counter, 1));
}
}
#[test]
fn test_debounced_first_call() {
let last = AtomicU64::new(0);
assert!(log_debounced(&last, 5000));
}
#[test]
fn test_debounced_within_interval() {
let last = AtomicU64::new(0);
assert!(log_debounced(&last, 60_000)); assert!(!log_debounced(&last, 60_000));
}
}