use std::time::{Duration, Instant};
use parking_lot::Mutex;
const MEASUREMENT_TTL: Duration = Duration::from_secs(5);
pub struct Meter<T> {
last: Mutex<Option<Sample<T>>>,
}
impl<T> Default for Meter<T> {
fn default() -> Self {
Meter {
last: Mutex::new(None),
}
}
}
impl<T: Copy> Meter<T> {
pub fn measure(&self, is_reusable: impl FnOnce(T) -> bool, measure: impl FnOnce() -> T) -> T {
let now = Instant::now();
let mut last = self.last.lock();
if let Some(sample) = *last
&& now.duration_since(sample.taken_at) < MEASUREMENT_TTL
&& is_reusable(sample.value)
{
return sample.value;
}
let value = measure();
*last = Some(Sample {
value,
taken_at: now,
});
value
}
}
#[derive(Debug, Clone, Copy)]
struct Sample<T> {
value: T,
taken_at: Instant,
}
pub fn reusable(percent: Option<u8>, limit: Option<u8>) -> bool {
match (percent, limit) {
(Some(percent), Some(limit)) => percent < limit,
_ => true,
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use super::*;
fn below(limit: u8) -> impl Fn(Option<u8>) -> bool {
move |percent| reusable(percent, Some(limit))
}
#[test]
fn a_reading_below_the_limit_is_reused() {
let meter = Meter::default();
let measurements = Cell::new(0);
let measure = || {
measurements.set(measurements.get() + 1);
Some(50)
};
assert_eq!(meter.measure(below(90), measure), Some(50));
assert_eq!(meter.measure(below(90), measure), Some(50));
assert_eq!(measurements.get(), 1, "second check should not measure");
let meter = Meter::default();
assert_eq!(meter.measure(below(90), || None), None);
assert_eq!(meter.measure(below(90), || unreachable!()), None);
}
#[test]
fn a_reading_at_its_limit_is_measured_again() {
let meter = Meter::default();
assert_eq!(meter.measure(below(90), || Some(90)), Some(90));
assert_eq!(meter.measure(below(90), || Some(10)), Some(10));
assert_eq!(meter.measure(below(90), || unreachable!()), Some(10));
assert_eq!(meter.measure(below(5), || Some(45)), Some(45));
assert_eq!(
meter.measure(|percent| reusable(percent, None), || unreachable!()),
Some(45),
);
}
}