gpu-histop 0.1.0

High-resolution GPU history monitor for NVIDIA, AMDGPU, and Apple Silicon
Documentation
mod amdgpu;
mod apple;
mod nvidia_smi;
mod nvml;

use anyhow::{Result, anyhow, bail};

use crate::config::BackendChoice;
use crate::model::{GpuInfo, GpuSample};

pub trait GpuBackend: Send {
    fn label(&self) -> &str;
    fn devices(&self) -> &[GpuInfo];
    fn sample(&mut self) -> Result<Vec<GpuSample>>;
}

pub fn create_backend(kind: BackendChoice) -> Result<Box<dyn GpuBackend>> {
    match kind {
        BackendChoice::Auto => create_auto_backend(),
        BackendChoice::Nvml => Ok(Box::new(nvml::NvmlBackend::new()?)),
        BackendChoice::NvidiaSmi => Ok(Box::new(nvidia_smi::NvidiaSmiBackend::new()?)),
        BackendChoice::Amdgpu => Ok(Box::new(amdgpu::AmdGpuBackend::new()?)),
        BackendChoice::Apple => Ok(Box::new(apple::AppleBackend::new()?)),
    }
}

fn create_auto_backend() -> Result<Box<dyn GpuBackend>> {
    if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
        return apple::AppleBackend::new()
            .map(|backend| Box::new(backend) as Box<dyn GpuBackend>)
            .map_err(|error| {
                anyhow!("no real GPU metrics backend is available on Apple Silicon: {error:#}")
            });
    }

    let mut errors = Vec::new();

    match nvml::NvmlBackend::new() {
        Ok(backend) => return Ok(Box::new(backend)),
        Err(error) => errors.push(format!("NVML: {error:#}")),
    }

    match nvidia_smi::NvidiaSmiBackend::new() {
        Ok(backend) => return Ok(Box::new(backend)),
        Err(error) => errors.push(format!("nvidia-smi: {error:#}")),
    }

    match amdgpu::AmdGpuBackend::new() {
        Ok(backend) => return Ok(Box::new(backend)),
        Err(error) => errors.push(format!("AMDGPU sysfs: {error:#}")),
    }

    match apple::AppleBackend::new() {
        Ok(backend) => return Ok(Box::new(backend)),
        Err(error) => errors.push(format!("Apple Silicon: {error:#}")),
    }

    Err(anyhow!(
        "no real GPU metrics backend is available; tried {}",
        errors.join("; ")
    ))
}

fn require_devices(devices: &[GpuInfo], backend: &str) -> Result<()> {
    if devices.is_empty() {
        bail!("{backend} reported no GPUs");
    }
    Ok(())
}