#[derive(Debug, Clone)]
pub struct ThroughputTracker {
total: u64,
prev_total: u64,
last_time_us: u64,
rate: f64,
peak_rate: f64,
}
impl Default for ThroughputTracker {
fn default() -> Self {
Self::new()
}
}
impl ThroughputTracker {
#[must_use]
pub fn new() -> Self {
Self {
total: 0,
prev_total: 0,
last_time_us: 0,
rate: 0.0,
peak_rate: 0.0,
}
}
pub fn add(&mut self, count: u64) {
self.total += 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.total.saturating_sub(self.prev_total);
self.rate = (delta as f64 * 1_000_000.0) / elapsed_us as f64;
self.peak_rate = self.peak_rate.max(self.rate);
}
}
self.prev_total = self.total;
self.last_time_us = now;
self.rate
}
#[must_use]
pub fn rate(&self) -> f64 {
self.rate
}
#[must_use]
pub fn peak_rate(&self) -> f64 {
self.peak_rate
}
#[must_use]
pub fn total(&self) -> u64 {
self.total
}
#[must_use]
pub fn format_rate(&self) -> String {
let rate = self.rate;
if rate >= 1_000_000_000.0 {
format!("{:.1}G/s", rate / 1_000_000_000.0)
} else if rate >= 1_000_000.0 {
format!("{:.1}M/s", rate / 1_000_000.0)
} else if rate >= 1_000.0 {
format!("{:.1}K/s", rate / 1_000.0)
} else {
format!("{:.0}/s", rate)
}
}
#[must_use]
pub fn format_bytes_rate(&self) -> String {
let rate = self.rate;
if rate >= 1_073_741_824.0 {
format!("{:.1}GB/s", rate / 1_073_741_824.0)
} else if rate >= 1_048_576.0 {
format!("{:.1}MB/s", rate / 1_048_576.0)
} else if rate >= 1_024.0 {
format!("{:.1}KB/s", rate / 1_024.0)
} else {
format!("{:.0}B/s", rate)
}
}
pub fn reset(&mut self) {
self.total = 0;
self.prev_total = 0;
self.last_time_us = 0;
self.rate = 0.0;
self.peak_rate = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct JitterTracker {
prev: f64,
jitter: f64,
peak_jitter: f64,
count: u64,
alpha: f64,
}
impl Default for JitterTracker {
fn default() -> Self {
Self::new()
}
}
impl JitterTracker {
#[must_use]
pub fn new() -> Self {
Self {
prev: 0.0,
jitter: 0.0,
peak_jitter: 0.0,
count: 0,
alpha: 1.0 / 16.0, }
}
#[must_use]
pub fn with_alpha(alpha: f64) -> Self {
Self {
prev: 0.0,
jitter: 0.0,
peak_jitter: 0.0,
count: 0,
alpha: alpha.clamp(0.0, 1.0),
}
}
pub fn update(&mut self, value: f64) {
self.count += 1;
if self.count == 1 {
self.prev = value;
return;
}
let diff = (value - self.prev).abs();
self.prev = value;
self.jitter += self.alpha * (diff - self.jitter);
self.peak_jitter = self.peak_jitter.max(self.jitter);
}
#[must_use]
pub fn jitter(&self) -> f64 {
self.jitter
}
#[must_use]
pub fn peak_jitter(&self) -> f64 {
self.peak_jitter
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[must_use]
pub fn exceeds(&self, threshold: f64) -> bool {
self.jitter > threshold
}
pub fn reset(&mut self) {
self.prev = 0.0;
self.jitter = 0.0;
self.peak_jitter = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct DerivativeTracker {
prev: f64,
prev_time_us: u64,
derivative: f64,
smoothed: f64,
alpha: f64,
count: u64,
}
impl Default for DerivativeTracker {
fn default() -> Self {
Self::new()
}
}
impl DerivativeTracker {
#[must_use]
pub fn new() -> Self {
Self {
prev: 0.0,
prev_time_us: 0,
derivative: 0.0,
smoothed: 0.0,
alpha: 0.3,
count: 0,
}
}
#[must_use]
pub fn with_alpha(alpha: f64) -> Self {
Self {
prev: 0.0,
prev_time_us: 0,
derivative: 0.0,
smoothed: 0.0,
alpha: alpha.clamp(0.0, 1.0),
count: 0,
}
}
pub fn update(&mut self, value: f64) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
self.count += 1;
if self.count == 1 {
self.prev = value;
self.prev_time_us = now;
return;
}
let dt = (now.saturating_sub(self.prev_time_us)) as f64 / 1_000_000.0; if dt > 0.0 {
self.derivative = (value - self.prev) / dt;
self.smoothed = self.alpha * self.derivative + (1.0 - self.alpha) * self.smoothed;
}
self.prev = value;
self.prev_time_us = now;
}
pub fn update_with_dt(&mut self, value: f64, dt_secs: f64) {
self.count += 1;
if self.count == 1 {
self.prev = value;
return;
}
if dt_secs > 0.0 {
self.derivative = (value - self.prev) / dt_secs;
self.smoothed = self.alpha * self.derivative + (1.0 - self.alpha) * self.smoothed;
}
self.prev = value;
}
#[must_use]
pub fn derivative(&self) -> f64 {
self.derivative
}
#[must_use]
pub fn smoothed(&self) -> f64 {
self.smoothed
}
#[must_use]
pub fn is_accelerating(&self) -> bool {
self.smoothed > 0.0
}
#[must_use]
pub fn is_decelerating(&self) -> bool {
self.smoothed < 0.0
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.prev = 0.0;
self.prev_time_us = 0;
self.derivative = 0.0;
self.smoothed = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct IntegralTracker {
prev: f64,
prev_time_us: u64,
integral: f64,
count: u64,
}
impl Default for IntegralTracker {
fn default() -> Self {
Self::new()
}
}
impl IntegralTracker {
#[must_use]
pub fn new() -> Self {
Self {
prev: 0.0,
prev_time_us: 0,
integral: 0.0,
count: 0,
}
}
pub fn update(&mut self, value: f64) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
self.count += 1;
if self.count == 1 {
self.prev = value;
self.prev_time_us = now;
return;
}
let dt = (now.saturating_sub(self.prev_time_us)) as f64 / 1_000_000.0; self.integral += (self.prev + value) / 2.0 * dt;
self.prev = value;
self.prev_time_us = now;
}
pub fn update_with_dt(&mut self, value: f64, dt_secs: f64) {
self.count += 1;
if self.count == 1 {
self.prev = value;
return;
}
self.integral += (self.prev + value) / 2.0 * dt_secs;
self.prev = value;
}
#[must_use]
pub fn integral(&self) -> f64 {
self.integral
}
#[must_use]
pub fn average(&self) -> f64 {
if self.count < 2 {
return self.prev;
}
self.prev
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.prev = 0.0;
self.prev_time_us = 0;
self.integral = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct CorrelationTracker {
mean_x: f64,
mean_y: f64,
cov_sum: f64,
var_x_sum: f64,
var_y_sum: f64,
count: u64,
}
impl Default for CorrelationTracker {
fn default() -> Self {
Self::new()
}
}
impl CorrelationTracker {
#[must_use]
pub fn new() -> Self {
Self {
mean_x: 0.0,
mean_y: 0.0,
cov_sum: 0.0,
var_x_sum: 0.0,
var_y_sum: 0.0,
count: 0,
}
}
pub fn update(&mut self, x: f64, y: f64) {
self.count += 1;
let n = self.count as f64;
let delta_x = x - self.mean_x;
let delta_y = y - self.mean_y;
self.mean_x += delta_x / n;
self.mean_y += delta_y / n;
let delta_x2 = x - self.mean_x;
let delta_y2 = y - self.mean_y;
self.cov_sum += delta_x * delta_y2;
self.var_x_sum += delta_x * delta_x2;
self.var_y_sum += delta_y * delta_y2;
}
#[must_use]
pub fn correlation(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
let denominator = (self.var_x_sum * self.var_y_sum).sqrt();
if denominator < f64::EPSILON {
return 0.0;
}
(self.cov_sum / denominator).clamp(-1.0, 1.0)
}
#[must_use]
pub fn is_positive(&self) -> bool {
self.correlation() > 0.5
}
#[must_use]
pub fn is_negative(&self) -> bool {
self.correlation() < -0.5
}
#[must_use]
pub fn is_strong(&self) -> bool {
self.correlation().abs() > 0.7
}
#[must_use]
pub fn covariance(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
self.cov_sum / (self.count - 1) as f64
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.mean_x = 0.0;
self.mean_y = 0.0;
self.cov_sum = 0.0;
self.var_x_sum = 0.0;
self.var_y_sum = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone)]
pub struct CircuitBreaker {
state: CircuitState,
failures: u64,
successes: u64,
failure_threshold: u64,
success_threshold: u64,
opened_at: u64,
timeout_us: u64,
}
impl Default for CircuitBreaker {
fn default() -> Self {
Self::new(5, 3, 30_000_000) }
}
impl CircuitBreaker {
#[must_use]
pub fn new(failure_threshold: u64, success_threshold: u64, timeout_us: u64) -> Self {
Self {
state: CircuitState::Closed,
failures: 0,
successes: 0,
failure_threshold,
success_threshold,
opened_at: 0,
timeout_us,
}
}
#[must_use]
pub fn for_network() -> Self {
Self::new(5, 3, 30_000_000)
}
#[must_use]
pub fn for_fast_fail() -> Self {
Self::new(3, 2, 5_000_000)
}
#[must_use]
pub fn is_allowed(&mut self) -> bool {
match self.state {
CircuitState::Closed => true,
CircuitState::Open => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
if now.saturating_sub(self.opened_at) >= self.timeout_us {
self.state = CircuitState::HalfOpen;
self.successes = 0;
true
} else {
false
}
}
CircuitState::HalfOpen => true,
}
}
pub fn record_success(&mut self) {
match self.state {
CircuitState::Closed => {
self.failures = 0;
}
CircuitState::HalfOpen => {
self.successes += 1;
if self.successes >= self.success_threshold {
self.state = CircuitState::Closed;
self.failures = 0;
}
}
CircuitState::Open => {}
}
}
pub fn record_failure(&mut self) {
match self.state {
CircuitState::Closed => {
self.failures += 1;
if self.failures >= self.failure_threshold {
self.state = CircuitState::Open;
self.opened_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
}
}
CircuitState::HalfOpen => {
self.state = CircuitState::Open;
self.opened_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
}
CircuitState::Open => {}
}
}
#[must_use]
pub fn state(&self) -> CircuitState {
self.state
}
#[must_use]
pub fn failures(&self) -> u64 {
self.failures
}
#[must_use]
pub fn is_open(&self) -> bool {
self.state == CircuitState::Open
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.state == CircuitState::Closed
}
pub fn reset(&mut self) {
self.state = CircuitState::Closed;
self.failures = 0;
self.successes = 0;
}
}
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
base_us: u64,
max_us: u64,
attempt: u64,
multiplier: f64,
jitter: bool,
}
impl Default for ExponentialBackoff {
fn default() -> Self {
Self::new(100_000, 30_000_000) }
}
impl ExponentialBackoff {
#[must_use]
pub fn new(base_us: u64, max_us: u64) -> Self {
Self {
base_us,
max_us,
attempt: 0,
multiplier: 2.0,
jitter: false,
}
}
#[must_use]
pub fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}
#[must_use]
pub fn with_multiplier(mut self, multiplier: f64) -> Self {
self.multiplier = multiplier.max(1.0);
self
}
#[must_use]
pub fn for_network() -> Self {
Self::new(100_000, 30_000_000).with_jitter()
}
#[must_use]
pub fn for_fast() -> Self {
Self::new(10_000, 1_000_000)
}
pub fn next_delay(&mut self) -> u64 {
let delay = self.current_delay();
self.attempt += 1;
delay
}
#[must_use]
pub fn current_delay(&self) -> u64 {
let delay = (self.base_us as f64 * self.multiplier.powi(self.attempt as i32)) as u64;
let capped = delay.min(self.max_us);
if self.jitter {
let jitter_factor = 0.5 + (self.attempt % 10) as f64 * 0.05;
((capped as f64) * jitter_factor) as u64
} else {
capped
}
}
#[must_use]
pub fn current_delay_ms(&self) -> u64 {
self.current_delay() / 1000
}
#[must_use]
pub fn attempt(&self) -> u64 {
self.attempt
}
#[must_use]
pub fn is_at_max(&self) -> bool {
self.current_delay() >= self.max_us
}
pub fn reset(&mut self) {
self.attempt = 0;
}
}
#[derive(Debug, Clone)]
pub struct SlidingMedian {
buckets: [u64; 10],
boundaries: [f64; 10],
count: u64,
min: f64,
max: f64,
}
impl Default for SlidingMedian {
fn default() -> Self {
Self::new()
}
}
impl SlidingMedian {
#[must_use]
pub fn new() -> Self {
Self {
buckets: [0; 10],
boundaries: [
100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 700.0, 800.0, 900.0, 1000.0,
],
count: 0,
min: f64::MAX,
max: f64::MIN,
}
}
#[must_use]
pub fn for_latency() -> Self {
Self {
buckets: [0; 10],
boundaries: [1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0],
count: 0,
min: f64::MAX,
max: f64::MIN,
}
}
#[must_use]
pub fn for_percentage() -> Self {
Self {
buckets: [0; 10],
boundaries: [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0],
count: 0,
min: f64::MAX,
max: f64::MIN,
}
}
pub fn update(&mut self, value: f64) {
self.count += 1;
self.min = self.min.min(value);
self.max = self.max.max(value);
for (i, &boundary) in self.boundaries.iter().enumerate() {
if value <= boundary {
self.buckets[i] += 1;
return;
}
}
self.buckets[9] += 1;
}
#[must_use]
pub fn median(&self) -> f64 {
self.percentile(50)
}
#[must_use]
pub fn percentile(&self, p: u8) -> f64 {
if self.count == 0 {
return 0.0;
}
let target = (self.count as f64 * p as f64 / 100.0) as u64;
let mut cumulative = 0u64;
for (i, &count) in self.buckets.iter().enumerate() {
cumulative += count;
if cumulative >= target {
let lower = if i == 0 { 0.0 } else { self.boundaries[i - 1] };
return (lower + self.boundaries[i]) / 2.0;
}
}
self.boundaries[9]
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[must_use]
pub fn min(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.min
}
}
#[must_use]
pub fn max(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.max
}
}
pub fn reset(&mut self) {
self.buckets = [0; 10];
self.count = 0;
self.min = f64::MAX;
self.max = f64::MIN;
}
}
#[derive(Debug, Clone)]
pub struct HysteresisFilter {
output: f64,
dead_band: f64,
count: u64,
}
impl Default for HysteresisFilter {
fn default() -> Self {
Self::new(1.0)
}
}
impl HysteresisFilter {
#[must_use]
pub fn new(dead_band: f64) -> Self {
Self {
output: 0.0,
dead_band: dead_band.abs(),
count: 0,
}
}
#[must_use]
pub fn for_percentage() -> Self {
Self::new(1.0)
}
#[must_use]
pub fn for_latency() -> Self {
Self::new(0.5)
}
#[must_use]
pub fn for_temperature() -> Self {
Self::new(0.5)
}
pub fn update(&mut self, value: f64) -> bool {
self.count += 1;
if self.count == 1 {
self.output = value;
return true;
}
if (value - self.output).abs() >= self.dead_band {
self.output = value;
return true;
}
false
}
#[must_use]
pub fn output(&self) -> f64 {
self.output
}
#[must_use]
pub fn dead_band(&self) -> f64 {
self.dead_band
}
pub fn set_dead_band(&mut self, dead_band: f64) {
self.dead_band = dead_band.abs();
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.output = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct SpikeFilter {
avg: f64,
threshold: f64,
alpha: f64,
spikes: u64,
count: u64,
last_accepted: f64,
}
impl Default for SpikeFilter {
fn default() -> Self {
Self::new(3.0)
}
}
impl SpikeFilter {
#[must_use]
pub fn new(threshold: f64) -> Self {
Self {
avg: 0.0,
threshold: threshold.abs(),
alpha: 0.1,
spikes: 0,
count: 0,
last_accepted: 0.0,
}
}
#[must_use]
pub fn for_percentage() -> Self {
Self::new(50.0) }
#[must_use]
pub fn for_latency() -> Self {
Self::new(100.0) }
pub fn update(&mut self, value: f64) -> f64 {
self.count += 1;
if self.count == 1 {
self.avg = value;
self.last_accepted = value;
return value;
}
let deviation = (value - self.avg).abs();
if deviation > self.threshold {
self.spikes += 1;
return self.last_accepted;
}
self.avg = self.alpha * value + (1.0 - self.alpha) * self.avg;
self.last_accepted = value;
value
}
#[must_use]
pub fn average(&self) -> f64 {
self.avg
}
#[must_use]
pub fn spikes(&self) -> u64 {
self.spikes
}
#[must_use]
pub fn spike_rate(&self) -> f64 {
if self.count == 0 {
0.0
} else {
(self.spikes as f64 / self.count as f64) * 100.0
}
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[must_use]
pub fn last_accepted(&self) -> f64 {
self.last_accepted
}
pub fn reset(&mut self) {
self.avg = 0.0;
self.spikes = 0;
self.count = 0;
self.last_accepted = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct GaugeTracker {
current: f64,
min: f64,
max: f64,
sum: f64,
count: u64,
}
impl Default for GaugeTracker {
fn default() -> Self {
Self::new()
}
}
impl GaugeTracker {
#[must_use]
pub fn new() -> Self {
Self {
current: 0.0,
min: f64::MAX,
max: f64::MIN,
sum: 0.0,
count: 0,
}
}
pub fn set(&mut self, value: f64) {
self.current = value;
self.min = self.min.min(value);
self.max = self.max.max(value);
self.sum += value;
self.count += 1;
}
pub fn inc(&mut self) {
self.set(self.current + 1.0);
}
pub fn dec(&mut self) {
self.set(self.current - 1.0);
}
pub fn add(&mut self, delta: f64) {
self.set(self.current + delta);
}
#[must_use]
pub fn current(&self) -> f64 {
self.current
}
#[must_use]
pub fn min(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.min
}
}
#[must_use]
pub fn max(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.max
}
}
#[must_use]
pub fn average(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.sum / self.count as f64
}
}
#[must_use]
pub fn range(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.max - self.min
}
}
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
pub fn reset(&mut self) {
self.current = 0.0;
self.min = f64::MAX;
self.max = f64::MIN;
self.sum = 0.0;
self.count = 0;
}
}
#[derive(Debug, Clone)]
pub struct CounterPair {
successes: u64,
failures: u64,
}
impl Default for CounterPair {
fn default() -> Self {
Self::new()
}
}
impl CounterPair {
#[must_use]
pub fn new() -> Self {
Self {
successes: 0,
failures: 0,
}
}
pub fn success(&mut self) {
self.successes += 1;
}
pub fn failure(&mut self) {
self.failures += 1;
}
pub fn add_successes(&mut self, count: u64) {
self.successes += count;
}
pub fn add_failures(&mut self, count: u64) {
self.failures += count;
}
#[must_use]
pub fn successes(&self) -> u64 {
self.successes
}
#[must_use]
pub fn failures(&self) -> u64 {
self.failures
}
#[must_use]
pub fn total(&self) -> u64 {
self.successes + self.failures
}
#[must_use]
pub fn success_rate(&self) -> f64 {
let total = self.total();
if total == 0 {
100.0
} else {
(self.successes as f64 / total as f64) * 100.0
}
}
#[must_use]
pub fn failure_rate(&self) -> f64 {
100.0 - self.success_rate()
}
#[must_use]
pub fn is_healthy(&self, threshold: f64) -> bool {
self.success_rate() >= threshold
}
pub fn reset(&mut self) {
self.successes = 0;
self.failures = 0;
}
}
#[derive(Debug, Clone)]
pub struct HealthScore {
scores: [f64; 8],
weights: [f64; 8],
active: usize,
}
impl Default for HealthScore {
fn default() -> Self {
Self::new()
}
}
impl HealthScore {
#[must_use]
pub fn new() -> Self {
Self {
scores: [100.0; 8],
weights: [1.0; 8],
active: 0,
}
}
pub fn set(&mut self, index: usize, score: f64) {
if index < 8 {
self.scores[index] = score.clamp(0.0, 100.0);
if index >= self.active {
self.active = index + 1;
}
}
}
pub fn set_weight(&mut self, index: usize, weight: f64) {
if index < 8 {
self.weights[index] = weight.max(0.0);
}
}
#[must_use]
pub fn score(&self) -> f64 {
if self.active == 0 {
return 100.0;
}
let mut weighted_sum = 0.0;
let mut weight_sum = 0.0;
for i in 0..self.active {
weighted_sum += self.scores[i] * self.weights[i];
weight_sum += self.weights[i];
}
if weight_sum < f64::EPSILON {
100.0
} else {
(weighted_sum / weight_sum).clamp(0.0, 100.0)
}
}
#[must_use]
pub fn status(&self) -> HealthStatus {
let score = self.score();
if score >= 90.0 {
HealthStatus::Healthy
} else if score >= 70.0 {
HealthStatus::Degraded
} else if score >= 50.0 {
HealthStatus::Warning
} else {
HealthStatus::Critical
}
}
#[must_use]
pub fn is_healthy(&self) -> bool {
self.score() >= 90.0
}
#[must_use]
pub fn min_score(&self) -> f64 {
if self.active == 0 {
return 100.0;
}
self.scores[..self.active]
.iter()
.fold(f64::MAX, |a, &b| a.min(b))
}
#[must_use]
pub fn active_components(&self) -> usize {
self.active
}
pub fn reset(&mut self) {
self.scores = [100.0; 8];
self.active = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Warning,
Critical,
}