mirror of
https://github.com/RGBCube/superfreq
synced 2025-08-02 11:57:46 +00:00
Compare commits
4 commits
a5151f475b
...
d5dbb36de4
Author | SHA1 | Date | |
---|---|---|---|
d5dbb36de4 | |||
67e2115588 | |||
3a4b5fb530 | |||
a862b0b456 |
9 changed files with 152 additions and 293 deletions
2
config.toml
Normal file
2
config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[[rule]]
|
||||
priority = 0
|
|
@ -255,7 +255,7 @@ pub enum Expression {
|
|||
pub struct Rule {
|
||||
priority: u8,
|
||||
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
#[serde(default, rename = "if", skip_serializing_if = "is_default")]
|
||||
if_: Expression,
|
||||
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
|
@ -265,7 +265,7 @@ pub struct Rule {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
|
||||
#[serde(transparent, default, rename_all = "kebab-case")]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct DaemonConfig {
|
||||
#[serde(rename = "rule")]
|
||||
rules: Vec<Rule>,
|
||||
|
|
60
src/cpu.rs
60
src/cpu.rs
|
@ -1,33 +1,9 @@
|
|||
use anyhow::{Context, bail};
|
||||
use yansi::Paint as _;
|
||||
|
||||
use std::{fmt, fs, path::Path, string::ToString};
|
||||
use std::{fmt, string::ToString};
|
||||
|
||||
fn exists(path: impl AsRef<Path>) -> bool {
|
||||
let path = path.as_ref();
|
||||
|
||||
path.exists()
|
||||
}
|
||||
|
||||
// Not doing any anyhow stuff here as all the calls of this ignore errors.
|
||||
fn read_u64(path: impl AsRef<Path>) -> anyhow::Result<u64> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
Ok(content.trim().parse()?)
|
||||
}
|
||||
|
||||
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
fs::write(path, value).with_context(|| {
|
||||
format!(
|
||||
"failed to write '{value}' to '{path}'",
|
||||
path = path.display(),
|
||||
)
|
||||
})
|
||||
}
|
||||
use crate::fs;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cpu {
|
||||
|
@ -96,11 +72,11 @@ impl Cpu {
|
|||
pub fn rescan(&mut self) -> anyhow::Result<()> {
|
||||
let Self { number, .. } = self;
|
||||
|
||||
if !exists(format!("/sys/devices/system/cpu/cpu{number}")) {
|
||||
if !fs::exists(format!("/sys/devices/system/cpu/cpu{number}")) {
|
||||
bail!("{self} does not exist");
|
||||
}
|
||||
|
||||
let has_cpufreq = exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq"));
|
||||
let has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq"));
|
||||
|
||||
self.has_cpufreq = has_cpufreq;
|
||||
|
||||
|
@ -110,7 +86,7 @@ impl Cpu {
|
|||
pub fn get_available_governors(&self) -> Vec<String> {
|
||||
let Self { number, .. } = self;
|
||||
|
||||
let Ok(content) = fs::read_to_string(format!(
|
||||
let Some(Ok(content)) = fs::read(format!(
|
||||
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors"
|
||||
)) else {
|
||||
return Vec::new();
|
||||
|
@ -137,7 +113,7 @@ impl Cpu {
|
|||
);
|
||||
}
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor"),
|
||||
governor,
|
||||
)
|
||||
|
@ -151,7 +127,7 @@ impl Cpu {
|
|||
pub fn get_available_epps(&self) -> Vec<String> {
|
||||
let Self { number, .. } = self;
|
||||
|
||||
let Ok(content) = fs::read_to_string(format!(
|
||||
let Some(Ok(content)) = fs::read(format!(
|
||||
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences"
|
||||
)) else {
|
||||
return Vec::new();
|
||||
|
@ -175,7 +151,7 @@ impl Cpu {
|
|||
);
|
||||
}
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference"),
|
||||
epp,
|
||||
)
|
||||
|
@ -226,7 +202,7 @@ impl Cpu {
|
|||
);
|
||||
}
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias"),
|
||||
epb,
|
||||
)
|
||||
|
@ -244,7 +220,7 @@ impl Cpu {
|
|||
let frequency_khz = frequency_mhz * 1000;
|
||||
let frequency_khz = frequency_khz.to_string();
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"),
|
||||
&frequency_khz,
|
||||
)
|
||||
|
@ -256,7 +232,7 @@ impl Cpu {
|
|||
fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
|
||||
let Self { number, .. } = self;
|
||||
|
||||
let Ok(minimum_frequency_khz) = read_u64(format!(
|
||||
let Ok(minimum_frequency_khz) = fs::read_u64(format!(
|
||||
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
|
||||
)) else {
|
||||
// Just let it pass if we can't find anything.
|
||||
|
@ -282,7 +258,7 @@ impl Cpu {
|
|||
let frequency_khz = frequency_mhz * 1000;
|
||||
let frequency_khz = frequency_khz.to_string();
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq"),
|
||||
&frequency_khz,
|
||||
)
|
||||
|
@ -294,7 +270,7 @@ impl Cpu {
|
|||
fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
|
||||
let Self { number, .. } = self;
|
||||
|
||||
let Ok(maximum_frequency_khz) = read_u64(format!(
|
||||
let Ok(maximum_frequency_khz) = fs::read_u64(format!(
|
||||
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
|
||||
)) else {
|
||||
// Just let it pass if we can't find anything.
|
||||
|
@ -331,16 +307,16 @@ impl Cpu {
|
|||
let generic_boost_path = "/sys/devices/system/cpu/cpufreq/boost";
|
||||
|
||||
// Try each boost control path in order of specificity
|
||||
if write(intel_boost_path_negated, value_boost_negated).is_ok() {
|
||||
if fs::write(intel_boost_path_negated, value_boost_negated).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if write(amd_boost_path, value_boost).is_ok() {
|
||||
if fs::write(amd_boost_path, value_boost).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if write(msr_boost_path, value_boost).is_ok() {
|
||||
if fs::write(msr_boost_path, value_boost).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if write(generic_boost_path, value_boost).is_ok() {
|
||||
if fs::write(generic_boost_path, value_boost).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -348,7 +324,7 @@ impl Cpu {
|
|||
if Self::all()?.iter().any(|cpu| {
|
||||
let Cpu { number, .. } = cpu;
|
||||
|
||||
write(
|
||||
fs::write(
|
||||
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/boost"),
|
||||
value_boost,
|
||||
)
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
ops,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::config;
|
||||
|
||||
/// Calculate the idle time multiplier based on system idle time.
|
||||
|
@ -33,13 +39,17 @@ struct Daemon {
|
|||
/// Last time when there was user activity.
|
||||
last_user_activity: Instant,
|
||||
|
||||
/// The last computed polling interval.
|
||||
last_polling_interval: Option<Duration>,
|
||||
|
||||
/// Whether if we are charging right now.
|
||||
charging: bool,
|
||||
|
||||
/// CPU usage and temperature log.
|
||||
cpu_log: VecDeque<CpuLog>,
|
||||
|
||||
/// Power supply status log.
|
||||
power_supply_log: VecDeque<PowerSupplyLog>,
|
||||
|
||||
charging: bool,
|
||||
}
|
||||
|
||||
struct CpuLog {
|
||||
|
@ -167,7 +177,7 @@ impl Daemon {
|
|||
}
|
||||
|
||||
impl Daemon {
|
||||
fn polling_interval(&self) -> Duration {
|
||||
fn polling_interval(&mut self) -> Duration {
|
||||
let mut interval = Duration::from_secs(5);
|
||||
|
||||
// We are on battery, so we must be more conservative with our polling.
|
||||
|
@ -185,6 +195,8 @@ impl Daemon {
|
|||
}
|
||||
}
|
||||
|
||||
// If we can't deterine the discharge rate, that means that
|
||||
// we were very recently started. Which is user activity.
|
||||
None => {
|
||||
interval *= 2;
|
||||
}
|
||||
|
@ -213,10 +225,38 @@ impl Daemon {
|
|||
}
|
||||
}
|
||||
|
||||
todo!("implement rest from daemon_old.rs")
|
||||
let interval = match self.last_polling_interval {
|
||||
Some(last_interval) => Duration::from_secs_f64(
|
||||
// 30% of current computed interval, 70% of last interval.
|
||||
interval.as_secs_f64() * 0.3 + last_interval.as_secs_f64() * 0.7,
|
||||
),
|
||||
|
||||
None => interval,
|
||||
};
|
||||
|
||||
let interval = Duration::from_secs_f64(interval.as_secs_f64().clamp(1.0, 30.0));
|
||||
|
||||
self.last_polling_interval = Some(interval);
|
||||
|
||||
interval
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(config: config::DaemonConfig) -> anyhow::Result<()> {
|
||||
log::info!("starting daemon...");
|
||||
|
||||
let cancelled = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let cancelled_ = Arc::clone(&cancelled);
|
||||
ctrlc::set_handler(move || {
|
||||
log::info!("received shutdown signal");
|
||||
cancelled_.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.context("failed to set Ctrl-C handler")?;
|
||||
|
||||
while !cancelled.load(Ordering::SeqCst) {}
|
||||
|
||||
log::info!("exiting...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,143 +12,6 @@ use std::sync::Arc;
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Parameters for computing optimal polling interval
|
||||
struct IntervalParams {
|
||||
/// Base polling interval in seconds
|
||||
base_interval: u64,
|
||||
/// Minimum allowed polling interval in seconds
|
||||
min_interval: u64,
|
||||
/// Maximum allowed polling interval in seconds
|
||||
max_interval: u64,
|
||||
/// How rapidly CPU usage is changing
|
||||
cpu_volatility: f32,
|
||||
/// How rapidly temperature is changing
|
||||
temp_volatility: f32,
|
||||
/// Battery discharge rate in %/hour if available
|
||||
battery_discharge_rate: Option<f32>,
|
||||
/// Time since last detected user activity
|
||||
last_user_activity: Duration,
|
||||
/// Whether the system appears to be idle
|
||||
is_system_idle: bool,
|
||||
/// Whether the system is running on battery power
|
||||
on_battery: bool,
|
||||
}
|
||||
|
||||
/// Calculate the idle time multiplier based on system idle duration
|
||||
///
|
||||
/// Returns a multiplier between 1.0 and 5.0 (capped):
|
||||
/// - For idle times < 2 minutes: Linear interpolation from 1.0 to 2.0
|
||||
/// - For idle times >= 2 minutes: Logarithmic scaling (1.0 + log2(minutes))
|
||||
fn idle_multiplier(idle_secs: u64) -> f32 {
|
||||
if idle_secs == 0 {
|
||||
return 1.0; // No idle time, no multiplier effect
|
||||
}
|
||||
|
||||
let idle_factor = if idle_secs < 120 {
|
||||
// Less than 2 minutes (0 to 119 seconds)
|
||||
// Linear interpolation from 1.0 (at 0s) to 2.0 (at 120s)
|
||||
1.0 + (idle_secs as f32) / 120.0
|
||||
} else {
|
||||
// 2 minutes (120 seconds) or more
|
||||
let idle_time_minutes = idle_secs / 60;
|
||||
// Logarithmic scaling: 1.0 + log2(minutes)
|
||||
1.0 + (idle_time_minutes as f32).log2().max(0.5)
|
||||
};
|
||||
|
||||
// Cap the multiplier to avoid excessive intervals
|
||||
idle_factor.min(5.0) // max factor of 5x
|
||||
}
|
||||
|
||||
/// Calculate optimal polling interval based on system conditions and history
|
||||
///
|
||||
/// Returns Ok with the calculated interval, or Err if the configuration is invalid
|
||||
fn compute_new(params: &IntervalParams, system_history: &SystemHistory) -> anyhow::Result<u64> {
|
||||
// Use the centralized validation function
|
||||
validate_poll_intervals(params.min_interval, params.max_interval)?;
|
||||
|
||||
// Start with base interval
|
||||
let mut adjusted_interval = params.base_interval;
|
||||
|
||||
// If we're on battery, we want to be more aggressive about saving power
|
||||
if params.on_battery {
|
||||
// Apply a multiplier based on battery discharge rate
|
||||
if let Some(discharge_rate) = params.battery_discharge_rate {
|
||||
if discharge_rate > 20.0 {
|
||||
// High discharge rate - increase polling interval significantly (3x)
|
||||
adjusted_interval = adjusted_interval.saturating_mul(3);
|
||||
} else if discharge_rate > 10.0 {
|
||||
// Moderate discharge - double polling interval (2x)
|
||||
adjusted_interval = adjusted_interval.saturating_mul(2);
|
||||
} else {
|
||||
// Low discharge rate - increase by 50% (multiply by 3/2)
|
||||
adjusted_interval = adjusted_interval.saturating_mul(3).saturating_div(2);
|
||||
}
|
||||
} else {
|
||||
// If we don't know discharge rate, use a conservative multiplier (2x)
|
||||
adjusted_interval = adjusted_interval.saturating_mul(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust for system idleness
|
||||
if params.is_system_idle {
|
||||
let idle_time_seconds = params.last_user_activity.as_secs();
|
||||
|
||||
// Apply adjustment only if the system has been idle for a non-zero duration
|
||||
if idle_time_seconds > 0 {
|
||||
let idle_factor = idle_multiplier(idle_time_seconds);
|
||||
|
||||
log::debug!(
|
||||
"System idle for {} seconds (approx. {} minutes), applying idle factor: {:.2}x",
|
||||
idle_time_seconds,
|
||||
(idle_time_seconds as f32 / 60.0).round(),
|
||||
idle_factor
|
||||
);
|
||||
|
||||
// Convert f32 multiplier to integer-safe math
|
||||
// Multiply by a large number first, then divide to maintain precision
|
||||
// Use 1000 as the scaling factor to preserve up to 3 decimal places
|
||||
let scaling_factor = 1000;
|
||||
let scaled_factor = (idle_factor * scaling_factor as f32) as u64;
|
||||
adjusted_interval = adjusted_interval
|
||||
.saturating_mul(scaled_factor)
|
||||
.saturating_div(scaling_factor);
|
||||
}
|
||||
// If idle_time_seconds is 0, no factor is applied by this block
|
||||
}
|
||||
|
||||
// Adjust for CPU/temperature volatility
|
||||
if params.cpu_volatility > 10.0 || params.temp_volatility > 2.0 {
|
||||
// For division by 2 (halving the interval), we can safely use integer division
|
||||
adjusted_interval = (adjusted_interval / 2).max(1);
|
||||
}
|
||||
|
||||
// Enforce a minimum of 1 second to prevent busy loops, regardless of params.min_interval
|
||||
let min_safe_interval = params.min_interval.max(1);
|
||||
let new_interval = adjusted_interval.clamp(min_safe_interval, params.max_interval);
|
||||
|
||||
// Blend the new interval with the cached value if available
|
||||
let blended_interval = if let Some(cached) = system_history.last_computed_interval {
|
||||
// Use a weighted average: 70% previous value, 30% new value
|
||||
// This smooths out drastic changes in polling frequency
|
||||
const PREVIOUS_VALUE_WEIGHT: u128 = 7; // 70%
|
||||
const NEW_VALUE_WEIGHT: u128 = 3; // 30%
|
||||
const TOTAL_WEIGHT: u128 = PREVIOUS_VALUE_WEIGHT + NEW_VALUE_WEIGHT; // 10
|
||||
|
||||
// XXX: Use u128 arithmetic to avoid overflow with large interval values
|
||||
let result = (u128::from(cached) * PREVIOUS_VALUE_WEIGHT
|
||||
+ u128::from(new_interval) * NEW_VALUE_WEIGHT)
|
||||
/ TOTAL_WEIGHT;
|
||||
|
||||
result as u64
|
||||
} else {
|
||||
new_interval
|
||||
};
|
||||
|
||||
// Blended result still needs to respect the configured bounds
|
||||
// Again enforce minimum of 1 second regardless of params.min_interval
|
||||
Ok(blended_interval.clamp(min_safe_interval, params.max_interval))
|
||||
}
|
||||
|
||||
/// Tracks historical system data for "advanced" adaptive polling
|
||||
#[derive(Debug)]
|
||||
struct SystemHistory {
|
||||
|
@ -174,23 +37,6 @@ struct SystemHistory {
|
|||
last_computed_interval: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for SystemHistory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cpu_usage_history: VecDeque::new(),
|
||||
temperature_history: VecDeque::new(),
|
||||
last_user_activity: Instant::now(),
|
||||
last_battery_percentage: None,
|
||||
last_battery_timestamp: None,
|
||||
battery_discharge_rate: None,
|
||||
state_durations: std::collections::HashMap::new(),
|
||||
last_state_change: Instant::now(),
|
||||
current_state: SystemState::default(),
|
||||
last_computed_interval: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemHistory {
|
||||
/// Update system history with new report data
|
||||
fn update(&mut self, report: &SystemReport) {
|
||||
|
@ -354,45 +200,6 @@ impl SystemHistory {
|
|||
self.cpu_usage_history.iter().sum::<f32>() / self.cpu_usage_history.len() as f32;
|
||||
recent_avg < 10.0 && self.get_cpu_volatility() < 5.0
|
||||
}
|
||||
|
||||
/// Calculate optimal polling interval based on system conditions
|
||||
fn calculate_optimal_interval(
|
||||
&self,
|
||||
config: &AppConfig,
|
||||
on_battery: bool,
|
||||
) -> anyhow::Result<u64> {
|
||||
let params = IntervalParams {
|
||||
base_interval: config.daemon.poll_interval_sec,
|
||||
min_interval: config.daemon.min_poll_interval_sec,
|
||||
max_interval: config.daemon.max_poll_interval_sec,
|
||||
cpu_volatility: self.get_cpu_volatility(),
|
||||
temp_volatility: self.get_temperature_volatility(),
|
||||
battery_discharge_rate: self.battery_discharge_rate,
|
||||
last_user_activity: self.last_user_activity.elapsed(),
|
||||
is_system_idle: self.is_system_idle(),
|
||||
on_battery,
|
||||
};
|
||||
|
||||
compute_new(¶ms, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates that poll interval configuration is consistent
|
||||
/// Returns Ok if configuration is valid, Err with a descriptive message if invalid
|
||||
fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> anyhow::Result<()> {
|
||||
if min_interval < 1 {
|
||||
bail!("min_interval must be ≥ 1");
|
||||
}
|
||||
if max_interval < 1 {
|
||||
bail!("max_interval must be ≥ 1");
|
||||
}
|
||||
if max_interval >= min_interval {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!(
|
||||
"Invalid interval configuration: max_interval ({max_interval}) is less than min_interval ({min_interval})"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the daemon
|
||||
|
@ -561,36 +368,6 @@ pub fn run_daemon(config: AppConfig) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Write current system stats to a file for --stats to read
|
||||
fn write_stats_file(path: &str, report: &SystemReport) -> Result<(), std::io::Error> {
|
||||
let mut file = File::create(path)?;
|
||||
|
||||
writeln!(file, "timestamp={:?}", report.timestamp)?;
|
||||
|
||||
// CPU info
|
||||
writeln!(file, "governor={:?}", report.cpu_global.current_governor)?;
|
||||
writeln!(file, "turbo={:?}", report.cpu_global.turbo_status)?;
|
||||
if let Some(temp) = report.cpu_global.average_temperature_celsius {
|
||||
writeln!(file, "cpu_temp={temp:.1}")?;
|
||||
}
|
||||
|
||||
// Battery info
|
||||
if !report.batteries.is_empty() {
|
||||
let battery = &report.batteries[0];
|
||||
writeln!(file, "ac_power={}", battery.ac_connected)?;
|
||||
if let Some(cap) = battery.capacity_percent {
|
||||
writeln!(file, "battery_percent={cap}")?;
|
||||
}
|
||||
}
|
||||
|
||||
// System load
|
||||
writeln!(file, "load_1m={:.2}", report.system_load.load_avg_1min)?;
|
||||
writeln!(file, "load_5m={:.2}", report.system_load.load_avg_5min)?;
|
||||
writeln!(file, "load_15m={:.2}", report.system_load.load_avg_15min)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Simplified system state used for determining when to adjust polling interval
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
|
||||
enum SystemState {
|
||||
|
|
55
src/fs.rs
Normal file
55
src/fs.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::{fs, io, path::Path};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub fn exists(path: impl AsRef<Path>) -> bool {
|
||||
let path = path.as_ref();
|
||||
|
||||
path.exists()
|
||||
}
|
||||
|
||||
pub fn read_dir(path: impl AsRef<Path>) -> anyhow::Result<fs::ReadDir> {
|
||||
let path = path.as_ref();
|
||||
|
||||
fs::read_dir(path)
|
||||
.with_context(|| format!("failed to read directory '{path}'", path = path.display()))
|
||||
}
|
||||
|
||||
pub fn read(path: impl AsRef<Path>) -> Option<anyhow::Result<String>> {
|
||||
let path = path.as_ref();
|
||||
|
||||
match fs::read_to_string(path) {
|
||||
Ok(string) => Some(Ok(string)),
|
||||
|
||||
Err(error) if error.kind() == io::ErrorKind::NotFound => None,
|
||||
|
||||
Err(error) => Some(
|
||||
Err(error).with_context(|| format!("failed to read '{path}", path = path.display())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_u64(path: impl AsRef<Path>) -> anyhow::Result<u64> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let content = fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read '{path}'", path = path.display()))?;
|
||||
|
||||
Ok(content.trim().parse().with_context(|| {
|
||||
format!(
|
||||
"failed to parse contents of '{path}' as a unsigned number",
|
||||
path = path.display(),
|
||||
)
|
||||
})?)
|
||||
}
|
||||
|
||||
pub fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
fs::write(path, value).with_context(|| {
|
||||
format!(
|
||||
"failed to write '{value}' to '{path}'",
|
||||
path = path.display(),
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
mod cpu;
|
||||
mod power_supply;
|
||||
mod system;
|
||||
|
||||
mod fs;
|
||||
|
||||
mod config;
|
||||
// mod core;
|
||||
mod cpu;
|
||||
mod daemon;
|
||||
// mod engine;
|
||||
// mod monitor;
|
||||
mod power_supply;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser as _;
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
use anyhow::{Context, bail};
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use yansi::Paint as _;
|
||||
|
||||
use std::{
|
||||
fmt, fs,
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
// TODO: Migrate to central utils file. Same exists in cpu.rs.
|
||||
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
fs::write(path, value).with_context(|| {
|
||||
format!(
|
||||
"failed to write '{value}' to '{path}'",
|
||||
path = path.display(),
|
||||
)
|
||||
})
|
||||
}
|
||||
use crate::fs;
|
||||
|
||||
/// Represents a pattern of path suffixes used to control charge thresholds
|
||||
/// for different device vendors.
|
||||
|
@ -136,7 +126,8 @@ impl PowerSupply {
|
|||
fn get_type(&self) -> anyhow::Result<String> {
|
||||
let type_path = self.path.join("type");
|
||||
|
||||
let type_ = fs::read_to_string(&type_path)
|
||||
let type_ = fs::read(&type_path)
|
||||
.with_context(|| format!("'{path}' doesn't exist", path = type_path.display()))?
|
||||
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
|
||||
|
||||
Ok(type_)
|
||||
|
@ -180,9 +171,9 @@ impl PowerSupply {
|
|||
}
|
||||
|
||||
pub fn set_charge_threshold_start(&self, charge_threshold_start: u8) -> anyhow::Result<()> {
|
||||
write(
|
||||
fs::write(
|
||||
&self.charge_threshold_path_start().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
anyhow!(
|
||||
"power supply '{name}' does not support changing charge threshold levels",
|
||||
name = self.name,
|
||||
)
|
||||
|
@ -197,9 +188,9 @@ impl PowerSupply {
|
|||
}
|
||||
|
||||
pub fn set_charge_threshold_end(&self, charge_threshold_end: u8) -> anyhow::Result<()> {
|
||||
write(
|
||||
fs::write(
|
||||
&self.charge_threshold_path_end().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
anyhow!(
|
||||
"power supply '{name}' does not support changing charge threshold levels",
|
||||
name = self.name,
|
||||
)
|
||||
|
@ -216,7 +207,7 @@ impl PowerSupply {
|
|||
pub fn get_available_platform_profiles() -> Vec<String> {
|
||||
let path = "/sys/firmware/acpi/platform_profile_choices";
|
||||
|
||||
let Ok(content) = fs::read_to_string(path) else {
|
||||
let Some(Ok(content)) = fs::read(path) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
|
@ -245,7 +236,7 @@ impl PowerSupply {
|
|||
);
|
||||
}
|
||||
|
||||
write("/sys/firmware/acpi/platform_profile", profile)
|
||||
fs::write("/sys/firmware/acpi/platform_profile", profile)
|
||||
.context("this probably means that your system does not support changing ACPI profiles")
|
||||
}
|
||||
}
|
||||
|
|
14
src/system.rs
Normal file
14
src/system.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
pub struct System {
|
||||
pub is_desktop: bool,
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let mut system = Self { is_desktop: false };
|
||||
system.rescan()?;
|
||||
|
||||
Ok(system)
|
||||
}
|
||||
|
||||
pub fn rescan(&mut self) -> anyhow::Result<()> {}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue