blob: 6b87c7586a0edec0327788cf036469d9795c6a55 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use num_traits::SaturatingAdd;
use std::collections::VecDeque;
use std::default::Default;
pub struct WindowedStats<T> {
stats: VecDeque<T>,
capacity: usize,
}
impl<T: Default + SaturatingAdd> WindowedStats<T> {
pub fn new(capacity: usize) -> Self {
let mut stats = VecDeque::with_capacity(capacity);
stats.push_back(T::default());
Self { stats, capacity }
}
pub fn windowed_stat(&self) -> T {
let mut total = T::default();
for item in self.stats.iter() {
total = total.saturating_add(item);
}
total
}
pub fn saturating_add(&mut self, addition: &T) {
// Safe to unwrap because `self.stats` should always have at least one entry.
let latest = self.stats.back_mut().unwrap();
*latest = latest.saturating_add(addition);
}
pub fn slide_window(&mut self) {
if !self.stats.is_empty() && self.stats.len() >= self.capacity {
let _ = self.stats.pop_front();
}
self.stats.push_back(T::default());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn windowed_stats_partial_period() {
let mut windowed_stats = WindowedStats::<u32>::new(3);
windowed_stats.saturating_add(&1u32);
windowed_stats.saturating_add(&2u32);
assert_eq!(windowed_stats.windowed_stat(), 3u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&3u32);
assert_eq!(windowed_stats.windowed_stat(), 6u32);
}
#[test]
fn windowed_stats_full_period() {
let mut windowed_stats = WindowedStats::<u32>::new(3);
windowed_stats.saturating_add(&1u32);
assert_eq!(windowed_stats.windowed_stat(), 1u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&2u32);
assert_eq!(windowed_stats.windowed_stat(), 3u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&3u32);
assert_eq!(windowed_stats.windowed_stat(), 6u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&10u32);
// Value 1 from the first window is excluded
assert_eq!(windowed_stats.windowed_stat(), 15u32);
}
#[test]
fn windowed_stats_large_number() {
let mut windowed_stats = WindowedStats::<u32>::new(3);
windowed_stats.saturating_add(&10u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&10u32);
windowed_stats.slide_window();
windowed_stats.saturating_add(&(u32::MAX - 20u32));
assert_eq!(windowed_stats.windowed_stat(), u32::MAX);
windowed_stats.slide_window();
windowed_stats.saturating_add(&9u32);
assert_eq!(windowed_stats.windowed_stat(), u32::MAX - 1);
}
#[test]
fn windowed_stats_test_overflow() {
let mut windowed_stats = WindowedStats::<u32>::new(3);
// Overflow in a single window
windowed_stats.saturating_add(&u32::MAX);
windowed_stats.saturating_add(&1u32);
assert_eq!(windowed_stats.windowed_stat(), u32::MAX);
windowed_stats.slide_window();
windowed_stats.saturating_add(&10u32);
assert_eq!(windowed_stats.windowed_stat(), u32::MAX);
windowed_stats.slide_window();
windowed_stats.saturating_add(&5u32);
assert_eq!(windowed_stats.windowed_stat(), u32::MAX);
windowed_stats.slide_window();
windowed_stats.saturating_add(&3u32);
assert_eq!(windowed_stats.windowed_stat(), 18u32);
}
}