use std::collections::VecDeque;
use web_time::Instant;
use crate::viz::metric::Metric;
#[derive(Clone, Copy)]
pub struct SystemSample {
pub frame_ns: u64,
pub memory_mb: f64,
pub io_ns: u64,
pub io_bytes: u64,
#[cfg(feature = "gpu")]
pub gpu_util: f32,
#[cfg(feature = "mac")]
pub gpu_sm: f32,
#[cfg(feature = "cuda")]
pub pcie_bps: u64,
#[cfg(feature = "gpu")]
pub gpu_power_w: f32,
#[cfg(feature = "gpu")]
pub gpu_mem_bytes: u64,
#[cfg(feature = "cuda")]
pub gpu_temp_c: f32,
#[cfg(feature = "cuda")]
pub gpu_clock_mhz: f32,
}
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
}
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(),
});
}