#[derive(Debug, Clone)]
pub struct EmaTracker {
value: f64,
alpha: f64,
initialized: bool,
}
impl Default for EmaTracker {
fn default() -> Self {
Self::new(0.1) }
}
impl EmaTracker {
#[must_use]
pub fn new(alpha: f64) -> Self {
Self {
value: 0.0,
alpha: alpha.clamp(0.0, 1.0),
initialized: false,
}
}
#[must_use]
pub fn for_fps() -> Self {
Self::new(0.3)
}
#[must_use]
pub fn for_load() -> Self {
Self::new(0.05)
}
pub fn update(&mut self, sample: f64) {
if self.initialized {
self.value = self.alpha * sample + (1.0 - self.alpha) * self.value;
} else {
self.value = sample;
self.initialized = true;
}
}
#[must_use]
pub fn value(&self) -> f64 {
self.value
}
#[must_use]
pub fn is_initialized(&self) -> bool {
self.initialized
}
#[must_use]
pub fn alpha(&self) -> f64 {
self.alpha
}
pub fn reset(&mut self) {
self.value = 0.0;
self.initialized = false;
}
pub fn set_alpha(&mut self, alpha: f64) {
self.alpha = alpha.clamp(0.0, 1.0);
}
}
#[derive(Debug, Clone)]
pub struct RateLimiter {
last_allowed_us: u64,
interval_us: u64,
}
impl Default for RateLimiter {
fn default() -> Self {
Self::new_hz(60) }
}
impl RateLimiter {
#[must_use]
pub fn new(interval_us: u64) -> Self {
Self {
last_allowed_us: 0,
interval_us,
}
}
#[must_use]
pub fn new_hz(hz: u32) -> Self {
let interval_us = if hz == 0 {
1_000_000
} else {
1_000_000 / hz as u64
};
Self::new(interval_us)
}
#[must_use]
pub fn new_ms(ms: u64) -> Self {
Self::new(ms * 1000)
}
pub fn check(&mut self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
if now >= self.last_allowed_us + self.interval_us {
self.last_allowed_us = now;
true
} else {
false
}
}
#[must_use]
pub fn would_allow(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
now >= self.last_allowed_us + self.interval_us
}
#[must_use]
pub fn interval_us(&self) -> u64 {
self.interval_us
}
#[must_use]
pub fn hz(&self) -> f64 {
if self.interval_us == 0 {
0.0
} else {
1_000_000.0 / self.interval_us as f64
}
}
pub fn reset(&mut self) {
self.last_allowed_us = 0;
}
}
#[derive(Debug, Clone)]
pub struct ThresholdDetector {
low: f64,
high: f64,
is_high: bool,
}
impl ThresholdDetector {
#[must_use]
pub fn new(low: f64, high: f64) -> Self {
Self {
low,
high: high.max(low), is_high: false,
}
}
#[must_use]
pub fn percent(low: f64, high: f64) -> Self {
Self::new(low.clamp(0.0, 100.0), high.clamp(0.0, 100.0))
}
#[must_use]
pub fn for_resource() -> Self {
Self::new(70.0, 90.0)
}
#[must_use]
pub fn for_temperature() -> Self {
Self::new(60.0, 80.0)
}
pub fn update(&mut self, value: f64) -> bool {
let was_high = self.is_high;
if self.is_high && value < self.low {
self.is_high = false;
} else if !self.is_high && value > self.high {
self.is_high = true;
}
was_high != self.is_high
}
#[must_use]
pub fn is_high(&self) -> bool {
self.is_high
}
#[must_use]
pub fn is_low(&self) -> bool {
!self.is_high
}
#[must_use]
pub fn low_threshold(&self) -> f64 {
self.low
}
#[must_use]
pub fn high_threshold(&self) -> f64 {
self.high
}
pub fn reset(&mut self) {
self.is_high = false;
}
pub fn set_high(&mut self) {
self.is_high = true;
}
}
#[derive(Debug, Clone)]
pub struct SampleCounter {
count: u64,
last_count: u64,
last_time_us: u64,
rate: f64,
}
impl Default for SampleCounter {
fn default() -> Self {
Self::new()
}
}
impl SampleCounter {
#[must_use]
pub fn new() -> Self {
Self {
count: 0,
last_count: 0,
last_time_us: 0,
rate: 0.0,
}
}
pub fn increment(&mut self) {
self.count += 1;
}
pub fn add(&mut self, n: u64) {
self.count += n;
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn calculate_rate(&mut self) -> f64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
if self.last_time_us > 0 {
let elapsed_us = now.saturating_sub(self.last_time_us);
if elapsed_us > 0 {
let delta = self.count.saturating_sub(self.last_count);
self.rate = (delta as f64 * 1_000_000.0) / elapsed_us as f64;
}
}
self.last_count = self.count;
self.last_time_us = now;
self.rate
}
#[must_use]
pub fn rate(&self) -> f64 {
self.rate
}
pub fn reset(&mut self) {
self.count = 0;
self.last_count = 0;
self.last_time_us = 0;
self.rate = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct BudgetTracker {
budget: f64,
usage: f64,
peak: f64,
}
impl BudgetTracker {
#[must_use]
pub fn new(budget: f64) -> Self {
Self {
budget: budget.max(0.0),
usage: 0.0,
peak: 0.0,
}
}
#[must_use]
pub fn for_render() -> Self {
contract_pre_render!();
Self::new(16_000.0) }
#[must_use]
pub fn for_compute() -> Self {
Self::new(1_000.0) }
pub fn record(&mut self, usage: f64) {
self.usage = usage;
self.peak = self.peak.max(usage);
}
#[must_use]
pub fn usage(&self) -> f64 {
self.usage
}
#[must_use]
pub fn peak(&self) -> f64 {
self.peak
}
#[must_use]
pub fn budget(&self) -> f64 {
self.budget
}
#[must_use]
pub fn utilization(&self) -> f64 {
if self.budget <= 0.0 {
0.0
} else {
(self.usage / self.budget) * 100.0
}
}
#[must_use]
pub fn peak_utilization(&self) -> f64 {
if self.budget <= 0.0 {
0.0
} else {
(self.peak / self.budget) * 100.0
}
}
#[must_use]
pub fn is_over_budget(&self) -> bool {
self.usage > self.budget
}
#[must_use]
pub fn remaining(&self) -> f64 {
(self.budget - self.usage).max(0.0)
}
pub fn reset(&mut self) {
self.usage = 0.0;
self.peak = 0.0;
}
pub fn set_budget(&mut self, budget: f64) {
self.budget = budget.max(0.0);
}
}
#[derive(Debug, Clone)]
pub struct MinMaxTracker {
min: f64,
max: f64,
min_time_us: u64,
max_time_us: u64,
count: u64,
}
impl Default for MinMaxTracker {
fn default() -> Self {
Self::new()
}
}
impl MinMaxTracker {
#[must_use]
pub fn new() -> Self {
Self {
min: f64::MAX,
max: f64::MIN,
min_time_us: 0,
max_time_us: 0,
count: 0,
}
}
pub fn record(&mut self, value: f64) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
if value < self.min {
self.min = value;
self.min_time_us = now;
}
if value > self.max {
self.max = value;
self.max_time_us = now;
}
self.count += 1;
}
#[must_use]
pub fn min(&self) -> Option<f64> {
if self.count > 0 {
Some(self.min)
} else {
None
}
}
#[must_use]
pub fn max(&self) -> Option<f64> {
if self.count > 0 {
Some(self.max)
} else {
None
}
}
#[must_use]
pub fn range(&self) -> Option<f64> {
if self.count > 0 {
Some(self.max - self.min)
} else {
None
}
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[must_use]
pub fn time_since_min_us(&self) -> u64 {
if self.min_time_us == 0 {
return 0;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
now.saturating_sub(self.min_time_us)
}
#[must_use]
pub fn time_since_max_us(&self) -> u64 {
if self.max_time_us == 0 {
return 0;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
now.saturating_sub(self.max_time_us)
}
pub fn reset(&mut self) {
self.min = f64::MAX;
self.max = f64::MIN;
self.min_time_us = 0;
self.max_time_us = 0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct MovingWindow {
current_sum: f64,
current_count: u64,
prev_sum: f64,
prev_count: u64,
window_us: u64,
bucket_start_us: u64,
}
impl MovingWindow {
#[must_use]
pub fn new(window_ms: u64) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
Self {
current_sum: 0.0,
current_count: 0,
prev_sum: 0.0,
prev_count: 0,
window_us: window_ms * 1000,
bucket_start_us: now,
}
}
#[must_use]
pub fn one_second() -> Self {
Self::new(1000)
}
#[must_use]
pub fn one_minute() -> Self {
Self::new(60_000)
}
pub fn record(&mut self, value: f64) {
self.maybe_rotate();
self.current_sum += value;
self.current_count += 1;
}
pub fn increment(&mut self) {
self.record(1.0);
}
fn maybe_rotate(&mut self) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
let elapsed = now.saturating_sub(self.bucket_start_us);
if elapsed >= self.window_us {
self.prev_sum = self.current_sum;
self.prev_count = self.current_count;
self.current_sum = 0.0;
self.current_count = 0;
self.bucket_start_us = now;
}
}
#[must_use]
pub fn sum(&mut self) -> f64 {
self.maybe_rotate();
self.current_sum + self.prev_sum
}
#[must_use]
pub fn count(&mut self) -> u64 {
self.maybe_rotate();
self.current_count + self.prev_count
}
#[must_use]
pub fn rate_per_second(&mut self) -> f64 {
self.maybe_rotate();
let total = self.current_sum + self.prev_sum;
let window_secs = (self.window_us as f64) / 1_000_000.0;
if window_secs > 0.0 {
total / window_secs
} else {
0.0
}
}
#[must_use]
pub fn count_rate(&mut self) -> f64 {
self.maybe_rotate();
let total = self.current_count + self.prev_count;
let window_secs = (self.window_us as f64) / 1_000_000.0;
if window_secs > 0.0 {
total as f64 / window_secs
} else {
0.0
}
}
pub fn reset(&mut self) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
self.current_sum = 0.0;
self.current_count = 0;
self.prev_sum = 0.0;
self.prev_count = 0;
self.bucket_start_us = now;
}
}
#[derive(Debug, Clone)]
pub struct PercentileTracker {
buckets: [u64; 10],
count: u64,
boundaries: [u64; 10],
}
impl Default for PercentileTracker {
fn default() -> Self {
Self::new()
}
}
impl PercentileTracker {
#[must_use]
pub fn new() -> Self {
Self {
buckets: [0; 10],
count: 0,
boundaries: [
1_000, 5_000, 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, u64::MAX, ],
}
}
#[must_use]
pub fn with_boundaries(boundaries: [u64; 10]) -> Self {
Self {
buckets: [0; 10],
count: 0,
boundaries,
}
}
pub fn record_us(&mut self, value_us: u64) {
for (i, &boundary) in self.boundaries.iter().enumerate() {
if value_us < boundary {
self.buckets[i] += 1;
self.count += 1;
return;
}
}
self.buckets[9] += 1;
self.count += 1;
}
pub fn record_ms(&mut self, value_ms: f64) {
self.record_us((value_ms * 1000.0) as u64);
}
#[must_use]
pub fn percentile_us(&self, pct: f64) -> u64 {
if self.count == 0 {
return 0;
}
let target = ((pct / 100.0) * self.count as f64) as u64;
let mut cumulative = 0u64;
for (i, &bucket_count) in self.buckets.iter().enumerate() {
cumulative += bucket_count;
if cumulative >= target {
let lower = if i == 0 { 0 } else { self.boundaries[i - 1] };
let upper = self.boundaries[i];
if upper == u64::MAX {
return lower + 500_000; }
return (lower + upper) / 2;
}
}
self.boundaries[8] }
#[must_use]
pub fn percentile_ms(&self, pct: f64) -> f64 {
self.percentile_us(pct) as f64 / 1000.0
}
#[must_use]
pub fn p50_ms(&self) -> f64 {
self.percentile_ms(50.0)
}
#[must_use]
pub fn p90_ms(&self) -> f64 {
self.percentile_ms(90.0)
}
#[must_use]
pub fn p99_ms(&self) -> f64 {
self.percentile_ms(99.0)
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.buckets = [0; 10];
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct StateTracker<const N: usize> {
current: usize,
entered_us: u64,
durations: [u64; N],
transitions: [u64; N],
}
impl<const N: usize> Default for StateTracker<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> StateTracker<N> {
#[must_use]
pub fn new() -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
let mut transitions = [0u64; N];
if N > 0 {
transitions[0] = 1; }
Self {
current: 0,
entered_us: now,
durations: [0u64; N],
transitions,
}
}
pub fn transition(&mut self, new_state: usize) {
if new_state >= N {
return; }
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
let elapsed = now.saturating_sub(self.entered_us);
self.durations[self.current] += elapsed;
self.current = new_state;
self.entered_us = now;
self.transitions[new_state] += 1;
}
#[must_use]
pub fn current(&self) -> usize {
self.current
}
#[must_use]
pub fn time_in_current_us(&self) -> u64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
now.saturating_sub(self.entered_us)
}
#[must_use]
pub fn total_time_in_state_us(&self, state: usize) -> u64 {
if state >= N {
return 0;
}
if state == self.current {
self.durations[state] + self.time_in_current_us()
} else {
self.durations[state]
}
}
#[must_use]
pub fn transition_count(&self, state: usize) -> u64 {
if state >= N {
0
} else {
self.transitions[state]
}
}
#[must_use]
pub fn total_transitions(&self) -> u64 {
self.transitions.iter().sum()
}
pub fn reset(&mut self) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
self.current = 0;
self.entered_us = now;
self.durations = [0u64; N];
self.transitions = [0u64; N];
if N > 0 {
self.transitions[0] = 1;
}
}
}
#[derive(Debug, Clone)]
pub struct ChangeDetector {
baseline: f64,
abs_threshold: f64,
rel_threshold: f64,
last_value: f64,
change_count: u64,
}
impl Default for ChangeDetector {
fn default() -> Self {
Self::new(0.0, 1.0, 5.0)
}
}
impl ChangeDetector {
#[must_use]
pub fn new(baseline: f64, abs_threshold: f64, rel_threshold: f64) -> Self {
Self {
baseline,
abs_threshold: abs_threshold.abs(),
rel_threshold: rel_threshold.abs(),
last_value: baseline,
change_count: 0,
}
}
#[must_use]
pub fn for_percentage() -> Self {
Self::new(0.0, 1.0, 5.0)
}
#[must_use]
pub fn for_latency() -> Self {
Self::new(0.0, 1000.0, 10.0)
}
#[must_use]
pub fn has_changed(&self, value: f64) -> bool {
let abs_diff = (value - self.last_value).abs();
if abs_diff >= self.abs_threshold {
return true;
}
if self.last_value.abs() > f64::EPSILON {
let rel_diff = (abs_diff / self.last_value.abs()) * 100.0;
if rel_diff >= self.rel_threshold {
return true;
}
}
false
}
pub fn update(&mut self, value: f64) -> bool {
let changed = self.has_changed(value);
if changed {
self.change_count += 1;
}
self.last_value = value;
changed
}
pub fn update_baseline(&mut self) {
self.baseline = self.last_value;
}
pub fn set_baseline(&mut self, baseline: f64) {
self.baseline = baseline;
}
#[must_use]
pub fn baseline(&self) -> f64 {
self.baseline
}
#[must_use]
pub fn last_value(&self) -> f64 {
self.last_value
}
#[must_use]
pub fn change_count(&self) -> u64 {
self.change_count
}
#[must_use]
pub fn change_from_baseline(&self) -> f64 {
self.last_value - self.baseline
}
#[must_use]
pub fn relative_change(&self) -> f64 {
if self.baseline.abs() > f64::EPSILON {
((self.last_value - self.baseline) / self.baseline.abs()) * 100.0
} else {
0.0
}
}
pub fn reset(&mut self) {
self.last_value = self.baseline;
self.change_count = 0;
}
}
#[derive(Debug, Clone)]
pub struct Accumulator {
value: u64,
prev_raw: u64,
initialized: bool,
overflows: u64,
}
impl Default for Accumulator {
fn default() -> Self {
Self::new()
}
}
impl Accumulator {
#[must_use]
pub fn new() -> Self {
Self {
value: 0,
prev_raw: 0,
initialized: false,
overflows: 0,
}
}
pub fn update(&mut self, raw: u64) {
if !self.initialized {
self.prev_raw = raw;
self.initialized = true;
return;
}
let delta = if raw >= self.prev_raw {
raw - self.prev_raw
} else {
self.overflows += 1;
(u64::MAX - self.prev_raw) + raw + 1
};
self.value += delta;
self.prev_raw = raw;
}
pub fn add(&mut self, delta: u64) {
self.value += delta;
self.initialized = true;
}
#[must_use]
pub fn value(&self) -> u64 {
self.value
}
#[must_use]
pub fn overflows(&self) -> u64 {
self.overflows
}
#[must_use]
pub fn is_initialized(&self) -> bool {
self.initialized
}
#[must_use]
pub fn last_raw(&self) -> u64 {
self.prev_raw
}
pub fn reset(&mut self) {
self.value = 0;
self.prev_raw = 0;
self.initialized = false;
self.overflows = 0;
}
}
#[derive(Debug, Clone)]
pub struct EventCounter<const N: usize> {
counts: [u64; N],
total: u64,
}
impl<const N: usize> Default for EventCounter<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> EventCounter<N> {
#[must_use]
pub fn new() -> Self {
Self {
counts: [0u64; N],
total: 0,
}
}
pub fn increment(&mut self, category: usize) {
if category < N {
self.counts[category] += 1;
self.total += 1;
}
}
pub fn add(&mut self, category: usize, count: u64) {
if category < N {
self.counts[category] += count;
self.total += count;
}
}
#[must_use]
pub fn count(&self, category: usize) -> u64 {
if category < N {
self.counts[category]
} else {
0
}
}
#[must_use]
pub fn total(&self) -> u64 {
self.total
}
#[must_use]
pub fn percentage(&self, category: usize) -> f64 {
if self.total == 0 || category >= N {
0.0
} else {
(self.counts[category] as f64 / self.total as f64) * 100.0
}
}
#[must_use]
pub fn dominant(&self) -> Option<usize> {
if self.total == 0 {
return None;
}
self.counts
.iter()
.enumerate()
.max_by_key(|(_, &count)| count)
.map(|(idx, _)| idx)
}
pub fn reset(&mut self) {
self.counts = [0u64; N];
self.total = 0;
}
}
#[derive(Debug, Clone)]
pub struct TrendDetector {
sum: f64,
sum_xy: f64,
index: u64,
count: u64,
threshold: f64,
}
impl Default for TrendDetector {
fn default() -> Self {
Self::new(0.1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trend {
Up,
Down,
Flat,
Unknown,
}
impl TrendDetector {
#[must_use]
pub fn new(threshold: f64) -> Self {
Self {
sum: 0.0,
sum_xy: 0.0,
index: 0,
count: 0,
threshold: threshold.abs(),
}
}
#[must_use]
pub fn for_percentage() -> Self {
Self::new(0.5)
}
#[must_use]
pub fn for_latency() -> Self {
Self::new(1.0)
}
pub fn update(&mut self, value: f64) {
self.sum += value;
self.sum_xy += (self.index as f64) * value;
self.index += 1;
self.count += 1;
}
#[must_use]
pub fn slope(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
let n = self.count as f64;
let sum_x = (self.count * (self.count - 1)) as f64 / 2.0; let sum_x2 = (self.count * (self.count - 1) * (2 * self.count - 1)) as f64 / 6.0;
let sum_x_squared = sum_x.powi(2);
let denominator = n * sum_x2 - sum_x_squared;
if denominator.abs() < f64::EPSILON {
return 0.0;
}
(n * self.sum_xy - sum_x * self.sum) / denominator
}
#[must_use]
pub fn trend(&self) -> Trend {
if self.count < 3 {
return Trend::Unknown;
}
let slope = self.slope();
if slope > self.threshold {
Trend::Up
} else if slope < -self.threshold {
Trend::Down
} else {
Trend::Flat
}
}
#[must_use]
pub fn is_trending_up(&self) -> bool {
self.trend() == Trend::Up
}
#[must_use]
pub fn is_trending_down(&self) -> bool {
self.trend() == Trend::Down
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.sum = 0.0;
self.sum_xy = 0.0;
self.index = 0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct AnomalyDetector {
mean: f64,
m2: f64,
count: u64,
threshold: f64,
last_value: f64,
anomaly_count: u64,
}
impl Default for AnomalyDetector {
fn default() -> Self {
Self::new(3.0)
}
}
impl AnomalyDetector {
#[must_use]
pub fn new(threshold: f64) -> Self {
Self {
mean: 0.0,
m2: 0.0,
count: 0,
threshold: threshold.abs(),
last_value: 0.0,
anomaly_count: 0,
}
}
#[must_use]
pub fn two_sigma() -> Self {
Self::new(2.0)
}
#[must_use]
pub fn three_sigma() -> Self {
Self::new(3.0)
}
pub fn update(&mut self, value: f64) -> bool {
self.last_value = value;
self.count += 1;
if self.count == 1 {
self.mean = value;
return false;
}
let is_anomaly = self.is_anomaly(value);
if is_anomaly {
self.anomaly_count += 1;
}
let delta = value - self.mean;
self.mean += delta / self.count as f64;
let delta2 = value - self.mean;
self.m2 += delta * delta2;
is_anomaly
}
#[must_use]
pub fn is_anomaly(&self, value: f64) -> bool {
if self.count < 10 {
return false; }
let z = self.z_score(value);
z.abs() > self.threshold
}
#[must_use]
pub fn z_score(&self, value: f64) -> f64 {
let std_dev = self.std_dev();
if std_dev < f64::EPSILON {
return 0.0;
}
(value - self.mean) / std_dev
}
#[must_use]
pub fn mean(&self) -> f64 {
self.mean
}
#[must_use]
pub fn variance(&self) -> f64 {
if self.count < 2 {
0.0
} else {
self.m2 / (self.count - 1) as f64
}
}
#[must_use]
pub fn std_dev(&self) -> f64 {
self.variance().sqrt()
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[must_use]
pub fn anomaly_count(&self) -> u64 {
self.anomaly_count
}
#[must_use]
pub fn anomaly_rate(&self) -> f64 {
if self.count == 0 {
0.0
} else {
(self.anomaly_count as f64 / self.count as f64) * 100.0
}
}
#[must_use]
pub fn threshold(&self) -> f64 {
self.threshold
}
pub fn reset(&mut self) {
self.mean = 0.0;
self.m2 = 0.0;
self.count = 0;
self.last_value = 0.0;
self.anomaly_count = 0;
}
}