mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-09-28 13:35:37 +00:00
feature: add process swap column for Linux (#2224)
Adds Linux support for process swap tracking + a column in the widget.
This commit is contained in:
@@ -158,7 +158,7 @@
|
||||
# Processes widget configuration
|
||||
#[processes]
|
||||
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%, Nice, Priority
|
||||
# PID, Name, CPU%, Mem%, Swap, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%, Nice, Priority
|
||||
#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%", "Priority"]
|
||||
|
||||
# The default sort column when bottom starts. Accepts any of the column names above.
|
||||
|
||||
@@ -1039,6 +1039,7 @@
|
||||
"Read",
|
||||
"Rps",
|
||||
"State",
|
||||
"Swap",
|
||||
"T.Read",
|
||||
"T.Write",
|
||||
"TRead",
|
||||
@@ -1072,6 +1073,7 @@
|
||||
"read",
|
||||
"rps",
|
||||
"state",
|
||||
"swap",
|
||||
"t.read",
|
||||
"t.write",
|
||||
"time",
|
||||
|
||||
@@ -48,6 +48,7 @@ pub struct AppConfigFields {
|
||||
pub use_current_cpu_total: bool,
|
||||
pub unnormalized_cpu: bool,
|
||||
pub get_process_threads: bool,
|
||||
pub get_process_swap: bool,
|
||||
pub use_basic_mode: bool,
|
||||
pub default_time_value: u64,
|
||||
pub time_interval: u64,
|
||||
|
||||
@@ -168,6 +168,7 @@ pub struct DataCollector {
|
||||
use_current_cpu_total: bool,
|
||||
show_average_cpu: bool,
|
||||
get_process_threads: bool,
|
||||
get_process_swap: bool,
|
||||
|
||||
last_list_collection_time: Instant,
|
||||
should_run_less_routine_tasks: bool,
|
||||
@@ -226,6 +227,7 @@ impl DataCollector {
|
||||
use_current_cpu_total: false,
|
||||
unnormalized_cpu: false,
|
||||
get_process_threads: false,
|
||||
get_process_swap: false,
|
||||
last_collection_time,
|
||||
total_rx: 0,
|
||||
total_tx: 0,
|
||||
@@ -300,6 +302,10 @@ impl DataCollector {
|
||||
self.get_process_threads = get_process_threads;
|
||||
}
|
||||
|
||||
pub fn set_get_process_swap(&mut self, get_process_swap: bool) {
|
||||
self.get_process_swap = get_process_swap;
|
||||
}
|
||||
|
||||
pub fn set_include_unmounted_disks(&mut self, include_unmounted_disks: bool) {
|
||||
self.include_unmounted_disks = include_unmounted_disks;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,9 @@ pub struct ProcessHarvest {
|
||||
/// Virtual memory.
|
||||
pub virtual_mem: Bytes,
|
||||
|
||||
/// Swapped memory.
|
||||
pub swap_bytes: Option<Bytes>,
|
||||
|
||||
/// The name of the process.
|
||||
pub name: String,
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ fn read_proc(
|
||||
stat,
|
||||
io,
|
||||
cmdline,
|
||||
swap_bytes,
|
||||
} = process;
|
||||
|
||||
let ReadProcArgs {
|
||||
@@ -152,6 +153,7 @@ fn read_proc(
|
||||
time_difference_in_secs,
|
||||
system_uptime,
|
||||
get_process_threads: _,
|
||||
get_process_swap: _,
|
||||
} = args;
|
||||
|
||||
let process_state_char = stat.state;
|
||||
@@ -272,6 +274,7 @@ fn read_proc(
|
||||
mem_usage_percent,
|
||||
mem_usage,
|
||||
virtual_mem,
|
||||
swap_bytes,
|
||||
name,
|
||||
command,
|
||||
read_per_sec,
|
||||
@@ -349,6 +352,7 @@ pub(crate) struct ReadProcArgs {
|
||||
pub time_difference_in_secs: u64,
|
||||
pub system_uptime: u64,
|
||||
pub get_process_threads: bool,
|
||||
pub get_process_swap: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn linux_process_data(
|
||||
@@ -364,6 +368,8 @@ pub(crate) fn linux_process_data(
|
||||
unnormalized_cpu: collector.unnormalized_cpu,
|
||||
get_process_threads: collector.get_process_threads,
|
||||
};
|
||||
let get_swap = collector.get_process_swap;
|
||||
|
||||
let prev_process_details = &mut collector.prev_process_details;
|
||||
let user_table = &mut collector.user_table;
|
||||
|
||||
@@ -420,6 +426,7 @@ pub(crate) fn linux_process_data(
|
||||
time_difference_in_secs,
|
||||
system_uptime: sysinfo::System::uptime(),
|
||||
get_process_threads: get_threads,
|
||||
get_process_swap: get_swap,
|
||||
};
|
||||
|
||||
// TODO: Maybe pre-allocate these buffers in the future w/ routine cleanup.
|
||||
@@ -428,9 +435,12 @@ pub(crate) fn linux_process_data(
|
||||
|
||||
let mut process_vector: Vec<ProcessHarvest> = pids
|
||||
.filter_map(|pid_path| {
|
||||
if let Ok((process, threads)) =
|
||||
Process::from_path(pid_path, &mut buffer, args.get_process_threads)
|
||||
{
|
||||
if let Ok((process, threads)) = Process::from_path(
|
||||
pid_path,
|
||||
&mut buffer,
|
||||
args.get_process_threads,
|
||||
args.get_process_swap,
|
||||
) {
|
||||
let pid = process.pid;
|
||||
let prev_proc_details = prev_process_details.entry(pid).or_default();
|
||||
|
||||
@@ -474,7 +484,8 @@ pub(crate) fn linux_process_data(
|
||||
// Get thread data.
|
||||
for (pid, tid_paths) in process_threads_to_check {
|
||||
for tid_path in tid_paths {
|
||||
if let Ok((process, _)) = Process::from_path(tid_path, &mut buffer, false) {
|
||||
// VmSwap is process-wide, so don't collect it for individual threads.
|
||||
if let Ok((process, _)) = Process::from_path(tid_path, &mut buffer, false, false) {
|
||||
let tid = process.pid;
|
||||
let prev_proc_details = prev_process_details.entry(tid).or_default();
|
||||
|
||||
|
||||
@@ -246,6 +246,33 @@ impl Io {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper that reads the `VmSwap` line from `/proc/<PID>/status`.
|
||||
///
|
||||
/// NB: `buffer` must be empty.
|
||||
///
|
||||
/// See the [`proc_pid_status(5)`](https://man7.org/linux/man-pages/man5/proc_pid_status.5.html)
|
||||
/// documentation for details about this file and field.
|
||||
fn get_swap_bytes(f: File, buffer: &mut String) -> anyhow::Result<u64> {
|
||||
let mut reader = BufReader::new(f);
|
||||
|
||||
while reader.read_line(buffer)? > 0 {
|
||||
let mut parts = buffer.split_whitespace();
|
||||
|
||||
if parts.next() == Some("VmSwap:") {
|
||||
let swap_kib: u64 = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("VmSwap value missing"))?
|
||||
.parse()?;
|
||||
|
||||
return Ok(swap_kib.saturating_mul(1024));
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
Err(anyhow!("VmSwap field not found"))
|
||||
}
|
||||
|
||||
/// 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).
|
||||
@@ -255,6 +282,7 @@ pub(crate) struct Process {
|
||||
pub stat: Stat,
|
||||
pub io: Option<Io>,
|
||||
pub cmdline: Option<String>,
|
||||
pub swap_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -277,7 +305,7 @@ impl Process {
|
||||
/// buffer.
|
||||
#[inline]
|
||||
pub(crate) fn from_path(
|
||||
pid_path: PathBuf, buffer: &mut String, get_threads: bool,
|
||||
pid_path: PathBuf, buffer: &mut String, get_threads: bool, get_swap: bool,
|
||||
) -> anyhow::Result<(Process, Vec<PathBuf>)> {
|
||||
buffer.clear();
|
||||
|
||||
@@ -333,6 +361,16 @@ impl Process {
|
||||
|
||||
reset(&mut root, buffer);
|
||||
|
||||
let swap_bytes = if get_swap && !stat.is_kernel_thread {
|
||||
let bytes = open_at(&mut root, "status", &pid_dir)
|
||||
.and_then(|file| get_swap_bytes(file, buffer))
|
||||
.ok();
|
||||
reset(&mut root, buffer);
|
||||
bytes
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let threads = threads(&mut root, pid, get_threads);
|
||||
|
||||
Ok((
|
||||
@@ -342,6 +380,7 @@ impl Process {
|
||||
stat,
|
||||
io,
|
||||
cmdline,
|
||||
swap_bytes,
|
||||
},
|
||||
threads,
|
||||
))
|
||||
@@ -426,6 +465,14 @@ mod tests {
|
||||
Stat::from_file(file, &mut String::new())
|
||||
}
|
||||
|
||||
fn swap_file(status: &str) -> anyhow::Result<u64> {
|
||||
let mut file = tempfile::tempfile()?;
|
||||
file.write_all(status.as_bytes())?;
|
||||
file.rewind()?;
|
||||
|
||||
get_swap_bytes(file, &mut String::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_short_comm() {
|
||||
let stat = stat_from_name("kworker/u16:2").unwrap();
|
||||
@@ -463,4 +510,36 @@ mod tests {
|
||||
assert!(stat_file("1 (blah)").is_err(), "too short");
|
||||
assert!(stat_file("1 )(").is_err(), "wrong order");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_swap_bytes() {
|
||||
let status = "Name:\ttest\nVmSwap:\t4096 kB\n";
|
||||
|
||||
let swap_bytes = swap_file(status).unwrap();
|
||||
|
||||
assert_eq!(swap_bytes, 4_194_304);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_zero_swap_bytes() {
|
||||
let status = "Name:\ttest\nVmSwap:\t0 kB\n";
|
||||
|
||||
let swap_bytes = swap_file(status).unwrap();
|
||||
|
||||
assert_eq!(swap_bytes, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_vm_swap_is_an_error() {
|
||||
let status = "Name:\ttest\nVmSize:\t4096 kB\n";
|
||||
|
||||
assert!(swap_file(status).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_vm_swap_is_an_error() {
|
||||
let status = "Name:\ttest\nVmSwap:\tinvalid kB\n";
|
||||
|
||||
assert!(swap_file(status).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ pub(crate) trait UnixProcessExt {
|
||||
},
|
||||
mem_usage: process_val.memory(),
|
||||
virtual_mem: process_val.virtual_memory(),
|
||||
swap_bytes: None,
|
||||
cpu_usage_percent: process_cpu_usage,
|
||||
read_per_sec: disk_usage.read_bytes,
|
||||
write_per_sec: disk_usage.written_bytes,
|
||||
|
||||
@@ -136,6 +136,7 @@ pub fn sysinfo_process_data(
|
||||
} as f32,
|
||||
mem_usage: process.memory(),
|
||||
virtual_mem: process.virtual_memory(),
|
||||
swap_bytes: None,
|
||||
cpu_usage_percent: process_cpu_usage,
|
||||
read_per_sec: disk_usage.read_bytes,
|
||||
write_per_sec: disk_usage.written_bytes,
|
||||
|
||||
+1
-1
@@ -407,7 +407,7 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
|
||||
# Processes widget configuration
|
||||
#[processes]
|
||||
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%, Nice, Priority
|
||||
# PID, Name, CPU%, Mem%, Swap, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%, Nice, Priority
|
||||
#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%", "Priority"]
|
||||
|
||||
# The default sort column when bottom starts. Accepts any of the column names above.
|
||||
|
||||
@@ -225,6 +225,7 @@ fn create_collection_thread(
|
||||
let show_average_cpu = app_config_fields.show_average_cpu;
|
||||
let update_sleep = app_config_fields.update_rate;
|
||||
let get_process_threads = app_config_fields.get_process_threads;
|
||||
let get_process_swap = app_config_fields.get_process_swap;
|
||||
#[cfg(feature = "zfs")]
|
||||
let get_arc_free = app_config_fields.free_arc;
|
||||
let include_unmounted_disks =
|
||||
@@ -238,6 +239,7 @@ fn create_collection_thread(
|
||||
data_collector.set_unnormalized_cpu(unnormalized_cpu);
|
||||
data_collector.set_show_average_cpu(show_average_cpu);
|
||||
data_collector.set_get_process_threads(get_process_threads);
|
||||
data_collector.set_get_process_swap(get_process_swap);
|
||||
#[cfg(feature = "zfs")]
|
||||
data_collector.set_free_arc_mem(get_arc_free);
|
||||
data_collector.set_include_unmounted_disks(include_unmounted_disks);
|
||||
|
||||
@@ -481,6 +481,9 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
|
||||
flags.unnormalized_cpu,
|
||||
),
|
||||
get_process_threads: is_flag_enabled_in!(get_threads, args.process, config.processes),
|
||||
get_process_swap: proc_columns
|
||||
.as_ref()
|
||||
.is_some_and(|columns| columns.contains(&ProcWidgetColumn::Swap)),
|
||||
use_basic_mode,
|
||||
default_time_value,
|
||||
time_interval: get_time_interval(args, config, retention_ms)?,
|
||||
|
||||
@@ -96,12 +96,12 @@ mod test {
|
||||
fn valid_process_column_config() {
|
||||
#[cfg(unix)]
|
||||
let config = r#"
|
||||
columns = ["CPU%", "PiD", "user", "MEM", "virt", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state", "prioRity", "Nice"]
|
||||
columns = ["CPU%", "PiD", "user", "MEM", "virt", "swap", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state", "prioRity", "Nice"]
|
||||
"#;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let config = r#"
|
||||
columns = ["CPU%", "PiD", "user", "MEM", "virt", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state", "prioRity"]
|
||||
columns = ["CPU%", "PiD", "user", "MEM", "virt", "swap", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state", "prioRity"]
|
||||
"#;
|
||||
|
||||
let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap();
|
||||
@@ -113,6 +113,7 @@ mod test {
|
||||
ProcWidgetColumn::User,
|
||||
ProcWidgetColumn::Mem,
|
||||
ProcWidgetColumn::VirtualMem,
|
||||
ProcWidgetColumn::Swap,
|
||||
ProcWidgetColumn::TotalRead,
|
||||
ProcWidgetColumn::TotalWrite,
|
||||
ProcWidgetColumn::ReadPerSecond,
|
||||
|
||||
@@ -144,6 +144,7 @@ fn make_column(column: ProcColumn) -> SortColumn<ProcColumn> {
|
||||
MemValue => SortColumn::new(MemValue).default_descending(),
|
||||
MemPercent => SortColumn::new(MemPercent).default_descending(),
|
||||
VirtualMem => SortColumn::new(VirtualMem).default_descending(),
|
||||
Swap => SortColumn::new(Swap).default_descending(),
|
||||
Pid => SortColumn::new(Pid),
|
||||
Count => SortColumn::new(Count),
|
||||
Name => SortColumn::soft(Name, Some(0.3)),
|
||||
@@ -186,6 +187,7 @@ pub enum ProcWidgetColumn {
|
||||
Cpu,
|
||||
Mem,
|
||||
VirtualMem,
|
||||
Swap,
|
||||
ReadPerSecond,
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
@@ -333,6 +335,7 @@ impl ProcWidgetState {
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::VirtualMem => VirtualMem,
|
||||
ProcWidgetColumn::Swap => Swap,
|
||||
ProcWidgetColumn::ReadPerSecond => ReadPerSecond,
|
||||
ProcWidgetColumn::WritePerSecond => WritePerSecond,
|
||||
ProcWidgetColumn::TotalRead => TotalRead,
|
||||
@@ -391,6 +394,7 @@ impl ProcWidgetState {
|
||||
CpuPercent => ProcWidgetColumn::Cpu,
|
||||
MemValue | MemPercent => ProcWidgetColumn::Mem,
|
||||
VirtualMem => ProcWidgetColumn::VirtualMem,
|
||||
Swap => ProcWidgetColumn::Swap,
|
||||
Pid | Count => ProcWidgetColumn::PidOrCount,
|
||||
Name | Command => ProcWidgetColumn::ProcNameOrCommand,
|
||||
ReadPerSecond => ProcWidgetColumn::ReadPerSecond,
|
||||
@@ -840,6 +844,12 @@ impl ProcWidgetState {
|
||||
}
|
||||
}
|
||||
|
||||
pwd.swap_bytes = match (pwd.swap_bytes, process.swap_bytes) {
|
||||
(Some(a), Some(b)) => Some(a + b),
|
||||
(Some(a), None) | (None, Some(a)) => Some(a),
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
pwd.rps += process.read_per_sec;
|
||||
pwd.wps += process.write_per_sec;
|
||||
pwd.total_read += process.total_read;
|
||||
@@ -1183,6 +1193,7 @@ mod test {
|
||||
cpu_usage_percent: 0.0,
|
||||
mem_usage: MemUsage::Percent(1.1),
|
||||
virtual_mem: 100,
|
||||
swap_bytes: Some(100),
|
||||
rps: 0,
|
||||
wps: 0,
|
||||
total_read: 0,
|
||||
@@ -1213,6 +1224,7 @@ mod test {
|
||||
id: "B".into(),
|
||||
cpu_usage_percent: 1.1,
|
||||
mem_usage: MemUsage::Percent(2.2),
|
||||
swap_bytes: Some(200),
|
||||
..(a.clone())
|
||||
};
|
||||
|
||||
@@ -1222,6 +1234,7 @@ mod test {
|
||||
id: "C".into(),
|
||||
cpu_usage_percent: 2.2,
|
||||
mem_usage: MemUsage::Percent(0.0),
|
||||
swap_bytes: Some(50),
|
||||
..(a.clone())
|
||||
};
|
||||
|
||||
@@ -1231,8 +1244,10 @@ mod test {
|
||||
id: "D".into(),
|
||||
cpu_usage_percent: 0.0,
|
||||
mem_usage: MemUsage::Percent(0.0),
|
||||
swap_bytes: None,
|
||||
..(a.clone())
|
||||
};
|
||||
|
||||
let mut data = vec![d.clone(), b.clone(), c.clone(), a.clone()];
|
||||
|
||||
// Assume we had sorted over by pid.
|
||||
@@ -1265,6 +1280,20 @@ mod test {
|
||||
[&c, &d, &a, &b].iter().map(|d| d.pid).collect::<Vec<_>>(),
|
||||
data.iter().map(|d| d.pid).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
data.sort_by_key(|p| p.pid);
|
||||
sort_skip_pid_asc(&ProcColumn::Swap, &mut data, SortOrder::Descending);
|
||||
assert_eq!(
|
||||
vec![2, 1, 3, 4],
|
||||
data.iter().map(|process| process.pid).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
data.sort_by_key(|p| p.pid);
|
||||
sort_skip_pid_asc(&ProcColumn::Swap, &mut data, SortOrder::Ascending);
|
||||
assert_eq!(
|
||||
vec![4, 3, 1, 2],
|
||||
data.iter().map(|process| process.pid).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
fn get_columns(table: &ProcessTable) -> Vec<ProcColumn> {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub enum ProcColumn {
|
||||
MemValue,
|
||||
MemPercent,
|
||||
VirtualMem,
|
||||
Swap,
|
||||
Pid,
|
||||
Count,
|
||||
Name,
|
||||
@@ -54,6 +55,7 @@ impl ProcColumn {
|
||||
// TODO: Change this
|
||||
ProcColumn::MemValue | ProcColumn::MemPercent => &["Mem", "Mem%", "Memory", "Memory%"],
|
||||
ProcColumn::VirtualMem => &["Virt", "Virtual", "VirtMem", "Virtual Memory"],
|
||||
ProcColumn::Swap => &["Swap"],
|
||||
ProcColumn::ReadPerSecond => &["R/s", "Read", "Rps"],
|
||||
ProcColumn::WritePerSecond => &["W/s", "Write", "Wps"],
|
||||
ProcColumn::TotalRead => &["T.Read", "TRead", "Total Read"],
|
||||
@@ -80,6 +82,7 @@ impl ColumnHeader for ProcColumn {
|
||||
ProcColumn::MemValue => "Mem",
|
||||
ProcColumn::MemPercent => "Mem%",
|
||||
ProcColumn::VirtualMem => "Virt",
|
||||
ProcColumn::Swap => "Swap",
|
||||
ProcColumn::Pid => "PID",
|
||||
ProcColumn::Count => "Count",
|
||||
ProcColumn::Name => "Name",
|
||||
@@ -136,6 +139,9 @@ impl SortsRow for ProcColumn {
|
||||
ProcColumn::VirtualMem => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.virtual_mem, &b.virtual_mem));
|
||||
}
|
||||
ProcColumn::Swap => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.swap_bytes, &b.swap_bytes));
|
||||
}
|
||||
ProcColumn::Pid => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(a.pid, b.pid));
|
||||
}
|
||||
@@ -209,6 +215,7 @@ impl ProcColumn {
|
||||
"cpu%" => Some(ProcColumn::CpuPercent),
|
||||
"mem" | "mem%" | "memory" | "memory%" => Some(ProcColumn::MemPercent),
|
||||
"virt" | "virtual" | "virtmem" | "virtual memory" => Some(ProcColumn::VirtualMem),
|
||||
"swap" => Some(ProcColumn::Swap),
|
||||
"pid" => Some(ProcColumn::Pid),
|
||||
"count" => Some(ProcColumn::Count),
|
||||
"name" => Some(ProcColumn::Name),
|
||||
@@ -251,6 +258,7 @@ impl From<&ProcColumn> for ProcWidgetColumn {
|
||||
ProcColumn::CpuPercent => ProcWidgetColumn::Cpu,
|
||||
ProcColumn::MemPercent | ProcColumn::MemValue => ProcWidgetColumn::Mem,
|
||||
ProcColumn::VirtualMem => ProcWidgetColumn::VirtualMem,
|
||||
ProcColumn::Swap => ProcWidgetColumn::Swap,
|
||||
ProcColumn::ReadPerSecond => ProcWidgetColumn::ReadPerSecond,
|
||||
ProcColumn::WritePerSecond => ProcWidgetColumn::WritePerSecond,
|
||||
ProcColumn::TotalRead => ProcWidgetColumn::TotalRead,
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
Painter,
|
||||
components::data_table::{DataTableColumn, DataToCell},
|
||||
},
|
||||
collection::processes::{Pid, ProcessHarvest},
|
||||
collection::processes::{Bytes, Pid, ProcessHarvest},
|
||||
dec_bytes_per_second_string,
|
||||
utils::data_units::{GIBI_LIMIT, GIGA_LIMIT, get_binary_bytes, get_decimal_bytes},
|
||||
};
|
||||
@@ -219,6 +219,7 @@ pub struct ProcWidgetData {
|
||||
pub cpu_usage_percent: f32,
|
||||
pub mem_usage: MemUsage,
|
||||
pub virtual_mem: u64,
|
||||
pub swap_bytes: Option<Bytes>,
|
||||
pub rps: u64,
|
||||
pub wps: u64,
|
||||
pub total_read: u64,
|
||||
@@ -265,6 +266,7 @@ impl ProcWidgetData {
|
||||
cpu_usage_percent: process.cpu_usage_percent,
|
||||
mem_usage,
|
||||
virtual_mem: process.virtual_mem,
|
||||
swap_bytes: process.swap_bytes,
|
||||
rps: process.read_per_sec,
|
||||
wps: process.write_per_sec,
|
||||
total_read: process.total_read,
|
||||
@@ -309,6 +311,11 @@ impl ProcWidgetData {
|
||||
self.total_read += other.total_read;
|
||||
self.total_write += other.total_write;
|
||||
self.time = self.time.max(other.time);
|
||||
self.swap_bytes = match (self.swap_bytes, other.swap_bytes) {
|
||||
(Some(a), Some(b)) => Some(a + b),
|
||||
(Some(a), None) | (None, Some(a)) => Some(a),
|
||||
(None, None) => None,
|
||||
};
|
||||
#[cfg(feature = "gpu")]
|
||||
{
|
||||
self.gpu_mem_usage = self.gpu_mem_usage + other.gpu_mem_usage;
|
||||
@@ -324,6 +331,10 @@ impl ProcWidgetData {
|
||||
ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent),
|
||||
ProcColumn::MemValue | ProcColumn::MemPercent => self.mem_usage.to_string(),
|
||||
ProcColumn::VirtualMem => binary_byte_string(self.virtual_mem),
|
||||
ProcColumn::Swap => self
|
||||
.swap_bytes
|
||||
.map(binary_byte_string)
|
||||
.unwrap_or_else(|| "N/A".to_string()),
|
||||
ProcColumn::Pid => self.pid.to_string(),
|
||||
ProcColumn::Count => self.num_similar.to_string(),
|
||||
ProcColumn::Name | ProcColumn::Command => self.id.to_prefixed_string(),
|
||||
@@ -360,6 +371,11 @@ impl DataToCell<ProcColumn> for ProcWidgetData {
|
||||
ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent).into(),
|
||||
ProcColumn::MemValue | ProcColumn::MemPercent => self.mem_usage.to_string().into(),
|
||||
ProcColumn::VirtualMem => binary_byte_string(self.virtual_mem).into(),
|
||||
ProcColumn::Swap => self
|
||||
.swap_bytes
|
||||
.map(binary_byte_string)
|
||||
.unwrap_or_else(|| "N/A".to_string())
|
||||
.into(),
|
||||
ProcColumn::Pid => self.pid.to_string().into(),
|
||||
ProcColumn::Count => self.num_similar.to_string().into(),
|
||||
ProcColumn::Name | ProcColumn::Command => self.id.to_prefixed_string().into(),
|
||||
@@ -487,4 +503,30 @@ mod test {
|
||||
"10.4TiB".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_addition() {
|
||||
let process = ProcessHarvest {
|
||||
swap_bytes: Some(100),
|
||||
..Default::default()
|
||||
};
|
||||
let other = ProcessHarvest {
|
||||
swap_bytes: Some(200),
|
||||
..Default::default()
|
||||
};
|
||||
let mut data = ProcWidgetData::from_data(&process, false, false);
|
||||
let other = ProcWidgetData::from_data(&other, false, false);
|
||||
|
||||
data.add(&other);
|
||||
assert_eq!(data.swap_bytes, Some(300));
|
||||
|
||||
let unknown = ProcWidgetData::from_data(&ProcessHarvest::default(), false, false);
|
||||
data.add(&unknown);
|
||||
assert_eq!(data.swap_bytes, Some(300));
|
||||
|
||||
let known = ProcWidgetData::from_data(&process, false, false);
|
||||
let mut unknown = ProcWidgetData::from_data(&ProcessHarvest::default(), false, false);
|
||||
unknown.add(&known);
|
||||
assert_eq!(unknown.swap_bytes, Some(100));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user