Replace heim with sysfs and dont wake devices (#805)

* Replace heim with sysfs and dont wake devices

This commit replaces heim sensor reading with manual sysfs sensor reading, and skips reading sensors for any device that is in ACPI D3cold

This has the notable downside of still keeping a device awake, which I hope to solve in a later commit

* Update docs

They were referring to files i ultimately decided against using in this implementation, and so were no longer relevant to document.

* has_temp check should be before reading hwmon_name

* should_read_temp doesn't have to be mutable

* Fix sensor for zenpower kernel module
This commit is contained in:
Diana 2022-09-16 02:06:17 -07:00 committed by GitHub
parent c6c7fb3a30
commit c3e4a95d04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 97 additions and 19 deletions

View File

@ -3,11 +3,25 @@
use super::{is_temp_filtered, temp_vec_sort, TempHarvest, TemperatureType};
use crate::app::Filter;
/// Get temperature sensors from the linux sysfs interface `/sys/class/hwmon`
///
/// This method will return `0` as the temperature for devices, such as GPUs,
/// that support power management features and power themselves off.
///
/// Specifically, in laptops with iGPUs and dGPUs, if the dGPU is capable of
/// entering ACPI D3cold, reading the temperature sensors will wake it,
/// and keep it awake, wasting power.
///
/// For such devices, this method will only query the sensors IF the
/// device is already in ACPI D0
///
/// This has the notable issue that once this happens,
/// the device will be *kept* on through the sensor reading,
/// and not be able to re-enter ACPI D3cold.
pub async fn get_temperature_data(
temp_type: &TemperatureType, actually_get: bool, filter: &Option<Filter>,
) -> crate::utils::error::Result<Option<Vec<TempHarvest>>> {
use futures::StreamExt;
use heim::units::thermodynamic_temperature;
use std::{fs, path::Path};
if !actually_get {
return Ok(None);
@ -15,34 +29,98 @@ pub async fn get_temperature_data(
let mut temperature_vec: Vec<TempHarvest> = Vec::new();
let mut sensor_data = heim::sensors::temperatures().boxed_local();
while let Some(sensor) = sensor_data.next().await {
if let Ok(sensor) = sensor {
let component_name = Some(sensor.unit().to_string());
let component_label = sensor.label().map(|label| label.to_string());
// Documented at https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-hwmon
let path = Path::new("/sys/class/hwmon");
let name = match (component_name, component_label) {
(Some(name), Some(label)) => format!("{}: {}", name, label),
// NOTE: Technically none of this is async, *but* sysfs is in memory,
// so in theory none of this should block if we're slightly careful.
// Of note is that reading the temperature sensors of a device that has
// `/sys/class/hwmon/hwmon*/device/power_state` == `D3cold` will
// wake the device up, and will block until it initializes.
//
// Reading the `hwmon*/device/power_state` or `hwmon*/temp*_label` properties
// will not wake the device, and thus not block,
// and meaning no sensors have to be hidden depending on `power_state`
//
// It would probably be more ideal to use a proper async runtime..
for entry in path.read_dir()? {
let file = entry?;
let path = file.path();
// hwmon includes many sensors, we only want ones with at least one temperature sensor
// Reading this file will wake the device, but we're only checking existence.
if !path.join("temp1_input").exists() {
continue;
}
let hwmon_name = path.join("name");
let hwmon_name = Some(fs::read_to_string(&hwmon_name)?);
// Whether the temperature should *actually* be read during enumeration
// Set to false if the device is in ACPI D3cold
let should_read_temp = {
// Documented at https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-power_state
let device = path.join("device");
let power_state = device.join("power_state");
if power_state.exists() {
let state = fs::read_to_string(power_state)?;
let state = state.trim();
// The zenpower3 kernel module (incorrectly?) reports "unknown"
// causing this check to fail and temperatures to appear as zero
// instead of having the file not exist..
// their self-hosted git instance has disabled sign up,
// so this bug cant be reported either.
state == "D0" || state == "unknown"
} else {
true
}
};
// Enumerate the devices temperature sensors
for entry in path.read_dir()? {
let file = entry?;
let name = file.file_name();
// This should always be ASCII
let name = name.to_str().unwrap();
// We only want temperature sensors, skip others early
if !(name.starts_with("temp") && name.ends_with("input")) {
continue;
}
let temp = file.path();
let temp_label = path.join(name.replace("input", "label"));
let temp_label = fs::read_to_string(temp_label).ok();
let name = match (&hwmon_name, &temp_label) {
(Some(name), Some(label)) => format!("{}: {}", name.trim(), label.trim()),
(None, Some(label)) => label.to_string(),
(Some(name), None) => name.to_string(),
(None, None) => String::default(),
};
if is_temp_filtered(filter, &name) {
use heim::units::{thermodynamic_temperature, ThermodynamicTemperature};
let temp = if should_read_temp {
let temp = fs::read_to_string(temp)?;
let temp = temp.trim_end().parse::<f32>().map_err(|e| {
crate::utils::error::BottomError::ConversionError(e.to_string())
})?;
temp / 1_000.0
} else {
0.0
};
let temp = ThermodynamicTemperature::new::<thermodynamic_temperature::degree_celsius>(
temp,
);
temperature_vec.push(TempHarvest {
name,
temperature: match temp_type {
TemperatureType::Celsius => sensor
.current()
.get::<thermodynamic_temperature::degree_celsius>(
),
TemperatureType::Kelvin => {
sensor.current().get::<thermodynamic_temperature::kelvin>()
TemperatureType::Celsius => {
temp.get::<thermodynamic_temperature::degree_celsius>()
}
TemperatureType::Kelvin => temp.get::<thermodynamic_temperature::kelvin>(),
TemperatureType::Fahrenheit => {
temp.get::<thermodynamic_temperature::degree_fahrenheit>()
}
TemperatureType::Fahrenheit => sensor
.current()
.get::<thermodynamic_temperature::degree_fahrenheit>(
),
},
});
}