use anyhow::Result;
use std::path::Path;
pub fn machine_name() -> String {
let name = hostname::get()
.ok()
.and_then(|s| s.into_string().ok())
.unwrap_or_else(|| "unknown-host".to_string());
tracing::debug!(
target: "studio_worker::sys",
op = "machine_name",
value = %name,
"resolved host machine name"
);
name
}
pub fn username() -> String {
let user = whoami::username();
tracing::debug!(
target: "studio_worker::sys",
op = "username",
value = %user,
"resolved OS user"
);
user
}
pub fn detect_vram_gb() -> Result<f32> {
#[cfg(target_os = "linux")]
let gb = detect_vram_gb_from_sysfs(Path::new("/proc/driver/nvidia/gpus"));
#[cfg(not(target_os = "linux"))]
let gb = {
tracing::info!(
target: "studio_worker::sys",
op = "probe_vram",
source = "unsupported_platform",
vram_gb = 0.0,
"VRAM probe unsupported on this OS — defaulting to 0 GB"
);
0.0_f32
};
Ok(gb)
}
pub fn detect_vram_gb_from_sysfs(root: &Path) -> f32 {
let entries = match std::fs::read_dir(root) {
Ok(e) => e,
Err(_) => {
tracing::info!(
target: "studio_worker::sys",
op = "probe_vram",
source = "no_nvidia_sysfs",
vram_gb = 0.0,
root = %root.display(),
"no NVIDIA sysfs tree at probe root — defaulting to 0 GB VRAM"
);
return 0.0;
}
};
let mut total_mib: f64 = 0.0;
let mut gpu_count: u32 = 0;
let mut parseable: u32 = 0;
for entry in entries.flatten() {
gpu_count += 1;
let info_path = entry.path().join("information");
if let Ok(content) = std::fs::read_to_string(&info_path) {
let mut found = false;
for line in content.lines() {
if let Some(rest) = line.trim().strip_prefix("Video Memory:") {
if let Some(mib) = parse_mib(rest) {
total_mib += mib;
found = true;
}
}
}
if found {
parseable += 1;
}
}
}
let vram_gb = (total_mib / 1024.0) as f32;
if parseable > 0 {
tracing::info!(
target: "studio_worker::sys",
op = "probe_vram",
source = "nvidia_sysfs",
vram_gb = vram_gb,
gpu_count = parseable,
"detected NVIDIA VRAM via sysfs"
);
} else {
tracing::warn!(
target: "studio_worker::sys",
op = "probe_vram",
source = "sysfs_unparseable",
vram_gb = 0.0,
gpu_count = gpu_count,
root = %root.display(),
"NVIDIA sysfs entries present but no Video Memory line parsed — driver layout change?"
);
}
vram_gb
}
fn parse_mib(s: &str) -> Option<f64> {
let trimmed = s.trim();
let mut parts = trimmed.split_whitespace();
let value = parts.next()?.parse::<f64>().ok()?;
let unit = parts.next().unwrap_or("MiB");
match unit.to_ascii_lowercase().as_str() {
"mib" | "mb" => Some(value),
"gib" | "gb" => Some(value * 1024.0),
_ => Some(value),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_mib_handles_mib() {
assert_eq!(parse_mib(" 24576 MiB"), Some(24576.0));
assert_eq!(parse_mib("12288 MB"), Some(12288.0));
assert_eq!(parse_mib("24 GiB"), Some(24576.0));
}
#[test]
fn machine_name_returns_non_empty() {
assert!(!machine_name().is_empty());
}
#[test]
fn username_returns_non_empty() {
assert!(!username().is_empty());
}
#[test]
fn detect_vram_gb_from_sysfs_returns_zero_when_root_missing() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("nope");
assert_eq!(detect_vram_gb_from_sysfs(&missing), 0.0);
}
#[test]
fn detect_vram_gb_from_sysfs_sums_parseable_gpus() {
let dir = tempfile::tempdir().unwrap();
for (bus, mib) in [("0000:01:00.0", "12288"), ("0000:02:00.0", "24576")] {
let gpu = dir.path().join(bus);
std::fs::create_dir_all(&gpu).unwrap();
std::fs::write(
gpu.join("information"),
format!("Model: x\nVideo Memory: {mib} MiB\n"),
)
.unwrap();
}
let gb = detect_vram_gb_from_sysfs(dir.path());
assert!((gb - 36.0).abs() < 1e-3, "got {gb}");
}
}