1
Fork 0
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:
NotAShelf 2025-05-13 20:55:59 +03:00
parent c88b3c00af
commit e6e46e18a8
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
3 changed files with 272 additions and 99 deletions

View file

@ -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()?;