gpu-histop 0.1.0

High-resolution GPU history monitor for NVIDIA, AMDGPU, and Apple Silicon
Documentation
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(),
        }
    }
}