mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
monitor: implement CPU usage calculation
This commit is contained in:
parent
0d91c57d99
commit
90b63cac66
1 changed files with 258 additions and 43 deletions
295
src/monitor.rs
295
src/monitor.rs
|
@ -1,12 +1,21 @@
|
||||||
use crate::core::{SystemInfo, CpuCoreInfo, CpuGlobalInfo, BatteryInfo, SystemLoad, SystemReport};
|
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use std::{fs, io, path::{Path, PathBuf}, str::FromStr, time::SystemTime};
|
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs, io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SysMonitorError {
|
pub enum SysMonitorError {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
ReadError(String),
|
ReadError(String),
|
||||||
ParseError(String),
|
ParseError(String),
|
||||||
|
ProcStatParseError(String),
|
||||||
NotAvailable(String),
|
NotAvailable(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +31,9 @@ impl std::fmt::Display for SysMonitorError {
|
||||||
SysMonitorError::Io(e) => write!(f, "I/O error: {}", e),
|
SysMonitorError::Io(e) => write!(f, "I/O error: {}", e),
|
||||||
SysMonitorError::ReadError(s) => write!(f, "Failed to read sysfs path: {}", s),
|
SysMonitorError::ReadError(s) => write!(f, "Failed to read sysfs path: {}", s),
|
||||||
SysMonitorError::ParseError(s) => write!(f, "Failed to parse value: {}", s),
|
SysMonitorError::ParseError(s) => write!(f, "Failed to parse value: {}", s),
|
||||||
|
SysMonitorError::ProcStatParseError(s) => {
|
||||||
|
write!(f, "Failed to parse /proc/stat: {}", s)
|
||||||
|
}
|
||||||
SysMonitorError::NotAvailable(s) => write!(f, "Information not available: {}", s),
|
SysMonitorError::NotAvailable(s) => write!(f, "Information not available: {}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +89,8 @@ pub fn get_system_info() -> Result<SystemInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Ok(lsb_release) = fs::read_to_string("/etc/lsb-release") { // fallback for some systems
|
} else if let Ok(lsb_release) = fs::read_to_string("/etc/lsb-release") {
|
||||||
|
// fallback for some systems
|
||||||
for line in lsb_release.lines() {
|
for line in lsb_release.lines() {
|
||||||
if line.starts_with("DISTRIB_DESCRIPTION=") {
|
if line.starts_with("DISTRIB_DESCRIPTION=") {
|
||||||
if let Some(val) = line.split('=').nth(1) {
|
if let Some(val) = line.split('=').nth(1) {
|
||||||
|
@ -88,7 +101,6 @@ pub fn get_system_info() -> Result<SystemInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(SystemInfo {
|
Ok(SystemInfo {
|
||||||
cpu_model,
|
cpu_model,
|
||||||
architecture,
|
architecture,
|
||||||
|
@ -104,13 +116,16 @@ fn get_logical_core_count() -> Result<u32> {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let name = entry.file_name();
|
let name = entry.file_name();
|
||||||
if let Some(name_str) = name.to_str() {
|
if let Some(name_str) = name.to_str() {
|
||||||
if name_str.starts_with("cpu") &&
|
if name_str.starts_with("cpu")
|
||||||
name_str.len() > 3 &&
|
&& name_str.len() > 3
|
||||||
name_str[3..].chars().all(char::is_numeric) {
|
&& name_str[3..].chars().all(char::is_numeric)
|
||||||
|
{
|
||||||
// Check if it's a directory representing a core that can have cpufreq
|
// Check if it's a directory representing a core that can have cpufreq
|
||||||
if entry.path().join("cpufreq").exists() {
|
if entry.path().join("cpufreq").exists() {
|
||||||
count += 1;
|
count += 1;
|
||||||
} else if Path::new(&format!("/sys/devices/system/cpu/{}/online", name_str)).exists() {
|
} else if Path::new(&format!("/sys/devices/system/cpu/{}/online", name_str))
|
||||||
|
.exists()
|
||||||
|
{
|
||||||
// Fallback for cores that might not have cpufreq but are online (e.g. E-cores on some setups before driver loads)
|
// Fallback for cores that might not have cpufreq but are online (e.g. E-cores on some setups before driver loads)
|
||||||
// This is a simplification; true cpufreq capability is key.
|
// This is a simplification; true cpufreq capability is key.
|
||||||
// If cpufreq dir doesn't exist, it might not be controllable by this tool.
|
// If cpufreq dir doesn't exist, it might not be controllable by this tool.
|
||||||
|
@ -129,8 +144,132 @@ fn get_logical_core_count() -> Result<u32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct CpuTimes {
|
||||||
|
user: u64,
|
||||||
|
nice: u64,
|
||||||
|
system: u64,
|
||||||
|
idle: u64,
|
||||||
|
iowait: u64,
|
||||||
|
irq: u64,
|
||||||
|
softirq: u64,
|
||||||
|
steal: u64,
|
||||||
|
guest: u64,
|
||||||
|
guest_nice: u64,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_cpu_core_info(core_id: u32) -> Result<CpuCoreInfo> {
|
impl CpuTimes {
|
||||||
|
fn total_time(&self) -> u64 {
|
||||||
|
self.user
|
||||||
|
+ self.nice
|
||||||
|
+ self.system
|
||||||
|
+ self.idle
|
||||||
|
+ self.iowait
|
||||||
|
+ self.irq
|
||||||
|
+ self.softirq
|
||||||
|
+ self.steal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idle_time(&self) -> u64 {
|
||||||
|
self.idle + self.iowait
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_all_cpu_times() -> Result<HashMap<u32, CpuTimes>> {
|
||||||
|
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).map_or(false, |c| c.is_digit(10)) {
|
||||||
|
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::<u32>().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]
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
guest: parts[9].parse().map_err(|_| {
|
||||||
|
SysMonitorError::ProcStatParseError(format!(
|
||||||
|
"Failed to parse guest time: {}",
|
||||||
|
parts[9]
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
guest_nice: parts[10].parse().map_err(|_| {
|
||||||
|
SysMonitorError::ProcStatParseError(format!(
|
||||||
|
"Failed to parse guest_nice time: {}",
|
||||||
|
parts[10]
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
cpu_times_map.insert(core_id, times);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(cpu_times_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cpu_core_info(
|
||||||
|
core_id: u32,
|
||||||
|
prev_times: &CpuTimes,
|
||||||
|
current_times: &CpuTimes,
|
||||||
|
) -> Result<CpuCoreInfo> {
|
||||||
let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/cpufreq/", core_id));
|
let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/cpufreq/", core_id));
|
||||||
|
|
||||||
let current_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_cur_freq"))
|
let current_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_cur_freq"))
|
||||||
|
@ -155,15 +294,20 @@ pub fn get_cpu_core_info(core_id: u32) -> Result<CpuCoreInfo> {
|
||||||
// This is highly system-dependent, and not all systems will have this. For now,
|
// This is highly system-dependent, and not all systems will have this. For now,
|
||||||
// we'll try a common pattern for "coretemp" driver because it works:tm: on my system.
|
// we'll try a common pattern for "coretemp" driver because it works:tm: on my system.
|
||||||
if let Ok(name) = read_sysfs_file_trimmed(hw_path.join("name")) {
|
if let Ok(name) = read_sysfs_file_trimmed(hw_path.join("name")) {
|
||||||
if name == "coretemp" { // Common driver for Intel core temperatures
|
if name == "coretemp" {
|
||||||
for i in 1..=16 { // Check a reasonable number of temp inputs
|
// Common driver for Intel core temperatures
|
||||||
|
for i in 1..=16 {
|
||||||
|
// Check a reasonable number of temp inputs
|
||||||
let label_path = hw_path.join(format!("temp{}_label", i));
|
let label_path = hw_path.join(format!("temp{}_label", i));
|
||||||
let input_path = hw_path.join(format!("temp{}_input", i));
|
let input_path = hw_path.join(format!("temp{}_input", i));
|
||||||
if label_path.exists() && input_path.exists() {
|
if label_path.exists() && input_path.exists() {
|
||||||
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
|
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
|
||||||
// Example: "Core 0", "Core 1", etc. or "Physical id 0" for package
|
// Example: "Core 0", "Core 1", etc. or "Physical id 0" for package
|
||||||
if label.eq_ignore_ascii_case(&format!("Core {}", core_id)) ||
|
if label.eq_ignore_ascii_case(&format!("Core {}", core_id))
|
||||||
label.eq_ignore_ascii_case(&format!("Package id {}", core_id)) { //core_id might map to package for some sensors
|
|| label
|
||||||
|
.eq_ignore_ascii_case(&format!("Package id {}", core_id))
|
||||||
|
{
|
||||||
|
//core_id might map to package for some sensors
|
||||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||||
temperature_celsius = Some(temp_mc as f32 / 1000.0);
|
temperature_celsius = Some(temp_mc as f32 / 1000.0);
|
||||||
break; // found temp for this core
|
break; // found temp for this core
|
||||||
|
@ -174,15 +318,30 @@ pub fn get_cpu_core_info(core_id: u32) -> Result<CpuCoreInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if temperature_celsius.is_some() { break; }
|
if temperature_celsius.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is a placeholder so that I can actually run the code. It is a little
|
let usage_percent: Option<f32> = {
|
||||||
//complex to calculate from raw sysfs/procfs data. It typically involves reading /proc/stat
|
let prev_idle = prev_times.idle_time();
|
||||||
// and calculating deltas over time. This is out of scope for simple sysfs reads here.
|
let current_idle = current_times.idle_time();
|
||||||
// We will be returning here to this later.
|
|
||||||
let usage_percent: Option<f32> = None;
|
let prev_total = prev_times.total_time();
|
||||||
|
let current_total = current_times.total_time();
|
||||||
|
|
||||||
|
let total_diff = current_total.saturating_sub(prev_total);
|
||||||
|
let idle_diff = current_idle.saturating_sub(prev_idle);
|
||||||
|
|
||||||
|
// Avoid division by zero if no time has passed or counters haven't changed
|
||||||
|
if total_diff == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let usage = 100.0 * (1.0 - (idle_diff as f32 / total_diff as f32));
|
||||||
|
Some(usage.max(0.0).min(100.0)) // clamp between 0 and 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(CpuCoreInfo {
|
Ok(CpuCoreInfo {
|
||||||
core_id,
|
core_id,
|
||||||
|
@ -195,8 +354,31 @@ pub fn get_cpu_core_info(core_id: u32) -> Result<CpuCoreInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
|
pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
|
||||||
let num_cores = get_logical_core_count()?;
|
let initial_cpu_times = read_all_cpu_times()?;
|
||||||
(0..num_cores).map(get_cpu_core_info).collect()
|
thread::sleep(Duration::from_millis(250)); // Interval for CPU usage calculation
|
||||||
|
let final_cpu_times = read_all_cpu_times()?;
|
||||||
|
|
||||||
|
let num_cores = get_logical_core_count()?; // Or derive from keys in cpu_times
|
||||||
|
let mut core_infos = Vec::with_capacity(num_cores as usize);
|
||||||
|
|
||||||
|
for core_id in 0..num_cores {
|
||||||
|
if let (Some(prev), Some(curr)) = (
|
||||||
|
initial_cpu_times.get(&core_id),
|
||||||
|
final_cpu_times.get(&core_id),
|
||||||
|
) {
|
||||||
|
match get_cpu_core_info(core_id, prev, curr) {
|
||||||
|
Ok(info) => core_infos.push(info),
|
||||||
|
Err(e) => {
|
||||||
|
// Log or handle error for a single core, maybe push a partial info or skip
|
||||||
|
eprintln!("Error getting info for core {}: {}", core_id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Log or handle missing times for a core
|
||||||
|
eprintln!("Missing CPU time data for core {}", core_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(core_infos)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
||||||
|
@ -206,20 +388,28 @@ pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
||||||
|
|
||||||
let current_governor = if cpufreq_base.join("scaling_governor").exists() {
|
let current_governor = if cpufreq_base.join("scaling_governor").exists() {
|
||||||
read_sysfs_file_trimmed(cpufreq_base.join("scaling_governor")).ok()
|
read_sysfs_file_trimmed(cpufreq_base.join("scaling_governor")).ok()
|
||||||
} else { None };
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let available_governors = if cpufreq_base.join("scaling_available_governors").exists() {
|
let available_governors = if cpufreq_base.join("scaling_available_governors").exists() {
|
||||||
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors"))
|
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors"))
|
||||||
.map(|s| s.split_whitespace().map(String::from).collect())
|
.map(|s| s.split_whitespace().map(String::from).collect())
|
||||||
.unwrap_or_else(|_| vec![])
|
.unwrap_or_else(|_| vec![])
|
||||||
} else { vec![] };
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
let turbo_status = if Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo").exists() {
|
let turbo_status = if Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo").exists() {
|
||||||
// 0 means turbo enabled, 1 means disabled for intel_pstate
|
// 0 means turbo enabled, 1 means disabled for intel_pstate
|
||||||
read_sysfs_value::<u8>("/sys/devices/system/cpu/intel_pstate/no_turbo").map(|val| val == 0).ok()
|
read_sysfs_value::<u8>("/sys/devices/system/cpu/intel_pstate/no_turbo")
|
||||||
|
.map(|val| val == 0)
|
||||||
|
.ok()
|
||||||
} else if Path::new("/sys/devices/system/cpu/cpufreq/boost").exists() {
|
} else if Path::new("/sys/devices/system/cpu/cpufreq/boost").exists() {
|
||||||
// 1 means turbo enabled, 0 means disabled for generic cpufreq boost
|
// 1 means turbo enabled, 0 means disabled for generic cpufreq boost
|
||||||
read_sysfs_value::<u8>("/sys/devices/system/cpu/cpufreq/boost").map(|val| val == 1).ok()
|
read_sysfs_value::<u8>("/sys/devices/system/cpu/cpufreq/boost")
|
||||||
|
.map(|val| val == 1)
|
||||||
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -230,8 +420,8 @@ pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
||||||
let epb = read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_bias")).ok();
|
let epb = read_sysfs_file_trimmed(cpufreq_base.join("energy_performance_bias")).ok();
|
||||||
|
|
||||||
let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok();
|
let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok();
|
||||||
let _platform_profile_choices = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok();
|
let _platform_profile_choices =
|
||||||
|
read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok();
|
||||||
|
|
||||||
Ok(CpuGlobalInfo {
|
Ok(CpuGlobalInfo {
|
||||||
current_governor,
|
current_governor,
|
||||||
|
@ -247,11 +437,15 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
|
||||||
let mut batteries = Vec::new();
|
let mut batteries = Vec::new();
|
||||||
let power_supply_path = Path::new("/sys/class/power_supply");
|
let power_supply_path = Path::new("/sys/class/power_supply");
|
||||||
|
|
||||||
if !power_supply_path.exists() {
|
if (!power_supply_path.exists()) {
|
||||||
return Ok(batteries); // no power supply directory
|
return Ok(batteries); // no power supply directory
|
||||||
}
|
}
|
||||||
|
|
||||||
let ignored_supplies = config.ignored_power_supplies.as_ref().cloned().unwrap_or_default();
|
let ignored_supplies = config
|
||||||
|
.ignored_power_supplies
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Determine overall AC connection status
|
// Determine overall AC connection status
|
||||||
let mut overall_ac_connected = false;
|
let mut overall_ac_connected = false;
|
||||||
|
@ -262,7 +456,14 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
|
||||||
|
|
||||||
// Check for AC adapter type (common names: AC, ACAD, ADP)
|
// Check for AC adapter type (common names: AC, ACAD, ADP)
|
||||||
if let Ok(ps_type) = read_sysfs_file_trimmed(ps_path.join("type")) {
|
if let Ok(ps_type) = read_sysfs_file_trimmed(ps_path.join("type")) {
|
||||||
if ps_type == "Mains" || ps_type == "USB_PD_DRP" || ps_type == "USB_PD" || ps_type == "USB_DCP" || ps_type == "USB_CDP" || ps_type == "USB_ACA" { // USB types can also provide power
|
if ps_type == "Mains"
|
||||||
|
|| ps_type == "USB_PD_DRP"
|
||||||
|
|| ps_type == "USB_PD"
|
||||||
|
|| ps_type == "USB_DCP"
|
||||||
|
|| ps_type == "USB_CDP"
|
||||||
|
|| ps_type == "USB_ACA"
|
||||||
|
{
|
||||||
|
// USB types can also provide power
|
||||||
if let Ok(online) = read_sysfs_value::<u8>(ps_path.join("online")) {
|
if let Ok(online) = read_sysfs_value::<u8>(ps_path.join("online")) {
|
||||||
if online == 1 {
|
if online == 1 {
|
||||||
overall_ac_connected = true;
|
overall_ac_connected = true;
|
||||||
|
@ -270,7 +471,8 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if name.starts_with("AC") || name.contains("ACAD") || name.contains("ADP") { // fallback for type file missing
|
} else if name.starts_with("AC") || name.contains("ACAD") || name.contains("ADP") {
|
||||||
|
// fallback for type file missing
|
||||||
if let Ok(online) = read_sysfs_value::<u8>(ps_path.join("online")) {
|
if let Ok(online) = read_sysfs_value::<u8>(ps_path.join("online")) {
|
||||||
if online == 1 {
|
if online == 1 {
|
||||||
overall_ac_connected = true;
|
overall_ac_connected = true;
|
||||||
|
@ -298,18 +500,26 @@ pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
|
||||||
read_sysfs_value::<i32>(ps_path.join("power_now")) // uW
|
read_sysfs_value::<i32>(ps_path.join("power_now")) // uW
|
||||||
.map(|uw| uw as f32 / 1_000_000.0)
|
.map(|uw| uw as f32 / 1_000_000.0)
|
||||||
.ok()
|
.ok()
|
||||||
} else if ps_path.join("current_now").exists() && ps_path.join("voltage_now").exists() {
|
} else if ps_path.join("current_now").exists()
|
||||||
|
&& ps_path.join("voltage_now").exists()
|
||||||
|
{
|
||||||
let current_ua = read_sysfs_value::<i32>(ps_path.join("current_now")).ok(); // uA
|
let current_ua = read_sysfs_value::<i32>(ps_path.join("current_now")).ok(); // uA
|
||||||
let voltage_uv = read_sysfs_value::<i32>(ps_path.join("voltage_now")).ok(); // uV
|
let voltage_uv = read_sysfs_value::<i32>(ps_path.join("voltage_now")).ok(); // uV
|
||||||
if let (Some(c), Some(v)) = (current_ua, voltage_uv) {
|
if let (Some(c), Some(v)) = (current_ua, voltage_uv) {
|
||||||
// Power (W) = (Voltage (V) * Current (A))
|
// Power (W) = (Voltage (V) * Current (A))
|
||||||
// (v / 1e6 V) * (c / 1e6 A) = (v * c / 1e12) W
|
// (v / 1e6 V) * (c / 1e6 A) = (v * c / 1e12) W
|
||||||
Some((c as f64 * v as f64 / 1_000_000_000_000.0) as f32)
|
Some((c as f64 * v as f64 / 1_000_000_000_000.0) as f32)
|
||||||
} else { None }
|
} else {
|
||||||
} else { None };
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let charge_start_threshold = read_sysfs_value::<u8>(ps_path.join("charge_control_start_threshold")).ok();
|
let charge_start_threshold =
|
||||||
let charge_stop_threshold = read_sysfs_value::<u8>(ps_path.join("charge_control_end_threshold")).ok();
|
read_sysfs_value::<u8>(ps_path.join("charge_control_start_threshold")).ok();
|
||||||
|
let charge_stop_threshold =
|
||||||
|
read_sysfs_value::<u8>(ps_path.join("charge_control_end_threshold")).ok();
|
||||||
|
|
||||||
batteries.push(BatteryInfo {
|
batteries.push(BatteryInfo {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
|
@ -334,9 +544,15 @@ pub fn get_system_load() -> Result<SystemLoad> {
|
||||||
"Could not parse /proc/loadavg: expected at least 3 parts".to_string(),
|
"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_1min = parts[0].parse().map_err(|_| {
|
||||||
let load_avg_5min = parts[1].parse().map_err(|_| SysMonitorError::ParseError(format!("Failed to parse 5min load: {}", parts[1])))?;
|
SysMonitorError::ParseError(format!("Failed to parse 1min load: {}", parts[0]))
|
||||||
let load_avg_15min = parts[2].parse().map_err(|_| SysMonitorError::ParseError(format!("Failed to parse 15min load: {}", parts[2])))?;
|
})?;
|
||||||
|
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 {
|
Ok(SystemLoad {
|
||||||
load_avg_1min,
|
load_avg_1min,
|
||||||
|
@ -361,4 +577,3 @@ pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
|
||||||
timestamp: SystemTime::now(),
|
timestamp: SystemTime::now(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue