summaryrefslogtreecommitdiffstats
path: root/vendor/sysinfo/src/linux
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sysinfo/src/linux')
-rw-r--r--vendor/sysinfo/src/linux/component.rs405
-rw-r--r--vendor/sysinfo/src/linux/cpu.rs208
-rw-r--r--vendor/sysinfo/src/linux/disk.rs22
-rw-r--r--vendor/sysinfo/src/linux/network.rs2
-rw-r--r--vendor/sysinfo/src/linux/process.rs439
-rw-r--r--vendor/sysinfo/src/linux/system.rs268
-rw-r--r--vendor/sysinfo/src/linux/utils.rs130
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
+}