From ec3452601267f39fc0c75c4d872d259f5a8d6245 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Tue, 20 May 2025 19:41:53 +0300 Subject: [PATCH 1/5] cpu&power: add more attributes --- src/fs.rs | 19 ++++---- src/main.rs | 2 +- src/monitor.rs | 104 -------------------------------------------- src/power_supply.rs | 48 ++++++++++++++++++++ src/system.rs | 98 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 155 insertions(+), 116 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index b1d1c71..9b150b3 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -29,18 +29,19 @@ pub fn read(path: impl AsRef) -> Option> { } } -pub fn read_u64(path: impl AsRef) -> anyhow::Result { +pub fn read_u64(path: impl AsRef) -> Option> { let path = path.as_ref(); - let content = fs::read_to_string(path) - .with_context(|| format!("failed to read '{path}'", path = path.display()))?; + match read(path)? { + Ok(content) => Some(content.trim().parse().with_context(|| { + format!( + "failed to parse contents of '{path}' as a unsigned number", + path = path.display(), + ) + })), - Ok(content.trim().parse().with_context(|| { - format!( - "failed to parse contents of '{path}' as a unsigned number", - path = path.display(), - ) - })?) + Err(error) => Some(Err(error)), + } } pub fn write(path: impl AsRef, value: &str) -> anyhow::Result<()> { diff --git a/src/main.rs b/src/main.rs index 825465d..cd6258f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ enum Command { /// Start the daemon. Start { /// The daemon config path. - #[arg(long, env = "SUPERFREQ_CONFIG")] + #[arg(long, env = "WATT_CONFIG")] config: PathBuf, }, diff --git a/src/monitor.rs b/src/monitor.rs index 79d2635..cda52dc 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -599,110 +599,6 @@ pub fn get_battery_info(config: &AppConfig) -> anyhow::Result> Ok(batteries) } -/// Check if a battery is likely a peripheral (mouse, keyboard, etc) not a laptop battery -fn is_peripheral_battery(ps_path: &Path, name: &str) -> bool { - // Convert name to lowercase once for case-insensitive matching - let name_lower = name.to_lowercase(); - - // Common peripheral battery names - if name_lower.contains("mouse") - || name_lower.contains("keyboard") - || name_lower.contains("trackpad") - || name_lower.contains("gamepad") - || name_lower.contains("controller") - || name_lower.contains("headset") - || name_lower.contains("headphone") - { - return true; - } - - // Small capacity batteries are likely not laptop batteries - if let Ok(energy_full) = read_sysfs_value::(ps_path.join("energy_full")) { - // Most laptop batteries are at least 20,000,000 µWh (20 Wh) - // Peripheral batteries are typically much smaller - if energy_full < 10_000_000 { - // 10 Wh in µWh - return true; - } - } - - // Check for model name that indicates a peripheral - if let Ok(model) = read_sysfs_file_trimmed(ps_path.join("model_name")) { - if model.contains("bluetooth") || model.contains("wireless") { - return true; - } - } - - false -} - -/// Determine if this is likely a desktop system rather than a laptop -fn is_likely_desktop_system() -> bool { - // Check for DMI system type information - if let Ok(chassis_type) = fs::read_to_string("/sys/class/dmi/id/chassis_type") { - let chassis_type = chassis_type.trim(); - - // Chassis types: - // 3=Desktop, 4=Low Profile Desktop, 5=Pizza Box, 6=Mini Tower - // 7=Tower, 8=Portable, 9=Laptop, 10=Notebook, 11=Hand Held, 13=All In One - // 14=Sub Notebook, 15=Space-saving, 16=Lunch Box, 17=Main Server Chassis - match chassis_type { - "3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => return true, // desktop form factors - "9" | "10" | "14" => return false, // laptop form factors - _ => {} // Unknown, continue with other checks - } - } - - // Check CPU power policies, desktops often don't have these - let power_saving_exists = Path::new("/sys/module/intel_pstate/parameters/no_hwp").exists() - || Path::new("/sys/devices/system/cpu/cpufreq/conservative").exists(); - - if !power_saving_exists { - return true; // likely a desktop - } - - // Check battery-specific ACPI paths that laptops typically have - let laptop_acpi_paths = [ - "/sys/class/power_supply/BAT0", - "/sys/class/power_supply/BAT1", - "/proc/acpi/battery", - ]; - - for path in &laptop_acpi_paths { - if Path::new(path).exists() { - return false; // Likely a laptop - } - } - - // Default to assuming desktop if we can't determine - true -} - -pub fn get_system_load() -> anyhow::Result { - let loadavg_str = read_sysfs_file_trimmed("/proc/loadavg")?; - let parts: Vec<&str> = loadavg_str.split_whitespace().collect(); - if parts.len() < 3 { - return Err(SysMonitorError::ParseError( - "Could not parse /proc/loadavg: expected at least 3 parts".to_string(), - )); - } - let load_avg_1min = parts[0].parse().map_err(|_| { - SysMonitorError::ParseError(format!("Failed to parse 1min load: {}", parts[0])) - })?; - let load_avg_5min = parts[1].parse().map_err(|_| { - SysMonitorError::ParseError(format!("Failed to parse 5min load: {}", parts[1])) - })?; - let load_avg_15min = parts[2].parse().map_err(|_| { - SysMonitorError::ParseError(format!("Failed to parse 15min load: {}", parts[2])) - })?; - - Ok(SystemLoad { - load_avg_1min, - load_avg_5min, - load_avg_15min, - }) -} - pub fn collect_system_report(config: &AppConfig) -> anyhow::Result { let system_info = get_system_info(); let cpu_cores = get_all_cpu_core_info()?; diff --git a/src/power_supply.rs b/src/power_supply.rs index f1dcb41..025ac75 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -48,6 +48,9 @@ const POWER_SUPPLY_THRESHOLD_CONFIGS: &[PowerSupplyThresholdConfig] = &[ pub struct PowerSupply { pub name: String, pub path: PathBuf, + + pub is_from_peripheral: bool, + pub threshold_config: Option, } @@ -74,6 +77,9 @@ impl PowerSupply { let mut power_supply = Self { path: Path::new(POWER_SUPPLY_PATH).join(&name), name, + + is_from_peripheral: false, + threshold_config: None, }; @@ -94,6 +100,8 @@ impl PowerSupply { path, + is_from_peripheral: false, + threshold_config: None, }; @@ -157,6 +165,46 @@ impl PowerSupply { self.threshold_config = threshold_config; + self.is_from_peripheral = 'is_from_peripheral: { + let name_lower = self.name.to_lowercase(); + + // Common peripheral battery names. + if name_lower.contains("mouse") + || name_lower.contains("keyboard") + || name_lower.contains("trackpad") + || name_lower.contains("gamepad") + || name_lower.contains("controller") + || name_lower.contains("headset") + || name_lower.contains("headphone") + { + break 'is_from_peripheral true; + } + + // Small capacity batteries are likely not laptop batteries. + if let Some(energy_full) = fs::read_u64(self.path.join("energy_full")) { + let energy_full = energy_full + .with_context(|| format!("failed to read the max charge '{self}' can hold"))?; + + // Most laptop batteries are at least 20,000,000 µWh (20 Wh). + // Peripheral batteries are typically much smaller. + if energy_full < 10_000_000 { + // 10 Wh in µWh. + break 'is_from_peripheral true; + } + } + // Check for model name that indicates a peripheral + if let Some(model) = fs::read(self.path.join("model_name")) { + let model = + model.with_context(|| format!("failed to read the model name of '{self}'"))?; + + if model.contains("bluetooth") || model.contains("wireless") { + break 'is_from_peripheral true; + } + } + + false + }; + Ok(()) } diff --git a/src/system.rs b/src/system.rs index 1d3e697..cc39776 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,14 +1,108 @@ +use anyhow::{Context, bail}; + +use crate::fs; + pub struct System { pub is_desktop: bool, + + pub load_average_1min: f64, + pub load_average_5min: f64, + pub load_average_15min: f64, } impl System { pub fn new() -> anyhow::Result { - let mut system = Self { is_desktop: false }; + let mut system = Self { + is_desktop: false, + + load_average_1min: 0.0, + load_average_5min: 0.0, + load_average_15min: 0.0, + }; + system.rescan()?; Ok(system) } - pub fn rescan(&mut self) -> anyhow::Result<()> {} + pub fn rescan(&mut self) -> anyhow::Result<()> { + self.is_desktop = self.is_desktop()?; + + let (load_average_1min, load_average_5min, load_average_15min) = self.load_average()?; + self.load_average_1min = load_average_1min; + self.load_average_5min = load_average_5min; + self.load_average_15min = load_average_15min; + + Ok(()) + } + + fn is_desktop(&self) -> anyhow::Result { + if let Some(chassis_type) = fs::read("/sys/class/dmi/id/chassis_type") { + let chassis_type = chassis_type.context("failed to read chassis type")?; + + // 3=Desktop, 4=Low Profile Desktop, 5=Pizza Box, 6=Mini Tower + // 7=Tower, 8=Portable, 9=Laptop, 10=Notebook, 11=Hand Held, 13=All In One + // 14=Sub Notebook, 15=Space-saving, 16=Lunch Box, 17=Main Server Chassis + match chassis_type.trim() { + // Desktop form factors. + "3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => return Ok(true), + // Laptop form factors. + "9" | "10" | "14" => return Ok(false), + // Unknown, continue with other checks + _ => {} + } + } + + // Check CPU power policies, desktops often don't have these + let power_saving_exists = fs::exists("/sys/module/intel_pstate/parameters/no_hwp") + || fs::exists("/sys/devices/system/cpu/cpufreq/conservative"); + + if !power_saving_exists { + return Ok(true); // Likely a desktop. + } + + // Check battery-specific ACPI paths that laptops typically have + let laptop_acpi_paths = [ + "/sys/class/power_supply/BAT0", + "/sys/class/power_supply/BAT1", + "/proc/acpi/battery", + ]; + + for path in laptop_acpi_paths { + if fs::exists(path) { + return Ok(false); // Likely a laptop. + } + } + + // Default to assuming desktop if we can't determine. + Ok(true) + } + + fn load_average(&self) -> anyhow::Result<(f64, f64, f64)> { + let content = fs::read("/proc/loadavg") + .context("load average file doesn't exist, are you on linux?")? + .context("failed to read load average")?; + + let mut parts = content.split_whitespace(); + + let (Some(load_average_1min), Some(load_average_5min), Some(load_average_15min)) = + (parts.next(), parts.next(), parts.next()) + else { + bail!( + "failed to parse first 3 load average entries due to there not being enough, content: {content}" + ); + }; + + Ok(( + load_average_1min + .parse() + .context("failed to parse load average")?, + load_average_5min + .parse() + .context("failed to parse load average")?, + load_average_15min + .parse() + .context("failed to parse load average")?, + )) + } } From 45a9fd4749edc9fe9c48bff27d5cd8cc7c4de1d5 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 00:24:36 +0300 Subject: [PATCH 2/5] cpu: cpu times --- src/cpu.rs | 105 +++++++++++++++++++++++++++++++++++++++++--- src/monitor.rs | 105 -------------------------------------------- src/power_supply.rs | 4 +- src/system.rs | 54 +++++++++++++---------- 4 files changed, 131 insertions(+), 137 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 0179746..2e0db57 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -5,10 +5,37 @@ use std::{fmt, string::ToString}; use crate::fs; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Cpu { pub number: u32, + pub has_cpufreq: bool, + + pub time_user: u64, + pub time_nice: u64, + pub time_system: u64, + pub time_idle: u64, + pub time_iowait: u64, + pub time_irq: u64, + pub time_softirq: u64, + pub time_steal: u64, +} + +impl Cpu { + pub fn time_total(&self) -> u64 { + self.time_user + + self.time_nice + + self.time_system + + self.time_idle + + self.time_iowait + + self.time_irq + + self.time_softirq + + self.time_steal + } + + pub fn time_idle(&self) -> u64 { + self.time_idle + self.time_iowait + } } impl fmt::Display for Cpu { @@ -24,6 +51,15 @@ impl Cpu { let mut cpu = Self { number, has_cpufreq: false, + + time_user: 0, + time_nice: 0, + time_system: 0, + time_idle: 0, + time_iowait: 0, + time_irq: 0, + time_softirq: 0, + time_steal: 0, }; cpu.rescan()?; @@ -76,9 +112,68 @@ impl Cpu { bail!("{self} does not exist"); } - let has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); + self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); - self.has_cpufreq = has_cpufreq; + self.rescan_times()?; + + Ok(()) + } + + fn rescan_times(&mut self) -> anyhow::Result<()> { + let content = fs::read("/proc/stat") + .context("/proc/stat does not exist")? + .context("failed to read CPU stat")?; + + let cpu_name = format!("cpu{number}", number = self.number); + + let mut stats = content + .lines() + .find_map(|line| { + line.starts_with(&cpu_name) + .then(|| line.split_whitespace().skip(1)) + }) + .with_context(|| format!("failed to find {self} in CPU stats"))?; + + self.time_user = stats + .next() + .with_context(|| format!("failed to find {self} user time"))? + .parse() + .with_context(|| format!("failed to parse {self} user time"))?; + self.time_nice = stats + .next() + .with_context(|| format!("failed to find {self} nice time"))? + .parse() + .with_context(|| format!("failed to parse {self} nice time"))?; + self.time_system = stats + .next() + .with_context(|| format!("failed to find {self} system time"))? + .parse() + .with_context(|| format!("failed to parse {self} system time"))?; + self.time_idle = stats + .next() + .with_context(|| format!("failed to find {self} idle time"))? + .parse() + .with_context(|| format!("failed to parse {self} idle time"))?; + self.time_iowait = stats + .next() + .with_context(|| format!("failed to find {self} iowait time"))? + .parse() + .with_context(|| format!("failed to parse {self} iowait time"))?; + self.time_irq = stats + .next() + .with_context(|| format!("failed to find {self} irq time"))? + .parse() + .with_context(|| format!("failed to parse {self} irq time"))?; + self.time_softirq = stats + .next() + .with_context(|| format!("failed to find {self} softirq time"))? + .parse() + .with_context(|| format!("failed to parse {self} softirq time"))?; + self.time_steal = stats + .next() + .with_context(|| format!("failed to find {self} steal time"))? + .parse() + .with_context(|| format!("failed to parse {self} steal time"))?; Ok(()) } @@ -232,7 +327,7 @@ impl Cpu { fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; - let Ok(minimum_frequency_khz) = fs::read_u64(format!( + let Some(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. @@ -270,7 +365,7 @@ impl Cpu { fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; - let Ok(maximum_frequency_khz) = fs::read_u64(format!( + let Some(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. diff --git a/src/monitor.rs b/src/monitor.rs index cda52dc..1cd0dc4 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -43,111 +43,6 @@ pub fn get_system_info() -> SystemInfo { } } -#[derive(Debug, Clone, Copy)] -pub struct CpuTimes { - user: u64, - nice: u64, - system: u64, - idle: u64, - iowait: u64, - irq: u64, - softirq: u64, - steal: u64, -} - -impl CpuTimes { - const fn total_time(&self) -> u64 { - self.user - + self.nice - + self.system - + self.idle - + self.iowait - + self.irq - + self.softirq - + self.steal - } - - const fn idle_time(&self) -> u64 { - self.idle + self.iowait - } -} - -fn read_all_cpu_times() -> anyhow::Result> { - let content = fs::read_to_string("/proc/stat").map_err(SysMonitorError::Io)?; - let mut cpu_times_map = HashMap::new(); - - for line in content.lines() { - if line.starts_with("cpu") && line.chars().nth(3).is_some_and(|c| c.is_ascii_digit()) { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() < 11 { - return Err(SysMonitorError::ProcStatParseError(format!( - "Line too short: {line}" - ))); - } - - let core_id_str = &parts[0][3..]; - let core_id = core_id_str.parse::().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse core_id: {core_id_str}" - )) - })?; - - let times = CpuTimes { - user: parts[1].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse user time: {}", - parts[1] - )) - })?, - nice: parts[2].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse nice time: {}", - parts[2] - )) - })?, - system: parts[3].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse system time: {}", - parts[3] - )) - })?, - idle: parts[4].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse idle time: {}", - parts[4] - )) - })?, - iowait: parts[5].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse iowait time: {}", - parts[5] - )) - })?, - irq: parts[6].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse irq time: {}", - parts[6] - )) - })?, - softirq: parts[7].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse softirq time: {}", - parts[7] - )) - })?, - steal: parts[8].parse().map_err(|_| { - SysMonitorError::ProcStatParseError(format!( - "Failed to parse steal time: {}", - parts[8] - )) - })?, - }; - cpu_times_map.insert(core_id, times); - } - } - Ok(cpu_times_map) -} - pub fn get_cpu_core_info( core_id: u32, prev_times: &CpuTimes, diff --git a/src/power_supply.rs b/src/power_supply.rs index 025ac75..5bbcebc 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -146,7 +146,7 @@ impl PowerSupply { bail!("{self} does not exist"); } - let threshold_config = self + self.threshold_config = self .get_type() .with_context(|| format!("failed to determine what type of power supply '{self}' is"))? .eq("Battery") @@ -163,8 +163,6 @@ impl PowerSupply { }) .flatten(); - self.threshold_config = threshold_config; - self.is_from_peripheral = 'is_from_peripheral: { let name_lower = self.name.to_lowercase(); diff --git a/src/system.rs b/src/system.rs index cc39776..f8820b1 100644 --- a/src/system.rs +++ b/src/system.rs @@ -26,17 +26,13 @@ impl System { } pub fn rescan(&mut self) -> anyhow::Result<()> { - self.is_desktop = self.is_desktop()?; - - let (load_average_1min, load_average_5min, load_average_15min) = self.load_average()?; - self.load_average_1min = load_average_1min; - self.load_average_5min = load_average_5min; - self.load_average_15min = load_average_15min; + self.rescan_is_desktop()?; + self.rescan_load_average()?; Ok(()) } - fn is_desktop(&self) -> anyhow::Result { + fn rescan_is_desktop(&mut self) -> anyhow::Result<()> { if let Some(chassis_type) = fs::read("/sys/class/dmi/id/chassis_type") { let chassis_type = chassis_type.context("failed to read chassis type")?; @@ -45,9 +41,16 @@ impl System { // 14=Sub Notebook, 15=Space-saving, 16=Lunch Box, 17=Main Server Chassis match chassis_type.trim() { // Desktop form factors. - "3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => return Ok(true), + "3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => { + self.is_desktop = true; + return Ok(()); + } // Laptop form factors. - "9" | "10" | "14" => return Ok(false), + "9" | "10" | "14" => { + self.is_desktop = false; + return Ok(()); + } + // Unknown, continue with other checks _ => {} } @@ -58,7 +61,8 @@ impl System { || fs::exists("/sys/devices/system/cpu/cpufreq/conservative"); if !power_saving_exists { - return Ok(true); // Likely a desktop. + self.is_desktop = true; + return Ok(()); // Likely a desktop. } // Check battery-specific ACPI paths that laptops typically have @@ -70,15 +74,17 @@ impl System { for path in laptop_acpi_paths { if fs::exists(path) { - return Ok(false); // Likely a laptop. + self.is_desktop = false; // Likely a laptop. + return Ok(()); } } // Default to assuming desktop if we can't determine. - Ok(true) + self.is_desktop = true; + Ok(()) } - fn load_average(&self) -> anyhow::Result<(f64, f64, f64)> { + fn rescan_load_average(&mut self) -> anyhow::Result<()> { let content = fs::read("/proc/loadavg") .context("load average file doesn't exist, are you on linux?")? .context("failed to read load average")?; @@ -93,16 +99,16 @@ impl System { ); }; - Ok(( - load_average_1min - .parse() - .context("failed to parse load average")?, - load_average_5min - .parse() - .context("failed to parse load average")?, - load_average_15min - .parse() - .context("failed to parse load average")?, - )) + self.load_average_1min = load_average_1min + .parse() + .context("failed to parse load average")?; + self.load_average_5min = load_average_5min + .parse() + .context("failed to parse load average")?; + self.load_average_15min = load_average_15min + .parse() + .context("failed to parse load average")?; + + Ok(()) } } From 542c41ccbe2335ba686a9ae9713bb64aa29a308d Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 00:28:06 +0300 Subject: [PATCH 3/5] cpu: add TODO --- src/cpu.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cpu.rs b/src/cpu.rs index 2e0db57..736008a 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -120,6 +120,8 @@ impl Cpu { } fn rescan_times(&mut self) -> anyhow::Result<()> { + // TODO: Don't read this per CPU. Share the read or + // find something in /sys/.../cpu{N} that does it. let content = fs::read("/proc/stat") .context("/proc/stat does not exist")? .context("failed to read CPU stat")?; From fb5a891d423e941789e689b7003ccdd2fac1229f Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 00:37:10 +0300 Subject: [PATCH 4/5] main: use yansi::whenever --- Cargo.lock | 22 +++++++++++++++++++++- Cargo.toml | 2 +- src/main.rs | 2 ++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b0446d..f077741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" + [[package]] name = "indexmap" version = "2.9.0" @@ -271,6 +277,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi 0.5.1", + "libc", + "windows-sys", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -364,7 +381,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -697,3 +714,6 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] diff --git a/Cargo.toml b/Cargo.toml index 287929e..aeecd4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,5 @@ thiserror = "2.0" anyhow = "1.0" jiff = "0.2.13" clap-verbosity-flag = "3.0.2" -yansi = "1.0.1" +yansi = { version = "1.0.1", features = ["detect-env", "detect-tty"] } derive_more = { version = "2.0.1", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index cd6258f..e435cee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,6 +50,8 @@ enum Command { fn real_main() -> anyhow::Result<()> { let cli = Cli::parse(); + yansi::whenever(yansi::Condition::TTY_AND_COLOR); + env_logger::Builder::new() .filter_level(cli.verbosity.log_level_filter()) .format_timestamp(None) From dfa788009c498a123c2e134c809983be8ecddac1 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 01:01:45 +0300 Subject: [PATCH 5/5] monitor: delete old code --- src/core.rs | 2 -- src/monitor.rs | 71 +------------------------------------------------- 2 files changed, 1 insertion(+), 72 deletions(-) diff --git a/src/core.rs b/src/core.rs index 07581aa..a3f4e33 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,8 +1,6 @@ pub struct SystemInfo { // Overall system details pub cpu_model: String, - pub architecture: String, - pub linux_distribution: String, } pub struct CpuCoreInfo { diff --git a/src/monitor.rs b/src/monitor.rs index 1cd0dc4..5d0468b 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -10,37 +10,10 @@ use std::{ time::SystemTime, }; -// Read a sysfs file to a string, trimming whitespace -fn read_sysfs_file_trimmed(path: impl AsRef) -> anyhow::Result { - fs::read_to_string(path.as_ref()) - .map(|s| s.trim().to_string()) - .map_err(|e| { - SysMonitorError::ReadError(format!("Path: {:?}, Error: {}", path.as_ref().display(), e)) - }) -} - -// Read a sysfs file and parse it to a specific type -fn read_sysfs_value(path: impl AsRef) -> anyhow::Result { - let content = read_sysfs_file_trimmed(path.as_ref())?; - content.parse::().map_err(|_| { - SysMonitorError::ParseError(format!( - "Could not parse '{}' from {:?}", - content, - path.as_ref().display() - )) - }) -} - pub fn get_system_info() -> SystemInfo { let cpu_model = get_cpu_model().unwrap_or_else(|_| "Unknown".to_string()); - let linux_distribution = get_linux_distribution().unwrap_or_else(|_| "Unknown".to_string()); - let architecture = std::env::consts::ARCH.to_string(); - SystemInfo { - cpu_model, - architecture, - linux_distribution, - } + SystemInfo { cpu_model } } pub fn get_cpu_core_info( @@ -529,45 +502,3 @@ pub fn get_cpu_model() -> anyhow::Result { "Could not find CPU model name in /proc/cpuinfo.".to_string(), )) } - -pub fn get_linux_distribution() -> anyhow::Result { - let os_release_path = Path::new("/etc/os-release"); - let content = fs::read_to_string(os_release_path).map_err(|_| { - SysMonitorError::ReadError(format!( - "Cannot read contents of {}.", - os_release_path.display() - )) - })?; - - for line in content.lines() { - if line.starts_with("PRETTY_NAME=") { - if let Some(val) = line.split('=').nth(1) { - let linux_distribution = val.trim_matches('"').to_string(); - return Ok(linux_distribution); - } - } - } - - let lsb_release_path = Path::new("/etc/lsb-release"); - let content = fs::read_to_string(lsb_release_path).map_err(|_| { - SysMonitorError::ReadError(format!( - "Cannot read contents of {}.", - lsb_release_path.display() - )) - })?; - - for line in content.lines() { - if line.starts_with("DISTRIB_DESCRIPTION=") { - if let Some(val) = line.split('=').nth(1) { - let linux_distribution = val.trim_matches('"').to_string(); - return Ok(linux_distribution); - } - } - } - - Err(SysMonitorError::ParseError(format!( - "Could not find distribution name in {} or {}.", - os_release_path.display(), - lsb_release_path.display() - ))) -}