summaryrefslogtreecommitdiffstats
path: root/vendor/sysinfo/src/linux/component.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sysinfo/src/linux/component.rs')
-rw-r--r--vendor/sysinfo/src/linux/component.rs405
1 files changed, 284 insertions, 121 deletions
diff --git a/vendor/sysinfo/src/linux/component.rs b/vendor/sysinfo/src/linux/component.rs
index 3a10588e5..7815103b4 100644
--- a/vendor/sysinfo/src/linux/component.rs
+++ b/vendor/sysinfo/src/linux/component.rs
@@ -1,143 +1,309 @@
// Take a look at the license at the top of the repository in the LICENSE file.
+// Information about values readable from `hwmon` sysfs.
+//
+// Values in /sys/class/hwmonN are `c_long` or `c_ulong`
+// transposed to rust we only read `u32` or `i32` values.
use crate::ComponentExt;
use std::collections::HashMap;
-use std::fs::{metadata, read_dir, File};
+use std::fs::{read_dir, File};
use std::io::Read;
use std::path::{Path, PathBuf};
#[doc = include_str!("../../md_doc/component.md")]
+#[derive(Default)]
pub struct Component {
- temperature: f32,
- max: f32,
- critical: Option<f32>,
+ /// Optional associated device of a `Component`.
+ device_model: Option<String>,
+ /// The chip name.
+ ///
+ /// Kernel documentation extract:
+ /// ```txt
+ /// This should be a short, lowercase string, not containing
+ /// whitespace, dashes, or the wildcard character '*'.
+ /// This attribute represents the chip name. It is the only
+ /// mandatory attribute.
+ /// I2C devices get this attribute created automatically.
+ /// ```
+ name: String,
+ /// Temperature current value
+ /// - Read in: `temp[1-*]_input`.
+ /// - Unit: read as millidegree Celsius converted to Celsius.
+ temperature: Option<f32>,
+ /// Maximum value computed by sysinfo
+ max: Option<f32>,
+ /// Max threshold provided by the chip/kernel
+ /// - Read in:`temp[1-*]_max`
+ /// - Unit: read as millidegree Celsius converted to Celsius.
+ threshold_max: Option<f32>,
+ /// Min threshold provided by the chip/kernel.
+ /// - Read in:`temp[1-*]_min`
+ /// - Unit: read as millidegree Celsius converted to Celsius.
+ threshold_min: Option<f32>,
+ /// Critical threshold provided by the chip/kernel previous user write.
+ /// Read in `temp[1-*]_crit`:
+ /// Typically greater than corresponding temp_max values.
+ /// - Unit: read as millidegree Celsius converted to Celsius.
+ threshold_critical: Option<f32>,
+ /// Sensor type, not common but can exist!
+ ///
+ /// Read in: `temp[1-*]_type` Sensor type selection.
+ /// Values integer:
+ /// - 1: CPU embedded diode
+ /// - 2: 3904 transistor
+ /// - 3: thermal diode
+ /// - 4: thermistor
+ /// - 5: AMD AMDSI
+ /// - 6: Intel PECI
+ /// Not all types are supported by all chips
+ sensor_type: Option<TermalSensorType>,
+ /// Component Label
+ ///
+ /// For formating detail see `Component::label` function docstring.
+ ///
+ /// ## Linux implementation details
+ ///
+ /// read n: `temp[1-*]_label` Suggested temperature channel label.
+ /// Value: Text string
+ ///
+ /// Should only be created if the driver has hints about what
+ /// this temperature channel is being used for, and user-space
+ /// doesn't. In all other cases, the label is provided by user-space.
label: String,
- input_file: PathBuf,
+ // TODO: not used now.
+ // Historical minimum temperature
+ // - Read in:`temp[1-*]_lowest
+ // - Unit: millidegree Celsius
+ //
+ // Temperature critical min value, typically lower than
+ // corresponding temp_min values.
+ // - Read in:`temp[1-*]_lcrit`
+ // - Unit: millidegree Celsius
+ //
+ // Temperature emergency max value, for chips supporting more than
+ // two upper temperature limits. Must be equal or greater than
+ // corresponding temp_crit values.
+ // - temp[1-*]_emergency
+ // - Unit: millidegree Celsius
+ /// File to read current temperature shall be `temp[1-*]_input`
+ /// It may be absent but we don't continue if absent.
+ input_file: Option<PathBuf>,
+ /// `temp[1-*]_highest file` to read if disponnible highest value.
+ highest_file: Option<PathBuf>,
}
+// Read arbitrary data from sysfs.
fn get_file_line(file: &Path, capacity: usize) -> Option<String> {
let mut reader = String::with_capacity(capacity);
- if let Ok(mut f) = File::open(file) {
- if f.read_to_string(&mut reader).is_ok() {
- Some(reader)
- } else {
- None
- }
- } else {
- None
+ let mut f = File::open(file).ok()?;
+ f.read_to_string(&mut reader).ok()?;
+ reader.truncate(reader.trim_end().len());
+ Some(reader)
+}
+
+/// Designed at first for reading an `i32` or `u32` aka `c_long`
+/// from a `/sys/class/hwmon` sysfs file.
+fn read_number_from_file<N>(file: &Path) -> Option<N>
+where
+ N: std::str::FromStr,
+{
+ let mut reader = [0u8; 32];
+ let mut f = File::open(file).ok()?;
+ let n = f.read(&mut reader).ok()?;
+ // parse and trim would complain about `\0`.
+ let number = &reader[..n];
+ let number = std::str::from_utf8(number).ok()?;
+ let number = number.trim();
+ // Assert that we cleaned a little bit that string.
+ if cfg!(feature = "debug") {
+ assert!(!number.contains('\n') && !number.contains('\0'));
}
+ number.parse().ok()
}
-fn is_file<T: AsRef<Path>>(path: T) -> bool {
- metadata(path).ok().map(|m| m.is_file()).unwrap_or(false)
+// Read a temperature from a `tempN_item` sensor form the sysfs.
+// number returned will be in mili-celsius.
+//
+// Don't call it on `label`, `name` or `type` file.
+#[inline]
+fn get_temperature_from_file(file: &Path) -> Option<f32> {
+ let temp = read_number_from_file(file);
+ convert_temp_celsius(temp)
}
-fn append_files(components: &mut Vec<Component>, folder: &Path) {
- let mut matchings: HashMap<u32, Vec<String>> = HashMap::with_capacity(10);
+/// Takes a raw temperature in mili-celsius and convert it to celsius
+#[inline]
+fn convert_temp_celsius(temp: Option<i32>) -> Option<f32> {
+ temp.map(|n| (n as f32) / 1000f32)
+}
- if let Ok(dir) = read_dir(folder) {
- for entry in dir.flatten() {
- let entry = entry.path();
- if entry.is_dir()
- || !entry
- .file_name()
- .and_then(|x| x.to_str())
- .unwrap_or("")
- .starts_with("temp")
- {
- continue;
- }
- if let Some(entry) = entry.file_name() {
- if let Some(entry) = entry.to_str() {
- let mut parts = entry.split('_');
- if let Some(Some(id)) = parts.next().map(|s| s[4..].parse::<u32>().ok()) {
- matchings
- .entry(id)
- .or_insert_with(|| Vec::with_capacity(5))
- .push(
- parts
- .next()
- .map(|s| format!("_{}", s))
- .unwrap_or_else(String::new),
- );
- }
- }
- }
+/// Information about thermal sensor. It may be unavailable as it's
+/// kernel module and chip dependant.
+enum TermalSensorType {
+ /// 1: CPU embedded diode
+ CPUEmbeddedDiode,
+ /// 2: 3904 transistor
+ Transistor3904,
+ /// 3: thermal diode
+ ThermalDiode,
+ /// 4: thermistor
+ Thermistor,
+ /// 5: AMD AMDSI
+ AMDAMDSI,
+ /// 6: Intel PECI
+ IntelPECI,
+ /// Not all types are supported by all chips so we keep space for
+ /// unknown sensors.
+ Unknown(u8),
+}
+
+impl From<u8> for TermalSensorType {
+ fn from(input: u8) -> Self {
+ match input {
+ 0 => Self::CPUEmbeddedDiode,
+ 1 => Self::Transistor3904,
+ 3 => Self::ThermalDiode,
+ 4 => Self::Thermistor,
+ 5 => Self::AMDAMDSI,
+ 6 => Self::IntelPECI,
+ n => Self::Unknown(n),
}
- for (key, val) in &matchings {
- let mut found_input = None;
- let mut found_label = None;
- for (pos, v) in val.iter().enumerate() {
- match v.as_str() {
- // raspberry has empty string for temperature input
- "_input" | "" => {
- found_input = Some(pos);
- }
- "_label" => {
- found_label = Some(pos);
- }
- _ => {}
- }
- }
- if let (Some(_), Some(found_input)) = (found_label, found_input) {
- let mut p_label = folder.to_path_buf();
- let mut p_input = folder.to_path_buf();
- let mut p_crit = folder.to_path_buf();
- let mut p_max = folder.to_path_buf();
-
- p_label.push(&format!("temp{}_label", key));
- p_input.push(&format!("temp{}{}", key, val[found_input]));
- p_max.push(&format!("temp{}_max", key));
- p_crit.push(&format!("temp{}_crit", key));
- if is_file(&p_input) {
- let label = get_file_line(p_label.as_path(), 10)
- .unwrap_or_else(|| format!("Component {}", key)) // needed for raspberry pi
- .replace('\n', "");
- let max = get_file_line(p_max.as_path(), 10).map(|max| {
- max.replace('\n', "").parse::<f32>().unwrap_or(100_000f32) / 1000f32
- });
- let crit = get_file_line(p_crit.as_path(), 10).map(|crit| {
- crit.replace('\n', "").parse::<f32>().unwrap_or(100_000f32) / 1000f32
- });
- components.push(Component::new(label, p_input.as_path(), max, crit));
- }
+ }
+}
+
+/// Check given `item` dispatch to read the right `file` with the right parsing and store data in
+/// given `component`. `id` is provided for `label` creation.
+fn fill_component(component: &mut Component, item: &str, folder: &Path, file: &str) {
+ let hwmon_file = folder.join(file);
+ match item {
+ "type" => {
+ component.sensor_type =
+ read_number_from_file::<u8>(&hwmon_file).map(TermalSensorType::from)
+ }
+ "input" => {
+ let temperature = get_temperature_from_file(&hwmon_file);
+ component.input_file = Some(hwmon_file);
+ component.temperature = temperature;
+ // Maximum know try to get it from `highest` if not available
+ // use current temperature
+ if component.max.is_none() {
+ component.max = temperature;
}
}
+ "label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(),
+ "highest" => {
+ component.max = get_temperature_from_file(&hwmon_file).or(component.temperature);
+ component.highest_file = Some(hwmon_file);
+ }
+ "max" => component.threshold_max = get_temperature_from_file(&hwmon_file),
+ "min" => component.threshold_min = get_temperature_from_file(&hwmon_file),
+ "crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file),
+ _ => {
+ sysinfo_debug!(
+ "This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}",
+ hwmon_file,
+ );
+ }
}
}
impl Component {
- /// Creates a new component with the given information.
- pub(crate) fn new(
- label: String,
- input_path: &Path,
- max: Option<f32>,
- critical: Option<f32>,
- ) -> Component {
- let mut c = Component {
- temperature: 0f32,
+ /// Read out `hwmon` info (hardware monitor) from `folder`
+ /// to get values' path to be used on refresh as well as files containing `max`,
+ /// `critical value` and `label`. Then we store everything into `components`.
+ ///
+ /// Note that a thermal [Component] must have a way to read its temperature.
+ /// If not, it will be ignored and not added into `components`.
+ ///
+ /// ## What is read:
+ ///
+ /// - Mandatory: `name` the name of the `hwmon`.
+ /// - Mandatory: `tempN_input` Drop [Component] if missing
+ /// - Optional: sensor `label`, in the general case content of `tempN_label`
+ /// see below for special cases
+ /// - Optional: `label`
+ /// - Optional: `/device/model`
+ /// - Optional: hightest historic value in `tempN_hightest`.
+ /// - Optional: max threshold value defined in `tempN_max`
+ /// - Optional: critical threshold value defined in `tempN_crit`
+ ///
+ /// Where `N` is a u32 associated to a sensor like `temp1_max`, `temp1_input`.
+ ///
+ /// ## Doc to Linux kernel API.
+ ///
+ /// Kernel hwmon API: https://www.kernel.org/doc/html/latest/hwmon/hwmon-kernel-api.html
+ /// DriveTemp kernel API: https://docs.kernel.org/gpu/amdgpu/thermal.html#hwmon-interfaces
+ /// Amdgpu hwmon interface: https://www.kernel.org/doc/html/latest/hwmon/drivetemp.html
+ fn from_hwmon(components: &mut Vec<Component>, folder: &Path) -> Option<()> {
+ let dir = read_dir(folder).ok()?;
+ let mut matchings: HashMap<u32, Component> = HashMap::with_capacity(10);
+ for entry in dir.flatten() {
+ let entry = entry.path();
+ let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or("");
+ if entry.is_dir() || !filename.starts_with("temp") {
+ continue;
+ }
+
+ let (id, item) = filename.split_once('_')?;
+ let id = id.get(4..)?.parse::<u32>().ok()?;
+
+ let component = matchings.entry(id).or_insert_with(Component::default);
+ let name = get_file_line(&folder.join("name"), 16);
+ component.name = name.unwrap_or_default();
+ let device_model = get_file_line(&folder.join("device/model"), 16);
+ component.device_model = device_model;
+ fill_component(component, item, folder, filename);
+ }
+ let compo = matchings
+ .into_iter()
+ .map(|(id, mut c)| {
+ // sysinfo expose a generic interface with a `label`.
+ // Problem: a lot of sensors don't have a label or a device model! ¯\_(ツ)_/¯
+ // So let's pretend we have a unique label!
+ // See the table in `Component::label` documentation for the table detail.
+ c.label = c.format_label("temp", id);
+ c
+ })
+ // Remove components without `tempN_input` file termal. `Component` doesn't support this kind of sensors yet
+ .filter(|c| c.input_file.is_some());
+
+ components.extend(compo);
+ Some(())
+ }
+
+ /// Compute a label out of available information.
+ /// See the table in `Component::label`'s documentation.
+ fn format_label(&self, class: &str, id: u32) -> String {
+ let Component {
+ device_model,
+ name,
label,
- input_file: input_path.to_path_buf(),
- max: max.unwrap_or(0.0),
- critical,
- };
- c.refresh();
- c
+ ..
+ } = self;
+ let has_label = !label.is_empty();
+ match (has_label, device_model) {
+ (true, Some(device_model)) => {
+ format!("{name} {label} {device_model} {class}{id}")
+ }
+ (true, None) => format!("{name} {label}"),
+ (false, Some(device_model)) => format!("{name} {device_model}"),
+ (false, None) => format!("{name} {class}{id}"),
+ }
}
}
impl ComponentExt for Component {
fn temperature(&self) -> f32 {
- self.temperature
+ self.temperature.unwrap_or(f32::NAN)
}
fn max(&self) -> f32 {
- self.max
+ self.max.unwrap_or(f32::NAN)
}
fn critical(&self) -> Option<f32> {
- self.critical
+ self.threshold_critical
}
fn label(&self) -> &str {
@@ -145,22 +311,28 @@ impl ComponentExt for Component {
}
fn refresh(&mut self) {
- if let Some(content) = get_file_line(self.input_file.as_path(), 10) {
- self.temperature = content
- .replace('\n', "")
- .parse::<f32>()
- .unwrap_or(100_000f32)
- / 1000f32;
- if self.temperature > self.max {
- self.max = self.temperature;
- }
- }
+ let current = self
+ .input_file
+ .as_ref()
+ .and_then(|file| get_temperature_from_file(file.as_path()));
+ // tries to read out kernel highest if not compute something from temperature.
+ let max = self
+ .highest_file
+ .as_ref()
+ .and_then(|file| get_temperature_from_file(file.as_path()))
+ .or_else(|| {
+ let last = self.temperature?;
+ let current = current?;
+ Some(last.max(current))
+ });
+ self.max = max;
+ self.temperature = current;
}
}
pub(crate) fn get_components() -> Vec<Component> {
let mut components = Vec::with_capacity(10);
- if let Ok(dir) = read_dir(&Path::new("/sys/class/hwmon/")) {
+ if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) {
for entry in dir.flatten() {
let entry = entry.path();
if !entry.is_dir()
@@ -172,18 +344,9 @@ pub(crate) fn get_components() -> Vec<Component> {
{
continue;
}
- append_files(&mut components, &entry);
+ Component::from_hwmon(&mut components, &entry);
}
components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase()));
}
- if is_file("/sys/class/thermal/thermal_zone0/temp") {
- // Specfic to raspberry pi.
- components.push(Component::new(
- "CPU".to_owned(),
- Path::new("/sys/class/thermal/thermal_zone0/temp"),
- None,
- None,
- ));
- }
components
}