use std::process::Command;
#[derive(Default, Clone, Debug)]
pub struct MetricAvailability {
pub proc_pressure: bool,
pub sys_hwmon_dimm: bool,
pub sys_hwmon_nvme: bool,
pub smartctl: bool,
pub ipmitool: bool,
pub privileged_commands: bool,
}
impl MetricAvailability {
pub fn probe() -> Self {
let smartctl = Self::check_command_available("smartctl");
let ipmitool = Self::check_command_available("ipmitool");
let needs_privileges = smartctl || ipmitool;
let privileged_commands =
Self::has_elevated_privileges() || (needs_privileges && Self::has_sudo_access());
Self {
proc_pressure: std::fs::read_to_string("/proc/pressure/cpu").is_ok(),
sys_hwmon_dimm: Self::check_dimm_sensors(),
sys_hwmon_nvme: Self::check_nvme_sensors(),
smartctl,
ipmitool,
privileged_commands,
}
}
fn check_dimm_sensors() -> bool {
if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
for entry in entries.flatten() {
let name_path = entry.path().join("name");
if let Ok(name) = std::fs::read_to_string(&name_path) {
if name.trim() == "jc42" {
return true;
}
}
}
}
false
}
fn check_nvme_sensors() -> bool {
if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
for entry in entries.flatten() {
let name_path = entry.path().join("name");
if let Ok(name) = std::fs::read_to_string(&name_path) {
if name.trim() == "nvme" {
return true;
}
}
}
}
false
}
fn check_command_available(cmd: &str) -> bool {
Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn get_warnings(&self) -> Vec<String> {
let mut warnings = Vec::new();
if !self.proc_pressure {
warnings.push("PSI unavailable (requires Linux 4.20+ with CONFIG_PSI)".into());
}
if !self.sys_hwmon_dimm {
warnings.push("RAM temp sensors not found (no jc42 hwmon devices)".into());
}
if !self.sys_hwmon_nvme {
warnings.push("NVMe temp sensors not found".into());
}
if !self.smartctl {
warnings.push("smartctl not found (install smartmontools for disk health)".into());
} else if !self.privileged_commands {
warnings.push("SMART health unavailable (run with sudo)".into());
}
if !self.ipmitool && self.privileged_commands {
warnings.push("ipmitool not found (install for BMC/IPMI sensors)".into());
} else if self.ipmitool && !self.privileged_commands {
warnings.push("IPMI/BMC sensors unavailable (run with sudo)".into());
}
warnings
}
pub fn has_elevated_privileges() -> bool {
unsafe { libc::geteuid() == 0 }
}
pub fn has_sudo_access() -> bool {
Command::new("sudo")
.args(["-n", "true"])
.output()
.map(|s| s.status.success())
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::MetricAvailability;
#[test]
fn warnings_include_privilege_gaps_for_installed_tools() {
let availability = MetricAvailability {
proc_pressure: true,
sys_hwmon_dimm: true,
sys_hwmon_nvme: true,
smartctl: true,
ipmitool: true,
privileged_commands: false,
};
let warnings = availability.get_warnings();
assert!(warnings
.iter()
.any(|w| w.contains("SMART health unavailable")));
assert!(warnings.iter().any(|w| w.contains("IPMI/BMC sensors")));
}
}