1
Fork 0
mirror of https://github.com/RGBCube/watt synced 2025-08-02 19:07:47 +00:00

Compare commits

..

No commits in common. "24fa53914d16da87c43a1828d754ac20523d19fc" and "dfa788009c498a123c2e134c809983be8ecddac1" have entirely different histories.

5 changed files with 151 additions and 194 deletions

View file

@ -50,7 +50,7 @@ pub struct CpuDelta {
impl CpuDelta {
pub fn apply(&self) -> anyhow::Result<()> {
let mut cpus = match &self.for_ {
let cpus = match &self.for_ {
Some(numbers) => {
let mut cpus = Vec::with_capacity(numbers.len());
@ -63,7 +63,7 @@ impl CpuDelta {
None => cpu::Cpu::all().context("failed to get all CPUs and their information")?,
};
for cpu in &mut cpus {
for cpu in cpus {
if let Some(governor) = self.governor.as_ref() {
cpu.set_governor(governor)?;
}
@ -77,11 +77,11 @@ impl CpuDelta {
}
if let Some(mhz_minimum) = self.frequency_mhz_minimum {
cpu.set_frequency_mhz_minimum(mhz_minimum)?;
cpu.set_frequency_minimum(mhz_minimum)?;
}
if let Some(mhz_maximum) = self.frequency_mhz_maximum {
cpu.set_frequency_mhz_maximum(mhz_maximum)?;
cpu.set_frequency_maximum(mhz_maximum)?;
}
}

View file

@ -6,12 +6,17 @@ pub struct SystemInfo {
pub struct CpuCoreInfo {
// Per-core data
pub core_id: u32,
pub current_frequency_mhz: Option<u32>,
pub min_frequency_mhz: Option<u32>,
pub max_frequency_mhz: Option<u32>,
pub usage_percent: Option<f32>,
pub temperature_celsius: Option<f32>,
}
pub struct CpuGlobalInfo {
// System-wide CPU settings
pub current_governor: Option<String>,
pub available_governors: Vec<String>,
pub turbo_status: Option<bool>, // true for enabled, false for disabled
pub epp: Option<String>, // Energy Performance Preference
pub epb: Option<String>, // Energy Performance Bias

View file

@ -11,19 +11,6 @@ pub struct Cpu {
pub has_cpufreq: bool,
pub available_governors: Vec<String>,
pub governor: Option<String>,
pub frequency_mhz: Option<u64>,
pub frequency_mhz_minimum: Option<u64>,
pub frequency_mhz_maximum: Option<u64>,
pub available_epps: Vec<String>,
pub epp: Option<String>,
pub available_epbs: Vec<String>,
pub epb: Option<String>,
pub time_user: u64,
pub time_nice: u64,
pub time_system: u64,
@ -65,19 +52,6 @@ impl Cpu {
number,
has_cpufreq: false,
available_governors: Vec::new(),
governor: None,
frequency_mhz: None,
frequency_mhz_minimum: None,
frequency_mhz_maximum: None,
available_epps: Vec::new(),
epp: None,
available_epbs: Vec::new(),
epb: None,
time_user: 0,
time_nice: 0,
time_system: 0,
@ -142,13 +116,6 @@ impl Cpu {
self.rescan_times()?;
if self.has_cpufreq {
self.rescan_governor()?;
self.rescan_frequency()?;
self.rescan_epp()?;
self.rescan_epb()?;
}
Ok(())
}
@ -213,134 +180,25 @@ impl Cpu {
Ok(())
}
fn rescan_governor(&mut self) -> anyhow::Result<()> {
let Self { number, .. } = *self;
self.available_governors = 'available_governors: {
let Some(Ok(content)) = fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors"
)) else {
break 'available_governors Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
};
self.governor = Some(
fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor"
))
.with_context(|| format!("failed to find {self} scaling governor"))?
.with_context(|| format!("failed to read {self} scaling governor"))?,
);
Ok(())
}
fn rescan_frequency(&mut self) -> anyhow::Result<()> {
let Self { number, .. } = *self;
let frequency_khz = fs::read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_cur_freq"
))
.with_context(|| format!("failed to find {self} frequency"))?
.with_context(|| format!("failed to parse {self} frequency"))?;
let frequency_khz_minimum = fs::read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
))
.with_context(|| format!("failed to find {self} frequency minimum"))?
.with_context(|| format!("failed to parse {self} frequency"))?;
let frequency_khz_maximum = fs::read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq"
))
.with_context(|| format!("failed to find {self} frequency maximum"))?
.with_context(|| format!("failed to parse {self} frequency"))?;
self.frequency_mhz = Some(frequency_khz / 1000);
self.frequency_mhz_minimum = Some(frequency_khz_minimum / 1000);
self.frequency_mhz_maximum = Some(frequency_khz_maximum / 1000);
Ok(())
}
fn rescan_epp(&mut self) -> anyhow::Result<()> {
pub fn get_available_governors(&self) -> Vec<String> {
let Self { number, .. } = self;
self.available_epps = 'available_epps: {
let Some(Ok(content)) = fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences"
)) else {
break 'available_epps Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
let Some(Ok(content)) = fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors"
)) else {
return Vec::new();
};
self.epp = Some(
fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference"
))
.with_context(|| format!("failed to find {self} EPP"))?
.with_context(|| format!("failed to read {self} EPP"))?,
);
Ok(())
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
fn rescan_epb(&mut self) -> anyhow::Result<()> {
pub fn set_governor(&self, governor: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
self.available_epbs = if self.has_cpufreq {
vec![
"1".to_owned(),
"2".to_owned(),
"3".to_owned(),
"4".to_owned(),
"5".to_owned(),
"6".to_owned(),
"7".to_owned(),
"8".to_owned(),
"9".to_owned(),
"10".to_owned(),
"11".to_owned(),
"12".to_owned(),
"13".to_owned(),
"14".to_owned(),
"15".to_owned(),
"performance".to_owned(),
"balance-performance".to_owned(),
"balance_performance".to_owned(), // Alternative form with underscore.
"balance-power".to_owned(),
"balance_power".to_owned(), // Alternative form with underscore.
"power".to_owned(),
]
} else {
Vec::new()
};
self.epb = Some(
fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias"
))
.with_context(|| format!("failed to find {self} EPB"))?
.with_context(|| format!("failed to read {self} EPB"))?,
);
Ok(())
}
pub fn set_governor(&mut self, governor: &str) -> anyhow::Result<()> {
let Self {
number,
available_governors: ref governors,
..
} = *self;
let governors = self.get_available_governors();
if !governors
.iter()
@ -360,19 +218,28 @@ impl Cpu {
format!(
"this probably means that {self} doesn't exist or doesn't support changing governors"
)
})?;
self.governor = Some(governor.to_owned());
Ok(())
})
}
pub fn set_epp(&mut self, epp: &str) -> anyhow::Result<()> {
let Self {
number,
available_epps: ref epps,
..
} = *self;
pub fn get_available_epps(&self) -> Vec<String> {
let Self { number, .. } = self;
let Some(Ok(content)) = fs::read(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences"
)) else {
return Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
pub fn set_epp(&self, epp: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
let epps = self.get_available_epps();
if !epps.iter().any(|avail_epp| avail_epp == epp) {
bail!(
@ -390,14 +257,42 @@ impl Cpu {
})
}
pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> {
let Self {
number,
available_epbs: ref epbs,
..
} = *self;
pub fn get_available_epbs(&self) -> &'static [&'static str] {
if !self.has_cpufreq {
return &[];
}
if !epbs.iter().any(|avail_epb| avail_epb == epb) {
&[
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"performance",
"balance-performance",
"balance_performance", // Alternative form with underscore.
"balance-power",
"balance_power", // Alternative form with underscore.
"power",
]
}
pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
let epbs = self.get_available_epbs();
if !epbs.contains(&epb) {
bail!(
"EPB value '{epb}' is not available for {self}. available EPB values: {valid}",
valid = epbs.join(", "),
@ -413,10 +308,10 @@ impl Cpu {
})
}
pub fn set_frequency_mhz_minimum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = *self;
pub fn set_frequency_minimum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
self.validate_frequency_mhz_minimum(frequency_mhz)?;
self.validate_frequency_minimum(frequency_mhz)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = frequency_mhz * 1000;
@ -428,14 +323,10 @@ impl Cpu {
)
.with_context(|| {
format!("this probably means that {self} doesn't exist or doesn't support changing minimum frequency")
})?;
self.frequency_mhz_minimum = Some(frequency_mhz);
Ok(())
})
}
fn validate_frequency_mhz_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Some(Ok(minimum_frequency_khz)) = fs::read_u64(format!(
@ -455,10 +346,10 @@ impl Cpu {
Ok(())
}
pub fn set_frequency_mhz_maximum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = *self;
pub fn set_frequency_maximum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
self.validate_frequency_mhz_maximum(frequency_mhz)?;
self.validate_frequency_maximum(frequency_mhz)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = frequency_mhz * 1000;
@ -470,14 +361,10 @@ impl Cpu {
)
.with_context(|| {
format!("this probably means that {self} doesn't exist or doesn't support changing maximum frequency")
})?;
self.frequency_mhz_maximum = Some(frequency_mhz);
Ok(())
})
}
fn validate_frequency_mhz_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Some(Ok(maximum_frequency_khz)) = fs::read_u64(format!(

View file

@ -19,7 +19,7 @@ pub fn read(path: impl AsRef<Path>) -> Option<anyhow::Result<String>> {
let path = path.as_ref();
match fs::read_to_string(path) {
Ok(string) => Some(Ok(string.trim().to_owned())),
Ok(string) => Some(Ok(string)),
Err(error) if error.kind() == io::ErrorKind::NotFound => None,

View file

@ -21,6 +21,18 @@ pub fn get_cpu_core_info(
prev_times: &CpuTimes,
current_times: &CpuTimes,
) -> anyhow::Result<CpuCoreInfo> {
let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{core_id}/cpufreq/"));
let current_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_cur_freq"))
.map(|khz| khz / 1000)
.ok();
let min_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_min_freq"))
.map(|khz| khz / 1000)
.ok();
let max_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_max_freq"))
.map(|khz| khz / 1000)
.ok();
// Temperature detection.
// Should be generic enough to be able to support for multiple hardware sensors
// with the possibility of extending later down the road.
@ -132,6 +144,9 @@ pub fn get_cpu_core_info(
Ok(CpuCoreInfo {
core_id,
current_frequency_mhz,
min_frequency_mhz,
max_frequency_mhz,
usage_percent,
temperature_celsius,
})
@ -238,9 +253,47 @@ pub fn get_all_cpu_core_info() -> anyhow::Result<Vec<CpuCoreInfo>> {
}
pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
// Find a valid CPU to read global settings from
// Try cpu0 first, then fall back to any available CPU with cpufreq
let mut cpufreq_base_path_buf = PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/");
if !cpufreq_base_path_buf.exists() {
let core_count = get_real_cpus().unwrap_or_else(|e| {
eprintln!("Warning: {e}");
0
});
for i in 0..core_count {
let test_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{i}/cpufreq/"));
if test_path.exists() {
cpufreq_base_path_buf = test_path;
break; // Exit the loop as soon as we find a valid path
}
}
}
let turbo_status_path = Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo");
let boost_path = Path::new("/sys/devices/system/cpu/cpufreq/boost");
let current_governor = if cpufreq_base_path_buf.join("scaling_governor").exists() {
read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_governor")).ok()
} else {
None
};
let available_governors = if cpufreq_base_path_buf
.join("scaling_available_governors")
.exists()
{
read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_available_governors"))
.map_or_else(
|_| vec![],
|s| s.split_whitespace().map(String::from).collect(),
)
} else {
vec![]
};
let turbo_status = if turbo_status_path.exists() {
// 0 means turbo enabled, 1 means disabled for intel_pstate
read_sysfs_value::<u8>(turbo_status_path)
@ -253,6 +306,14 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
None
};
// EPP (Energy Performance Preference)
let energy_perf_pref =
read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_preference")).ok();
// EPB (Energy Performance Bias)
let energy_perf_bias =
read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_bias")).ok();
let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok();
// Calculate average CPU temperature from the core temperatures
@ -279,7 +340,11 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
// Return the constructed CpuGlobalInfo
CpuGlobalInfo {
current_governor,
available_governors,
turbo_status,
epp: energy_perf_pref,
epb: energy_perf_bias,
platform_profile,
average_temperature_celsius,
}