use std::path::Path;
use serde::{Deserialize, Serialize};
use super::env::EnvProbe;
pub const FIELD_CPU_MODEL: &str = "cpu_model";
pub const FIELD_CPU_MICROARCHITECTURE: &str = "cpu_microarchitecture";
pub const FIELD_PHYSICAL_CORES: &str = "physical_cores";
pub const FIELD_RUSTC_VERSION: &str = "rustc_version";
pub const FIELD_TARGET_TRIPLE: &str = "target_triple";
pub const FIELD_RUSTFLAGS: &str = "rustflags";
pub const FIELD_BUILD_PROFILE: &str = "build_profile";
pub const FIELD_FREQUENCY_GOVERNOR: &str = "frequency_governor";
pub const FIELD_BENCH_CORE: &str = "bench_core";
pub const DEFAULT_BUILD_PROFILE: &str = "release";
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fingerprint {
pub cpu_model: String,
pub cpu_microarchitecture: String,
pub physical_cores: u32,
pub rustc_version: String,
pub target_triple: String,
pub rustflags: String,
pub build_profile: String,
pub frequency_governor: String,
pub git_commit: String,
pub bench_core: String,
}
#[derive(Debug, thiserror::Error)]
pub enum FingerprintError {
#[error("MISSING_BASELINE: no baseline fingerprint found")]
MissingBaseline,
#[error("INCOMPARABLE_ENVIRONMENT: {field} mismatch (baseline={baseline}, current={current})")]
IncomparableEnvironment {
field: &'static str,
baseline: String,
current: String,
},
#[error("invalid fingerprint JSON: {0}")]
Json(#[from] serde_json::Error),
#[error("fingerprint I/O error: {0}")]
Io(#[from] std::io::Error),
}
impl Fingerprint {
pub fn from_env_probe(probe: &EnvProbe, bench_core: &str) -> Self {
Fingerprint {
cpu_model: probe.cpu_model.clone(),
cpu_microarchitecture: probe.effective_isa.to_string(),
physical_cores: probe.physical_cores,
rustc_version: probe.rustc_version.clone(),
target_triple: probe.host_triple.clone(),
rustflags: probe.rustflags.clone(),
build_profile: DEFAULT_BUILD_PROFILE.to_string(),
frequency_governor: probe.frequency_governor.clone(),
git_commit: probe.git_commit.clone(),
bench_core: bench_core.to_string(),
}
}
pub fn probe(bench_core: &str) -> Self {
Self::from_env_probe(&EnvProbe::probe(), bench_core)
}
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json_str(input: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(input)
}
pub fn write_to_path(&self, path: &Path) -> Result<(), FingerprintError> {
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
std::fs::create_dir_all(parent)?;
}
let json = self.to_json_pretty()?;
std::fs::write(path, json)?;
Ok(())
}
pub fn read_from_path(path: &Path) -> Result<Self, FingerprintError> {
if !path.exists() {
return Err(FingerprintError::MissingBaseline);
}
let text = std::fs::read_to_string(path)?;
Ok(Self::from_json_str(&text)?)
}
pub fn compare(&self, baseline: &Fingerprint) -> Result<(), FingerprintError> {
if self.cpu_model != baseline.cpu_model {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_CPU_MODEL,
baseline: baseline.cpu_model.clone(),
current: self.cpu_model.clone(),
});
}
if self.cpu_microarchitecture != baseline.cpu_microarchitecture {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_CPU_MICROARCHITECTURE,
baseline: baseline.cpu_microarchitecture.clone(),
current: self.cpu_microarchitecture.clone(),
});
}
if self.rustc_version != baseline.rustc_version {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_RUSTC_VERSION,
baseline: baseline.rustc_version.clone(),
current: self.rustc_version.clone(),
});
}
if self.target_triple != baseline.target_triple {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_TARGET_TRIPLE,
baseline: baseline.target_triple.clone(),
current: self.target_triple.clone(),
});
}
if self.rustflags != baseline.rustflags {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_RUSTFLAGS,
baseline: baseline.rustflags.clone(),
current: self.rustflags.clone(),
});
}
if self.build_profile != baseline.build_profile {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_BUILD_PROFILE,
baseline: baseline.build_profile.clone(),
current: self.build_profile.clone(),
});
}
if self.frequency_governor != "performance" {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_FREQUENCY_GOVERNOR,
baseline: baseline.frequency_governor.clone(),
current: self.frequency_governor.clone(),
});
}
if !baseline.frequency_governor.is_empty()
&& self.frequency_governor != baseline.frequency_governor
{
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_FREQUENCY_GOVERNOR,
baseline: baseline.frequency_governor.clone(),
current: self.frequency_governor.clone(),
});
}
if !baseline.bench_core.is_empty() && self.bench_core != baseline.bench_core {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_BENCH_CORE,
baseline: baseline.bench_core.clone(),
current: self.bench_core.clone(),
});
}
if baseline.physical_cores != 0 && self.physical_cores != baseline.physical_cores {
return Err(FingerprintError::IncomparableEnvironment {
field: FIELD_PHYSICAL_CORES,
baseline: baseline.physical_cores.to_string(),
current: self.physical_cores.to_string(),
});
}
Ok(())
}
pub fn compare_against_path(&self, path: &Path) -> Result<(), FingerprintError> {
let baseline = Self::read_from_path(path)?;
self.compare(&baseline)
}
}
#[cfg(test)]
#[path = "fingerprint_test.rs"]
mod fingerprint_test;