use crate::constants::NVIDIA_SMI_TIMEOUT_SECS;
use std::sync::OnceLock;
pub fn system_ram_bytes() -> Option<u64> {
static CACHE: OnceLock<Option<u64>> = OnceLock::new();
*CACHE.get_or_init(detect_system_ram)
}
fn detect_system_ram() -> Option<u64> {
let mut sys = sysinfo::System::new();
sys.refresh_memory();
let total = sys.total_memory(); (total > 0).then_some(total)
}
pub async fn gpu_vram_bytes() -> Option<u64> {
static CACHE: tokio::sync::OnceCell<Option<u64>> = tokio::sync::OnceCell::const_new();
*CACHE.get_or_init(detect_vram).await
}
async fn detect_vram() -> Option<u64> {
if let Some(v) = nvidia_vram_bytes().await {
return Some(v);
}
if cfg!(target_os = "macos") {
return system_ram_bytes();
}
None
}
async fn nvidia_vram_bytes() -> Option<u64> {
let mut cmd = tokio::process::Command::new("nvidia-smi");
cmd.args(["--query-gpu=memory.total", "--format=csv,noheader,nounits"]);
cmd.kill_on_drop(true);
let out = tokio::time::timeout(
std::time::Duration::from_secs(NVIDIA_SMI_TIMEOUT_SECS),
cmd.output(),
)
.await
.ok()? .ok()?;
if !out.status.success() {
return None;
}
let max_mib = String::from_utf8_lossy(&out.stdout)
.lines()
.filter_map(|l| l.trim().parse::<u64>().ok())
.max()?;
max_mib.checked_mul(1024 * 1024)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_ram_is_detected_and_plausible() {
let ram = system_ram_bytes().expect("system RAM should be detectable");
assert!(
ram > 256 * 1024 * 1024,
"implausibly small RAM ({ram} bytes) — units regression?"
);
}
#[tokio::test]
async fn gpu_vram_probe_is_best_effort() {
let _ = gpu_vram_bytes().await;
}
}