From dde938b638d951171c7df6ff042f35e9a240b39a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 00:15:18 +0300 Subject: [PATCH] cli: more command logic to a dedicated module; add debug cmd --- src/cli/debug.rs | 166 ++++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 1 + src/config/watcher.rs | 73 +++++++++++++++++++ src/engine.rs | 4 +- src/main.rs | 4 + src/monitor.rs | 9 ++- 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 src/cli/debug.rs create mode 100644 src/cli/mod.rs create mode 100644 src/config/watcher.rs diff --git a/src/cli/debug.rs b/src/cli/debug.rs new file mode 100644 index 0000000..3e5ac54 --- /dev/null +++ b/src/cli/debug.rs @@ -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> { + 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), + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..2f36523 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1 @@ +pub mod debug; diff --git a/src/config/watcher.rs b/src/config/watcher.rs new file mode 100644 index 0000000..78ece3c --- /dev/null +++ b/src/config/watcher.rs @@ -0,0 +1,73 @@ +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use std::path::Path; +use std::sync::mpsc::{channel, Receiver}; +use std::time::Duration; +use std::thread; +use std::error::Error; + +use crate::config::{load_config, AppConfig}; + +/// Watches a configuration file for changes and reloads it when modified +pub struct ConfigWatcher { + rx: Receiver>, + _watcher: RecommendedWatcher, // keep watcher alive while watching + config_path: String, +} + +impl ConfigWatcher { + /// Initialize a new config watcher for the given path + pub fn new(config_path: &str) -> Result { + 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(), + }) + } + + /// Check for config file changes and reload if necessary + /// + /// # Returns + /// + /// `Some(AppConfig)` if the config was reloaded, `None`` otherwise + pub fn check_for_changes(&self) -> Option>> { + // Non-blocking check for file events + match self.rx.try_recv() { + Ok(Ok(event)) => { + // Only process write/modify events + if matches!(event.kind, EventKind::Modify(_)) { + // Add a small delay to ensure the file write is complete + thread::sleep(Duration::from_millis(100)); + + // Attempt to reload the config + match load_config() { + Ok(config) => { + println!("Configuration file changed. Reloaded configuration."); + Some(Ok(config)) + } + Err(e) => { + eprintln!("Error reloading configuration: {e}"); + Some(Err(Box::new(e))) + } + } + } else { + None + } + } + // No events or channel errors + _ => None, + } + } + + /// Get the path of the config file being watched + pub const fn config_path(&self) -> &String { + &self.config_path + } +} \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index c898256..4f031d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -67,8 +67,8 @@ pub fn determine_and_apply_settings( // If no batteries, assume AC power (desktop). // Otherwise, check the ac_connected status from the (first) battery. // XXX: This relies on the setting ac_connected in BatteryInfo being set correctly. - let on_ac_power = report.batteries.is_empty() - || report.batteries.first().map_or(false, |b| b.ac_connected); + let on_ac_power = + report.batteries.is_empty() || report.batteries.first().is_some_and(|b| b.ac_connected); if on_ac_power { println!("Engine: On AC power, selecting Charger profile."); diff --git a/src/main.rs b/src/main.rs index 83fb35c..eb8afbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod cli; mod config; mod conflict; mod core; @@ -45,6 +46,8 @@ enum Commands { #[clap(value_enum)] setting: TurboSetting, }, + /// Display comprehensive debug information + Debug, /// Set Energy Performance Preference (EPP) SetEpp { epp: String, @@ -202,6 +205,7 @@ fn main() { Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile) .map_err(|e| Box::new(e) as Box), Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose), + Some(Commands::Debug) => cli::debug::run_debug(&config), None => { println!("Welcome to superfreq! Use --help for commands."); println!("Current effective configuration: {config:?}"); diff --git a/src/monitor.rs b/src/monitor.rs index a91bd19..18795d8 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -417,7 +417,14 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - let available_governors = get_platform_profiles().unwrap_or_else(|_| vec![]); + let available_governors = if cpufreq_base.join("scaling_available_governors").exists() { + read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else( + |_| vec![], + |s| s.split_whitespace().map(String::from).collect(), + ) + } else { + vec![] + }; let turbo_status = if turbo_status_path.exists() { // 0 means turbo enabled, 1 means disabled for intel_pstate