diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
commit | 4547b622d8d29df964fa2914213088b148c498fc (patch) | |
tree | 9fc6b25f3c3add6b745be9a2400a6e96140046e9 /vendor/sysinfo/src/linux | |
parent | Releasing progress-linux version 1.66.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-4547b622d8d29df964fa2914213088b148c498fc.tar.xz rustc-4547b622d8d29df964fa2914213088b148c498fc.zip |
Merging upstream version 1.67.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo/src/linux')
-rw-r--r-- | vendor/sysinfo/src/linux/component.rs | 405 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/cpu.rs | 208 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/disk.rs | 22 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/network.rs | 2 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/process.rs | 439 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/system.rs | 268 | ||||
-rw-r--r-- | vendor/sysinfo/src/linux/utils.rs | 130 |
7 files changed, 906 insertions, 568 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 } diff --git a/vendor/sysinfo/src/linux/cpu.rs b/vendor/sysinfo/src/linux/cpu.rs index f3970c318..103f5362a 100644 --- a/vendor/sysinfo/src/linux/cpu.rs +++ b/vendor/sysinfo/src/linux/cpu.rs @@ -4,9 +4,209 @@ use std::collections::HashSet; use std::fs::File; -use std::io::Read; +use std::io::{BufRead, BufReader, Read}; -use crate::CpuExt; +use crate::sys::utils::to_u64; +use crate::{CpuExt, CpuRefreshKind}; + +macro_rules! to_str { + ($e:expr) => { + unsafe { std::str::from_utf8_unchecked($e) } + }; +} + +pub(crate) struct CpusWrapper { + pub(crate) global_cpu: Cpu, + pub(crate) cpus: Vec<Cpu>, + /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`. + /// + /// The reason behind this is to avoid calling the `update_cpus` more than necessary. + /// For example when running `refresh_all` or `refresh_specifics`. + need_cpus_update: bool, + got_cpu_frequency: bool, +} + +impl CpusWrapper { + pub(crate) fn new() -> Self { + Self { + global_cpu: Cpu::new_with_values( + "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + String::new(), + String::new(), + ), + cpus: Vec::with_capacity(4), + need_cpus_update: true, + got_cpu_frequency: false, + } + } + + pub(crate) fn refresh_if_needed( + &mut self, + only_update_global_cpu: bool, + refresh_kind: CpuRefreshKind, + ) { + if self.need_cpus_update { + self.refresh(only_update_global_cpu, refresh_kind); + } + } + + pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { + let f = match File::open("/proc/stat") { + Ok(f) => f, + Err(_e) => { + sysinfo_debug!("failed to retrieve CPU information: {:?}", _e); + return; + } + }; + let buf = BufReader::new(f); + + self.need_cpus_update = false; + let mut i: usize = 0; + let first = self.cpus.is_empty(); + let mut it = buf.split(b'\n'); + let (vendor_id, brand) = if first { + get_vendor_id_and_brand() + } else { + (String::new(), String::new()) + }; + + if first || refresh_kind.cpu_usage() { + if let Some(Ok(line)) = it.next() { + if &line[..4] != b"cpu " { + return; + } + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.global_cpu.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); + } else { + parts.next(); + } + self.global_cpu.set( + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + ); + } + if first || !only_update_global_cpu { + while let Some(Ok(line)) = it.next() { + if &line[..3] != b"cpu" { + break; + } + + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.cpus.push(Cpu::new_with_values( + to_str!(parts.next().unwrap_or(&[])), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + 0, + vendor_id.clone(), + brand.clone(), + )); + } else { + parts.next(); // we don't want the name again + self.cpus[i].set( + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + ); + } + + i += 1; + } + } + } + + if refresh_kind.frequency() { + #[cfg(feature = "multithread")] + use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator, + }; + + #[cfg(feature = "multithread")] + // This function is voluntarily made generic in case we want to generalize it. + fn iter_mut<'a, T>( + val: &'a mut T, + ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter + where + &'a mut T: rayon::iter::IntoParallelIterator, + { + val.par_iter_mut() + } + + #[cfg(not(feature = "multithread"))] + fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> { + val.iter_mut() + } + + // `get_cpu_frequency` is very slow, so better run it in parallel. + self.global_cpu.frequency = iter_mut(&mut self.cpus) + .enumerate() + .map(|(pos, proc_)| { + proc_.frequency = get_cpu_frequency(pos); + proc_.frequency + }) + .max() + .unwrap_or(0); + + self.got_cpu_frequency = true; + } + + if first { + self.global_cpu.vendor_id = vendor_id; + self.global_cpu.brand = brand; + } + } + + pub(crate) fn get_global_raw_times(&self) -> (u64, u64) { + (self.global_cpu.total_time, self.global_cpu.old_total_time) + } + + pub(crate) fn len(&self) -> usize { + self.cpus.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.cpus.is_empty() + } + + pub(crate) fn set_need_cpus_update(&mut self) { + self.need_cpus_update = true; + } +} /// Struct containing values to compute a CPU usage. #[derive(Clone, Copy)] @@ -224,10 +424,6 @@ impl CpuExt for Cpu { } } -pub(crate) fn get_raw_times(p: &Cpu) -> (u64, u64) { - (p.total_time, p.old_total_time) -} - pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 { let mut s = String::new(); if File::open(format!( diff --git a/vendor/sysinfo/src/linux/disk.rs b/vendor/sysinfo/src/linux/disk.rs index 5a313fd27..6d7fc083c 100644 --- a/vendor/sysinfo/src/linux/disk.rs +++ b/vendor/sysinfo/src/linux/disk.rs @@ -1,7 +1,7 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::sys::utils::get_all_data; -use crate::{utils, DiskExt, DiskType}; +use crate::sys::utils::{get_all_data, to_cpath}; +use crate::{DiskExt, DiskType}; use libc::statvfs; use std::ffi::{OsStr, OsString}; @@ -60,7 +60,7 @@ impl DiskExt for Disk { fn refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); - let mount_point_cpath = utils::to_cpath(&self.mount_point); + let mount_point_cpath = to_cpath(&self.mount_point); if statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat) == 0 { let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail)); self.available_space = cast!(tmp); @@ -78,7 +78,7 @@ fn new_disk( file_system: &[u8], removable_entries: &[PathBuf], ) -> Option<Disk> { - let mount_point_cpath = utils::to_cpath(mount_point); + let mount_point_cpath = to_cpath(mount_point); let type_ = find_type_for_device_name(device_name); let mut total = 0; let mut available = 0; @@ -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 diff --git a/vendor/sysinfo/src/linux/network.rs b/vendor/sysinfo/src/linux/network.rs index 0153c151a..c8da2bcb3 100644 --- a/vendor/sysinfo/src/linux/network.rs +++ b/vendor/sysinfo/src/linux/network.rs @@ -297,7 +297,7 @@ mod test { let itf1_dir = sys_net_dir.path().join("itf1"); let itf2_dir = sys_net_dir.path().join("itf2"); fs::create_dir(&itf1_dir).expect("failed to create subdirectory"); - fs::create_dir(&itf2_dir).expect("failed to create subdirectory"); + fs::create_dir(itf2_dir).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); diff --git a/vendor/sysinfo/src/linux/process.rs b/vendor/sysinfo/src/linux/process.rs index 0b06e26f1..d7d61b5bc 100644 --- a/vendor/sysinfo/src/linux/process.rs +++ b/vendor/sysinfo/src/linux/process.rs @@ -11,8 +11,10 @@ use std::str::FromStr; use libc::{gid_t, kill, uid_t}; -use crate::sys::system::{SystemInfo, REMAINING_FILES}; -use crate::sys::utils::{get_all_data, get_all_data_from_file, realpath}; +use crate::sys::system::SystemInfo; +use crate::sys::utils::{ + get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush, +}; use crate::utils::into_iter; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; @@ -93,7 +95,7 @@ pub struct Process { pub(crate) status: ProcessStatus, /// Tasks run by this process. pub tasks: HashMap<Pid, Process>, - pub(crate) stat_file: Option<File>, + pub(crate) stat_file: Option<FileCounter>, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, @@ -101,16 +103,11 @@ pub struct Process { } impl Process { - pub(crate) fn new( - pid: Pid, - parent: Option<Pid>, - start_time_without_boot_time: u64, - info: &SystemInfo, - ) -> Process { + pub(crate) fn new(pid: Pid) -> Process { Process { name: String::with_capacity(20), pid, - parent, + parent: None, cmd: Vec::with_capacity(2), environ: Vec::with_capacity(10), exe: PathBuf::new(), @@ -124,8 +121,8 @@ impl Process { old_utime: 0, old_stime: 0, updated: true, - start_time_without_boot_time, - start_time: start_time_without_boot_time.saturating_add(info.boot_time), + start_time_without_boot_time: 0, + start_time: 0, run_time: 0, user_id: None, group_id: None, @@ -222,14 +219,16 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { self.group_id } -} -impl Drop for Process { - fn drop(&mut self) { - if self.stat_file.is_some() { - unsafe { - if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { - **x += 1; + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if libc::waitpid(self.pid.0, &mut status, 0) < 0 { + // attempt failed (non-child process) so loop until process ends + let duration = std::time::Duration::from_millis(10); + while kill(self.pid.0, 0) == 0 { + std::thread::sleep(duration); } } } @@ -260,9 +259,7 @@ pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) { } pub(crate) fn update_process_disk_activity(p: &mut Process, path: &Path) { - let mut path = PathBuf::from(path); - path.push("io"); - let data = match get_all_data(&path, 16_384) { + let data = match get_all_data(path.join("io"), 16_384) { Ok(d) => d, Err(_) => return, }; @@ -306,75 +303,50 @@ impl<'a, T> Wrap<'a, T> { unsafe impl<'a, T> Send for Wrap<'a, T> {} unsafe impl<'a, T> Sync for Wrap<'a, T> {} -pub(crate) fn _get_process_data( - path: &Path, - proc_list: &mut Process, - pid: Pid, - uptime: u64, - info: &SystemInfo, - refresh_kind: ProcessRefreshKind, -) -> Result<(Option<Process>, Pid), ()> { - let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { - Some(Ok(nb)) if nb != pid => nb, - _ => return Err(()), - }; +#[inline(always)] +fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 { + // To be noted that the start time is invalid here, it still needs to be converted into + // "real" time. + u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle +} - let get_status = |p: &mut Process, part: &str| { - p.status = part - .chars() - .next() - .map(ProcessStatus::from) - .unwrap_or_else(|| ProcessStatus::Unknown(0)); - }; - let parent_memory = proc_list.memory; - let parent_virtual_memory = proc_list.virtual_memory; - if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { - let data = if let Some(ref mut f) = entry.stat_file { - get_all_data_from_file(f, 1024).map_err(|_| ())? - } else { - let mut tmp = PathBuf::from(path); - tmp.push("stat"); - let mut file = File::open(tmp).map_err(|_| ())?; - let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; - entry.stat_file = check_nb_open_files(file); - data - }; - let parts = parse_stat_file(&data)?; - get_status(entry, parts[2]); - update_time_and_memory( - path, - entry, - &parts, - parent_memory, - parent_virtual_memory, - uptime, - info, - refresh_kind, - ); - if refresh_kind.disk_usage() { - update_process_disk_activity(entry, path); - } - if refresh_kind.user() && entry.user_id.is_none() { - let mut tmp = PathBuf::from(path); - tmp.push("status"); - if let Some((user_id, group_id)) = get_uid_and_gid(&tmp) { - entry.user_id = Some(Uid(user_id)); - entry.group_id = Some(Gid(group_id)); - } - } - return Ok((None, pid)); - } +fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> { + let mut file = File::open(path.join("stat")).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + *stat_file = FileCounter::new(file); + Ok(data) +} - let mut tmp = PathBuf::from(path); +#[inline(always)] +fn get_status(p: &mut Process, part: &str) { + p.status = part + .chars() + .next() + .map(ProcessStatus::from) + .unwrap_or_else(|| ProcessStatus::Unknown(0)); +} - tmp.push("stat"); - let mut file = std::fs::File::open(&tmp).map_err(|_| ())?; - let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; - let stat_file = check_nb_open_files(file); - let parts = parse_stat_file(&data)?; +fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) { + if let Some((user_id, group_id)) = get_uid_and_gid(path.join("status")) { + p.user_id = Some(Uid(user_id)); + p.group_id = Some(Gid(group_id)); + } +} + +fn retrieve_all_new_process_info( + pid: Pid, + proc_list: &Process, + parts: &[&str], + path: &Path, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, + uptime: u64, +) -> Process { + let mut p = Process::new(pid); + let mut tmp = PathHandler::new(path); let name = parts[1]; - let parent_pid = if proc_list.pid.0 != 0 { + p.parent = if proc_list.pid.0 != 0 { Some(proc_list.pid) } else { match Pid::from_str(parts[3]) { @@ -383,20 +355,15 @@ pub(crate) fn _get_process_data( } }; - // To be noted that the start time is invalid here, it still needs - let start_time = u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle; - let mut p = Process::new(pid, parent_pid, start_time, info); + p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info); + p.start_time = p + .start_time_without_boot_time + .saturating_add(info.boot_time); - p.stat_file = stat_file; get_status(&mut p, parts[2]); if refresh_kind.user() { - tmp.pop(); - tmp.push("status"); - if let Some((user_id, group_id)) = get_uid_and_gid(&tmp) { - p.user_id = Some(Uid(user_id)); - p.group_id = Some(Gid(group_id)); - } + refresh_user_group_ids(&mut p, &mut tmp); } if proc_list.pid.0 != 0 { @@ -410,38 +377,28 @@ pub(crate) fn _get_process_data( p.root = proc_list.root.clone(); } else { p.name = name.into(); - tmp.pop(); - tmp.push("cmdline"); - p.cmd = copy_from_file(&tmp); - tmp.pop(); - tmp.push("exe"); - match tmp.read_link() { + + match tmp.join("exe").read_link() { Ok(exe_path) => { p.exe = exe_path; } Err(_) => { - p.exe = if let Some(cmd) = p.cmd.get(0) { - PathBuf::from(cmd) - } else { - PathBuf::new() - }; + // Do not use cmd[0] because it is not the same thing. + // See https://github.com/GuillaumeGomez/sysinfo/issues/697. + p.exe = PathBuf::new() } } - tmp.pop(); - tmp.push("environ"); - p.environ = copy_from_file(&tmp); - tmp.pop(); - tmp.push("cwd"); - p.cwd = realpath(&tmp); - tmp.pop(); - tmp.push("root"); - p.root = realpath(&tmp); + + p.cmd = copy_from_file(tmp.join("cmdline")); + p.environ = copy_from_file(tmp.join("environ")); + p.cwd = realpath(tmp.join("cwd")); + p.root = realpath(tmp.join("root")); } update_time_and_memory( path, &mut p, - &parts, + parts, proc_list.memory, proc_list.virtual_memory, uptime, @@ -451,7 +408,91 @@ pub(crate) fn _get_process_data( if refresh_kind.disk_usage() { update_process_disk_activity(&mut p, path); } - Ok((Some(p), pid)) + p +} + +pub(crate) fn _get_process_data( + path: &Path, + proc_list: &mut Process, + pid: Pid, + uptime: u64, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, +) -> Result<(Option<Process>, Pid), ()> { + let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { + Some(Ok(nb)) if nb != pid => nb, + _ => return Err(()), + }; + + let parent_memory = proc_list.memory; + let parent_virtual_memory = proc_list.virtual_memory; + + let data; + let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { + data = if let Some(mut f) = entry.stat_file.take() { + match get_all_data_from_file(&mut f, 1024) { + Ok(data) => { + // Everything went fine, we put back the file descriptor. + entry.stat_file = Some(f); + data + } + Err(_) => { + // It's possible that the file descriptor is no longer valid in case the + // original process was terminated and another one took its place. + _get_stat_data(path, &mut entry.stat_file)? + } + } + } else { + _get_stat_data(path, &mut entry.stat_file)? + }; + 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. + // If the start time differs, then it means it's not the same process anymore and that we + // need to get all its information, hence why we check it here. + if start_time_without_boot_time == entry.start_time_without_boot_time { + get_status(entry, parts[2]); + update_time_and_memory( + path, + entry, + &parts, + parent_memory, + parent_virtual_memory, + uptime, + info, + refresh_kind, + ); + if refresh_kind.disk_usage() { + update_process_disk_activity(entry, path); + } + if refresh_kind.user() && entry.user_id.is_none() { + refresh_user_group_ids(entry, &mut PathBuf::from(path)); + } + return Ok((None, pid)); + } + parts + } else { + let mut stat_file = None; + let data = _get_stat_data(path, &mut stat_file)?; + 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); + p.stat_file = stat_file; + return Ok((Some(p), pid)); + }; + + // If we're here, it means that the PID still exists but it's a different process. + let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); + match proc_list.tasks.get_mut(&pid) { + Some(ref mut entry) => **entry = p, + // If it ever enters this case, it means that the process was removed from the HashMap + // in-between with the usage of dark magic. + None => unreachable!(), + } + // Since this PID is already in the HashMap, no need to add it again. + Ok((None, pid)) } #[allow(clippy::too_many_arguments)] @@ -473,9 +514,9 @@ fn update_time_and_memory( if entry.memory >= parent_memory { entry.memory -= parent_memory; } - // vsz correspond to the Virtual memory size in bytes. Divising by 1_000 gives us kb. + // vsz correspond to the Virtual memory size in bytes. // see: https://man7.org/linux/man-pages/man5/proc.5.html - entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0) / 1_000; + entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); if entry.virtual_memory >= parent_virtual_memory { entry.virtual_memory -= parent_virtual_memory; } @@ -504,73 +545,65 @@ pub(crate) fn refresh_procs( info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) -> bool { - if let Ok(d) = fs::read_dir(path) { - let folders = d - .filter_map(|entry| { - if let Ok(entry) = entry { - let entry = entry.path(); - - if entry.is_dir() { - Some(entry) - } else { - None - } - } else { - None - } + let d = match fs::read_dir(path) { + Ok(d) => d, + Err(_) => return false, + }; + let folders = d + .filter_map(|entry| { + let entry = entry.ok()?; + let entry = entry.path(); + + if entry.is_dir() { + Some(entry) + } else { + None + } + }) + .collect::<Vec<_>>(); + if pid.0 == 0 { + let proc_list = Wrap(UnsafeCell::new(proc_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(folders) + .filter_map(|e| { + let (p, _) = _get_process_data( + e.as_path(), + proc_list.get(), + pid, + uptime, + info, + refresh_kind, + ) + .ok()?; + p }) - .collect::<Vec<_>>(); - if pid.0 == 0 { - let proc_list = Wrap(UnsafeCell::new(proc_list)); - - #[cfg(feature = "multithread")] - use rayon::iter::ParallelIterator; - - into_iter(folders) - .filter_map(|e| { - if let Ok((p, _)) = _get_process_data( - e.as_path(), - proc_list.get(), - pid, - uptime, - info, - refresh_kind, - ) { - p - } else { - None - } - }) - .collect::<Vec<_>>() - } else { - let mut updated_pids = Vec::with_capacity(folders.len()); - let new_tasks = folders - .iter() - .filter_map(|e| { - if let Ok((p, pid)) = - _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) - { - updated_pids.push(pid); - p - } else { - None - } - }) - .collect::<Vec<_>>(); - // Sub-tasks are not cleaned up outside so we do it here directly. - proc_list - .tasks - .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); - new_tasks - } - .into_iter() - .for_each(|e| { - proc_list.tasks.insert(e.pid(), e); - }); - true + .collect::<Vec<_>>() } else { - false - } + let mut updated_pids = Vec::with_capacity(folders.len()); + let new_tasks = folders + .iter() + .filter_map(|e| { + let (p, pid) = + _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) + .ok()?; + updated_pids.push(pid); + p + }) + .collect::<Vec<_>>(); + // Sub-tasks are not cleaned up outside so we do it here directly. + proc_list + .tasks + .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); + new_tasks + } + .into_iter() + .for_each(|e| { + proc_list.tasks.insert(e.pid(), e); + }); + true } fn copy_from_file(entry: &Path) -> Vec<String> { @@ -618,7 +651,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 @@ -653,29 +686,7 @@ fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { } } -fn check_nb_open_files(f: File) -> Option<File> { - unsafe { - if let Ok(ref mut x) = REMAINING_FILES.lock() { - if **x > 0 { - **x -= 1; - return Some(f); - } - } - } - // Something bad happened... - None -} - -macro_rules! unwrap_or_return { - ($data:expr) => {{ - match $data { - Some(x) => x, - None => return Err(()), - } - }}; -} - -fn parse_stat_file(data: &str) -> Result<Vec<&str>, ()> { +fn parse_stat_file(data: &str) -> Option<Vec<&str>> { // 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 @@ -687,14 +698,14 @@ fn parse_stat_file(data: &str) -> Result<Vec<&str>, ()> { 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) } diff --git a/vendor/sysinfo/src/linux/system.rs b/vendor/sysinfo/src/linux/system.rs index 7a8d0a010..3c4fce345 100644 --- a/vendor/sysinfo/src/linux/system.rs +++ b/vendor/sysinfo/src/linux/system.rs @@ -4,7 +4,7 @@ use crate::sys::component::{self, Component}; use crate::sys::cpu::*; use crate::sys::disk; use crate::sys::process::*; -use crate::sys::utils::get_all_data; +use crate::sys::utils::{get_all_data, to_u64}; use crate::{ CpuRefreshKind, Disk, LoadAvg, Networks, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; @@ -63,12 +63,6 @@ pub(crate) fn get_max_nb_fds() -> isize { } } -macro_rules! to_str { - ($e:expr) => { - unsafe { std::str::from_utf8_unchecked($e) } - }; -} - fn boot_time() -> u64 { if let Ok(f) = File::open("/proc/stat") { let buf = BufReader::new(f); @@ -111,7 +105,7 @@ impl SystemInfo { fn new() -> Self { unsafe { Self { - page_size_kb: (sysconf(_SC_PAGESIZE) / 1024) as _, + page_size_kb: sysconf(_SC_PAGESIZE) as _, clock_cycle: sysconf(_SC_CLK_TCK) as _, boot_time: boot_time(), } @@ -163,22 +157,16 @@ pub struct System { mem_available: u64, mem_buffers: u64, mem_page_cache: u64, + mem_shmem: u64, mem_slab_reclaimable: u64, swap_total: u64, swap_free: u64, - global_cpu: Cpu, - cpus: Vec<Cpu>, components: Vec<Component>, disks: Vec<Disk>, networks: Networks, users: Vec<User>, - /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`. - /// - /// The reason behind this is to avoid calling the `update_cpus` more than necessary. - /// For example when running `refresh_all` or `refresh_specifics`. - need_cpus_update: bool, info: SystemInfo, - got_cpu_frequency: bool, + cpus: CpusWrapper, } impl System { @@ -193,15 +181,14 @@ impl System { fn clear_procs(&mut self, refresh_kind: ProcessRefreshKind) { let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() { - if self.need_cpus_update { - self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); - } + self.cpus + .refresh_if_needed(true, CpuRefreshKind::new().with_cpu_usage()); if self.cpus.is_empty() { sysinfo_debug!("cannot compute processes CPU usage: no CPU found..."); (0., false, 0.) } else { - let (new, old) = get_raw_times(&self.global_cpu); + let (new, old) = self.cpus.get_global_raw_times(); let total_time = if old > new { 1 } else { new - old }; ( total_time as f32 / self.cpus.len() as f32, @@ -226,128 +213,7 @@ impl System { } fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { - if let Ok(f) = File::open("/proc/stat") { - self.need_cpus_update = false; - - let buf = BufReader::new(f); - let mut i: usize = 0; - let first = self.cpus.is_empty(); - let mut it = buf.split(b'\n'); - let (vendor_id, brand) = if first { - get_vendor_id_and_brand() - } else { - (String::new(), String::new()) - }; - - if first || refresh_kind.cpu_usage() { - if let Some(Ok(line)) = it.next() { - if &line[..4] != b"cpu " { - return; - } - let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); - if first { - self.global_cpu.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); - } else { - parts.next(); - } - self.global_cpu.set( - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - ); - } - if first || !only_update_global_cpu { - while let Some(Ok(line)) = it.next() { - if &line[..3] != b"cpu" { - break; - } - - let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); - if first { - self.cpus.push(Cpu::new_with_values( - to_str!(parts.next().unwrap_or(&[])), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - 0, - vendor_id.clone(), - brand.clone(), - )); - } else { - parts.next(); // we don't want the name again - self.cpus[i].set( - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - parts.next().map(to_u64).unwrap_or(0), - ); - } - - i += 1; - } - } - } - - if refresh_kind.frequency() && !self.got_cpu_frequency { - #[cfg(feature = "multithread")] - use rayon::iter::{ - IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator, - }; - - #[cfg(feature = "multithread")] - // This function is voluntarily made generic in case we want to generalize it. - fn iter_mut<'a, T>( - val: &'a mut T, - ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter - where - &'a mut T: rayon::iter::IntoParallelIterator, - { - val.par_iter_mut() - } - - #[cfg(not(feature = "multithread"))] - fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> { - val.iter_mut() - } - - // `get_cpu_frequency` is very slow, so better run it in parallel. - self.global_cpu.frequency = iter_mut(&mut self.cpus) - .enumerate() - .map(|(pos, proc_)| { - proc_.frequency = get_cpu_frequency(pos); - proc_.frequency - }) - .max() - .unwrap_or(0); - - self.got_cpu_frequency = true; - } - - if first { - self.global_cpu.vendor_id = vendor_id; - self.global_cpu.brand = brand; - } - } + self.cpus.refresh(only_update_global_cpu, refresh_kind); } } @@ -356,8 +222,7 @@ impl SystemExt for System { const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); fn new_with_specifics(refreshes: RefreshKind) -> System { - let info = SystemInfo::new(); - let process_list = Process::new(Pid(0), None, 0, &info); + let process_list = Process::new(Pid(0)); let mut s = System { process_list, mem_total: 0, @@ -365,33 +230,16 @@ impl SystemExt for System { mem_available: 0, mem_buffers: 0, mem_page_cache: 0, + mem_shmem: 0, mem_slab_reclaimable: 0, swap_total: 0, swap_free: 0, - global_cpu: Cpu::new_with_values( - "", - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - String::new(), - String::new(), - ), - cpus: Vec::with_capacity(4), + cpus: CpusWrapper::new(), components: Vec::new(), disks: Vec::with_capacity(2), networks: Networks::new(), users: Vec::new(), - need_cpus_update: true, - info, - got_cpu_frequency: false, + info: SystemInfo::new(), }; s.refresh_specifics(refreshes); s @@ -403,14 +251,20 @@ impl SystemExt for System { fn refresh_memory(&mut self) { if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { + let mut mem_available_found = false; + for line in data.split('\n') { let mut iter = line.split(':'); let field = match iter.next() { Some("MemTotal") => &mut self.mem_total, Some("MemFree") => &mut self.mem_free, - Some("MemAvailable") => &mut self.mem_available, + Some("MemAvailable") => { + mem_available_found = true; + &mut self.mem_available + } Some("Buffers") => &mut self.mem_buffers, Some("Cached") => &mut self.mem_page_cache, + Some("Shmem") => &mut self.mem_shmem, Some("SReclaimable") => &mut self.mem_slab_reclaimable, Some("SwapTotal") => &mut self.swap_total, Some("SwapFree") => &mut self.swap_free, @@ -419,10 +273,21 @@ impl SystemExt for System { if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) { if let Ok(value) = u64::from_str(val_str) { // /proc/meminfo reports KiB, though it says "kB". Convert it. - *field = value.saturating_mul(128) / 125; + *field = value.saturating_mul(1_024); } } } + + // Linux < 3.14 may not have MemAvailable in /proc/meminfo + // So it should fallback to the old way of estimating available memory + // https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716 + if !mem_available_found { + self.mem_available = self.mem_free + + self.mem_buffers + + self.mem_page_cache + + self.mem_slab_reclaimable + - self.mem_shmem; + } } } @@ -441,7 +306,7 @@ impl SystemExt for System { refresh_kind, ); self.clear_procs(refresh_kind); - self.need_cpus_update = true; + self.cpus.set_need_cpus_update(); } fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { @@ -469,7 +334,7 @@ impl SystemExt for System { sysinfo_debug!("Cannot compute process CPU usage: no cpus found..."); return found; } - let (new, old) = get_raw_times(&self.global_cpu); + let (new, old) = self.cpus.get_global_raw_times(); let total_time = (if old >= new { 1 } else { new - old }) as f32; let max_cpu_usage = self.get_max_process_cpu_usage(); @@ -513,11 +378,11 @@ impl SystemExt for System { } fn global_cpu_info(&self) -> &Cpu { - &self.global_cpu + &self.cpus.global_cpu } fn cpus(&self) -> &[Cpu] { - &self.cpus + &self.cpus.cpus } fn physical_core_count(&self) -> Option<usize> { @@ -537,11 +402,7 @@ impl SystemExt for System { } fn used_memory(&self) -> u64 { - self.mem_total - - self.mem_free - - self.mem_buffers - - self.mem_page_cache - - self.mem_slab_reclaimable + self.mem_total - self.mem_available } fn total_swap(&self) -> u64 { @@ -573,6 +434,13 @@ impl SystemExt for System { &mut self.disks } + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + fn uptime(&self) -> u64 { let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); content @@ -691,6 +559,25 @@ impl SystemExt for System { fn os_version(&self) -> Option<String> { get_system_info_android(InfoType::OsVersion) } + + #[cfg(not(target_os = "android"))] + fn distribution_id(&self) -> String { + get_system_info_linux( + InfoType::DistributionID, + Path::new("/etc/os-release"), + Path::new(""), + ) + .unwrap_or_else(|| std::env::consts::OS.to_owned()) + } + + #[cfg(target_os = "android")] + fn distribution_id(&self) -> String { + // Currently get_system_info_android doesn't support InfoType::DistributionID and always + // returns None. This call is done anyway for consistency with non-Android implementation + // and to suppress dead-code warning for DistributionID on Android. + get_system_info_android(InfoType::DistributionID) + .unwrap_or_else(|| std::env::consts::OS.to_owned()) + } } impl Default for System { @@ -699,16 +586,6 @@ impl Default for System { } } -fn to_u64(v: &[u8]) -> u64 { - let mut x = 0; - - for c in v { - x *= 10; - x += u64::from(c - b'0'); - } - x -} - #[derive(PartialEq, Eq)] enum InfoType { /// The end-user friendly name of: @@ -716,6 +593,9 @@ enum InfoType { /// - Linux: The distributions name Name, OsVersion, + /// Machine-parseable ID of a distribution, see + /// https://www.freedesktop.org/software/systemd/man/os-release.html#ID= + DistributionID, } #[cfg(not(target_os = "android"))] @@ -726,6 +606,7 @@ fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> O let info_str = match info { InfoType::Name => "NAME=", InfoType::OsVersion => "VERSION_ID=", + InfoType::DistributionID => "ID=", }; for line in reader.lines().flatten() { @@ -744,6 +625,10 @@ fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> O let info_str = match info { InfoType::OsVersion => "DISTRIB_RELEASE=", InfoType::Name => "DISTRIB_ID=", + InfoType::DistributionID => { + // lsb-release is inconsistent with os-release and unsupported. + return None; + } }; for line in reader.lines().flatten() { if let Some(stripped) = line.strip_prefix(info_str) { @@ -759,6 +644,10 @@ fn get_system_info_android(info: InfoType) -> Option<String> { let name: &'static [u8] = match info { InfoType::Name => b"ro.product.model\0", InfoType::OsVersion => b"ro.build.version.release\0", + InfoType::DistributionID => { + // Not supported. + return None; + } }; let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; @@ -792,6 +681,7 @@ mod test { fn lsb_release_fallback_android() { assert!(get_system_info_android(InfoType::OsVersion).is_some()); assert!(get_system_info_android(InfoType::Name).is_some()); + assert!(get_system_info_android(InfoType::DistributionID).is_none()); } #[test] @@ -838,6 +728,10 @@ DISTRIB_DESCRIPTION="Ubuntu 20.10" get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), Some("Ubuntu".to_owned()) ); + assert_eq!( + get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")), + Some("ubuntu".to_owned()) + ); // Check for the "fallback" path: "/etc/lsb-release" assert_eq!( @@ -848,5 +742,9 @@ DISTRIB_DESCRIPTION="Ubuntu 20.10" get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), Some("Ubuntu".to_owned()) ); + assert_eq!( + get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2), + None + ); } } diff --git a/vendor/sysinfo/src/linux/utils.rs b/vendor/sysinfo/src/linux/utils.rs index cd881a3d1..60a9aa2b0 100644 --- a/vendor/sysinfo/src/linux/utils.rs +++ b/vendor/sysinfo/src/linux/utils.rs @@ -2,7 +2,9 @@ use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; -use std::path::Path; +use std::path::{Path, PathBuf}; + +use crate::sys::system::REMAINING_FILES; pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result<String> { let mut buf = String::with_capacity(size); @@ -17,39 +19,105 @@ pub(crate) fn get_all_data<P: AsRef<Path>>(file_path: P, size: usize) -> io::Res } #[allow(clippy::useless_conversion)] -pub(crate) fn realpath(original: &Path) -> std::path::PathBuf { - use libc::{lstat, stat, S_IFLNK, S_IFMT}; - use std::fs; - use std::mem::MaybeUninit; - use std::path::PathBuf; - - fn and(x: u32, y: u32) -> u32 { - x & y - } - - // let ori = Path::new(original.to_str().unwrap()); - // Right now lstat on windows doesn't work quite well - // if cfg!(windows) { - // return PathBuf::from(ori); - // } - let result = PathBuf::from(original); - let mut result_s = result.to_str().unwrap_or("").as_bytes().to_vec(); - result_s.push(0); - let mut buf = MaybeUninit::<stat>::uninit(); - unsafe { - let res = lstat(result_s.as_ptr() as *const _, buf.as_mut_ptr()); - if res < 0 { +pub(crate) fn realpath(path: &Path) -> std::path::PathBuf { + match std::fs::read_link(path) { + Ok(f) => f, + Err(_e) => { + sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e); PathBuf::new() - } else { - let buf = buf.assume_init(); - if and(buf.st_mode.into(), S_IFMT.into()) != S_IFLNK.into() { - PathBuf::new() - } else { - match fs::read_link(&result) { - Ok(f) => f, - Err(_) => PathBuf::new(), + } + } +} + +/// Type used to correctly handle the `REMAINING_FILES` global. +pub(crate) struct FileCounter(File); + +impl FileCounter { + pub(crate) fn new(f: File) -> Option<Self> { + unsafe { + if let Ok(ref mut x) = REMAINING_FILES.lock() { + if **x > 0 { + **x -= 1; + return Some(Self(f)); } + // All file descriptors we were allowed are being used. + } + } + None + } +} + +impl std::ops::Deref for FileCounter { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for FileCounter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Drop for FileCounter { + fn drop(&mut self) { + unsafe { + if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { + **x += 1; } } } } + +/// This type is used in `retrieve_all_new_process_info` because we have a "parent" path and +/// from it, we `pop`/`join` every time because it's more memory efficient than using `Path::join`. +pub(crate) struct PathHandler(PathBuf); + +impl PathHandler { + pub(crate) fn new(path: &Path) -> Self { + // `path` is the "parent" for all paths which will follow so we add a fake element at + // the end since every `PathHandler::join` call will first call `pop` internally. + Self(path.join("a")) + } +} + +pub(crate) trait PathPush { + fn join(&mut self, p: &str) -> &Path; +} + +impl PathPush for PathHandler { + fn join(&mut self, p: &str) -> &Path { + self.0.pop(); + self.0.push(p); + self.0.as_path() + } +} + +// This implementation allows to skip one allocation that is done in `PathHandler`. +impl PathPush for PathBuf { + fn join(&mut self, p: &str) -> &Path { + self.push(p); + self.as_path() + } +} + +pub(crate) fn to_u64(v: &[u8]) -> u64 { + let mut x = 0; + + for c in v { + x *= 10; + x += u64::from(c - b'0'); + } + x +} + +/// Converts a path to a NUL-terminated `Vec<u8>` suitable for use with C functions. +pub(crate) fn to_cpath(path: &std::path::Path) -> Vec<u8> { + use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; + + let path_os: &OsStr = path.as_ref(); + let mut cpath = path_os.as_bytes().to_vec(); + cpath.push(0); + cpath +} |