use std::fs;
use std::process::Command;
pub const ISA_AVX512: &str = "AVX-512";
pub const ISA_X86_64_V3: &str = "x86-64-v3 (AVX2/FMA/F16C/BMI)";
pub const ISA_AVX2_INCOMPLETE: &str = "AVX2 (incompleto / unsupported)";
pub const ISA_X86_64_BASE: &str = "x86-64 (base)";
pub const CPUINFO_PATH: &str = "/proc/cpuinfo";
pub const GOVERNOR_PATH: &str = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CpuInfo {
pub model_name: String,
pub effective_isa: &'static str,
pub physical_cores: u32,
}
pub fn parse_cpuinfo(text: &str) -> CpuInfo {
let mut out = CpuInfo {
model_name: String::new(),
effective_isa: ISA_X86_64_BASE,
physical_cores: 0,
};
for line in text.lines() {
if line.starts_with("model name") && out.model_name.is_empty() {
if let Some((_, value)) = line.split_once(':') {
out.model_name = value.trim().to_string();
}
} else if line.starts_with("flags") {
out.effective_isa = classify_isa(line);
} else if line.starts_with("cpu cores")
&& out.physical_cores == 0
&& let Some(field) = line.split_whitespace().nth(3)
{
out.physical_cores = field.parse::<u32>().unwrap_or(0);
}
}
out
}
pub fn classify_isa(flags_line: &str) -> &'static str {
let flags: Vec<&str> = flags_line.split_whitespace().collect();
let has = |flag: &str| flags.contains(&flag);
if has("avx512f") {
ISA_AVX512
} else if has("avx")
&& has("avx2")
&& has("bmi1")
&& has("bmi2")
&& has("f16c")
&& has("fma")
&& (has("lzcnt") || has("abm"))
&& has("movbe")
{
ISA_X86_64_V3
} else if has("avx2") {
ISA_AVX2_INCOMPLETE
} else {
ISA_X86_64_BASE
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvProbe {
pub cpu_model: String,
pub effective_isa: &'static str,
pub physical_cores: u32,
pub rustc_version: String,
pub host_triple: String,
pub rustflags: String,
pub frequency_governor: String,
pub git_commit: String,
pub git_dirty: bool,
}
impl EnvProbe {
pub fn probe() -> Self {
let cpuinfo_text = fs::read_to_string(CPUINFO_PATH).unwrap_or_default();
Self::probe_from_cpuinfo(&cpuinfo_text)
}
pub fn probe_from_cpuinfo(cpuinfo_text: &str) -> Self {
let cpu = parse_cpuinfo(cpuinfo_text);
let physical_cores = if cpu.physical_cores == 0 {
probe_nproc().unwrap_or(1)
} else {
cpu.physical_cores
};
EnvProbe {
cpu_model: if cpu.model_name.is_empty() {
"unknown".to_string()
} else {
cpu.model_name
},
effective_isa: cpu.effective_isa,
physical_cores,
rustc_version: first_line_of_command(&["rustc", "--version"])
.unwrap_or_else(|| "unknown".to_string()),
host_triple: rustc_host_triple().unwrap_or_else(|| "unknown".to_string()),
rustflags: std::env::var("RUSTFLAGS").unwrap_or_default(),
frequency_governor: read_trimmed(GOVERNOR_PATH)
.unwrap_or_else(|| "unknown".to_string()),
git_commit: first_line_of_command(&["git", "rev-parse", "HEAD"])
.unwrap_or_else(|| "unknown".to_string()),
git_dirty: git_is_dirty(),
}
}
}
fn first_line_of_command(parts: &[&str]) -> Option<String> {
let output = Command::new(parts[0]).args(&parts[1..]).output().ok()?;
if !output.status.success() {
return None;
}
String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.map(|line| line.trim().to_string())
}
fn rustc_host_triple() -> Option<String> {
let output = Command::new("rustc").arg("-vV").output().ok()?;
if !output.status.success() {
return None;
}
String::from_utf8_lossy(&output.stdout)
.lines()
.find_map(|line| {
line.strip_prefix("host: ")
.map(|value| value.trim().to_string())
})
}
fn probe_nproc() -> Option<u32> {
let count = first_line_of_command(&["nproc"])?;
count.parse::<u32>().ok().filter(|n| *n > 0)
}
fn read_trimmed(path: &str) -> Option<String> {
let text = fs::read_to_string(path).ok()?;
let trimmed = text.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
fn git_is_dirty() -> bool {
let output = Command::new("git").args(["status", "--porcelain"]).output();
matches!(output, Ok(out) if out.status.success() && !out.stdout.is_empty())
}
#[cfg(test)]
#[path = "env_test.rs"]
mod env_test;