summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2023-04-16 01:28:37 -0400
committerClement Tsang <34804052+ClementTsang@users.noreply.github.com>2023-04-17 03:36:00 -0400
commitafc180f004cfbbdb01741ccab9b3a205482f388b (patch)
tree62346b70c135e1c5261cd0e4a9796b69d957ebbe
parent6ea3635b28b9885ac04a69d52ab63ee85e6c5c89 (diff)
other: directly implement procfs functionality with small optimizationsoptimize_procfs
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock68
-rw-r--r--Cargo.toml3
-rw-r--r--src/app/data_harvester.rs6
-rw-r--r--src/app/data_harvester/processes/linux.rs110
-rw-r--r--src/app/data_harvester/processes/linux/process.rs276
-rw-r--r--src/data_conversion.rs1
-rw-r--r--src/lib.rs1
-rw-r--r--src/utils/error.rs27
9 files changed, 386 insertions, 108 deletions
diff --git a/.gitignore b/.gitignore
index 1fe7c3a7..702b63da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,4 @@ site/
# dhat heap profiling
dhat-heap.json
-dhat/ \ No newline at end of file
+dhat/
diff --git a/Cargo.lock b/Cargo.lock
index eab63a38..60eee165 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,7 +70,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
"libc",
"winapi",
]
@@ -134,9 +134,10 @@ dependencies = [
"nvml-wrapper",
"once_cell",
"predicates",
- "procfs",
"ratatui",
+ "rayon",
"regex",
+ "rustix",
"serde",
"serde_json",
"starship-battery",
@@ -447,13 +448,13 @@ dependencies = [
[[package]]
name = "errno"
-version = "0.2.8"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -549,10 +550,10 @@ dependencies = [
]
[[package]]
-name = "hex"
-version = "0.4.3"
+name = "hermit-abi"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "humantime"
@@ -588,12 +589,13 @@ dependencies = [
[[package]]
name = "io-lifetimes"
-version = "1.0.4"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
+ "hermit-abi 0.3.1",
"libc",
- "windows-sys 0.42.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -650,9 +652,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.1.4"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
[[package]]
name = "lock_api"
@@ -760,7 +762,7 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
"libc",
]
@@ -872,19 +874,6 @@ dependencies = [
]
[[package]]
-name = "procfs"
-version = "0.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f"
-dependencies = [
- "bitflags",
- "byteorder",
- "hex",
- "lazy_static",
- "rustix",
-]
-
-[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -908,21 +897,19 @@ dependencies = [
[[package]]
name = "rayon"
-version = "1.5.2"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
- "autocfg",
- "crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
-version = "1.9.2"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -987,16 +974,16 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustix"
-version = "0.36.6"
+version = "0.37.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
+checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
- "windows-sys 0.42.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -1439,6 +1426,15 @@ dependencies = [
]
[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 314f0fca..8f232f01 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -108,7 +108,8 @@ unicode-width = "0.1.10"
libc = "0.2.141"
[target.'cfg(target_os = "linux")'.dependencies]
-procfs = { version = "0.15.1", default-features = false }
+rayon = "1.7.0"
+rustix = { version = "0.37.11", features = ["fs", "param", "process"] }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.3"
diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs
index 2f4633bf..798e6086 100644
--- a/src/app/data_harvester.rs
+++ b/src/app/data_harvester.rs
@@ -25,6 +25,9 @@ pub mod network;
pub mod processes;
pub mod temperature;
+// Refresh certain details once every minute. If it's too frequent it can cause segfaults.
+const LIST_REFRESH_TIME: Duration = Duration::from_secs(60);
+
#[derive(Clone, Debug)]
pub struct Data {
pub last_collection_time: Instant,
@@ -229,8 +232,6 @@ impl DataCollector {
/// - Disk (Windows)
/// - Temperatures (non-Linux)
fn refresh_sysinfo_data(&mut self) {
- // Refresh once every minute. If it's too frequent it can cause segfaults.
- const LIST_REFRESH_TIME: Duration = Duration::from_secs(60);
let refresh_start = Instant::now();
if self.widgets_to_harvest.use_cpu || self.widgets_to_harvest.use_proc {
@@ -344,6 +345,7 @@ impl DataCollector {
.duration_since(self.last_collection_time)
.as_secs();
+ // TODO: Can I reuse the previous data's buffer?
processes::get_process_data(
&self.sys,
prev_proc,
diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs
index 4918230f..48adb8d7 100644
--- a/src/app/data_harvester/processes/linux.rs
+++ b/src/app/data_harvester/processes/linux.rs
@@ -1,10 +1,12 @@
//! Process data collection for Linux.
-use std::fs::File;
+mod process;
+use process::*;
+
+use std::fs::{self, File};
use std::io::{BufRead, BufReader};
use hashbrown::{HashMap, HashSet};
-use procfs::process::{Process, Stat};
use sysinfo::{ProcessStatus, System};
use super::{ProcessHarvest, UserTable};
@@ -96,7 +98,7 @@ fn cpu_usage_calculation(prev_idle: &mut f64, prev_non_idle: &mut f64) -> error:
/// Returns the usage and a new set of process times.
///
-/// NB: cpu_fraction should be represented WITHOUT the x100 factor!
+/// NB: `cpu_fraction` should be represented WITHOUT the x100 factor!
fn get_linux_cpu_usage(
stat: &Stat, cpu_usage: f64, cpu_fraction: f64, prev_proc_times: u64,
use_current_cpu_total: bool,
@@ -115,14 +117,21 @@ fn get_linux_cpu_usage(
}
fn read_proc(
- prev_proc: &PrevProcDetails, process: &Process, cpu_usage: f64, cpu_fraction: f64,
+ prev_proc: &PrevProcDetails, process: Process, cpu_usage: f64, cpu_fraction: f64,
use_current_cpu_total: bool, time_difference_in_secs: u64, total_memory: u64,
user_table: &mut UserTable,
) -> error::Result<(ProcessHarvest, u64)> {
- let stat = process.stat()?;
+ let Process {
+ pid: _,
+ uid,
+ stat,
+ io,
+ cmdline,
+ } = process;
+
let (command, name) = {
let truncated_name = stat.comm.as_str();
- if let Ok(cmdline) = process.cmdline() {
+ if let Ok(cmdline) = cmdline {
if cmdline.is_empty() {
(format!("[{}]", truncated_name), truncated_name.to_string())
} else {
@@ -168,7 +177,7 @@ fn read_proc(
// This can fail if permission is denied!
let (total_read_bytes, total_write_bytes, read_bytes_per_sec, write_bytes_per_sec) =
- if let Ok(io) = process.io() {
+ if let Ok(io) = io {
let total_read_bytes = io.read_bytes;
let total_write_bytes = io.write_bytes;
let prev_total_read_bytes = prev_proc.total_read_bytes;
@@ -194,7 +203,14 @@ fn read_proc(
(0, 0, 0, 0)
};
- let uid = process.uid()?;
+ let user = uid
+ .and_then(|uid| {
+ user_table
+ .get_uid_to_username_mapping(uid)
+ .map(Into::into)
+ .ok()
+ })
+ .unwrap_or_else(|| "N/A".into());
Ok((
ProcessHarvest {
@@ -210,11 +226,8 @@ fn read_proc(
total_read_bytes,
total_write_bytes,
process_state,
- uid: Some(uid),
- user: user_table
- .get_uid_to_username_mapping(uid)
- .map(Into::into)
- .unwrap_or_else(|_| "N/A".into()),
+ uid,
+ user,
},
new_process_times,
))
@@ -230,11 +243,15 @@ pub(crate) struct ProcHarvestOptions {
pub unnormalized_cpu: bool,
}
+fn is_str_numeric(s: &str) -> bool {
+ s.chars().all(|c| c.is_ascii_digit())
+}
+
pub(crate) fn get_process_data(
sys: &System, prev_proc: PrevProc<'_>, pid_mapping: &mut HashMap<Pid, PrevProcDetails>,
proc_harvest_options: ProcHarvestOptions, time_difference_in_secs: u64, total_memory: u64,
user_table: &mut UserTable,
-) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
+) -> error::Result<Vec<ProcessHarvest>> {
let ProcHarvestOptions {
use_current_cpu_total,
unnormalized_cpu,
@@ -263,36 +280,47 @@ pub(crate) fn get_process_data(
let mut pids_to_clear: HashSet<Pid> = pid_mapping.keys().cloned().collect();
- let process_vector: Vec<ProcessHarvest> = std::fs::read_dir("/proc")?
+ let pids = fs::read_dir("/proc")?
+ .flatten()
.filter_map(|dir| {
- if let Ok(dir) = dir {
- if let Ok(pid) = dir.file_name().to_string_lossy().trim().parse::<Pid>() {
- let Ok(process) = Process::new(pid) else {
- return None;
- };
- let prev_proc_details = pid_mapping.entry(pid).or_default();
-
- if let Ok((process_harvest, new_process_times)) = read_proc(
- prev_proc_details,
- &process,
- cpu_usage,
- cpu_fraction,
- use_current_cpu_total,
- time_difference_in_secs,
- total_memory,
- user_table,
- ) {
- prev_proc_details.cpu_time = new_process_times;
- prev_proc_details.total_read_bytes = process_harvest.total_read_bytes;
- prev_proc_details.total_write_bytes = process_harvest.total_write_bytes;
-
- pids_to_clear.remove(&pid);
- return Some(process_harvest);
- }
+ if is_str_numeric(dir.file_name().to_string_lossy().trim()) {
+ Some(dir.path())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ use rayon::prelude::*;
+ let process_vector: Vec<ProcessHarvest> = pids
+ .into_par_iter()
+ .filter_map(|pid_path| {
+ if let Ok(process) = Process::from_path(pid_path) {
+ let pid = process.pid;
+ let prev_proc_details = pid_mapping.entry(pid).or_default();
+
+ if let Ok((process_harvest, new_process_times)) = read_proc(
+ prev_proc_details,
+ process,
+ cpu_usage,
+ cpu_fraction,
+ use_current_cpu_total,
+ time_difference_in_secs,
+ total_memory,
+ user_table,
+ ) {
+ prev_proc_details.cpu_time = new_process_times;
+ prev_proc_details.total_read_bytes = process_harvest.total_read_bytes;
+ prev_proc_details.total_write_bytes = process_harvest.total_write_bytes;
+
+ pids_to_clear.remove(&pid);
+ Some(process_harvest)
+ } else {
+ None
}
+ } else {
+ None
}
-
- None
})
.collect();
diff --git a/src/app/data_harvester/processes/linux/process.rs b/src/app/data_harvester/processes/linux/process.rs
new file mode 100644
index 00000000..f2fc6327
--- /dev/null
+++ b/src/app/data_harvester/processes/linux/process.rs
@@ -0,0 +1,276 @@
+//! Linux process code for getting process data via `/proc/`.
+//! Based on the [procfs](https://github.com/eminence/procfs) crate.
+
+use std::{
+ fs::File,
+ io::{self, BufRead, BufReader, Read},
+ path::PathBuf,
+};
+
+use anyhow::anyhow;
+use libc::uid_t;
+use once_cell::sync::Lazy;
+use rustix::{
+ fd::OwnedFd,
+ fs::{Mode, OFlags},
+ path::Arg,
+};
+
+use crate::Pid;
+
+static PAGESIZE: Lazy<u64> = Lazy::new(|| rustix::param::page_size() as u64);
+
+fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io::Error> {
+ iter.next()
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))
+}
+
+/// A wrapper around the data in `/proc/<PID>/stat`. For documentation, see [here](https://man7.org/linux/man-pages/man5/proc.5.html).
+///
+/// Note this does not necessarily get all fields, only the ones we use in bottom.
+pub(crate) struct Stat {
+ /// The filename of the executable without parentheses.
+ pub comm: String,
+
+ /// The current process state, represented by a char.
+ pub state: char,
+
+ /// The parent process PID.
+ pub ppid: Pid,
+
+ /// The amount of time this process has been scheduled in user mode in clock ticks.
+ pub utime: u64,
+
+ /// The amount of time this process has been scheduled in kernel mode in clock ticks.
+ pub stime: u64,
+
+ /// The resident set size, or the number of pages the process has in real memory.
+ pub rss: u64,
+}
+
+impl Stat {
+ fn from_file(mut f: File) -> anyhow::Result<Stat> {
+ // Since this is just one line, we can read it all at once. However, since it might have non-utf8 characters,
+ // we can't just use read_to_string.
+ let mut buffer = Vec::with_capacity(500);
+ f.read_to_end(&mut buffer)?;
+
+ let line = buffer.to_string_lossy();
+ let line = line.trim();
+
+ let (comm, rest) = {
+ let start_paren = line
+ .find('(')
+ .ok_or_else(|| anyhow!("start paren missing"))?;
+ let end_paren = line.find(')').ok_or_else(|| anyhow!("end paren missing"))?;
+
+ (
+ line[start_paren + 1..end_paren].to_string(),
+ &line[end_paren + 2..],
+ )
+ };
+
+ let mut rest = rest.split(' ');
+ let state = next_part(&mut rest)?
+ .chars()
+ .next()
+ .ok_or_else(|| anyhow!("missing state"))?;
+
+ let ppid: Pid = next_part(&mut rest)?.parse()?;
+
+ // Skip 9 fields until utime (pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt).
+ let mut rest = rest.skip(9);
+
+ let utime: u64 = next_part(&mut rest)?.parse()?;
+ let stime: u64 = next_part(&mut rest)?.parse()?;
+
+ // Skip 8 fields until rss (cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize).
+ let mut rest = rest.skip(8);
+
+ let rss: u64 = next_part(&mut rest)?.parse()?;
+
+ Ok(Stat {
+ comm,
+ state,
+ ppid,
+ utime,
+ stime,
+ rss,
+ })
+ }
+
+ /// Returns the Resident Set Size in bytes.
+ pub fn rss_bytes(&self) -> u64 {
+ self.rss * *PAGESIZE
+ }
+}
+
+/// A wrapper around the data in `/proc/<PID>/io`.
+///
+/// Note this does not necessarily get all fields, only the ones we use in bottom.
+pub(crate) struct Io {
+ pub read_bytes: u64,
+ pub write_bytes: u64,
+}
+
+impl Io {
+ fn from_file(f: File) -> anyhow::Result<Io> {
+ const NUM_FIELDS: u16 = 0; // Make sure to update this if you want more fields!
+ enum Fields {
+ ReadBytes,
+ WriteBytes,
+ }
+
+ let mut read_fields = 0;
+ let mut line = String::new();
+ let mut reader = BufReader::new(f);
+
+ let mut read_bytes = 0;
+ let mut write_bytes = 0;
+
+ // This saves us from doing a string allocation on each iteration compared to `lines()`.
+ while let Ok(bytes) = reader.read_line(&mut line) {
+ if bytes > 0 {
+ if line.is_empty() {
+ // Empty, no need to clear.
+ continue;
+ }
+
+ let mut parts = line.split_whitespace();
+
+ if let Some(field) = parts.next() {
+ let curr_field = match field {
+ "read_bytes:" => Fields::ReadBytes,
+ "write_bytes:" => Fields::WriteBytes,
+ _ => {
+ line.clear();
+ continue;
+ }
+ };
+
+ if let Some(value) = parts.next() {
+ let value = value.parse::<u64>()?;
+ match curr_field {
+ Fields::ReadBytes => {
+ read_bytes = value;
+ read_fields += 1;
+ }
+ Fields::WriteBytes => {
+ write_bytes = value;
+ read_fields += 1;
+ }
+ }
+ }
+ }
+
+ // Quick short circuit if we read all required fields.
+ if read_fields == NUM_FIELDS {
+ break;
+ }
+
+ line.clear();
+ } else {
+ break;
+ }
+ }
+
+ Ok(Io {
+ read_bytes,
+ write_bytes,
+ })
+ }
+}
+
+/// A wrapper around a Linux process operations in `/proc/<PID>`.
+///
+/// Core documentation based on [proc's manpages](https://man7.org/linux/man-pages/man5/proc.5.html).
+pub(crate) struct Process {
+ pub pid: Pid,
+ pub uid: Option<uid_t>,
+ pub stat: Stat,
+ pub io: anyhow::Result<Io>,
+ pub cmdline: anyhow::Result<Vec<String>>,
+}
+
+impl Process {
+ /// Creates a new [`Process`] given a `/proc/<PID>` path. This may fail if the process
+ /// no longer exists or there are permissions issues.
+ ///
+ /// Note that this pre-allocates fields on **creation**! As such, some data might end
+ /// up "outdated" depending on when you call some of the methods. Therefore, this struct
+ /// is only useful for either fields that are unlikely to change, or are short-lived and
+ /// will be discarded quickly.
+ pub(crate) fn from_path(pid_path: PathBuf) -> anyhow::Result<Process> {
+ // TODO: Pass in a buffer vec/string to share?
+
+ let fd = rustix::fs::openat(
+ rustix::fs::cwd(),
+ &pid_path,
+ OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
+ Mode::empty(),
+ )?;
+
+ let pid = pid_path
+ .as_path()
+ .components()
+ .last()
+ .and_then(|s| s.to_string_lossy().parse::<Pid>().ok())
+ .or_else(|| {
+ rustix::fs::readlinkat(rustix::fs::cwd(), &pid_path, vec![])
+ .ok()
+ .and_then(|s| s.to_string_lossy().parse::<Pid>().ok())
+ })
+ .ok_or_else(|| anyhow!("PID for {pid_path:?} was not found"))?;
+
+ let uid = {
+ let metadata = rustix::fs::fstat(&fd);
+ match metadata {
+ Ok(md) => Some(md.st_uid),
+ Err(_) => None,
+ }
+ };
+
+ let mut root = pid_path.clone();
+ let cmdline = cmdline(&mut root, &fd);
+ root.pop();
+ let stat = open_at(&mut root, "stat", &fd).and_then(Stat::from_file)?;
+ root.pop();
+ let io = open_at(&mut root, "io", &fd).and_then(Io::from_file);
+
+ Ok(Process {
+ pid,
+ uid,
+ stat,
+ io,
+ cmdline,
+ })
+ }
+}
+
+fn cmdline(root: &mut PathBuf, fd: &OwnedFd) -> anyhow::Result<Vec<String>> {
+ let mut buf = String::new();
+ open_at(root, "cmdline", fd)
+ .map(|mut file| file.read_to_string(&mut buf))
+ .map(|_| {
+ buf.split('\0')
+ .filter_map(|s| {
+ if !s.is_empty() {
+ Some(s.to_string())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>()
+ })
+ .map_err(Into::into)
+}
+
+/// Opens a path. Note that this function takes in a mutable root - this will mutate it to avoid allocations. You
+/// probably will want to pop the most recent child after if you need to use the buffer again.
+#[inline]
+fn open_at(root: &mut PathBuf, child: &str, fd: &OwnedFd) -> anyhow::Result<File> {
+ root.push(child);
+ let new_fd = rustix::fs::openat(&fd, &*root, OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty())?;
+
+ Ok(File::from(new_fd))
+}
diff --git a/src/data_conversion.rs b/src/data_conversion.rs
index 03044c9c..392bae3d 100644
--- a/src/data_conversion.rs
+++ b/src/data_conversion.rs
@@ -283,6 +283,7 @@ fn get_mem_binary_unit_and_denominator(bytes: u64) -> (&'static str, f64) {
/// Returns the unit type and denominator for given total amount of memory in kibibytes.
pub fn convert_mem_label(harvest: &MemHarvest) -> Option<(String, String)> {
+ // TODO: Add an alignment field.
if harvest.total_bytes > 0 {
Some((format!("{:3.0}%", harvest.use_percent.unwrap_or(0.0)), {
let (unit, denominator) = get_mem_binary_unit_and_denominator(harvest.total_bytes);
diff --git a/src/lib.rs b/src/lib.rs
index 5439b9f0..c630ab78 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -548,6 +548,7 @@ pub fn create_collection_thread(
}
let event = BottomEvent::Update(Box::from(data_state.data));
+ // TODO: Should we just keep copies of existing data to avoid reallocs?
data_state.data = data_harvester::Data::default();
if sender.send(event).is_err() {
break;
diff --git a/src/utils/error.rs b/src/utils/error.rs
index 3764469c..9e556f57 100644
--- a/src/utils/error.rs
+++ b/src/utils/error.rs
@@ -2,9 +2,6 @@ use std::{borrow::Cow, result};
use thiserror::Error;
-#[cfg(target_os = "linux")]
-use procfs::ProcError;
-
/// A type alias for handling errors related to Bottom.
pub type Result<T> = result::Result<T, BottomError>;
@@ -35,10 +32,6 @@ pub enum BottomError {
/// An error that just signifies something minor went wrong; no message.
#[error("Minor error.")]
MinorError,
- /// An error to represent errors with procfs
- #[cfg(target_os = "linux")]
- #[error("Procfs error, {0}")]
- ProcfsError(String),
}
impl From<std::io::Error> for BottomError {
@@ -93,23 +86,3 @@ impl From<regex::Error> for BottomError {
BottomError::QueryError(format!("Regex error: {}", error.last().unwrap_or(&"")).into())
}
}
-
-#[cfg(target_os = "linux")]
-impl From<ProcError> for BottomError {
- fn from(err: ProcError) -> Self {
- match err {
- ProcError::PermissionDenied(p) => {
- BottomError::ProcfsError(format!("Permission denied for {:?}", p))
- }
- ProcError::NotFound(p) => BottomError::ProcfsError(format!("{:?} not found", p)),
- ProcError::Incomplete(p) => BottomError::ProcfsError(format!("{:?} incomplete", p)),
- ProcError::Io(e, p) => {
- BottomError::ProcfsError(format!("io error: {:?} for {:?}", e, p))
- }
- ProcError::Other(s) => BottomError::ProcfsError(format!("Other procfs error: {}", s)),
- ProcError::InternalError(e) => {
- BottomError::ProcfsError(format!("procfs internal error: {:?}", e))
- }
- }
- }
-}