vor 0.1.0

Cross-platform performance instrumentation with an in-app egui panel and live system and GPU metrics.
Documentation
use std::collections::VecDeque;

use web_time::Instant;

use crate::viz::metric::Metric;

/// One frame's worth of vor-owned system metrics.
///
/// Populated by [`PanelState::tick`](super::state::PanelState::tick)
/// once per displayed frame.
#[derive(Clone, Copy)]
pub struct SystemSample {
    /// Wall time between this `tick` and the previous one.
    pub frame_ns: u64,
    /// Resident memory at sample time.
    pub memory_mb: f64,
    /// Wall time spent in I/O between this `tick` and the previous
    /// one, as recorded via [`record_io`](crate::record_io).
    pub io_ns: u64,
    /// Bytes transferred between this `tick` and the previous one.
    pub io_bytes: u64,
    /// GPU utilization, 0–100. Latest reading from the GPU poller
    /// the panel starts on construction.
    #[cfg(feature = "gpu")]
    pub gpu_util: f32,
    /// SM / shader-core utilization, 0–100. macOS only; NVML exposes
    /// no equivalent counter.
    #[cfg(feature = "mac")]
    pub gpu_sm: f32,
    /// PCIe throughput in bytes per second. NVIDIA only; the macOS
    /// backend does not read PCIe.
    #[cfg(feature = "cuda")]
    pub pcie_bps: u64,
    /// Instantaneous GPU power draw in watts.
    #[cfg(feature = "gpu")]
    pub gpu_power_w: f32,
    /// GPU memory in use, in bytes.
    #[cfg(feature = "gpu")]
    pub gpu_mem_bytes: u64,
    /// GPU temperature in °C. NVIDIA only (no clean source on macOS).
    #[cfg(feature = "cuda")]
    pub gpu_temp_c: f32,
    /// GPU SM clock in MHz. NVIDIA only.
    #[cfg(feature = "cuda")]
    pub gpu_clock_mhz: f32,
}

// GPU rows appear only where a backend supplies them; without one
// they would plot flat zeros, so they are dropped entirely. `gpu_sm`
// is macOS-only since NVML has no SM-occupancy counter.
pub(super) const SYSTEM_METRICS: &[Metric<SystemSample>] = &[
    Metric::new("frame_ms", frame_ms_of, "ms")
        .describe("Wall time between displayed frames. 1000 / frame_ms = FPS."),
    Metric::new("memory_mb", memory_mb_of, "MB").describe("Resident memory of this process."),
    Metric::new("io_ms", io_ms_of, "ms")
        .describe("Wall time spent in I/O this frame, as reported via record_io."),
    Metric::new("io_MB", io_mb_of, "MB").describe("Bytes transferred this frame, via record_io."),
    #[cfg(feature = "gpu")]
    Metric::new("gpu_util", gpu_util_of, "%").describe("Overall GPU utilization (0-100%)."),
    #[cfg(feature = "mac")]
    Metric::new("gpu_sm", gpu_sm_of, "%").describe("Shader / renderer-core utilization (0-100%)."),
    #[cfg(feature = "cuda")]
    Metric::new("pcie", pcie_mbps_of, "MB/s")
        .describe("PCIe throughput between host and GPU (TX + RX)."),
    #[cfg(feature = "gpu")]
    Metric::new("gpu_power", gpu_power_of, "W").describe("Instantaneous GPU power draw."),
    #[cfg(feature = "gpu")]
    Metric::new("gpu_mem", gpu_mem_of, "MB")
        .describe("GPU memory in use (unified memory on macOS)."),
    #[cfg(feature = "cuda")]
    Metric::new("gpu_temp", gpu_temp_of, "°C").describe("GPU core temperature."),
    #[cfg(feature = "cuda")]
    Metric::new("gpu_clock", gpu_clock_of, "MHz").describe("GPU SM clock frequency."),
];

const fn frame_ms_of(s: &SystemSample) -> f64 {
    s.frame_ns as f64 / 1e6
}
const fn memory_mb_of(s: &SystemSample) -> f64 {
    s.memory_mb
}
const fn io_ms_of(s: &SystemSample) -> f64 {
    s.io_ns as f64 / 1e6
}
const fn io_mb_of(s: &SystemSample) -> f64 {
    s.io_bytes as f64 / 1e6
}
#[cfg(feature = "gpu")]
const fn gpu_util_of(s: &SystemSample) -> f64 {
    s.gpu_util as f64
}
#[cfg(feature = "mac")]
const fn gpu_sm_of(s: &SystemSample) -> f64 {
    s.gpu_sm as f64
}
#[cfg(feature = "cuda")]
const fn pcie_mbps_of(s: &SystemSample) -> f64 {
    s.pcie_bps as f64 / 1e6
}
#[cfg(feature = "gpu")]
const fn gpu_power_of(s: &SystemSample) -> f64 {
    s.gpu_power_w as f64
}
#[cfg(feature = "gpu")]
const fn gpu_mem_of(s: &SystemSample) -> f64 {
    s.gpu_mem_bytes as f64 / 1e6
}
#[cfg(feature = "cuda")]
const fn gpu_temp_of(s: &SystemSample) -> f64 {
    s.gpu_temp_c as f64
}
#[cfg(feature = "cuda")]
const fn gpu_clock_of(s: &SystemSample) -> f64 {
    s.gpu_clock_mhz as f64
}

/// Sample one frame's system metrics.
pub(super) fn sample(
    ring: &mut VecDeque<SystemSample>,
    capacity: usize,
    last_tick: &mut Option<Instant>,
) {
    let now = Instant::now();
    let frame_ns = match *last_tick {
        Some(prev) => now.duration_since(prev).as_nanos() as u64,
        None => 0,
    };
    *last_tick = Some(now);
    let memory_mb = crate::current_memory_bytes()
        .map(|b| b as f64 / 1e6)
        .unwrap_or(0.0);
    let io = crate::drain_io();
    if ring.len() >= capacity {
        ring.pop_front();
    }
    ring.push_back(SystemSample {
        frame_ns,
        memory_mb,
        io_ns: io.elapsed_ns,
        io_bytes: io.bytes,
        #[cfg(feature = "gpu")]
        gpu_util: crate::gpu::read_gpu_util(),
        #[cfg(feature = "mac")]
        gpu_sm: crate::gpu::read_gpu_sm(),
        #[cfg(feature = "cuda")]
        pcie_bps: crate::gpu::read_pcie_bps(),
        #[cfg(feature = "gpu")]
        gpu_power_w: crate::gpu::read_gpu_power_w(),
        #[cfg(feature = "gpu")]
        gpu_mem_bytes: crate::gpu::read_gpu_mem_bytes(),
        #[cfg(feature = "cuda")]
        gpu_temp_c: crate::gpu::read_gpu_temp_c(),
        #[cfg(feature = "cuda")]
        gpu_clock_mhz: crate::gpu::read_gpu_clock_mhz(),
    });
}