mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
monitor: CPU average temp calculation
This commit is contained in:
parent
c88b3c00af
commit
e6e46e18a8
3 changed files with 272 additions and 99 deletions
|
@ -20,9 +20,10 @@ pub struct CpuGlobalInfo {
|
|||
pub current_governor: Option<String>,
|
||||
pub available_governors: Vec<String>,
|
||||
pub turbo_status: Option<bool>, // true for enabled, false for disabled
|
||||
pub epp: Option<String>, // Energy Performance Preference
|
||||
pub epb: Option<String>, // Energy Performance Bias
|
||||
pub epp: Option<String>, // Energy Performance Preference
|
||||
pub epb: Option<String>, // Energy Performance Bias
|
||||
pub platform_profile: Option<String>,
|
||||
pub average_temperature_celsius: Option<f32>, // Average temperature across all cores
|
||||
}
|
||||
|
||||
pub struct BatteryInfo {
|
||||
|
@ -59,8 +60,8 @@ pub enum OperationalMode {
|
|||
Performance,
|
||||
}
|
||||
|
||||
use serde::Deserialize;
|
||||
use clap::ValueEnum;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, ValueEnum)]
|
||||
pub enum TurboSetting {
|
||||
|
|
163
src/main.rs
163
src/main.rs
|
@ -1,12 +1,12 @@
|
|||
mod core;
|
||||
mod config;
|
||||
mod monitor;
|
||||
mod core;
|
||||
mod cpu;
|
||||
mod engine;
|
||||
mod monitor;
|
||||
|
||||
use clap::Parser;
|
||||
use crate::config::AppConfig;
|
||||
use crate::core::TurboSetting;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
|
@ -55,9 +55,7 @@ enum Commands {
|
|||
core_id: Option<u32>,
|
||||
},
|
||||
/// Set ACPI platform profile
|
||||
SetPlatformProfile {
|
||||
profile: String,
|
||||
},
|
||||
SetPlatformProfile { profile: String },
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -75,67 +73,97 @@ fn main() {
|
|||
};
|
||||
|
||||
let command_result = match cli.command {
|
||||
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);
|
||||
println!("Linux Distribution: {}", report.system_info.linux_distribution);
|
||||
println!("Timestamp: {:?}", report.timestamp);
|
||||
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);
|
||||
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);
|
||||
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!("\n--- CPU Global Info ---");
|
||||
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!("EPP: {:?}", report.cpu_global.epp);
|
||||
println!("EPB: {:?}", report.cpu_global.epb);
|
||||
println!("Platform Profile: {:?}", report.cpu_global.platform_profile);
|
||||
println!(
|
||||
"Average CPU Temperature: {}",
|
||||
report.cpu_global.average_temperature_celsius.map_or_else(
|
||||
|| "N/A (CPU temperature sensor not detected)".to_string(),
|
||||
|t| format!("{:.1}°C", t)
|
||||
)
|
||||
);
|
||||
|
||||
println!("\n--- CPU Core Info ---");
|
||||
for core_info in report.cpu_cores {
|
||||
println!("\n--- CPU Core Info ---");
|
||||
for core_info in report.cpu_cores {
|
||||
println!(
|
||||
" Core {}: Current Freq: {:?} MHz, Min Freq: {:?} MHz, Max Freq: {:?} MHz, Usage: {:?}%, Temp: {:?}°C",
|
||||
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!("{:.1}", f)),
|
||||
core_info
|
||||
.temperature_celsius
|
||||
.map_or_else(|| "N/A".to_string(), |f| format!("{:.1}", f))
|
||||
);
|
||||
}
|
||||
|
||||
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!(
|
||||
" Core {}: Current Freq: {:?} MHz, Min Freq: {:?} MHz, Max Freq: {:?} MHz, Usage: {:?}%, Temp: {:?}°C",
|
||||
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!("{:.1}", f)),
|
||||
core_info.temperature_celsius.map_or_else(|| "N/A".to_string(), |f| format!("{:.1}", f))
|
||||
" 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!("{:.2}", p)),
|
||||
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--- 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!("{:.2}", p)),
|
||||
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 ---");
|
||||
println!("Load Average (1m, 5m, 15m): {:.2}, {:.2}, {:.2}",
|
||||
report.system_load.load_avg_1min,
|
||||
report.system_load.load_avg_5min,
|
||||
report.system_load.load_avg_15min);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
|
||||
|
||||
println!("\n--- System Load ---");
|
||||
println!(
|
||||
"Load Average (1m, 5m, 15m): {:.2}, {:.2}, {:.2}",
|
||||
report.system_load.load_avg_1min,
|
||||
report.system_load.load_avg_5min,
|
||||
report.system_load.load_avg_15min
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some(Commands::SetGovernor { governor, core_id }) => {
|
||||
cpu::set_governor(&governor, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
|
||||
},
|
||||
Some(Commands::SetGovernor { governor, core_id }) => cpu::set_governor(&governor, core_id)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
|
||||
Some(Commands::SetTurbo { setting }) => {
|
||||
cpu::set_turbo(setting).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
|
@ -146,14 +174,15 @@ fn main() {
|
|||
cpu::set_epb(&epb, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
Some(Commands::SetMinFreq { freq_mhz, core_id }) => {
|
||||
cpu::set_min_frequency(freq_mhz, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
cpu::set_min_frequency(freq_mhz, core_id)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
Some(Commands::SetMaxFreq { freq_mhz, core_id }) => {
|
||||
cpu::set_max_frequency(freq_mhz, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
Some(Commands::SetPlatformProfile { profile }) => {
|
||||
cpu::set_platform_profile(&profile).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
cpu::set_max_frequency(freq_mhz, core_id)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||
}
|
||||
Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
|
||||
None => {
|
||||
println!("Welcome to superfreq! Use --help for commands.");
|
||||
println!("Current effective configuration: {:?}", config);
|
||||
|
@ -172,7 +201,9 @@ fn main() {
|
|||
// We'll revisit this in the future once CPU logic is more stable.
|
||||
if let Some(control_error) = e.downcast_ref::<cpu::ControlError>() {
|
||||
if matches!(control_error, cpu::ControlError::PermissionDenied(_)) {
|
||||
eprintln!("Hint: This operation may require administrator privileges (e.g., run with sudo).");
|
||||
eprintln!(
|
||||
"Hint: This operation may require administrator privileges (e.g., run with sudo)."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
201
src/monitor.rs
201
src/monitor.rs
|
@ -282,45 +282,93 @@ pub fn get_cpu_core_info(
|
|||
.map(|khz| khz / 1000)
|
||||
.ok();
|
||||
|
||||
// Temperature: Iterate through hwmon to find core-specific temperatures
|
||||
// This is a common but not universal approach.
|
||||
// Temperature detection.
|
||||
// Should be generic enough to be able to support for multiple hardware sensors
|
||||
// with the possibility of extending later down the road.
|
||||
let mut temperature_celsius: Option<f32> = None;
|
||||
|
||||
// Search for temperature in hwmon devices
|
||||
if let Ok(hwmon_dir) = fs::read_dir("/sys/class/hwmon") {
|
||||
for hw_entry in hwmon_dir.flatten() {
|
||||
let hw_path = hw_entry.path();
|
||||
// Try to find a label that indicates it's for this core or package
|
||||
// e.g. /sys/class/hwmon/hwmonX/name might be "coretemp" or similar
|
||||
// and /sys/class/hwmon/hwmonX/tempY_label might be "Core Z" or "Physical id 0"
|
||||
// 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.
|
||||
|
||||
// Check hwmon driver name
|
||||
if let Ok(name) = read_sysfs_file_trimmed(hw_path.join("name")) {
|
||||
// Intel CPU temperature driver
|
||||
if name == "coretemp" {
|
||||
// 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 input_path = hw_path.join(format!("temp{}_input", i));
|
||||
if label_path.exists() && input_path.exists() {
|
||||
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
|
||||
// Example: "Core 0", "Core 1", etc. or "Physical id 0" for package
|
||||
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
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||
temperature_celsius = Some(temp_mc as f32 / 1000.0);
|
||||
break; // found temp for this core
|
||||
}
|
||||
}
|
||||
if let Some(temp) = get_temperature_for_core(&hw_path, core_id, "Core") {
|
||||
temperature_celsius = Some(temp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// AMD CPU temperature driver
|
||||
// TODO: 'zenergy' can also report those stats, I think?
|
||||
else if name == "k10temp" || name == "zenpower" || name == "amdgpu" {
|
||||
// AMD's k10temp doesn't always label cores individually
|
||||
// First try to find core-specific temps
|
||||
if let Some(temp) = get_temperature_for_core(&hw_path, core_id, "Tdie") {
|
||||
temperature_celsius = Some(temp);
|
||||
break;
|
||||
}
|
||||
|
||||
// Try Tctl temperature (CPU control temp)
|
||||
if let Some(temp) = get_generic_sensor_temperature(&hw_path, "Tctl") {
|
||||
temperature_celsius = Some(temp);
|
||||
break;
|
||||
}
|
||||
|
||||
// Try CPU temperature
|
||||
if let Some(temp) = get_generic_sensor_temperature(&hw_path, "CPU") {
|
||||
temperature_celsius = Some(temp);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fall back to any available temperature input without a specific label
|
||||
temperature_celsius = get_fallback_temperature(&hw_path);
|
||||
if temperature_celsius.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Other CPU temperature drivers
|
||||
else if name.contains("cpu") || name.contains("temp") {
|
||||
// Try to find a label that matches this core
|
||||
if let Some(temp) = get_temperature_for_core(&hw_path, core_id, "Core") {
|
||||
temperature_celsius = Some(temp);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fall back to any temperature reading if specific core not found
|
||||
temperature_celsius = get_fallback_temperature(&hw_path);
|
||||
if temperature_celsius.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try /sys/devices/platform paths for thermal zones as a last resort
|
||||
if temperature_celsius.is_none() {
|
||||
if let Ok(thermal_zones) = fs::read_dir("/sys/devices/virtual/thermal") {
|
||||
for entry in thermal_zones.flatten() {
|
||||
let zone_path = entry.path();
|
||||
let name = entry.file_name().into_string().unwrap_or_default();
|
||||
|
||||
if name.starts_with("thermal_zone") {
|
||||
// Try to match by type
|
||||
if let Ok(zone_type) = read_sysfs_file_trimmed(zone_path.join("type")) {
|
||||
if zone_type.contains("cpu")
|
||||
|| zone_type.contains("x86")
|
||||
|| zone_type.contains("core")
|
||||
{
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(zone_path.join("temp")) {
|
||||
temperature_celsius = Some(temp_mc as f32 / 1000.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if temperature_celsius.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,6 +401,76 @@ pub fn get_cpu_core_info(
|
|||
})
|
||||
}
|
||||
|
||||
/// Finds core-specific temperature
|
||||
fn get_temperature_for_core(hw_path: &Path, core_id: u32, label_prefix: &str) -> Option<f32> {
|
||||
for i in 1..=32 {
|
||||
// Increased range to handle systems with many sensors
|
||||
let label_path = hw_path.join(format!("temp{}_label", i));
|
||||
let input_path = hw_path.join(format!("temp{}_input", i));
|
||||
|
||||
if label_path.exists() && input_path.exists() {
|
||||
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
|
||||
// Match various common label formats:
|
||||
// "Core X", "core X", "Core-X", "CPU Core X", etc.
|
||||
let core_pattern = format!("{} {}", label_prefix, core_id);
|
||||
let alt_pattern = format!("{}-{}", label_prefix, core_id);
|
||||
|
||||
if label.eq_ignore_ascii_case(&core_pattern)
|
||||
|| label.eq_ignore_ascii_case(&alt_pattern)
|
||||
|| label
|
||||
.to_lowercase()
|
||||
.contains(&format!("core {}", core_id).to_lowercase())
|
||||
{
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||
return Some(temp_mc as f32 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Finds generic sensor temperatures by label
|
||||
fn get_generic_sensor_temperature(hw_path: &Path, label_name: &str) -> Option<f32> {
|
||||
for i in 1..=32 {
|
||||
let label_path = hw_path.join(format!("temp{}_label", i));
|
||||
let input_path = hw_path.join(format!("temp{}_input", i));
|
||||
|
||||
if label_path.exists() && input_path.exists() {
|
||||
if let Ok(label) = read_sysfs_file_trimmed(&label_path) {
|
||||
if label.eq_ignore_ascii_case(label_name)
|
||||
|| label.to_lowercase().contains(&label_name.to_lowercase())
|
||||
{
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||
return Some(temp_mc as f32 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !label_path.exists() && input_path.exists() {
|
||||
// Some sensors might not have labels but still have valid temp inputs
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||
return Some(temp_mc as f32 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Fallback to any temperature reading from a sensor
|
||||
fn get_fallback_temperature(hw_path: &Path) -> Option<f32> {
|
||||
for i in 1..=32 {
|
||||
let input_path = hw_path.join(format!("temp{}_input", i));
|
||||
|
||||
if input_path.exists() {
|
||||
if let Ok(temp_mc) = read_sysfs_value::<i32>(&input_path) {
|
||||
return Some(temp_mc as f32 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
|
||||
let initial_cpu_times = read_all_cpu_times()?;
|
||||
thread::sleep(Duration::from_millis(250)); // Interval for CPU usage calculation
|
||||
|
@ -381,7 +499,7 @@ pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
|
|||
Ok(core_infos)
|
||||
}
|
||||
|
||||
pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
||||
pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> Result<CpuGlobalInfo> {
|
||||
// FIXME: Assume global settings can be read from cpu0 or are consistent.
|
||||
// This might not work properly for heterogeneous systems (e.g. big.LITTLE)
|
||||
let cpufreq_base = Path::new("/sys/devices/system/cpu/cpu0/cpufreq/");
|
||||
|
@ -423,6 +541,28 @@ pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
|||
let _platform_profile_choices =
|
||||
read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok();
|
||||
|
||||
// Calculate average CPU temperature from the core temperatures
|
||||
let average_temperature_celsius = if !cpu_cores.is_empty() {
|
||||
// Filter cores with temperature readings, then calculate average
|
||||
let cores_with_temp: Vec<&CpuCoreInfo> = cpu_cores
|
||||
.iter()
|
||||
.filter(|core| core.temperature_celsius.is_some())
|
||||
.collect();
|
||||
|
||||
if !cores_with_temp.is_empty() {
|
||||
// Sum up all temperatures and divide by count
|
||||
let sum: f32 = cores_with_temp
|
||||
.iter()
|
||||
.map(|core| core.temperature_celsius.unwrap())
|
||||
.sum();
|
||||
Some(sum / cores_with_temp.len() as f32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CpuGlobalInfo {
|
||||
current_governor,
|
||||
available_governors,
|
||||
|
@ -430,6 +570,7 @@ pub fn get_cpu_global_info() -> Result<CpuGlobalInfo> {
|
|||
epp,
|
||||
epb,
|
||||
platform_profile,
|
||||
average_temperature_celsius,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -564,7 +705,7 @@ pub fn get_system_load() -> Result<SystemLoad> {
|
|||
pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
|
||||
let system_info = get_system_info()?;
|
||||
let cpu_cores = get_all_cpu_core_info()?;
|
||||
let cpu_global = get_cpu_global_info()?;
|
||||
let cpu_global = get_cpu_global_info(&cpu_cores)?;
|
||||
let batteries = get_battery_info(config)?;
|
||||
let system_load = get_system_load()?;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue