gpu-histop 0.1.0

High-resolution GPU history monitor for NVIDIA, AMDGPU, and Apple Silicon
Documentation
use std::time::Instant;

#[derive(Debug, Clone)]
pub struct GpuInfo {
    pub id: usize,
    pub backend_index: u32,
    pub name: String,
    pub uuid: Option<String>,
}

#[derive(Debug, Clone)]
pub struct GpuSample {
    pub gpu_id: usize,
    pub at: Instant,
    pub gpu_util_percent: Option<f64>,
    pub mem_util_percent: Option<f64>,
    pub vram_used_bytes: Option<u64>,
    pub vram_total_bytes: Option<u64>,
    pub power_watts: Option<f64>,
    pub power_limit_watts: Option<f64>,
    pub temperature_celsius: Option<f64>,
    pub fan_percent: Option<f64>,
    pub graphics_clock_mhz: Option<f64>,
    pub memory_clock_mhz: Option<f64>,
    pub compute_processes: Option<u32>,
    pub processes: Vec<GpuProcess>,
}

impl GpuSample {
    pub fn vram_used_percent(&self) -> Option<f64> {
        let used = self.vram_used_bytes? as f64;
        let total = self.vram_total_bytes? as f64;
        (total > 0.0).then_some((used / total) * 100.0)
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum GpuProcessKind {
    Compute,
    Graphics,
    Mps,
}

impl GpuProcessKind {
    pub fn label(self) -> &'static str {
        match self {
            Self::Compute => "C",
            Self::Graphics => "G",
            Self::Mps => "MPS",
        }
    }
}

#[derive(Debug, Clone)]
pub struct GpuProcess {
    pub pid: u32,
    pub user: Option<String>,
    pub command: Option<String>,
    pub kinds: Vec<GpuProcessKind>,
    pub used_gpu_memory_bytes: Option<u64>,
    pub gpu_instance_id: Option<u32>,
    pub compute_instance_id: Option<u32>,
}

impl GpuProcess {
    pub fn kind_label(&self) -> String {
        self.kinds
            .iter()
            .map(|kind| kind.label())
            .collect::<Vec<_>>()
            .join("+")
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum MetricKind {
    GpuUtil,
    MemUtil,
    VramUsed,
    Power,
    Temperature,
    Fan,
}

impl MetricKind {
    pub const ALL: [Self; 6] = [
        Self::GpuUtil,
        Self::MemUtil,
        Self::VramUsed,
        Self::Power,
        Self::Temperature,
        Self::Fan,
    ];

    pub fn title(self) -> &'static str {
        match self {
            Self::GpuUtil => "GPU",
            Self::MemUtil => "MEM",
            Self::VramUsed => "VRAM",
            Self::Power => "POWER",
            Self::Temperature => "TEMP",
            Self::Fan => "FAN",
        }
    }

    pub fn unit(self) -> &'static str {
        match self {
            Self::GpuUtil | Self::MemUtil | Self::VramUsed | Self::Fan => "%",
            Self::Power => "W",
            Self::Temperature => "C",
        }
    }

    pub fn value(self, sample: &GpuSample) -> Option<f64> {
        match self {
            Self::GpuUtil => sample.gpu_util_percent,
            Self::MemUtil => sample.mem_util_percent,
            Self::VramUsed => sample.vram_used_percent(),
            Self::Power => sample.power_watts,
            Self::Temperature => sample.temperature_celsius,
            Self::Fan => sample.fan_percent,
        }
    }

    pub fn fixed_range(self, latest: Option<&GpuSample>) -> Option<(f64, f64)> {
        match self {
            Self::GpuUtil | Self::MemUtil | Self::VramUsed | Self::Fan => Some((0.0, 100.0)),
            Self::Temperature => Some((0.0, 100.0)),
            Self::Power => latest
                .and_then(|sample| sample.power_limit_watts)
                .map(|limit| (0.0, limit.max(1.0))),
        }
    }
}