1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

daemon: add conflict detection and governor override

Also adds governor override functionality allowing users to persistently force
a specific CPU governor mode.
This commit is contained in:
NotAShelf 2025-05-13 23:55:27 +03:00
parent 4bf4ab5673
commit 72ebbf3761
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
8 changed files with 377 additions and 128 deletions

View file

@ -20,21 +20,21 @@ pub enum SysMonitorError {
}
impl From<io::Error> for SysMonitorError {
fn from(err: io::Error) -> SysMonitorError {
SysMonitorError::Io(err)
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl std::fmt::Display for SysMonitorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SysMonitorError::Io(e) => write!(f, "I/O error: {}", e),
SysMonitorError::ReadError(s) => write!(f, "Failed to read sysfs path: {}", s),
SysMonitorError::ParseError(s) => write!(f, "Failed to parse value: {}", s),
SysMonitorError::ProcStatParseError(s) => {
write!(f, "Failed to parse /proc/stat: {}", s)
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::ReadError(s) => write!(f, "Failed to read sysfs path: {s}"),
Self::ParseError(s) => write!(f, "Failed to parse value: {s}"),
Self::ProcStatParseError(s) => {
write!(f, "Failed to parse /proc/stat: {s}")
}
SysMonitorError::NotAvailable(s) => write!(f, "Information not available: {}", s),
Self::NotAvailable(s) => write!(f, "Information not available: {s}"),
}
}
}
@ -123,7 +123,7 @@ fn get_logical_core_count() -> Result<u32> {
// Check if it's a directory representing a core that can have cpufreq
if entry.path().join("cpufreq").exists() {
count += 1;
} else if Path::new(&format!("/sys/devices/system/cpu/{}/online", name_str))
} else if Path::new(&format!("/sys/devices/system/cpu/{name_str}/online"))
.exists()
{
// Fallback for cores that might not have cpufreq but are online (e.g. E-cores on some setups before driver loads)
@ -159,7 +159,7 @@ struct CpuTimes {
}
impl CpuTimes {
fn total_time(&self) -> u64 {
const fn total_time(&self) -> u64 {
self.user
+ self.nice
+ self.system
@ -170,7 +170,7 @@ impl CpuTimes {
+ self.steal
}
fn idle_time(&self) -> u64 {
const fn idle_time(&self) -> u64 {
self.idle + self.iowait
}
}
@ -180,20 +180,18 @@ fn read_all_cpu_times() -> Result<HashMap<u32, CpuTimes>> {
let mut cpu_times_map = HashMap::new();
for line in content.lines() {
if line.starts_with("cpu") && line.chars().nth(3).map_or(false, |c| c.is_digit(10)) {
if line.starts_with("cpu") && line.chars().nth(3).is_some_and(|c| c.is_ascii_digit()) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 11 {
return Err(SysMonitorError::ProcStatParseError(format!(
"Line too short: {}",
line
"Line too short: {line}"
)));
}
let core_id_str = &parts[0][3..];
let core_id = core_id_str.parse::<u32>().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse core_id: {}",
core_id_str
"Failed to parse core_id: {core_id_str}"
))
})?;
@ -270,7 +268,7 @@ pub fn get_cpu_core_info(
prev_times: &CpuTimes,
current_times: &CpuTimes,
) -> Result<CpuCoreInfo> {
let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/cpufreq/", core_id));
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)
@ -405,21 +403,21 @@ pub fn get_cpu_core_info(
fn get_temperature_for_core(hw_path: &Path, core_id: u32, label_prefix: &str) -> Option<f32> {
for i in 1..=32 {
// Increased range to handle systems with many sensors
let label_path = hw_path.join(format!("temp{}_label", i));
let input_path = hw_path.join(format!("temp{}_input", i));
let label_path = hw_path.join(format!("temp{i}_label"));
let input_path = hw_path.join(format!("temp{i}_input"));
if label_path.exists() && input_path.exists() {
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
// Match various common label formats:
// "Core X", "core X", "Core-X", "CPU Core X", etc.
let core_pattern = format!("{} {}", label_prefix, core_id);
let alt_pattern = format!("{}-{}", label_prefix, core_id);
let core_pattern = format!("{label_prefix} {core_id}");
let alt_pattern = format!("{label_prefix}-{core_id}");
if label.eq_ignore_ascii_case(&core_pattern)
|| label.eq_ignore_ascii_case(&alt_pattern)
|| label
.to_lowercase()
.contains(&format!("core {}", core_id).to_lowercase())
.contains(&format!("core {core_id}").to_lowercase())
{
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
return Some(temp_mc as f32 / 1000.0);
@ -434,8 +432,8 @@ fn get_temperature_for_core(hw_path: &Path, core_id: u32, label_prefix: &str) ->
// Finds generic sensor temperatures by label
fn get_generic_sensor_temperature(hw_path: &Path, label_name: &str) -> Option<f32> {
for i in 1..=32 {
let label_path = hw_path.join(format!("temp{}_label", i));
let input_path = hw_path.join(format!("temp{}_input", i));
let label_path = hw_path.join(format!("temp{i}_label"));
let input_path = hw_path.join(format!("temp{i}_input"));
if label_path.exists() && input_path.exists() {
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
@ -460,7 +458,7 @@ fn get_generic_sensor_temperature(hw_path: &Path, label_name: &str) -> Option<f3
// Fallback to any temperature reading from a sensor
fn get_fallback_temperature(hw_path: &Path) -> Option<f32> {
for i in 1..=32 {
let input_path = hw_path.join(format!("temp{}_input", i));
let input_path = hw_path.join(format!("temp{i}_input"));
if input_path.exists() {
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
@ -488,12 +486,12 @@ pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
Ok(info) => core_infos.push(info),
Err(e) => {
// Log or handle error for a single core, maybe push a partial info or skip
eprintln!("Error getting info for core {}: {}", core_id, e);
eprintln!("Error getting info for core {core_id}: {e}");
}
}
} else {
// Log or handle missing times for a core
eprintln!("Missing CPU time data for core {}", core_id);
eprintln!("Missing CPU time data for core {core_id}");
}
}
Ok(core_infos)
@ -511,9 +509,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
};
let available_governors = if cpufreq_base.join("scaling_available_governors").exists() {
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors"))
.map(|s| s.split_whitespace().map(String::from).collect())
.unwrap_or_else(|_| vec![])
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else(|_| vec![], |s| s.split_whitespace().map(String::from).collect())
} else {
vec![]
};
@ -532,43 +528,46 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
None
};
let epp = read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_preference")).ok();
// EPP (Energy Performance Preference)
let energy_perf_pref =
read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_preference")).ok();
// EPB is often an integer 0-15. Reading as string for now.
let epb = read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_bias")).ok();
// EPB (Energy Performance Bias)
let energy_perf_bias =
read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_bias")).ok();
let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok();
let _platform_profile_choices =
read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok();
// Calculate average CPU temperature from the core temperatures
let average_temperature_celsius = if !cpu_cores.is_empty() {
let average_temperature_celsius = if cpu_cores.is_empty() {
None
} else {
// Filter cores with temperature readings, then calculate average
let cores_with_temp: Vec<&CpuCoreInfo> = cpu_cores
.iter()
.filter(|core| core.temperature_celsius.is_some())
.collect();
if !cores_with_temp.is_empty() {
if cores_with_temp.is_empty() {
None
} else {
// Sum up all temperatures and divide by count
let sum: f32 = cores_with_temp
.iter()
.map(|core| core.temperature_celsius.unwrap())
.sum();
Some(sum / cores_with_temp.len() as f32)
} else {
None
}
} else {
None
};
Ok(CpuGlobalInfo {
current_governor,
available_governors,
turbo_status,
epp,
epb,
epp: energy_perf_pref,
epb: energy_perf_bias,
platform_profile,
average_temperature_celsius,
})
@ -584,8 +583,7 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
let ignored_supplies = config
.ignored_power_supplies
.as_ref()
.cloned()
.clone()
.unwrap_or_default();
// Determine overall AC connection status
@ -649,7 +647,7 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
if let (Some(c), Some(v)) = (current_ua, voltage_uv) {
// Power (W) = (Voltage (V) * Current (A))
// (v / 1e6 V) * (c / 1e6 A) = (v * c / 1e12) W
Some((c as f64 * v as f64 / 1_000_000_000_000.0) as f32)
Some((f64::from(c) * f64::from(v) / 1_000_000_000_000.0) as f32)
} else {
None
}