mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
Merge pull request #13 from NotAShelf/config-watcher
config: config watcher and hotreload
This commit is contained in:
commit
166dc5a6a5
14 changed files with 719 additions and 136 deletions
173
Cargo.lock
generated
173
Cargo.lock
generated
|
@ -38,7 +38,7 @@ version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -49,9 +49,15 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
|
@ -123,7 +129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -144,7 +150,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -153,6 +159,27 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -192,12 +219,52 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||||
|
dependencies = [
|
||||||
|
"kqueue-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
|
@ -210,28 +277,75 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.30.1"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"filetime",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"notify-types",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-types"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -272,6 +386,15 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -283,6 +406,15 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
@ -325,6 +457,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"notify",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -414,12 +547,40 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|
|
@ -10,3 +10,4 @@ dirs = "6.0"
|
||||||
clap = { version = "4.0", features = ["derive"] }
|
clap = { version = "4.0", features = ["derive"] }
|
||||||
num_cpus = "1.16"
|
num_cpus = "1.16"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
notify = { version = "8.0.0", features = ["serde"] }
|
||||||
|
|
166
src/cli/debug.rs
Normal file
166
src/cli/debug.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use crate::config::AppConfig;
|
||||||
|
use crate::conflict;
|
||||||
|
use crate::cpu;
|
||||||
|
use crate::monitor;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
/// Prints comprehensive debug information about the system
|
||||||
|
pub fn run_debug(config: &AppConfig) -> Result<(), Box<dyn Error>> {
|
||||||
|
println!("=== SUPERFREQ DEBUG INFORMATION ===");
|
||||||
|
println!("Version: {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
// Current date and time
|
||||||
|
let now = std::time::SystemTime::now();
|
||||||
|
println!("Timestamp: {now:?}");
|
||||||
|
|
||||||
|
// Get system information and conflicts
|
||||||
|
match monitor::collect_system_report(config) {
|
||||||
|
Ok(report) => {
|
||||||
|
println!("\n--- SYSTEM INFORMATION ---");
|
||||||
|
println!("CPU Model: {}", report.system_info.cpu_model);
|
||||||
|
println!("Architecture: {}", report.system_info.architecture);
|
||||||
|
println!(
|
||||||
|
"Linux Distribution: {}",
|
||||||
|
report.system_info.linux_distribution
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("\n--- CONFIGURATION ---");
|
||||||
|
println!("Current Configuration: {config:#?}");
|
||||||
|
|
||||||
|
println!("\n--- CPU INFORMATION ---");
|
||||||
|
println!("Current Governor: {:?}", report.cpu_global.current_governor);
|
||||||
|
println!(
|
||||||
|
"Available Governors: {}",
|
||||||
|
report.cpu_global.available_governors.join(", ")
|
||||||
|
);
|
||||||
|
println!("Turbo Status: {:?}", report.cpu_global.turbo_status);
|
||||||
|
println!(
|
||||||
|
"Energy Performance Preference (EPP): {:?}",
|
||||||
|
report.cpu_global.epp
|
||||||
|
);
|
||||||
|
println!("Energy Performance Bias (EPB): {:?}", report.cpu_global.epb);
|
||||||
|
|
||||||
|
// Add governor override information
|
||||||
|
if let Some(override_governor) = cpu::get_governor_override() {
|
||||||
|
println!("Governor Override: {}", override_governor.trim());
|
||||||
|
} else {
|
||||||
|
println!("Governor Override: None");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- PLATFORM PROFILE ---");
|
||||||
|
println!(
|
||||||
|
"Current Platform Profile: {:?}",
|
||||||
|
report.cpu_global.platform_profile
|
||||||
|
);
|
||||||
|
match cpu::get_platform_profiles() {
|
||||||
|
Ok(profiles) => println!("Available Platform Profiles: {}", profiles.join(", ")),
|
||||||
|
Err(_) => println!("Available Platform Profiles: Not supported on this system"),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- CPU CORES DETAIL ---");
|
||||||
|
println!("Total CPU Cores: {}", report.cpu_cores.len());
|
||||||
|
for core in &report.cpu_cores {
|
||||||
|
println!("Core {}:", core.core_id);
|
||||||
|
println!(
|
||||||
|
" Current Frequency: {} MHz",
|
||||||
|
core.current_frequency_mhz
|
||||||
|
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Min Frequency: {} MHz",
|
||||||
|
core.min_frequency_mhz
|
||||||
|
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Max Frequency: {} MHz",
|
||||||
|
core.max_frequency_mhz
|
||||||
|
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Usage: {}%",
|
||||||
|
core.usage_percent
|
||||||
|
.map_or_else(|| "N/A".to_string(), |u| format!("{u:.1}"))
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Temperature: {}°C",
|
||||||
|
core.temperature_celsius
|
||||||
|
.map_or_else(|| "N/A".to_string(), |t| format!("{t:.1}"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- TEMPERATURE INFORMATION ---");
|
||||||
|
println!(
|
||||||
|
"Average CPU Temperature: {}",
|
||||||
|
report.cpu_global.average_temperature_celsius.map_or_else(
|
||||||
|
|| "N/A (CPU temperature sensor not detected)".to_string(),
|
||||||
|
|t| format!("{t:.1}°C")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("\n--- BATTERY INFORMATION ---");
|
||||||
|
if report.batteries.is_empty() {
|
||||||
|
println!("No batteries found or all are ignored.");
|
||||||
|
} else {
|
||||||
|
for battery in &report.batteries {
|
||||||
|
println!("Battery: {}", battery.name);
|
||||||
|
println!(" AC Connected: {}", battery.ac_connected);
|
||||||
|
println!(
|
||||||
|
" Charging State: {}",
|
||||||
|
battery.charging_state.as_deref().unwrap_or("N/A")
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Capacity: {}%",
|
||||||
|
battery
|
||||||
|
.capacity_percent
|
||||||
|
.map_or_else(|| "N/A".to_string(), |c| c.to_string())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Power Rate: {} W",
|
||||||
|
battery
|
||||||
|
.power_rate_watts
|
||||||
|
.map_or_else(|| "N/A".to_string(), |p| format!("{p:.2}"))
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Charge Start Threshold: {}",
|
||||||
|
battery
|
||||||
|
.charge_start_threshold
|
||||||
|
.map_or_else(|| "N/A".to_string(), |t| t.to_string())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Charge Stop Threshold: {}",
|
||||||
|
battery
|
||||||
|
.charge_stop_threshold
|
||||||
|
.map_or_else(|| "N/A".to_string(), |t| t.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- SYSTEM LOAD ---");
|
||||||
|
println!(
|
||||||
|
"Load Average (1 min): {:.2}",
|
||||||
|
report.system_load.load_avg_1min
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Load Average (5 min): {:.2}",
|
||||||
|
report.system_load.load_avg_5min
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Load Average (15 min): {:.2}",
|
||||||
|
report.system_load.load_avg_15min
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("\n--- CONFLICT DETECTION ---");
|
||||||
|
let conflicts = conflict::detect_conflicts();
|
||||||
|
println!("{}", conflicts.get_conflict_message());
|
||||||
|
|
||||||
|
println!("\n--- DAEMON STATUS ---");
|
||||||
|
// Simple check for daemon status - can be expanded later
|
||||||
|
let daemon_status = fs::metadata("/var/run/superfreq.pid").is_ok();
|
||||||
|
println!("Daemon Running: {daemon_status}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(Box::new(e) as Box<dyn Error>),
|
||||||
|
}
|
||||||
|
}
|
1
src/cli/mod.rs
Normal file
1
src/cli/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod debug;
|
102
src/config/load.rs
Normal file
102
src/config/load.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Configuration loading functionality
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig};
|
||||||
|
|
||||||
|
/// The primary function to load application configuration from a specific path or from default locations.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `specific_path` - If provided, only attempts to load from this path and errors if not found
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(AppConfig)` - Successfully loaded configuration
|
||||||
|
/// * `Err(ConfigError)` - Error loading or parsing configuration
|
||||||
|
pub fn load_config() -> Result<AppConfig, ConfigError> {
|
||||||
|
load_config_from_path(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load configuration from a specific path or try default paths
|
||||||
|
pub fn load_config_from_path(specific_path: Option<&str>) -> Result<AppConfig, ConfigError> {
|
||||||
|
// If a specific path is provided, only try that one
|
||||||
|
if let Some(path_str) = specific_path {
|
||||||
|
let path = Path::new(path_str);
|
||||||
|
if path.exists() {
|
||||||
|
return load_and_parse_config(path);
|
||||||
|
}
|
||||||
|
return Err(ConfigError::IoError(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
format!("Specified config file not found: {}", path.display()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try the standard paths
|
||||||
|
let mut config_paths: Vec<PathBuf> = Vec::new();
|
||||||
|
|
||||||
|
// User-specific path
|
||||||
|
if let Some(home_dir) = dirs::home_dir() {
|
||||||
|
let user_config_path = home_dir.join(".config/superfreq/config.toml");
|
||||||
|
config_paths.push(user_config_path);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Could not determine home directory. User-specific config will not be loaded."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// System-wide paths
|
||||||
|
config_paths.push(PathBuf::from("/etc/superfreq/config.toml"));
|
||||||
|
config_paths.push(PathBuf::from("/etc/superfreq.toml"));
|
||||||
|
|
||||||
|
for path in config_paths {
|
||||||
|
if path.exists() {
|
||||||
|
println!("Loading config from: {}", path.display());
|
||||||
|
match load_and_parse_config(&path) {
|
||||||
|
Ok(config) => return Ok(config),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error with config file {}: {}", path.display(), e);
|
||||||
|
// Continue trying other files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("No configuration file found or all failed to parse. Using default configuration.");
|
||||||
|
// Construct default AppConfig by converting default AppConfigToml
|
||||||
|
let default_toml_config = AppConfigToml::default();
|
||||||
|
Ok(AppConfig {
|
||||||
|
charger: ProfileConfig::from(default_toml_config.charger),
|
||||||
|
battery: ProfileConfig::from(default_toml_config.battery),
|
||||||
|
battery_charge_thresholds: default_toml_config.battery_charge_thresholds,
|
||||||
|
ignored_power_supplies: default_toml_config.ignored_power_supplies,
|
||||||
|
poll_interval_sec: default_toml_config.poll_interval_sec,
|
||||||
|
daemon: DaemonConfig::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load and parse a configuration file
|
||||||
|
fn load_and_parse_config(path: &Path) -> Result<AppConfig, ConfigError> {
|
||||||
|
let contents = fs::read_to_string(path).map_err(ConfigError::IoError)?;
|
||||||
|
|
||||||
|
let toml_app_config =
|
||||||
|
toml::from_str::<AppConfigToml>(&contents).map_err(ConfigError::TomlError)?;
|
||||||
|
|
||||||
|
// Convert AppConfigToml to AppConfig
|
||||||
|
Ok(AppConfig {
|
||||||
|
charger: ProfileConfig::from(toml_app_config.charger),
|
||||||
|
battery: ProfileConfig::from(toml_app_config.battery),
|
||||||
|
battery_charge_thresholds: toml_app_config.battery_charge_thresholds,
|
||||||
|
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
||||||
|
poll_interval_sec: toml_app_config.poll_interval_sec,
|
||||||
|
daemon: DaemonConfig {
|
||||||
|
poll_interval_sec: toml_app_config.daemon.poll_interval_sec,
|
||||||
|
adaptive_interval: toml_app_config.daemon.adaptive_interval,
|
||||||
|
min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec,
|
||||||
|
max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec,
|
||||||
|
throttle_on_battery: toml_app_config.daemon.throttle_on_battery,
|
||||||
|
log_level: toml_app_config.daemon.log_level,
|
||||||
|
stats_file_path: toml_app_config.daemon.stats_file_path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
9
src/config/mod.rs
Normal file
9
src/config/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub mod watcher;
|
||||||
|
|
||||||
|
// Re-export all configuration types and functions
|
||||||
|
pub use self::load::*;
|
||||||
|
pub use self::types::*;
|
||||||
|
|
||||||
|
// Internal organization of config submodules
|
||||||
|
mod load;
|
||||||
|
mod types;
|
|
@ -1,7 +1,6 @@
|
||||||
|
// Configuration types and structures for superfreq
|
||||||
use crate::core::TurboSetting;
|
use crate::core::TurboSetting;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
// Structs for configuration using serde::Deserialize
|
// Structs for configuration using serde::Deserialize
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
@ -52,29 +51,29 @@ const fn default_poll_interval_sec() -> u64 {
|
||||||
// Error type for config loading
|
// Error type for config loading
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
Io(std::io::Error),
|
IoError(std::io::Error),
|
||||||
Toml(toml::de::Error),
|
TomlError(toml::de::Error),
|
||||||
NoValidConfigFound,
|
NoValidConfigFound,
|
||||||
HomeDirNotFound,
|
HomeDirNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ConfigError {
|
impl From<std::io::Error> for ConfigError {
|
||||||
fn from(err: std::io::Error) -> Self {
|
fn from(err: std::io::Error) -> Self {
|
||||||
Self::Io(err)
|
Self::IoError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<toml::de::Error> for ConfigError {
|
impl From<toml::de::Error> for ConfigError {
|
||||||
fn from(err: toml::de::Error) -> Self {
|
fn from(err: toml::de::Error) -> Self {
|
||||||
Self::Toml(err)
|
Self::TomlError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ConfigError {
|
impl std::fmt::Display for ConfigError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Io(e) => write!(f, "I/O error: {e}"),
|
Self::IoError(e) => write!(f, "I/O error: {e}"),
|
||||||
Self::Toml(e) => write!(f, "TOML parsing error: {e}"),
|
Self::TomlError(e) => write!(f, "TOML parsing error: {e}"),
|
||||||
Self::NoValidConfigFound => write!(f, "No valid configuration file found."),
|
Self::NoValidConfigFound => write!(f, "No valid configuration file found."),
|
||||||
Self::HomeDirNotFound => write!(f, "Could not determine user home directory."),
|
Self::HomeDirNotFound => write!(f, "Could not determine user home directory."),
|
||||||
}
|
}
|
||||||
|
@ -83,82 +82,6 @@ impl std::fmt::Display for ConfigError {
|
||||||
|
|
||||||
impl std::error::Error for ConfigError {}
|
impl std::error::Error for ConfigError {}
|
||||||
|
|
||||||
// The primary function to load application configuration.
|
|
||||||
// It tries user-specific and then system-wide TOML files.
|
|
||||||
// Falls back to default settings if no file is found or if parsing fails.
|
|
||||||
pub fn load_config() -> Result<AppConfig, ConfigError> {
|
|
||||||
let mut config_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
|
|
||||||
// User-specific path
|
|
||||||
if let Some(home_dir) = dirs::home_dir() {
|
|
||||||
let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml");
|
|
||||||
config_paths.push(user_config_path);
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"Warning: Could not determine home directory. User-specific config will not be loaded."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// System-wide path
|
|
||||||
let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml");
|
|
||||||
config_paths.push(system_config_path);
|
|
||||||
|
|
||||||
for path in config_paths {
|
|
||||||
if path.exists() {
|
|
||||||
println!("Attempting to load config from: {}", path.display());
|
|
||||||
match fs::read_to_string(&path) {
|
|
||||||
Ok(contents) => {
|
|
||||||
match toml::from_str::<AppConfigToml>(&contents) {
|
|
||||||
Ok(toml_app_config) => {
|
|
||||||
// Convert AppConfigToml to AppConfig
|
|
||||||
let app_config = AppConfig {
|
|
||||||
charger: ProfileConfig::from(toml_app_config.charger),
|
|
||||||
battery: ProfileConfig::from(toml_app_config.battery),
|
|
||||||
battery_charge_thresholds: toml_app_config
|
|
||||||
.battery_charge_thresholds,
|
|
||||||
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
|
||||||
poll_interval_sec: toml_app_config.poll_interval_sec,
|
|
||||||
daemon: DaemonConfig {
|
|
||||||
poll_interval_sec: toml_app_config.daemon.poll_interval_sec,
|
|
||||||
adaptive_interval: toml_app_config.daemon.adaptive_interval,
|
|
||||||
min_poll_interval_sec: toml_app_config
|
|
||||||
.daemon
|
|
||||||
.min_poll_interval_sec,
|
|
||||||
max_poll_interval_sec: toml_app_config
|
|
||||||
.daemon
|
|
||||||
.max_poll_interval_sec,
|
|
||||||
throttle_on_battery: toml_app_config.daemon.throttle_on_battery,
|
|
||||||
log_level: toml_app_config.daemon.log_level,
|
|
||||||
stats_file_path: toml_app_config.daemon.stats_file_path,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return Ok(app_config);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error parsing config file {}: {}", path.display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error reading config file {}: {}", path.display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("No configuration file found or all failed to parse. Using default configuration.");
|
|
||||||
// Construct default AppConfig by converting default AppConfigToml
|
|
||||||
let default_toml_config = AppConfigToml::default();
|
|
||||||
Ok(AppConfig {
|
|
||||||
charger: ProfileConfig::from(default_toml_config.charger),
|
|
||||||
battery: ProfileConfig::from(default_toml_config.battery),
|
|
||||||
battery_charge_thresholds: default_toml_config.battery_charge_thresholds,
|
|
||||||
ignored_power_supplies: default_toml_config.ignored_power_supplies,
|
|
||||||
poll_interval_sec: default_toml_config.poll_interval_sec,
|
|
||||||
daemon: DaemonConfig::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intermediate structs for TOML parsing
|
// Intermediate structs for TOML parsing
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct ProfileConfigToml {
|
pub struct ProfileConfigToml {
|
95
src/config/watcher.rs
Normal file
95
src/config/watcher.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc::{Receiver, TryRecvError, channel};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::config::{AppConfig, load_config_from_path};
|
||||||
|
|
||||||
|
/// Watches a configuration file for changes and reloads it when modified
|
||||||
|
pub struct ConfigWatcher {
|
||||||
|
rx: Receiver<Result<Event, notify::Error>>,
|
||||||
|
_watcher: RecommendedWatcher, // keep watcher alive while watching
|
||||||
|
config_path: String,
|
||||||
|
last_event_time: std::time::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigWatcher {
|
||||||
|
/// Initialize a new config watcher for the given path
|
||||||
|
pub fn new(config_path: &str) -> Result<Self, notify::Error> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
// Create a watcher with default config
|
||||||
|
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
|
||||||
|
|
||||||
|
// Start watching the config file
|
||||||
|
watcher.watch(Path::new(config_path), RecursiveMode::NonRecursive)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
rx,
|
||||||
|
_watcher: watcher,
|
||||||
|
config_path: config_path.to_string(),
|
||||||
|
last_event_time: std::time::Instant::now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check for config file changes and reload if necessary
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `Some(AppConfig)` if the config was reloaded, `None` otherwise
|
||||||
|
pub fn check_for_changes(&mut self) -> Option<Result<AppConfig, Box<dyn Error>>> {
|
||||||
|
// Process all pending events before deciding to reload
|
||||||
|
let mut should_reload = false;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(Ok(event)) => {
|
||||||
|
// Only process write/modify events
|
||||||
|
if matches!(event.kind, EventKind::Modify(_)) {
|
||||||
|
should_reload = true;
|
||||||
|
self.last_event_time = std::time::Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
// File watcher error, log but continue
|
||||||
|
eprintln!("Error watching config file: {e}");
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
// No more events
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => {
|
||||||
|
// Channel disconnected, watcher is dead
|
||||||
|
eprintln!("Config watcher channel disconnected");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce rapid file changes (e.g., from editors that write multiple times)
|
||||||
|
if should_reload {
|
||||||
|
// Wait to ensure file writing is complete
|
||||||
|
let debounce_time = Duration::from_millis(250);
|
||||||
|
let time_since_last_event = self.last_event_time.elapsed();
|
||||||
|
|
||||||
|
if time_since_last_event < debounce_time {
|
||||||
|
thread::sleep(debounce_time - time_since_last_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to reload the config from the specific path being watched
|
||||||
|
match load_config_from_path(Some(&self.config_path)) {
|
||||||
|
Ok(config) => Some(Ok(config)),
|
||||||
|
Err(e) => Some(Err(Box::new(e))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path of the config file being watched
|
||||||
|
pub const fn config_path(&self) -> &String {
|
||||||
|
&self.config_path
|
||||||
|
}
|
||||||
|
}
|
43
src/cpu.rs
43
src/cpu.rs
|
@ -3,8 +3,6 @@ use crate::util::error::ControlError;
|
||||||
use core::str;
|
use core::str;
|
||||||
use std::{fs, io, path::Path, string::ToString};
|
use std::{fs, io, path::Path, string::ToString};
|
||||||
|
|
||||||
impl std::error::Error for ControlError {}
|
|
||||||
|
|
||||||
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
// Write a value to a sysfs file
|
// Write a value to a sysfs file
|
||||||
|
@ -104,23 +102,56 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> {
|
||||||
let value_boost = match setting {
|
let value_boost = match setting {
|
||||||
TurboSetting::Always => "1", // boost = 1 means turbo is enabled
|
TurboSetting::Always => "1", // boost = 1 means turbo is enabled
|
||||||
TurboSetting::Never => "0", // boost = 0 means turbo is disabled
|
TurboSetting::Never => "0", // boost = 0 means turbo is disabled
|
||||||
TurboSetting::Auto => return Err(ControlError::InvalidValueError("Turbo Auto cannot be directly set via intel_pstate/no_turbo or cpufreq/boost. System default.".to_string())),
|
TurboSetting::Auto => return Err(ControlError::InvalidValueError("Turbo Auto cannot be directly set via intel_pstate/no_turbo or cpufreq/boost. System default.".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// AMD specific paths
|
||||||
|
let amd_pstate_path = "/sys/devices/system/cpu/amd_pstate/cpufreq/boost";
|
||||||
|
let msr_boost_path = "/sys/devices/system/cpu/cpufreq/amd_pstate_enable_boost";
|
||||||
|
|
||||||
|
// Path priority (from most to least specific)
|
||||||
let pstate_path = "/sys/devices/system/cpu/intel_pstate/no_turbo";
|
let pstate_path = "/sys/devices/system/cpu/intel_pstate/no_turbo";
|
||||||
let boost_path = "/sys/devices/system/cpu/cpufreq/boost";
|
let boost_path = "/sys/devices/system/cpu/cpufreq/boost";
|
||||||
|
|
||||||
|
// Try each boost control path in order of specificity
|
||||||
if Path::new(pstate_path).exists() {
|
if Path::new(pstate_path).exists() {
|
||||||
write_sysfs_value(pstate_path, value_pstate)
|
write_sysfs_value(pstate_path, value_pstate)
|
||||||
|
} else if Path::new(amd_pstate_path).exists() {
|
||||||
|
write_sysfs_value(amd_pstate_path, value_boost)
|
||||||
|
} else if Path::new(msr_boost_path).exists() {
|
||||||
|
write_sysfs_value(msr_boost_path, value_boost)
|
||||||
} else if Path::new(boost_path).exists() {
|
} else if Path::new(boost_path).exists() {
|
||||||
write_sysfs_value(boost_path, value_boost)
|
write_sysfs_value(boost_path, value_boost)
|
||||||
} else {
|
} else {
|
||||||
Err(ControlError::NotSupported(
|
// Also try per-core cpufreq boost for some AMD systems
|
||||||
"Neither intel_pstate/no_turbo nor cpufreq/boost found.".to_string(),
|
let result = try_set_per_core_boost(value_boost)?;
|
||||||
))
|
if result {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ControlError::NotSupported(
|
||||||
|
"No supported CPU boost control mechanism found.".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to set boost on a per-core basis for systems that support it
|
||||||
|
fn try_set_per_core_boost(value: &str) -> Result<bool> {
|
||||||
|
let mut success = false;
|
||||||
|
let num_cores = get_logical_core_count()?;
|
||||||
|
|
||||||
|
for core_id in 0..num_cores {
|
||||||
|
let boost_path = format!("/sys/devices/system/cpu/cpu{core_id}/cpufreq/boost");
|
||||||
|
|
||||||
|
if Path::new(&boost_path).exists() {
|
||||||
|
write_sysfs_value(&boost_path, value)?;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(success)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_epp(epp: &str, core_id: Option<u32>) -> Result<()> {
|
pub fn set_epp(epp: &str, core_id: Option<u32>) -> Result<()> {
|
||||||
let action = |id: u32| {
|
let action = |id: u32| {
|
||||||
let path = format!("/sys/devices/system/cpu/cpu{id}/cpufreq/energy_performance_preference");
|
let path = format!("/sys/devices/system/cpu/cpu{id}/cpufreq/energy_performance_preference");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::config::watcher::ConfigWatcher;
|
||||||
use crate::config::{AppConfig, LogLevel};
|
use crate::config::{AppConfig, LogLevel};
|
||||||
use crate::conflict;
|
use crate::conflict;
|
||||||
use crate::core::SystemReport;
|
use crate::core::SystemReport;
|
||||||
|
@ -10,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
/// Run the daemon
|
/// Run the daemon
|
||||||
pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Set effective log level based on config and verbose flag
|
// Set effective log level based on config and verbose flag
|
||||||
let effective_log_level = if verbose {
|
let effective_log_level = if verbose {
|
||||||
LogLevel::Debug
|
LogLevel::Debug
|
||||||
|
@ -63,6 +64,48 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize config file watcher if a path is available
|
||||||
|
let config_file_path = if let Ok(path) = std::env::var("SUPERFREQ_CONFIG") {
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
// Check standard config paths
|
||||||
|
let default_paths = [
|
||||||
|
"/etc/superfreq/config.toml",
|
||||||
|
"/etc/superfreq.toml",
|
||||||
|
];
|
||||||
|
|
||||||
|
default_paths
|
||||||
|
.iter()
|
||||||
|
.find(|&path| std::path::Path::new(path).exists())
|
||||||
|
.map(|path| (*path).to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut config_watcher = if let Some(path) = config_file_path { match ConfigWatcher::new(&path) {
|
||||||
|
Ok(watcher) => {
|
||||||
|
log_message(
|
||||||
|
&effective_log_level,
|
||||||
|
LogLevel::Info,
|
||||||
|
&format!("Watching config file: {path}"),
|
||||||
|
);
|
||||||
|
Some(watcher)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log_message(
|
||||||
|
&effective_log_level,
|
||||||
|
LogLevel::Warning,
|
||||||
|
&format!("Failed to initialize config file watcher: {e}"),
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} } else {
|
||||||
|
log_message(
|
||||||
|
&effective_log_level,
|
||||||
|
LogLevel::Warning,
|
||||||
|
"No config file found to watch for changes.",
|
||||||
|
);
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Variables for adaptive polling
|
// Variables for adaptive polling
|
||||||
let mut current_poll_interval = config.daemon.poll_interval_sec;
|
let mut current_poll_interval = config.daemon.poll_interval_sec;
|
||||||
let mut last_settings_change = Instant::now();
|
let mut last_settings_change = Instant::now();
|
||||||
|
@ -72,6 +115,34 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::e
|
||||||
while running.load(Ordering::SeqCst) {
|
while running.load(Ordering::SeqCst) {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
// Check for configuration changes
|
||||||
|
if let Some(watcher) = &mut config_watcher {
|
||||||
|
if let Some(config_result) = watcher.check_for_changes() {
|
||||||
|
match config_result {
|
||||||
|
Ok(new_config) => {
|
||||||
|
log_message(
|
||||||
|
&effective_log_level,
|
||||||
|
LogLevel::Info,
|
||||||
|
"Config file changed, updating configuration",
|
||||||
|
);
|
||||||
|
config = new_config;
|
||||||
|
// Reset polling interval after config change
|
||||||
|
current_poll_interval = config.daemon.poll_interval_sec;
|
||||||
|
// Record this as a settings change for adaptive polling purposes
|
||||||
|
last_settings_change = Instant::now();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log_message(
|
||||||
|
&effective_log_level,
|
||||||
|
LogLevel::Error,
|
||||||
|
&format!("Error loading new configuration: {e}"),
|
||||||
|
);
|
||||||
|
// Continue with existing config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match monitor::collect_system_report(&config) {
|
match monitor::collect_system_report(&config) {
|
||||||
Ok(report) => {
|
Ok(report) => {
|
||||||
log_message(
|
log_message(
|
||||||
|
|
|
@ -1,37 +1,7 @@
|
||||||
use crate::config::{AppConfig, ProfileConfig};
|
use crate::config::{AppConfig, ProfileConfig};
|
||||||
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||||
use crate::cpu::{self};
|
use crate::cpu::{self};
|
||||||
use crate::util::error::ControlError;
|
use crate::util::error::EngineError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EngineError {
|
|
||||||
ControlError(ControlError),
|
|
||||||
ConfigurationError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ControlError> for EngineError {
|
|
||||||
fn from(err: ControlError) -> Self {
|
|
||||||
Self::ControlError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for EngineError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
|
|
||||||
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for EngineError {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::ControlError(e) => Some(e),
|
|
||||||
Self::ConfigurationError(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the appropriate CPU profile based on power status or forced mode,
|
/// Determines the appropriate CPU profile based on power status or forced mode,
|
||||||
/// and applies the settings using functions from the `cpu` module.
|
/// and applies the settings using functions from the `cpu` module.
|
||||||
|
@ -67,8 +37,8 @@ pub fn determine_and_apply_settings(
|
||||||
// If no batteries, assume AC power (desktop).
|
// If no batteries, assume AC power (desktop).
|
||||||
// Otherwise, check the ac_connected status from the (first) battery.
|
// Otherwise, check the ac_connected status from the (first) battery.
|
||||||
// XXX: This relies on the setting ac_connected in BatteryInfo being set correctly.
|
// XXX: This relies on the setting ac_connected in BatteryInfo being set correctly.
|
||||||
let on_ac_power = report.batteries.is_empty()
|
let on_ac_power =
|
||||||
|| report.batteries.first().map_or(false, |b| b.ac_connected);
|
report.batteries.is_empty() || report.batteries.first().is_some_and(|b| b.ac_connected);
|
||||||
|
|
||||||
if on_ac_power {
|
if on_ac_power {
|
||||||
println!("Engine: On AC power, selecting Charger profile.");
|
println!("Engine: On AC power, selecting Charger profile.");
|
||||||
|
@ -158,6 +128,14 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Validate the configuration to ensure it's usable
|
||||||
|
if turbo_settings.load_threshold_high <= turbo_settings.load_threshold_low {
|
||||||
|
return Err(EngineError::ConfigurationError(
|
||||||
|
"Invalid turbo auto settings: high threshold must be greater than low threshold"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Decision logic for enabling/disabling turbo
|
// Decision logic for enabling/disabling turbo
|
||||||
let enable_turbo = match (cpu_temp, avg_cpu_usage) {
|
let enable_turbo = match (cpu_temp, avg_cpu_usage) {
|
||||||
// If temperature is too high, disable turbo regardless of load
|
// If temperature is too high, disable turbo regardless of load
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod conflict;
|
mod conflict;
|
||||||
mod core;
|
mod core;
|
||||||
|
@ -45,6 +46,8 @@ enum Commands {
|
||||||
#[clap(value_enum)]
|
#[clap(value_enum)]
|
||||||
setting: TurboSetting,
|
setting: TurboSetting,
|
||||||
},
|
},
|
||||||
|
/// Display comprehensive debug information
|
||||||
|
Debug,
|
||||||
/// Set Energy Performance Preference (EPP)
|
/// Set Energy Performance Preference (EPP)
|
||||||
SetEpp {
|
SetEpp {
|
||||||
epp: String,
|
epp: String,
|
||||||
|
@ -202,6 +205,7 @@ fn main() {
|
||||||
Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile)
|
Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile)
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
|
||||||
Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose),
|
Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose),
|
||||||
|
Some(Commands::Debug) => cli::debug::run_debug(&config),
|
||||||
None => {
|
None => {
|
||||||
println!("Welcome to superfreq! Use --help for commands.");
|
println!("Welcome to superfreq! Use --help for commands.");
|
||||||
println!("Current effective configuration: {config:?}");
|
println!("Current effective configuration: {config:?}");
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
|
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
|
||||||
use crate::cpu::{get_logical_core_count, get_platform_profiles};
|
use crate::cpu::get_logical_core_count;
|
||||||
use crate::util::error::ControlError;
|
|
||||||
use crate::util::error::SysMonitorError;
|
use crate::util::error::SysMonitorError;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs, io,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
thread,
|
thread,
|
||||||
|
@ -13,8 +12,6 @@ use std::{
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl std::error::Error for SysMonitorError {}
|
|
||||||
|
|
||||||
pub type Result<T, E = SysMonitorError> = std::result::Result<T, E>;
|
pub type Result<T, E = SysMonitorError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
// Read a sysfs file to a string, trimming whitespace
|
// Read a sysfs file to a string, trimming whitespace
|
||||||
|
@ -417,7 +414,17 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let available_governors = get_platform_profiles().unwrap_or_else(|_| vec![]);
|
let available_governors = if cpufreq_base_path
|
||||||
|
.join("scaling_available_governors")
|
||||||
|
.exists()
|
||||||
|
{
|
||||||
|
read_sysfs_file_trimmed(cpufreq_base_path.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() {
|
let turbo_status = if turbo_status_path.exists() {
|
||||||
// 0 means turbo enabled, 1 means disabled for intel_pstate
|
// 0 means turbo enabled, 1 means disabled for intel_pstate
|
||||||
|
|
|
@ -39,6 +39,8 @@ impl std::fmt::Display for ControlError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ControlError {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SysMonitorError {
|
pub enum SysMonitorError {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
|
@ -67,3 +69,35 @@ impl std::fmt::Display for SysMonitorError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for SysMonitorError {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EngineError {
|
||||||
|
ControlError(ControlError),
|
||||||
|
ConfigurationError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ControlError> for EngineError {
|
||||||
|
fn from(err: ControlError) -> Self {
|
||||||
|
Self::ControlError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EngineError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
|
||||||
|
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for EngineError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::ControlError(e) => Some(e),
|
||||||
|
Self::ConfigurationError(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue