use std::time::{Duration, Instant};
const PER_UNIT_ALLOWANCE: Duration = Duration::from_millis(150);
const MAX_BUDGET: Duration = Duration::from_secs(30);
const STOW_CACHE_BUDGET_SECS_ENV: &str = "STOW_CACHE_BUDGET_SECS";
#[derive(Debug, Clone)]
pub struct CacheBudget {
started: Instant,
total: Duration,
}
impl CacheBudget {
#[must_use]
pub fn for_covered_units(covered_units: usize) -> Self {
let total = std::env::var(STOW_CACHE_BUDGET_SECS_ENV)
.ok()
.and_then(|raw| raw.parse::<u64>().ok())
.map_or_else(
|| {
PER_UNIT_ALLOWANCE
.saturating_mul(u32::try_from(covered_units).unwrap_or(u32::MAX))
.min(MAX_BUDGET)
},
Duration::from_secs,
);
Self {
started: Instant::now(),
total,
}
}
#[must_use]
pub fn remaining(&self) -> Duration {
self.total.saturating_sub(self.started.elapsed())
}
#[must_use]
pub fn is_exhausted(&self) -> bool {
self.remaining().is_zero()
}
#[must_use]
pub const fn total(&self) -> Duration {
self.total
}
}
#[cfg(test)]
mod tests {
use super::{CacheBudget, MAX_BUDGET, PER_UNIT_ALLOWANCE};
#[test]
fn nothing_cached_earns_no_budget() {
let budget = CacheBudget::for_covered_units(0);
assert!(budget.total().is_zero());
assert!(budget.is_exhausted());
}
#[test]
fn budget_scales_with_the_units_the_cache_can_serve() {
assert_eq!(
CacheBudget::for_covered_units(20).total(),
PER_UNIT_ALLOWANCE * 20
);
}
#[test]
fn budget_is_capped_however_large_the_graph() {
assert_eq!(
CacheBudget::for_covered_units(1_000_000).total(),
MAX_BUDGET
);
assert_eq!(
CacheBudget::for_covered_units(usize::MAX).total(),
MAX_BUDGET
);
}
}