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

Merge pull request #10 from NotAShelf/push-lxumomvptrmq

Push lxumomvptrmq
This commit is contained in:
raf 2025-05-14 01:04:32 +03:00 committed by GitHub
commit ea84a2997b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 184 additions and 191 deletions

View file

@ -1,46 +1,8 @@
use crate::core::{GovernorOverrideMode, TurboSetting};
use crate::util::error::ControlError;
use core::str;
use std::{fs, io, path::Path, string::ToString};
#[derive(Debug)]
pub enum ControlError {
Io(io::Error),
WriteError(String),
InvalidValueError(String),
NotSupported(String),
PermissionDenied(String),
InvalidProfile(String),
}
impl From<io::Error> for ControlError {
fn from(err: io::Error) -> Self {
match err.kind() {
io::ErrorKind::PermissionDenied => Self::PermissionDenied(err.to_string()),
_ => Self::Io(err),
}
}
}
impl std::fmt::Display for ControlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::WriteError(s) => write!(f, "Failed to write to sysfs path: {s}"),
Self::InvalidValueError(s) => write!(f, "Invalid value for setting: {s}"),
Self::NotSupported(s) => write!(f, "Control action not supported: {s}"),
Self::PermissionDenied(s) => {
write!(f, "Permission denied: {s}. Try running with sudo.")
}
Self::InvalidProfile(s) => {
write!(
f,
"Invalid platform control profile {s} supplied, please provide a valid one."
)
}
}
}
}
impl std::error::Error for ControlError {}
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
@ -58,16 +20,13 @@ fn write_sysfs_value(path: impl AsRef<Path>, value: &str) -> Result<()> {
})
}
fn for_each_cpu_core<F>(mut action: F) -> Result<()>
where
F: FnMut(u32) -> Result<()>,
{
pub fn get_logical_core_count() -> Result<u32> {
// Using num_cpus::get() for a reliable count of logical cores accessible.
// The monitor module's get_logical_core_count might be more specific to cpufreq-capable cores,
// but for applying settings, we might want to iterate over all reported by OS.
// However, settings usually apply to cores with cpufreq.
// Let's use a similar discovery to monitor's get_logical_core_count
let mut cores_to_act_on = Vec::new();
let mut num_cores: u32 = 0;
let path = Path::new("/sys/devices/system/cpu");
if !path.exists() {
return Err(ControlError::NotSupported(format!(
@ -97,20 +56,25 @@ where
continue;
}
if let Ok(core_id) = name[3..].parse::<u32>() {
cores_to_act_on.push(core_id);
if name[3..].parse::<u32>().is_ok() {
num_cores += 1;
}
}
if cores_to_act_on.is_empty() {
if num_cores == 0 {
// Fallback if sysfs iteration above fails to find any cpufreq cores
#[allow(clippy::cast_possible_truncation)]
let num_cores = num_cpus::get() as u32;
for core_id in 0..num_cores {
cores_to_act_on.push(core_id);
}
num_cores = num_cpus::get() as u32;
}
for core_id in cores_to_act_on {
Ok(num_cores)
}
fn for_each_cpu_core<F>(mut action: F) -> Result<()>
where
F: FnMut(u32) -> Result<()>,
{
let num_cores: u32 = get_logical_core_count()?;
for core_id in 0u32..num_cores {
action(core_id)?;
}
Ok(())
@ -264,13 +228,9 @@ pub fn get_platform_profiles() -> Result<Vec<String>> {
)));
}
let buf = fs::read(path)
let content = fs::read_to_string(path)
.map_err(|_| ControlError::PermissionDenied(format!("Cannot read contents of {path}.")))?;
let content = str::from_utf8(&buf).map_err(|_| {
ControlError::NotSupported(format!("No platform profile choices found at {path}."))
})?;
Ok(content
.split_whitespace()
.map(ToString::to_string)

View file

@ -1,6 +1,7 @@
use crate::config::{AppConfig, ProfileConfig};
use crate::core::{OperationalMode, SystemReport, TurboSetting};
use crate::cpu::{self, ControlError};
use crate::cpu::{self};
use crate::util::error::ControlError;
#[derive(Debug)]
pub enum EngineError {
@ -67,7 +68,7 @@ pub fn determine_and_apply_settings(
// Otherwise, check the ac_connected status from the (first) battery.
// XXX: This relies on the setting ac_connected in BatteryInfo being set correctly.
let on_ac_power = report.batteries.is_empty()
|| report.batteries.first().is_some_and(|b| b.ac_connected);
|| report.batteries.first().map_or(false, |b| b.ac_connected);
if on_ac_power {
println!("Engine: On AC power, selecting Charger profile.");

View file

@ -5,9 +5,11 @@ mod cpu;
mod daemon;
mod engine;
mod monitor;
mod util;
use crate::config::AppConfig;
use crate::core::{GovernorOverrideMode, TurboSetting};
use crate::util::error::ControlError;
use clap::Parser;
#[derive(Parser, Debug)]
@ -216,8 +218,8 @@ fn main() {
// For example, check if e.downcast_ref::<cpu::ControlError>() matches PermissionDenied
// and print a more specific message like "Try running with sudo."
// We'll revisit this in the future once CPU logic is more stable.
if let Some(control_error) = e.downcast_ref::<cpu::ControlError>() {
if matches!(control_error, cpu::ControlError::PermissionDenied(_)) {
if let Some(control_error) = e.downcast_ref::<ControlError>() {
if matches!(control_error, ControlError::PermissionDenied(_)) {
eprintln!(
"Hint: This operation may require administrator privileges (e.g., run with sudo)."
);

View file

@ -1,5 +1,8 @@
use crate::config::AppConfig;
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
use crate::cpu::{get_logical_core_count, get_platform_profiles};
use crate::util::error::ControlError;
use crate::util::error::SysMonitorError;
use std::{
collections::HashMap,
fs, io,
@ -10,35 +13,6 @@ use std::{
time::SystemTime,
};
#[derive(Debug)]
pub enum SysMonitorError {
Io(io::Error),
ReadError(String),
ParseError(String),
ProcStatParseError(String),
NotAvailable(String),
}
impl From<io::Error> for SysMonitorError {
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 {
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}")
}
Self::NotAvailable(s) => write!(f, "Information not available: {s}"),
}
}
}
impl std::error::Error for SysMonitorError {}
pub type Result<T, E = SysMonitorError> = std::result::Result<T, E>;
@ -64,83 +38,15 @@ fn read_sysfs_value<T: FromStr>(path: impl AsRef<Path>) -> Result<T> {
})
}
pub fn get_system_info() -> Result<SystemInfo> {
let mut cpu_model = "Unknown".to_string();
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
for line in cpuinfo.lines() {
if line.starts_with("model name") {
if let Some(val) = line.split(':').nth(1) {
cpu_model = val.trim().to_string();
break;
}
}
}
}
pub fn get_system_info() -> SystemInfo {
let cpu_model = get_cpu_model().unwrap_or_else(|_| "Unknown".to_string());
let linux_distribution = get_linux_distribution().unwrap_or_else(|_| "Unknown".to_string());
let architecture = std::env::consts::ARCH.to_string();
let mut linux_distribution = "Unknown".to_string();
if let Ok(os_release) = fs::read_to_string("/etc/os-release") {
for line in os_release.lines() {
if line.starts_with("PRETTY_NAME=") {
if let Some(val) = line.split('=').nth(1) {
linux_distribution = val.trim_matches('"').to_string();
break;
}
}
}
} else if let Ok(lsb_release) = fs::read_to_string("/etc/lsb-release") {
// fallback for some systems
for line in lsb_release.lines() {
if line.starts_with("DISTRIB_DESCRIPTION=") {
if let Some(val) = line.split('=').nth(1) {
linux_distribution = val.trim_matches('"').to_string();
break;
}
}
}
}
Ok(SystemInfo {
SystemInfo {
cpu_model,
architecture,
linux_distribution,
})
}
fn get_logical_core_count() -> Result<u32> {
let mut count = 0;
let path = Path::new("/sys/devices/system/cpu");
if path.exists() {
for entry in fs::read_dir(path)? {
let entry = entry?;
let name = entry.file_name();
if let Some(name_str) = name.to_str() {
if name_str.starts_with("cpu")
&& name_str.len() > 3
&& name_str[3..].chars().all(char::is_numeric)
{
// 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/{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)
// This is a simplification; true cpufreq capability is key.
// If cpufreq dir doesn't exist, it might not be controllable by this tool.
// For counting purposes, we count it if it's an online CPU.
count += 1;
}
}
}
}
}
if count == 0 {
// Fallback to num_cpus crate if sysfs parsing fails or yields 0
Ok(num_cpus::get() as u32)
} else {
Ok(count)
}
}
@ -474,7 +380,9 @@ pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
thread::sleep(Duration::from_millis(250)); // Interval for CPU usage calculation
let final_cpu_times = read_all_cpu_times()?;
let num_cores = get_logical_core_count()?; // Or derive from keys in cpu_times
let num_cores = get_logical_core_count()
.map_err(|_| SysMonitorError::ReadError("Could not get the number of cores".to_string()))?;
let mut core_infos = Vec::with_capacity(num_cores as usize);
for core_id in 0..num_cores {
@ -497,44 +405,38 @@ pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
Ok(core_infos)
}
pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
// FIXME: Assume global settings can be read from cpu0 or are consistent.
// This might not work properly for heterogeneous systems (e.g. big.LITTLE)
let cpufreq_base = Path::new("/sys/devices/system/cpu/cpu0/cpufreq/");
let current_governor = if cpufreq_base.join("scaling_governor").exists() {
read_sysfs_file_trimmed(cpufreq_base.join("scaling_governor")).ok()
let cpufreq_base_path = Path::new("/sys/devices/system/cpu/cpu0/cpufreq/");
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.join("scaling_governor").exists() {
read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_governor")).ok()
} else {
None
};
let available_governors = if cpufreq_base.join("scaling_available_governors").exists() {
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else(|_| vec![], |s| s.split_whitespace().map(String::from).collect())
} else {
vec![]
};
let available_governors = get_platform_profiles().unwrap_or_else(|_| vec![]);
let turbo_status = if Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo").exists() {
let turbo_status = if turbo_status_path.exists() {
// 0 means turbo enabled, 1 means disabled for intel_pstate
read_sysfs_value::<u8>("/sys/devices/system/cpu/intel_pstate/no_turbo")
read_sysfs_value::<u8>(turbo_status_path)
.map(|val| val == 0)
.ok()
} else if Path::new("/sys/devices/system/cpu/cpufreq/boost").exists() {
} else if boost_path.exists() {
// 1 means turbo enabled, 0 means disabled for generic cpufreq boost
read_sysfs_value::<u8>("/sys/devices/system/cpu/cpufreq/boost")
.map(|val| val == 1)
.ok()
read_sysfs_value::<u8>(boost_path).map(|val| val == 1).ok()
} else {
None
};
// EPP (Energy Performance Preference)
let energy_perf_pref =
read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_preference")).ok();
read_sysfs_file_trimmed(cpufreq_base_path.join("energy_performance_preference")).ok();
// EPB (Energy Performance Bias)
let energy_perf_bias =
read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_bias")).ok();
read_sysfs_file_trimmed(cpufreq_base_path.join("energy_performance_bias")).ok();
let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok();
let _platform_profile_choices =
@ -562,7 +464,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
}
};
Ok(CpuGlobalInfo {
CpuGlobalInfo {
current_governor,
available_governors,
turbo_status,
@ -570,7 +472,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
epb: energy_perf_bias,
platform_profile,
average_temperature_celsius,
})
}
}
pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
@ -581,10 +483,7 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
return Ok(batteries); // no power supply directory
}
let ignored_supplies = config
.ignored_power_supplies
.clone()
.unwrap_or_default();
let ignored_supplies = config.ignored_power_supplies.clone().unwrap_or_default();
// Determine overall AC connection status
let mut overall_ac_connected = false;
@ -701,9 +600,9 @@ pub fn get_system_load() -> Result<SystemLoad> {
}
pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
let system_info = get_system_info()?;
let system_info = get_system_info();
let cpu_cores = get_all_cpu_core_info()?;
let cpu_global = get_cpu_global_info(&cpu_cores)?;
let cpu_global = get_cpu_global_info(&cpu_cores);
let batteries = get_battery_info(config)?;
let system_load = get_system_load()?;
@ -716,3 +615,64 @@ pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
timestamp: SystemTime::now(),
})
}
pub fn get_cpu_model() -> Result<String> {
let path = Path::new("/proc/cpuinfo");
let content = fs::read_to_string(path).map_err(|_| {
SysMonitorError::ReadError(format!("Cannot read contents of {}.", path.display()))
})?;
for line in content.lines() {
if line.starts_with("model name") {
if let Some(val) = line.split(':').nth(1) {
let cpu_model = val.trim().to_string();
return Ok(cpu_model);
}
}
}
Err(SysMonitorError::ParseError(
"Could not find CPU model name in /proc/cpuinfo.".to_string(),
))
}
pub fn get_linux_distribution() -> Result<String> {
let os_release_path = Path::new("/etc/os-release");
let content = fs::read_to_string(os_release_path).map_err(|_| {
SysMonitorError::ReadError(format!(
"Cannot read contents of {}.",
os_release_path.display()
))
})?;
for line in content.lines() {
if line.starts_with("PRETTY_NAME=") {
if let Some(val) = line.split('=').nth(1) {
let linux_distribution = val.trim_matches('"').to_string();
return Ok(linux_distribution);
}
}
}
let lsb_release_path = Path::new("/etc/lsb-release");
let content = fs::read_to_string(lsb_release_path).map_err(|_| {
SysMonitorError::ReadError(format!(
"Cannot read contents of {}.",
lsb_release_path.display()
))
})?;
for line in content.lines() {
if line.starts_with("DISTRIB_DESCRIPTION=") {
if let Some(val) = line.split('=').nth(1) {
let linux_distribution = val.trim_matches('"').to_string();
return Ok(linux_distribution);
}
}
}
Err(SysMonitorError::ParseError(format!(
"Could not find distribution name in {} or {}.",
os_release_path.display(),
lsb_release_path.display()
)))
}

69
src/util/error.rs Normal file
View file

@ -0,0 +1,69 @@
use std::io;
#[derive(Debug)]
pub enum ControlError {
Io(io::Error),
WriteError(String),
InvalidValueError(String),
NotSupported(String),
PermissionDenied(String),
InvalidProfile(String),
}
impl From<io::Error> for ControlError {
fn from(err: io::Error) -> Self {
match err.kind() {
io::ErrorKind::PermissionDenied => Self::PermissionDenied(err.to_string()),
_ => Self::Io(err),
}
}
}
impl std::fmt::Display for ControlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::WriteError(s) => write!(f, "Failed to write to sysfs path: {s}"),
Self::InvalidValueError(s) => write!(f, "Invalid value for setting: {s}"),
Self::NotSupported(s) => write!(f, "Control action not supported: {s}"),
Self::PermissionDenied(s) => {
write!(f, "Permission denied: {s}. Try running with sudo.")
}
Self::InvalidProfile(s) => {
write!(
f,
"Invalid platform control profile {s} supplied, please provide a valid one."
)
}
}
}
}
#[derive(Debug)]
pub enum SysMonitorError {
Io(io::Error),
ReadError(String),
ParseError(String),
ProcStatParseError(String),
NotAvailable(String),
}
impl From<io::Error> for SysMonitorError {
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 {
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}")
}
Self::NotAvailable(s) => write!(f, "Information not available: {s}"),
}
}
}

1
src/util/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod error;