From fad56e6e1ea5fc405934577a30a88e4abf045037 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 02:05:12 +0300 Subject: [PATCH] core: improve formatting & display for `info` command --- Cargo.lock | 229 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/daemon.rs | 41 ++++---- src/main.rs | 266 +++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 457 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6afa2b5..f4280d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -52,6 +67,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bitflags" version = "1.3.2" @@ -64,6 +85,21 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cc" +version = "1.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -76,6 +112,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "4.5.38" @@ -122,6 +172,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "ctrlc" version = "3.4.7" @@ -209,6 +265,30 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -245,6 +325,16 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -346,6 +436,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -406,6 +505,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "same-file" version = "1.0.6" @@ -444,6 +549,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "strsim" version = "0.11.1" @@ -454,6 +565,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" name = "superfreq" version = "0.1.0" dependencies = [ + "chrono", "clap", "ctrlc", "dirs", @@ -563,6 +675,64 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -572,6 +742,65 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index d53a8b4..b88d13c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ clap = { version = "4.0", features = ["derive"] } num_cpus = "1.16" ctrlc = "3.4" notify = { version = "8.0.0", features = ["serde"] } +chrono = "0.4" diff --git a/src/daemon.rs b/src/daemon.rs index b31a7af..00b8b8d 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -69,10 +69,7 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box Result<(), Box { - log_message( - &effective_log_level, - LogLevel::Info, - &format!("Watching config file: {path}"), - ); - Some(watcher) + 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 + } } - Err(e) => { - log_message( - &effective_log_level, - LogLevel::Warning, - &format!("Failed to initialize config file watcher: {e}"), - ); - None - } - } } else { + } else { log_message( &effective_log_level, LogLevel::Warning, diff --git a/src/main.rs b/src/main.rs index eb8afbd..c1e8126 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,89 +91,237 @@ fn main() { }; let command_result = match cli.command { + // TODO: This will be moved to a different module in the future. Some(Commands::Info) => match monitor::collect_system_report(&config) { Ok(report) => { - println!("--- System Information ---"); - println!("CPU Model: {}", report.system_info.cpu_model); - println!("Architecture: {}", report.system_info.architecture); + // Format section headers with proper centering + let format_section = |title: &str| { + let title_len = title.len(); + let total_width = title_len + 8; // 8 is for padding (4 on each side) + let separator = "═".repeat(total_width); + + println!("\n╔{}╗", separator); + + // Calculate centering + println!("║ {} ║", title); + + println!("╚{}╝", separator); + }; + + format_section("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!("Timestamp: {:?}", report.timestamp); - println!("\n--- CPU Global Info ---"); - println!("Current Governor: {:?}", report.cpu_global.current_governor); + // Format timestamp in a readable way println!( - "Available Governors: {:?}", + "Current Time: {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S") + ); + + format_section("CPU Global Info"); + println!( + "Current Governor: {}", + report + .cpu_global + .current_governor + .as_deref() + .unwrap_or("N/A") + ); + println!( + "Available Governors: {}", report.cpu_global.available_governors.join(", ") ); - println!("Turbo Status: {:?}", report.cpu_global.turbo_status); - println!("EPP: {:?}", report.cpu_global.epp); - println!("EPB: {:?}", report.cpu_global.epb); - println!("Platform Profile: {:?}", report.cpu_global.platform_profile); println!( - "Average CPU Temperature: {}", + "Turbo Status: {}", + match report.cpu_global.turbo_status { + Some(true) => "Enabled", + Some(false) => "Disabled", + None => "Unknown", + } + ); + + println!( + "EPP: {}", + report.cpu_global.epp.as_deref().unwrap_or("N/A") + ); + println!( + "EPB: {}", + report.cpu_global.epb.as_deref().unwrap_or("N/A") + ); + println!( + "Platform Profile: {}", + report + .cpu_global + .platform_profile + .as_deref() + .unwrap_or("N/A") + ); + println!( + "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") + || "N/A (No sensor detected)".to_string(), + |t| format!("{:.1}°C", t) ) ); - println!("\n--- CPU Core Info ---"); - for core_info in report.cpu_cores { + format_section("CPU Core Info"); + + // Get max core ID length for padding + let max_core_id_len = report + .cpu_cores + .last() + .map_or(1, |core| core.core_id.to_string().len()); + + // Table headers + println!( + " {:>width$} │ {:^10} │ {:^10} │ {:^10} │ {:^7} │ {:^9}", + "Core", + "Current", + "Min", + "Max", + "Usage", + "Temp", + width = max_core_id_len + 4 + ); + println!( + " {:─>width$}──┼─{:─^10}─┼─{:─^10}─┼─{:─^10}─┼─{:─^7}─┼─{:─^9}", + "", + "", + "", + "", + "", + "", + width = max_core_id_len + 4 + ); + + for core_info in &report.cpu_cores { + // Format frequencies: if current > max, show in a special way + let current_freq = match core_info.current_frequency_mhz { + Some(freq) => { + let max_freq = core_info.max_frequency_mhz.unwrap_or(0); + if freq > max_freq && max_freq > 0 { + // Special format for boosted frequencies + format!("{}*", freq) + } else { + format!("{}", freq) + } + } + None => "N/A".to_string(), + }; + + // CPU core display println!( - " Core {}: Current Freq: {:?} MHz, Min Freq: {:?} MHz, Max Freq: {:?} MHz, Usage: {:?}%, Temp: {:?}°C", + " Core {:10} │ {:>10} │ {:>10} │ {:>7} │ {:>9}", core_info.core_id, - core_info - .current_frequency_mhz - .map_or_else(|| "N/A".to_string(), |f| f.to_string()), - core_info - .min_frequency_mhz - .map_or_else(|| "N/A".to_string(), |f| f.to_string()), - core_info - .max_frequency_mhz - .map_or_else(|| "N/A".to_string(), |f| f.to_string()), - core_info - .usage_percent - .map_or_else(|| "N/A".to_string(), |f| format!("{f:.1}")), - core_info - .temperature_celsius - .map_or_else(|| "N/A".to_string(), |f| format!("{f:.1}")) + format!("{} MHz", current_freq), + format!( + "{} MHz", + core_info + .min_frequency_mhz + .map_or_else(|| "N/A".to_string(), |f| f.to_string()) + ), + format!( + "{} MHz", + core_info + .max_frequency_mhz + .map_or_else(|| "N/A".to_string(), |f| f.to_string()) + ), + format!( + "{}%", + core_info + .usage_percent + .map_or_else(|| "N/A".to_string(), |f| format!("{:.1}", f)) + ), + format!( + "{}°C", + core_info + .temperature_celsius + .map_or_else(|| "N/A".to_string(), |f| format!("{:.1}", f)) + ), + width = max_core_id_len ); } - println!("\n--- Battery Info ---"); - if report.batteries.is_empty() { - println!(" No batteries found or all are ignored."); - } else { - for battery_info in report.batteries { - println!( - " Battery {}: AC Connected: {}, State: {:?}, Capacity: {:?}%, Power Rate: {:?} W, Charge Thresholds: {:?}-{:?}", - battery_info.name, - battery_info.ac_connected, - battery_info.charging_state.as_deref().unwrap_or("N/A"), - battery_info - .capacity_percent - .map_or_else(|| "N/A".to_string(), |c| c.to_string()), - battery_info - .power_rate_watts - .map_or_else(|| "N/A".to_string(), |p| format!("{p:.2}")), - battery_info - .charge_start_threshold - .map_or_else(|| "N/A".to_string(), |t| t.to_string()), - battery_info - .charge_stop_threshold - .map_or_else(|| "N/A".to_string(), |t| t.to_string()) - ); + // Only display battery info for systems that have real batteries + // Skip this section entirely on desktop systems + if !report.batteries.is_empty() { + let has_real_batteries = report.batteries.iter().any(|b| { + // Check if any battery has actual battery data + // (as opposed to peripherals like wireless mice) + b.capacity_percent.is_some() || b.power_rate_watts.is_some() + }); + + if has_real_batteries { + format_section("Battery Info"); + for battery_info in &report.batteries { + // Check if this appears to be a real system battery + if battery_info.capacity_percent.is_some() + || battery_info.power_rate_watts.is_some() + { + let power_status = if battery_info.ac_connected { + "Connected to AC" + } else { + "Running on Battery" + }; + + println!("Battery {}:", battery_info.name); + println!(" Power Status: {power_status}"); + println!( + " State: {}", + battery_info.charging_state.as_deref().unwrap_or("Unknown") + ); + + if let Some(capacity) = battery_info.capacity_percent { + println!(" Capacity: {capacity}%"); + } + + if let Some(power) = battery_info.power_rate_watts { + let direction = if power >= 0.0 { + "charging" + } else { + "discharging" + }; + println!( + " Power Rate: {:.2} W ({})", + power.abs(), + direction + ); + } + + // Display charge thresholds if available + if battery_info.charge_start_threshold.is_some() + || battery_info.charge_stop_threshold.is_some() + { + println!( + " Charge Thresholds: {}-{}", + battery_info + .charge_start_threshold + .map_or_else(|| "N/A".to_string(), |t| t.to_string()), + battery_info + .charge_stop_threshold + .map_or_else(|| "N/A".to_string(), |t| t.to_string()) + ); + } + } + } } } - println!("\n--- System Load ---"); + format_section("System Load"); println!( - "Load Average (1m, 5m, 15m): {:.2}, {:.2}, {:.2}", - report.system_load.load_avg_1min, - report.system_load.load_avg_5min, + "Load Average (1m): {:.2}", + report.system_load.load_avg_1min + ); + println!( + "Load Average (5m): {:.2}", + report.system_load.load_avg_5min + ); + println!( + "Load Average (15m): {:.2}", report.system_load.load_avg_15min ); Ok(())