use std::collections::VecDeque;
use std::time::{Duration, Instant};
use crate::model::GpuSample;
#[derive(Debug, Clone)]
pub struct History {
retention: Duration,
samples: VecDeque<GpuSample>,
}
impl History {
pub fn new(retention: Duration) -> Self {
Self {
retention,
samples: VecDeque::new(),
}
}
pub fn push(&mut self, sample: GpuSample) {
let newest = sample.at;
self.samples.push_back(sample);
self.prune(newest);
}
pub fn latest(&self) -> Option<&GpuSample> {
self.samples.back()
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
pub fn iter_window(
&self,
now: Instant,
window: Duration,
) -> impl DoubleEndedIterator<Item = &GpuSample> {
self.samples
.iter()
.filter(move |sample| sample.at <= now && now.duration_since(sample.at) <= window)
}
pub fn retention(&self) -> Duration {
self.retention
}
fn prune(&mut self, now: Instant) {
while let Some(front) = self.samples.front() {
if front.at <= now && now.duration_since(front.at) > self.retention {
self.samples.pop_front();
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::GpuSample;
#[test]
fn prunes_samples_older_than_retention() {
let t0 = Instant::now();
let mut history = History::new(Duration::from_secs(2));
history.push(sample_at(t0));
history.push(sample_at(t0 + Duration::from_secs(1)));
history.push(sample_at(t0 + Duration::from_secs(3)));
assert_eq!(history.len(), 2);
}
fn sample_at(at: Instant) -> GpuSample {
GpuSample {
gpu_id: 0,
at,
gpu_util_percent: Some(0.0),
mem_util_percent: None,
vram_used_bytes: None,
vram_total_bytes: None,
power_watts: None,
power_limit_watts: None,
temperature_celsius: None,
fan_percent: None,
graphics_clock_mhz: None,
memory_clock_mhz: None,
compute_processes: None,
processes: Vec::new(),
}
}
}