use std::collections::VecDeque;
use super::HostMetrics;
pub const HOST_HISTORY_CAPACITY: usize = 600;
pub const HOST_SAMPLE_INTERVAL_SECS: u64 = 1;
#[derive(Debug, Clone)]
pub struct MetricRing<T> {
capacity: usize,
items: VecDeque<T>,
}
impl<T> MetricRing<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
capacity,
items: VecDeque::with_capacity(capacity),
}
}
pub fn push(&mut self, item: T) {
if self.items.len() == self.capacity {
self.items.pop_front();
}
self.items.push_back(item);
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &T> {
self.items.iter()
}
#[must_use]
pub fn last(&self) -> Option<&T> {
self.items.back()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
}
impl<T: Clone> MetricRing<T> {
#[must_use]
pub fn snapshot(&self) -> Vec<T> {
self.items.iter().cloned().collect()
}
}
impl MetricRing<HostMetrics> {
#[must_use]
pub fn host_window() -> Self {
Self::new(HOST_HISTORY_CAPACITY)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_evicts_oldest_at_capacity() {
let mut ring: MetricRing<u32> = MetricRing::new(HOST_HISTORY_CAPACITY);
for i in 0..HOST_HISTORY_CAPACITY as u32 {
ring.push(i);
}
assert_eq!(ring.len(), HOST_HISTORY_CAPACITY);
assert_eq!(ring.snapshot().first().copied(), Some(0));
ring.push(HOST_HISTORY_CAPACITY as u32);
assert_eq!(
ring.len(),
HOST_HISTORY_CAPACITY,
"capacity must never be exceeded"
);
let items = ring.snapshot();
assert_eq!(items.first().copied(), Some(1), "oldest sample was evicted");
assert_eq!(
items.last().copied(),
Some(HOST_HISTORY_CAPACITY as u32),
"newest sample is at the back"
);
}
#[test]
fn push_preserves_insertion_order() {
let mut ring: MetricRing<u32> = MetricRing::new(3);
for i in 1..=5 {
ring.push(i);
}
assert_eq!(ring.snapshot(), vec![3, 4, 5]);
assert_eq!(ring.iter().copied().collect::<Vec<_>>(), vec![3, 4, 5]);
}
#[test]
fn last_reads_the_newest_item() {
let mut ring: MetricRing<u32> = MetricRing::new(2);
assert_eq!(ring.last(), None, "an empty ring has no newest item");
ring.push(1);
assert_eq!(ring.last().copied(), Some(1));
ring.push(2);
ring.push(3);
assert_eq!(ring.snapshot(), vec![2, 3], "1 was evicted");
assert_eq!(ring.last().copied(), Some(3));
}
#[test]
fn empty_ring_reads_empty() {
let ring: MetricRing<u32> = MetricRing::new(HOST_HISTORY_CAPACITY);
assert!(ring.is_empty());
assert_eq!(ring.len(), 0);
assert!(ring.snapshot().is_empty());
assert_eq!(ring.iter().count(), 0);
assert_eq!(ring.capacity(), HOST_HISTORY_CAPACITY);
}
#[test]
fn host_window_is_the_owner_ruling() {
assert_eq!(HOST_HISTORY_CAPACITY, 600);
assert_eq!(HOST_SAMPLE_INTERVAL_SECS, 1);
assert_eq!(
HOST_HISTORY_CAPACITY as u64 * HOST_SAMPLE_INTERVAL_SECS,
600,
"600 points at 1s is the owner's 10-minute window"
);
let ring = MetricRing::<HostMetrics>::host_window();
assert_eq!(ring.capacity(), HOST_HISTORY_CAPACITY);
assert!(ring.is_empty());
}
#[test]
fn capacity_of_zero_is_clamped_to_one() {
let mut ring: MetricRing<u32> = MetricRing::new(0);
assert_eq!(ring.capacity(), 1);
ring.push(7);
ring.push(8);
assert_eq!(ring.snapshot(), vec![8]);
}
}