1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use std::time::{Duration, Instant};

pub(crate) trait Stats: Default {
    type Measure;

    fn add(&mut self, measure: Self::Measure);
}

pub(crate) struct StatsWindow<T: Stats> {
    pub period: Duration,
    pub stats: T,
}

impl<T: Stats> Default for StatsWindow<T> {
    fn default() -> Self {
        Self {
            period: Duration::from_secs(1),
            stats: T::default(),
        }
    }
}

pub(crate) struct OnlineWindowedStats<T: Stats> {
    period: Duration,
    last: Option<Instant>,
    stats: T,
}

impl<T: Stats> OnlineWindowedStats<T> {
    pub fn new(period: Duration) -> Self {
        Self {
            period,
            last: None,
            stats: Default::default(),
        }
    }

    pub fn add(&mut self, now: Instant, measure: T::Measure) -> Option<StatsWindow<T>> {
        self.stats.add(measure);

        match self.last {
            None => {
                self.last = Some(now);
                None
            }
            Some(last) => {
                if now < last + self.period {
                    None
                } else {
                    let elapsed = now - last;
                    self.last = Some(now);
                    Some(StatsWindow {
                        period: elapsed,
                        stats: std::mem::take(&mut self.stats),
                    })
                }
            }
        }
    }
}

#[cfg(test)]
mod online_windowed_stats {
    use super::*;

    use std::ops::AddAssign;

    impl Stats for usize {
        type Measure = usize;

        fn add(&mut self, measure: Self::Measure) {
            self.add_assign(measure);
        }
    }

    #[test]
    fn add() {
        let ms = Duration::from_millis;
        let start = Instant::now();
        let mut stats = OnlineWindowedStats::<usize>::new(ms(1_000));

        stats.add(start, 0);

        let window = (1..3001)
            .flat_map(|n| {
                stats
                    .add(start + ms(n), 1)
                    .map(|window| (window.period, window.stats))
            })
            .collect::<Vec<_>>();

        assert_eq!(
            window,
            vec![
                (ms(1000), 1000 as usize),
                (ms(1000), 1000 as usize),
                (ms(1000), 1000 as usize)
            ]
        );
    }
}