use derive_setters::Setters;
use smart_default::SmartDefault;
use thiserror::Error;
use std::{
fmt::Display,
fs::{self, File},
io::{self, Write},
path::Path,
str::FromStr,
};
const HWP_DYNAMIC_BOOST: &str = "hwp_dynamic_boost";
const MAX_PERF_PCT: &str = "max_perf_pct";
const MIN_PERF_PCT: &str = "min_perf_pct";
const NO_TURBO: &str = "no_turbo";
#[derive(Debug, Error)]
pub enum PStateError {
#[error("failed to get {} pstate value", src)]
GetValue {
src: &'static str,
source: io::Error,
},
#[error("intel_pstate directory not found")]
NotFound,
#[error("failed to set {} pstate value", src)]
SetValue {
src: &'static str,
source: io::Error,
},
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Setters, SmartDefault)]
pub struct PStateValues {
#[setters(strip_option)]
pub hwp_dynamic_boost: Option<bool>,
pub min_perf_pct: u8,
#[default(100)]
pub max_perf_pct: u8,
pub no_turbo: bool,
}
pub struct PState {
path: &'static str,
}
impl PState {
pub fn new() -> Result<PState, PStateError> {
let path = "/sys/devices/system/cpu/intel_pstate/";
if Path::new(path).is_dir() {
Ok(PState { path })
} else {
Err(PStateError::NotFound)
}
}
fn file(&self, file: &str) -> String {
[self.path, file].concat()
}
pub fn hwp_dynamic_boost(&self) -> Result<Option<bool>, PStateError> {
let file = self.file(HWP_DYNAMIC_BOOST);
if Path::new(&*file).exists() {
return parse_file::<u8>(&file)
.map(|v| Some(v == 1))
.map_err(|source| PStateError::GetValue {
src: HWP_DYNAMIC_BOOST,
source,
});
}
Ok(None)
}
pub fn set_hwp_dynamic_boost(&self, boost: bool) -> Result<(), PStateError> {
write_value(&self.file(HWP_DYNAMIC_BOOST), if boost { "1" } else { "0" }).map_err(
|source| PStateError::SetValue {
src: HWP_DYNAMIC_BOOST,
source,
},
)
}
pub fn min_perf_pct(&self) -> Result<u8, PStateError> {
parse_file(&self.file(MIN_PERF_PCT)).map_err(|source| PStateError::GetValue {
src: MIN_PERF_PCT,
source,
})
}
pub fn set_min_perf_pct(&self, min: u8) -> Result<(), PStateError> {
write_value(&self.file(MIN_PERF_PCT), min).map_err(|source| PStateError::SetValue {
src: MIN_PERF_PCT,
source,
})
}
pub fn max_perf_pct(&self) -> Result<u8, PStateError> {
parse_file(&self.file(MAX_PERF_PCT)).map_err(|source| PStateError::GetValue {
src: MAX_PERF_PCT,
source,
})
}
pub fn set_max_perf_pct(&self, max: u8) -> Result<(), PStateError> {
write_value(&self.file(MAX_PERF_PCT), max).map_err(|source| PStateError::SetValue {
src: MAX_PERF_PCT,
source,
})
}
pub fn no_turbo(&self) -> Result<bool, PStateError> {
let value =
parse_file::<u8>(&self.file(NO_TURBO)).map_err(|source| PStateError::GetValue {
src: NO_TURBO,
source,
})?;
Ok(value > 0)
}
pub fn set_no_turbo(&self, no_turbo: bool) -> Result<(), PStateError> {
write_value(&self.file(NO_TURBO), if no_turbo { "1" } else { "0" }).map_err(|source| {
PStateError::SetValue {
src: NO_TURBO,
source,
}
})
}
pub fn values(&self) -> Result<PStateValues, PStateError> {
let values = PStateValues {
min_perf_pct: self.min_perf_pct()?,
max_perf_pct: self.max_perf_pct()?,
no_turbo: self.no_turbo()?,
hwp_dynamic_boost: self.hwp_dynamic_boost()?,
};
Ok(values)
}
pub fn set_values(&self, values: PStateValues) -> Result<(), PStateError> {
if let Some(boost) = values.hwp_dynamic_boost {
let _ = self.set_hwp_dynamic_boost(boost);
}
let result1 = self.set_min_perf_pct(values.min_perf_pct);
let result2 = self.set_max_perf_pct(values.max_perf_pct);
let result3 = self.set_no_turbo(values.no_turbo);
result1.and(result2).and(result3)
}
}
fn parse_file<F: FromStr>(path: &str) -> io::Result<F>
where
F::Err: Display,
{
fs::read_to_string(path)?
.trim()
.parse()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, format!("{}", err)))
}
fn write_value<V: Display>(path: &str, value: V) -> io::Result<()> {
write!(File::create(path)?, "{}", value)
}