From 6e80a172cc9807408a3c1293b2cb5c7b402433bd Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Thu, 1 Sep 2022 19:26:45 +0200 Subject: [PATCH 1/7] unwrap_or_return! Replace by ?. --- src/linux/process.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/linux/process.rs b/src/linux/process.rs index c9b8b08d9..e9abdc2da 100644 --- a/src/linux/process.rs +++ b/src/linux/process.rs @@ -447,7 +447,7 @@ pub(crate) fn _get_process_data( } else { _get_stat_data(path, &mut entry.stat_file)? }; - let parts = parse_stat_file(&data)?; + let parts = parse_stat_file(&data).ok_or(())?; let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info); // It's possible that a new process took this same PID when the "original one" terminated. @@ -477,7 +477,7 @@ pub(crate) fn _get_process_data( } else { let mut stat_file = None; let data = _get_stat_data(path, &mut stat_file)?; - let parts = parse_stat_file(&data)?; + let parts = parse_stat_file(&data).ok_or(())?; let mut p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); @@ -696,16 +696,7 @@ fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { } } -macro_rules! unwrap_or_return { - ($data:expr) => {{ - match $data { - Some(x) => x, - None => return Err(()), - } - }}; -} - -fn parse_stat_file(data: &str) -> Result, ()> { +fn parse_stat_file(data: &str) -> Option> { // The stat file is "interesting" to parse, because spaces cannot // be used as delimiters. The second field stores the command name // surrounded by parentheses. Unfortunately, whitespace and @@ -717,14 +708,14 @@ fn parse_stat_file(data: &str) -> Result, ()> { let mut parts = Vec::with_capacity(52); let mut data_it = data.splitn(2, ' '); - parts.push(unwrap_or_return!(data_it.next())); - let mut data_it = unwrap_or_return!(data_it.next()).rsplitn(2, ')'); - let data = unwrap_or_return!(data_it.next()); - parts.push(unwrap_or_return!(data_it.next())); + parts.push(data_it.next()?); + let mut data_it = data_it.next()?.rsplitn(2, ')'); + let data = data_it.next()?; + parts.push(data_it.next()?); parts.extend(data.split_whitespace()); // Remove command name '(' if let Some(name) = parts[1].strip_prefix('(') { parts[1] = name; } - Ok(parts) + Some(parts) } From 9ab4acc14536cba151e1aafa54f9b63bd436f427 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Thu, 1 Sep 2022 19:28:26 +0200 Subject: [PATCH 2/7] Use try in refresh_procs. --- src/linux/process.rs | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/linux/process.rs b/src/linux/process.rs index e9abdc2da..adfa629e8 100644 --- a/src/linux/process.rs +++ b/src/linux/process.rs @@ -550,14 +550,11 @@ pub(crate) fn refresh_procs( if let Ok(d) = fs::read_dir(path) { let folders = d .filter_map(|entry| { - if let Ok(entry) = entry { - let entry = entry.path(); + let entry = entry.ok()?; + let entry = entry.path(); - if entry.is_dir() { - Some(entry) - } else { - None - } + if entry.is_dir() { + Some(entry) } else { None } @@ -571,18 +568,16 @@ pub(crate) fn refresh_procs( into_iter(folders) .filter_map(|e| { - if let Ok((p, _)) = _get_process_data( + let (p, _) = _get_process_data( e.as_path(), proc_list.get(), pid, uptime, info, refresh_kind, - ) { - p - } else { - None - } + ) + .ok()?; + p }) .collect::>() } else { @@ -590,14 +585,11 @@ pub(crate) fn refresh_procs( let new_tasks = folders .iter() .filter_map(|e| { - if let Ok((p, pid)) = + let (p, pid) = _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) - { - updated_pids.push(pid); - p - } else { - None - } + .ok()?; + updated_pids.push(pid); + p }) .collect::>(); // Sub-tasks are not cleaned up outside so we do it here directly. From edb5075bdcd64f1cbbe05d7bc4352d379a9a156a Mon Sep 17 00:00:00 2001 From: changyongyun Date: Thu, 1 Sep 2022 16:20:14 +0800 Subject: [PATCH 3/7] fix: got nvme0n when passing device name 'nvme0n1' --- src/linux/disk.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/linux/disk.rs b/src/linux/disk.rs index 5a313fd27..c700a6c41 100644 --- a/src/linux/disk.rs +++ b/src/linux/disk.rs @@ -136,9 +136,10 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskType { real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); } else if device_name_path.starts_with("/dev/nvme") { // Turn "nvme0n1p1" into "nvme0n1" - real_path = real_path.trim_start_matches("/dev/"); - real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); - real_path = real_path.trim_end_matches(|c| c == 'p'); + real_path = match real_path.find('p') { + Some(idx) => &real_path["/dev/".len()..idx], + None => &real_path["/dev/".len()..], + }; } else if device_name_path.starts_with("/dev/root") { // Recursively solve, for example /dev/mmcblk0p1 if real_path != device_name_path { @@ -146,9 +147,10 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskType { } } else if device_name_path.starts_with("/dev/mmcblk") { // Turn "mmcblk0p1" into "mmcblk0" - real_path = real_path.trim_start_matches("/dev/"); - real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); - real_path = real_path.trim_end_matches(|c| c == 'p'); + real_path = match real_path.find('p') { + Some(idx) => &real_path["/dev/".len()..idx], + None => &real_path["/dev/".len()..], + }; } else { // Default case: remove /dev/ and expects the name presents under /sys/block/ // For example, /dev/dm-0 to dm-0 From 6f524c46c1d43108efad3a753c1ddf3328c5c161 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Fri, 2 Sep 2022 17:57:07 +0200 Subject: [PATCH 4/7] clippy --fix pass (#834) Fix clippy lint error --- src/linux/process.rs | 2 +- tests/code_checkers/docs.rs | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/linux/process.rs b/src/linux/process.rs index adfa629e8..3da50d30c 100644 --- a/src/linux/process.rs +++ b/src/linux/process.rs @@ -653,7 +653,7 @@ fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { } } - let status_data = get_all_data(&file_path, 16_385).ok()?; + let status_data = get_all_data(file_path, 16_385).ok()?; // We're only interested in the lines starting with Uid: and Gid: // here. From these lines, we're looking at the second entry to get diff --git a/tests/code_checkers/docs.rs b/tests/code_checkers/docs.rs index 918e64bb0..bc4aa06f2 100644 --- a/tests/code_checkers/docs.rs +++ b/tests/code_checkers/docs.rs @@ -21,14 +21,9 @@ fn to_correct_name(s: &str) -> String { } fn check_md_doc_path(p: &Path, md_line: &str, ty_line: &str) -> bool { - let parts = md_line.split("/").collect::>(); + let parts = md_line.split('/').collect::>(); if let Some(md_name) = parts.last().and_then(|n| n.split(".md").next()) { - if let Some(name) = ty_line - .split_whitespace() - .filter(|s| !s.is_empty()) - .skip(2) - .next() - { + if let Some(name) = ty_line.split_whitespace().filter(|s| !s.is_empty()).nth(2) { if let Some(name) = name .split('<') .next() @@ -54,7 +49,7 @@ fn check_md_doc_path(p: &Path, md_line: &str, ty_line: &str) -> bool { } else { show_error(p, &format!("Cannot extract md name from `{}`", md_line)); } - return false; + false } fn check_doc_comments_before(p: &Path, lines: &[&str], start: usize) -> bool { From 16532f76cfbc7c4683011dac906b7cb0fcc1daef Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Fri, 2 Sep 2022 13:14:06 +0200 Subject: [PATCH 5/7] [linux-hwmon] Remove raspberry-pi special code. Not needed since raspbian 10. --- src/linux/component.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/linux/component.rs b/src/linux/component.rs index f3a8d607a..ffdc7faf2 100644 --- a/src/linux/component.rs +++ b/src/linux/component.rs @@ -33,6 +33,7 @@ fn is_file>(path: T) -> bool { metadata(path).ok().map(|m| m.is_file()).unwrap_or(false) } + fn append_files(components: &mut Vec, folder: &Path) { let mut matchings: HashMap> = HashMap::with_capacity(10); @@ -176,14 +177,5 @@ pub(crate) fn get_components() -> Vec { } 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").to_path_buf(), - None, - None, - )); - } components } From 6eb07169140c8aeeebd413f68ff515a85db1ae66 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Mon, 29 Aug 2022 00:51:21 +0200 Subject: [PATCH 6/7] [linux-hwmon] Attempt to read new data from hwmon on linux What it does? Expose new things: (Read only) - Expose a label that mimic `sensors` own (breaking change) - Sensor type - Allow reading of everything with a temp sensor. - Highest form kernel/chip maximum historical read value, is read from hardware OR from computation. - Thresolds max, critical, low Also fully remove raspberry-pi special code. Not everything is used, maybe a HwmonLinux specific trait can be done and use a degraded view to comply with existing cross-os `Component`. Future work? Read Fan, Voltage, Intrusion alert? --- src/linux/component.rs | 393 +++++++++++++++++++++++++++++------------ 1 file changed, 282 insertions(+), 111 deletions(-) diff --git a/src/linux/component.rs b/src/linux/component.rs index ffdc7faf2..bc7e1f93b 100644 --- a/src/linux/component.rs +++ b/src/linux/component.rs @@ -1,144 +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, + /// Optional associated device of a `Component`. + device_model: Option, + /// 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, + /// Maximum value computed by sysinfo + max: Option, + /// Max threshold provided by the chip/kernel + /// - Read in:`temp[1-*]_max` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_max: Option, + /// Min threshold provided by the chip/kernel. + /// - Read in:`temp[1-*]_min` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_min: Option, + /// 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, + /// 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, + /// 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, + /// `temp[1-*]_highest file` to read if disponnible highest value. + highest_file: Option, } +// Read arbitrary data from sysfs. fn get_file_line(file: &Path, capacity: usize) -> Option { 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(file: &Path) -> Option +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>(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 { + let temp = read_number_from_file(file); + convert_temp_celsius(temp) } +/// Takes a raw temperature in mili-celsius and convert it to celsius +#[inline] +fn convert_temp_celsius(temp: Option) -> Option { + temp.map(|n| (n as f32) / 1000f32) +} -fn append_files(components: &mut Vec, folder: &Path) { - let mut matchings: HashMap> = HashMap::with_capacity(10); +/// 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), +} - 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::().ok()) { - matchings - .entry(id) - .or_insert_with(|| Vec::with_capacity(5)) - .push( - parts - .next() - .map(|s| format!("_{}", s)) - .unwrap_or_else(String::new), - ); - } - } - } +impl From 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::().unwrap_or(100_000f32) / 1000f32 - }); - let crit = get_file_line(p_crit.as_path(), 10).map(|crit| { - crit.replace('\n', "").parse::().unwrap_or(100_000f32) / 1000f32 - }); - components.push(Component::new(label, p_input, 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::(&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_file: PathBuf, - max: Option, - critical: Option, - ) -> 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, folder: &Path) -> Option<()> { + let dir = read_dir(folder).ok()?; + let mut matchings: HashMap = 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::().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, - 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 { - self.critical + self.threshold_critical } fn label(&self) -> &str { @@ -146,16 +311,22 @@ 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::() - .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; } } @@ -173,7 +344,7 @@ pub(crate) fn get_components() -> Vec { { 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())); } From 8e95cf8a53e991889673b12e760c4b7f4e19e87f Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Fri, 2 Sep 2022 17:03:32 +0200 Subject: [PATCH 7/7] ComponentExt: Add document to Linux details. --- src/traits.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index 096c1bfdb..1c6c2289d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1486,10 +1486,17 @@ pub trait ComponentExt: Debug { /// println!("{}°C", component.temperature()); /// } /// ``` + /// + /// ## Linux + /// + /// Returns `f32::NAN` if it failed to retrieve it. fn temperature(&self) -> f32; /// Returns the maximum temperature of the component (in celsius degree). /// + /// Note: if `temperature` is higher than the current `max`, + /// `max` value will be updated on refresh. + /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// @@ -1498,6 +1505,11 @@ pub trait ComponentExt: Debug { /// println!("{}°C", component.max()); /// } /// ``` + /// + /// ## Linux + /// + /// May be computed by sysinfo from kernel. + /// Returns `f32::NAN` if it failed to retrieve it. fn max(&self) -> f32; /// Returns the highest temperature before the component halts (in celsius degree). @@ -1510,6 +1522,10 @@ pub trait ComponentExt: Debug { /// println!("{:?}°C", component.critical()); /// } /// ``` + /// + /// ## Linux + /// + /// Critical threshold defined by chip or kernel. fn critical(&self) -> Option; /// Returns the label of the component. @@ -1522,6 +1538,19 @@ pub trait ComponentExt: Debug { /// println!("{}", component.label()); /// } /// ``` + /// + /// ## Linux + /// + /// Since components informations are retrieved thanks to `hwmon`, + /// the labels are generated as follows. + /// Note: it may change and it was inspired by `sensors` own formatting. + /// + /// | name | label | device_model | id_sensor | Computed label by `sysinfo` | + /// |---------|--------|------------|----------|----------------------| + /// | ✓ | ✓ | ✓ | ✓ | `"{name} {label} {device_model} temp{id}"` | + /// | ✓ | ✓ | ✗ | ✓ | `"{name} {label} {id}"` | + /// | ✓ | ✗ | ✓ | ✓ | `"{name} {device_model}"` | + /// | ✓ | ✗ | ✗ | ✓ | `"{name} temp{id}"` | fn label(&self) -> &str; /// Refreshes component.