mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
package: rename to watt
This commit is contained in:
parent
008e05b726
commit
a25ae59bde
3 changed files with 28 additions and 540 deletions
116
Cargo.lock
generated
116
Cargo.lock
generated
|
@ -182,27 +182,6 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs"
|
|
||||||
version = "6.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
|
||||||
dependencies = [
|
|
||||||
"dirs-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-sys"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"option-ext",
|
|
||||||
"redox_users",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -232,17 +211,6 @@ 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 = "getrandom"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
|
@ -301,12 +269,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jiff-static",
|
"jiff-static",
|
||||||
"jiff-tzdb-platform",
|
|
||||||
"log",
|
"log",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"portable-atomic-util",
|
"portable-atomic-util",
|
||||||
"serde",
|
"serde",
|
||||||
"windows-sys",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -320,37 +286,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff-tzdb"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff-tzdb-platform"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
|
||||||
dependencies = [
|
|
||||||
"jiff-tzdb",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libredox"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
|
@ -391,12 +332,6 @@ version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "option-ext"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
@ -430,17 +365,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_users"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
"libredox",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
@ -505,26 +429,6 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "superfreq"
|
|
||||||
version = "0.3.2"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"clap",
|
|
||||||
"clap-verbosity-flag",
|
|
||||||
"ctrlc",
|
|
||||||
"derive_more",
|
|
||||||
"dirs",
|
|
||||||
"env_logger",
|
|
||||||
"jiff",
|
|
||||||
"log",
|
|
||||||
"num_cpus",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"toml",
|
|
||||||
"yansi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.101"
|
version = "2.0.101"
|
||||||
|
@ -622,10 +526,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "watt"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
dependencies = [
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"clap-verbosity-flag",
|
||||||
|
"ctrlc",
|
||||||
|
"derive_more",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"num_cpus",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
|
|
26
Cargo.toml
26
Cargo.toml
|
@ -1,23 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "superfreq"
|
name = "watt"
|
||||||
description = "Modern CPU frequency and power management utility for Linux"
|
description = "Modern CPU frequency and power management utility for Linux"
|
||||||
version = "0.3.2"
|
version = "0.4.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["NotAShelf <raf@notashelf.dev>"]
|
authors = ["NotAShelf <raf@notashelf.dev>", "RGBCube <git@rgbcu.be>"]
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
toml = "0.8"
|
|
||||||
dirs = "6.0"
|
|
||||||
clap = { version = "4.0", features = ["derive", "env"] }
|
|
||||||
num_cpus = "1.16"
|
|
||||||
ctrlc = "3.4"
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.11"
|
|
||||||
thiserror = "2.0"
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
jiff = "0.2.13"
|
clap = { version = "4.0", features = ["derive", "env"] }
|
||||||
clap-verbosity-flag = "3.0.2"
|
clap-verbosity-flag = "3.0.2"
|
||||||
yansi = { version = "1.0.1", features = ["detect-env", "detect-tty"] }
|
ctrlc = "3.4"
|
||||||
derive_more = { version = "2.0.1", features = ["full"] }
|
derive_more = { version = "2.0.1", features = ["full"] }
|
||||||
|
env_logger = "0.11"
|
||||||
|
log = "0.4"
|
||||||
|
num_cpus = "1.16"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
thiserror = "2.0"
|
||||||
|
toml = "0.8"
|
||||||
|
yansi = { version = "1.0.1", features = ["detect-env", "detect-tty"] }
|
||||||
|
|
|
@ -1,426 +0,0 @@
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::bail;
|
|
||||||
|
|
||||||
use crate::config::AppConfig;
|
|
||||||
use crate::core::SystemReport;
|
|
||||||
use crate::engine;
|
|
||||||
use crate::monitor;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
/// Tracks historical system data for "advanced" adaptive polling
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SystemHistory {
|
|
||||||
/// Last several CPU usage measurements
|
|
||||||
cpu_usage_history: VecDeque<f32>,
|
|
||||||
/// Last several temperature readings
|
|
||||||
temperature_history: VecDeque<f32>,
|
|
||||||
/// Time of last detected user activity
|
|
||||||
last_user_activity: Instant,
|
|
||||||
/// Previous battery percentage (to calculate discharge rate)
|
|
||||||
last_battery_percentage: Option<f32>,
|
|
||||||
/// Timestamp of last battery reading
|
|
||||||
last_battery_timestamp: Option<Instant>,
|
|
||||||
/// Battery discharge rate (%/hour)
|
|
||||||
battery_discharge_rate: Option<f32>,
|
|
||||||
/// Time spent in each system state
|
|
||||||
state_durations: std::collections::HashMap<SystemState, Duration>,
|
|
||||||
/// Last time a state transition happened
|
|
||||||
last_state_change: Instant,
|
|
||||||
/// Current system state
|
|
||||||
current_state: SystemState,
|
|
||||||
/// Last computed optimal polling interval
|
|
||||||
last_computed_interval: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemHistory {
|
|
||||||
/// Update system history with new report data
|
|
||||||
fn update(&mut self, report: &SystemReport) {
|
|
||||||
// Update CPU usage history
|
|
||||||
if !report.cpu_cores.is_empty() {
|
|
||||||
let mut total_usage: f32 = 0.0;
|
|
||||||
let mut core_count: usize = 0;
|
|
||||||
|
|
||||||
for core in &report.cpu_cores {
|
|
||||||
if let Some(usage) = core.usage_percent {
|
|
||||||
total_usage += usage;
|
|
||||||
core_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if core_count > 0 {
|
|
||||||
let avg_usage = total_usage / core_count as f32;
|
|
||||||
|
|
||||||
// Keep only the last 5 measurements
|
|
||||||
if self.cpu_usage_history.len() >= 5 {
|
|
||||||
self.cpu_usage_history.pop_front();
|
|
||||||
}
|
|
||||||
self.cpu_usage_history.push_back(avg_usage);
|
|
||||||
|
|
||||||
// Update last_user_activity if CPU usage indicates activity
|
|
||||||
// Consider significant CPU usage or sudden change as user activity
|
|
||||||
if avg_usage > 20.0
|
|
||||||
|| (self.cpu_usage_history.len() > 1
|
|
||||||
&& (avg_usage - self.cpu_usage_history[self.cpu_usage_history.len() - 2])
|
|
||||||
.abs()
|
|
||||||
> 15.0)
|
|
||||||
{
|
|
||||||
self.last_user_activity = Instant::now();
|
|
||||||
log::debug!("User activity detected based on CPU usage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update temperature history
|
|
||||||
if let Some(temp) = report.cpu_global.average_temperature_celsius {
|
|
||||||
if self.temperature_history.len() >= 5 {
|
|
||||||
self.temperature_history.pop_front();
|
|
||||||
}
|
|
||||||
self.temperature_history.push_back(temp);
|
|
||||||
|
|
||||||
// Significant temperature increase can indicate user activity
|
|
||||||
if self.temperature_history.len() > 1 {
|
|
||||||
let temp_change =
|
|
||||||
temp - self.temperature_history[self.temperature_history.len() - 2];
|
|
||||||
if temp_change > 5.0 {
|
|
||||||
// 5°C rise in temperature
|
|
||||||
self.last_user_activity = Instant::now();
|
|
||||||
log::debug!("User activity detected based on temperature change");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update battery discharge rate
|
|
||||||
if let Some(battery) = report.batteries.first() {
|
|
||||||
// Reset when we are charging or have just connected AC
|
|
||||||
if battery.ac_connected {
|
|
||||||
// Reset discharge tracking but continue updating the rest of
|
|
||||||
// the history so we still detect activity/load changes on AC.
|
|
||||||
self.battery_discharge_rate = None;
|
|
||||||
self.last_battery_percentage = None;
|
|
||||||
self.last_battery_timestamp = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(current_percentage) = battery.capacity_percent {
|
|
||||||
let current_percent = f32::from(current_percentage);
|
|
||||||
|
|
||||||
if let (Some(last_percentage), Some(last_timestamp)) =
|
|
||||||
(self.last_battery_percentage, self.last_battery_timestamp)
|
|
||||||
{
|
|
||||||
let elapsed_hours = last_timestamp.elapsed().as_secs_f32() / 3600.0;
|
|
||||||
// Only calculate discharge rate if at least 30 seconds have passed
|
|
||||||
// and we're not on AC power
|
|
||||||
if elapsed_hours > 0.0083 && !battery.ac_connected {
|
|
||||||
// 0.0083 hours = 30 seconds
|
|
||||||
// Calculate discharge rate in percent per hour
|
|
||||||
let percent_change = last_percentage - current_percent;
|
|
||||||
if percent_change > 0.0 {
|
|
||||||
// Only if battery is discharging
|
|
||||||
let hourly_rate = percent_change / elapsed_hours;
|
|
||||||
// Clamp the discharge rate to a reasonable maximum value (100%/hour)
|
|
||||||
let clamped_rate = hourly_rate.min(100.0);
|
|
||||||
self.battery_discharge_rate = Some(clamped_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_battery_percentage = Some(current_percent);
|
|
||||||
self.last_battery_timestamp = Some(Instant::now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update system state tracking
|
|
||||||
let new_state = determine_system_state(report, self);
|
|
||||||
if new_state != self.current_state {
|
|
||||||
// Record time spent in previous state
|
|
||||||
let time_in_state = self.last_state_change.elapsed();
|
|
||||||
*self
|
|
||||||
.state_durations
|
|
||||||
.entry(self.current_state.clone())
|
|
||||||
.or_insert(Duration::ZERO) += time_in_state;
|
|
||||||
|
|
||||||
// State changes (except to Idle) likely indicate user activity
|
|
||||||
if new_state != SystemState::Idle && new_state != SystemState::LowLoad {
|
|
||||||
self.last_user_activity = Instant::now();
|
|
||||||
log::debug!("User activity detected based on system state change to {new_state:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
self.current_state = new_state;
|
|
||||||
self.last_state_change = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for significant load changes
|
|
||||||
if report.system_load.load_avg_1min > 1.0 {
|
|
||||||
self.last_user_activity = Instant::now();
|
|
||||||
log::debug!("User activity detected based on system load");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate CPU usage volatility (how much it's changing)
|
|
||||||
fn get_cpu_volatility(&self) -> f32 {
|
|
||||||
if self.cpu_usage_history.len() < 2 {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sum_of_changes = 0.0;
|
|
||||||
for i in 1..self.cpu_usage_history.len() {
|
|
||||||
sum_of_changes += (self.cpu_usage_history[i] - self.cpu_usage_history[i - 1]).abs();
|
|
||||||
}
|
|
||||||
|
|
||||||
sum_of_changes / (self.cpu_usage_history.len() - 1) as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate temperature volatility
|
|
||||||
fn get_temperature_volatility(&self) -> f32 {
|
|
||||||
if self.temperature_history.len() < 2 {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sum_of_changes = 0.0;
|
|
||||||
for i in 1..self.temperature_history.len() {
|
|
||||||
sum_of_changes += (self.temperature_history[i] - self.temperature_history[i - 1]).abs();
|
|
||||||
}
|
|
||||||
|
|
||||||
sum_of_changes / (self.temperature_history.len() - 1) as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine if the system appears to be idle
|
|
||||||
fn is_system_idle(&self) -> bool {
|
|
||||||
if self.cpu_usage_history.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// System considered idle if the average CPU usage of last readings is below 10%
|
|
||||||
let recent_avg =
|
|
||||||
self.cpu_usage_history.iter().sum::<f32>() / self.cpu_usage_history.len() as f32;
|
|
||||||
recent_avg < 10.0 && self.get_cpu_volatility() < 5.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the daemon
|
|
||||||
pub fn run_daemon(config: AppConfig) -> anyhow::Result<()> {
|
|
||||||
log::info!("Starting superfreq daemon...");
|
|
||||||
|
|
||||||
// Validate critical configuration values before proceeding
|
|
||||||
validate_poll_intervals(
|
|
||||||
config.daemon.min_poll_interval_sec,
|
|
||||||
config.daemon.max_poll_interval_sec,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Create a flag that will be set to true when a signal is received
|
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
|
||||||
let r = running.clone();
|
|
||||||
|
|
||||||
// Set up signal handlers
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
log::info!("Received shutdown signal, exiting...");
|
|
||||||
r.store(false, Ordering::SeqCst);
|
|
||||||
})
|
|
||||||
.context("failed to set Ctrl-C handler")?;
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
"Daemon initialized with poll interval: {}s",
|
|
||||||
config.daemon.poll_interval_sec
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set up stats file if configured
|
|
||||||
if let Some(stats_path) = &config.daemon.stats_file_path {
|
|
||||||
log::info!("Stats will be written to: {stats_path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables for adaptive polling
|
|
||||||
// Make sure that the poll interval is *never* zero to prevent a busy loop
|
|
||||||
let mut current_poll_interval = config.daemon.poll_interval_sec.max(1);
|
|
||||||
if config.daemon.poll_interval_sec == 0 {
|
|
||||||
log::warn!(
|
|
||||||
"Poll interval is set to zero in config, using 1s minimum to prevent a busy loop"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut system_history = SystemHistory::default();
|
|
||||||
|
|
||||||
// Main loop
|
|
||||||
while running.load(Ordering::SeqCst) {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
match monitor::collect_system_report(&config) {
|
|
||||||
Ok(report) => {
|
|
||||||
log::debug!("Collected system report, applying settings...");
|
|
||||||
|
|
||||||
// Store the current state before updating history
|
|
||||||
let previous_state = system_history.current_state.clone();
|
|
||||||
|
|
||||||
// Update system history with new data
|
|
||||||
system_history.update(&report);
|
|
||||||
|
|
||||||
// Update the stats file if configured
|
|
||||||
if let Some(stats_path) = &config.daemon.stats_file_path {
|
|
||||||
if let Err(e) = write_stats_file(stats_path, &report) {
|
|
||||||
log::error!("Failed to write stats file: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match engine::determine_and_apply_settings(&report, &config, None) {
|
|
||||||
Ok(()) => {
|
|
||||||
log::debug!("Successfully applied system settings");
|
|
||||||
|
|
||||||
// If system state changed, log the new state
|
|
||||||
if system_history.current_state != previous_state {
|
|
||||||
log::info!(
|
|
||||||
"System state changed to: {:?}",
|
|
||||||
system_history.current_state
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error applying system settings: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're on battery
|
|
||||||
let on_battery = !report.batteries.is_empty()
|
|
||||||
&& report.batteries.first().is_some_and(|b| !b.ac_connected);
|
|
||||||
|
|
||||||
// Calculate optimal polling interval if adaptive polling is enabled
|
|
||||||
if config.daemon.adaptive_interval {
|
|
||||||
match system_history.calculate_optimal_interval(&config, on_battery) {
|
|
||||||
Ok(optimal_interval) => {
|
|
||||||
// Store the new interval
|
|
||||||
system_history.last_computed_interval = Some(optimal_interval);
|
|
||||||
|
|
||||||
log::debug!("Recalculated optimal interval: {optimal_interval}s");
|
|
||||||
|
|
||||||
// Don't change the interval too dramatically at once
|
|
||||||
match optimal_interval.cmp(¤t_poll_interval) {
|
|
||||||
std::cmp::Ordering::Greater => {
|
|
||||||
current_poll_interval =
|
|
||||||
(current_poll_interval + optimal_interval) / 2;
|
|
||||||
}
|
|
||||||
std::cmp::Ordering::Less => {
|
|
||||||
current_poll_interval = current_poll_interval
|
|
||||||
- ((current_poll_interval - optimal_interval) / 2).max(1);
|
|
||||||
}
|
|
||||||
std::cmp::Ordering::Equal => {
|
|
||||||
// No change needed when they're equal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// Log the error and stop the daemon when an invalid configuration is detected
|
|
||||||
log::error!("Critical configuration error: {e}");
|
|
||||||
running.store(false, Ordering::SeqCst);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that we respect the (user) configured min and max limits
|
|
||||||
current_poll_interval = current_poll_interval.clamp(
|
|
||||||
config.daemon.min_poll_interval_sec,
|
|
||||||
config.daemon.max_poll_interval_sec,
|
|
||||||
);
|
|
||||||
|
|
||||||
log::debug!("Adaptive polling: set interval to {current_poll_interval}s");
|
|
||||||
} else {
|
|
||||||
// If adaptive polling is disabled, still apply battery-saving adjustment
|
|
||||||
if config.daemon.throttle_on_battery && on_battery {
|
|
||||||
let battery_multiplier = 2; // poll half as often on battery
|
|
||||||
|
|
||||||
// We need to make sure `poll_interval_sec` is *at least* 1
|
|
||||||
// before multiplying.
|
|
||||||
let safe_interval = config.daemon.poll_interval_sec.max(1);
|
|
||||||
current_poll_interval = (safe_interval * battery_multiplier)
|
|
||||||
.min(config.daemon.max_poll_interval_sec);
|
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"On battery power, increased poll interval to {current_poll_interval}s"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Use the configured poll interval
|
|
||||||
current_poll_interval = config.daemon.poll_interval_sec.max(1);
|
|
||||||
if config.daemon.poll_interval_sec == 0 {
|
|
||||||
log::debug!(
|
|
||||||
"Using minimum poll interval of 1s instead of configured 0s"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error collecting system report: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for the remaining time in the poll interval
|
|
||||||
let elapsed = start_time.elapsed();
|
|
||||||
let poll_duration = Duration::from_secs(current_poll_interval);
|
|
||||||
if elapsed < poll_duration {
|
|
||||||
let sleep_time = poll_duration - elapsed;
|
|
||||||
log::debug!("Sleeping for {}s until next cycle", sleep_time.as_secs());
|
|
||||||
std::thread::sleep(sleep_time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Daemon stopped");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simplified system state used for determining when to adjust polling interval
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
|
|
||||||
enum SystemState {
|
|
||||||
#[default]
|
|
||||||
Unknown,
|
|
||||||
OnAC,
|
|
||||||
OnBattery,
|
|
||||||
HighLoad,
|
|
||||||
LowLoad,
|
|
||||||
HighTemp,
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the current system state for adaptive polling
|
|
||||||
fn determine_system_state(report: &SystemReport, history: &SystemHistory) -> SystemState {
|
|
||||||
// Check power state first
|
|
||||||
if !report.batteries.is_empty() {
|
|
||||||
if let Some(battery) = report.batteries.first() {
|
|
||||||
if battery.ac_connected {
|
|
||||||
return SystemState::OnAC;
|
|
||||||
}
|
|
||||||
return SystemState::OnBattery;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No batteries means desktop, so always AC
|
|
||||||
if report.batteries.is_empty() {
|
|
||||||
return SystemState::OnAC;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check temperature
|
|
||||||
if let Some(temp) = report.cpu_global.average_temperature_celsius {
|
|
||||||
if temp > 80.0 {
|
|
||||||
return SystemState::HighTemp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check load first, as high load should take precedence over idle state
|
|
||||||
let avg_load = report.system_load.load_avg_1min;
|
|
||||||
if avg_load > 3.0 {
|
|
||||||
return SystemState::HighLoad;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check idle state only if we don't have high load
|
|
||||||
if history.is_system_idle() {
|
|
||||||
return SystemState::Idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for low load
|
|
||||||
if avg_load < 0.5 {
|
|
||||||
return SystemState::LowLoad;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default case
|
|
||||||
SystemState::Unknown
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue