feature: Add gpu proc info (#1276)

* Add gpu util, power and procs.

Consolidated gpu calls into `data_harvester`.

Changed config flag from `enable_gpu_memory` to `enable_gpu`.

Added GPU utilization to the cpu widget.

Added GPU process memory usage and utilization percentage to the proc widget.
Added key binds for gpu process toggling.

Added GPU power usage to the battery widget.
Added bounds check to battery widget header.
Show battery widget header when `gpu_enable`.

Added feature flag `legacy-functions` to `nvml-wrapper`.

updated config file(s).
updated help text.
updated docs.

* Code Review:

Remove GPU util from cpu widget
Remove GPU power from battery widget
Use reference for gpu widgets_to_harvest
Extract match arm to function for feature gate

* Code Review: add gmem% toggle

* Do not poll gpu temp when filtered

* Code Review Two Changes:

adjust doc wordings
remove extra references
remove extra widget harvest checks
init proc gpu values
use convert_temp_unit for gpu temp
This commit is contained in:
Justin Martin 2023-11-19 23:54:15 -05:00 committed by GitHub
parent 5df66006d8
commit e4a6e751ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 574 additions and 233 deletions

View File

@ -93,7 +93,7 @@ indexmap = "2.0.0"
itertools = "0.11.0" itertools = "0.11.0"
kstring = { version = "2.0.0", features = ["arc"] } kstring = { version = "2.0.0", features = ["arc"] }
log = { version = "0.4.20", optional = true } log = { version = "0.4.20", optional = true }
nvml-wrapper = { version = "0.9.0", optional = true } nvml-wrapper = { version = "0.9.0", optional = true, features = ["legacy-functions"] }
once_cell = "1.18.0" once_cell = "1.18.0"
regex = "1.9.4" regex = "1.9.4"
serde = { version = "=1.0.188 ", features = ["derive"] } serde = { version = "=1.0.188 ", features = ["derive"] }

View File

@ -20,7 +20,7 @@ see information on these flags by running `btm -h`, or run `btm --help` to displ
| --disable_click | Disables mouse clicks. | | --disable_click | Disables mouse clicks. |
| -m, --dot_marker | Uses a dot marker for graphs. | | -m, --dot_marker | Uses a dot marker for graphs. |
| --enable_cache_memory | Enable collecting and displaying cache and buffer memory. | | --enable_cache_memory | Enable collecting and displaying cache and buffer memory. |
| --enable_gpu_memory | Enable collecting and displaying GPU memory usage. | | --enable_gpu | Enable collecting and displaying GPU usage. |
| -e, --expanded | Expand the default widget upon starting the app. | | -e, --expanded | Expand the default widget upon starting the app. |
| -f, --fahrenheit | Sets the temperature type to Fahrenheit. | | -f, --fahrenheit | Sets the temperature type to Fahrenheit. |
| -g, --group | Groups processes with the same name by default. | | -g, --group | Groups processes with the same name by default. |

View File

@ -38,7 +38,7 @@ each time:
| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. | | `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. |
| `network_use_bytes` | Boolean | Displays the network widget using bytes. | | `network_use_bytes` | Boolean | Displays the network widget using bytes. |
| `network_use_log` | Boolean | Displays the network widget with a log scale. | | `network_use_log` | Boolean | Displays the network widget with a log scale. |
| `enable_gpu_memory` | Boolean | Shows the GPU memory widget. | | `enable_gpu` | Boolean | Shows the GPU widgets. |
| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. | | `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. |
| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. | | `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. |
| `expanded_on_startup` | Boolean | Expand the default widget upon starting the app. | | `expanded_on_startup` | Boolean | Expand the default widget upon starting the app. |

View File

@ -7,5 +7,5 @@ You can configure which columns are shown by the process widget by setting the `
```toml ```toml
[processes] [processes]
# Pick which columns you want to use in any order. # Pick which columns you want to use in any order.
columns = ["cpu%", "mem%", "pid", "name", "read", "write", "tread", "twrite", "state", "user", "time"] columns = ["cpu%", "mem%", "pid", "name", "read", "write", "tread", "twrite", "state", "user", "time", "gmem%", "gpu%"]
``` ```

View File

@ -13,7 +13,7 @@ If the total RAM or swap available is 0, then it is automatically hidden from th
One can also adjust the displayed time range through either the keyboard or mouse, with a range of 30s to 600s. One can also adjust the displayed time range through either the keyboard or mouse, with a range of 30s to 600s.
This widget can also be configured to display Nvidia GPU memory usage (`--enable_gpu_memory`) or cache memory usage (`--enable_cache_memory`). This widget can also be configured to display Nvidia GPU memory usage (`--enable_gpu` on Linux/Windows) or cache memory usage (`--enable_cache_memory`).
## Key bindings ## Key bindings

View File

@ -32,6 +32,12 @@ It can also additionally display the following columns:
- Process running time - Process running time
With the feature flag (`--enable_gpu` on Linux/Windows) and gpu process columns enabled in the configuration:
- GPU memory use percentage
- GPU core utilization percentage
See [the processes configuration page](../../configuration/config-file/processes.md) on how to customize which columns See [the processes configuration page](../../configuration/config-file/processes.md) on how to customize which columns
are shown. are shown.
@ -147,6 +153,9 @@ Note all keywords are case-insensitive. To search for a process/command that col
| `user` | `user=root` | Matches by user; supports regex | | `user` | `user=root` | Matches by user; supports regex |
| `state` | `state=running` | Matches by state; supports regex | | `state` | `state=running` | Matches by state; supports regex |
| `()` | `(<COND 1> AND <COND 2>) OR <COND 3>` | Group together a condition | | `()` | `(<COND 1> AND <COND 2>) OR <COND 3>` | Group together a condition |
| `gmem` | `gmem > 1000 b` | Matches the gpu memory column in terms of bytes; supports comparison operators |
| `gmem%` | `gmem% < 0.5` | Matches the gpu memory column in terms of percent; supports comparison operators|
| `gpu%` | `gpu% > 0` | Matches the gpu usage column in terms of percent; supports comparison operators |
#### Comparison operators #### Comparison operators
@ -207,6 +216,8 @@ Note that key bindings are generally case-sensitive.
| ++I++ | Invert the current sort | | ++I++ | Invert the current sort |
| ++"%"++ | Toggle between values and percentages for memory usage | | ++"%"++ | Toggle between values and percentages for memory usage |
| ++t++ , ++f5++ | Toggle tree mode | | ++t++ , ++f5++ | Toggle tree mode |
| ++M++ | Sort by gpu memory usage, press again to reverse sorting order |
| ++C++ | Sort by gpu usage, press again to reverse sorting order |
### Sort sub-widget ### Sort sub-widget

View File

@ -10,6 +10,8 @@ The temperature widget provides a table of temperature sensors and their current
The temperature widget provides the sensor name as well as its current temperature. The temperature widget provides the sensor name as well as its current temperature.
This widget can also be configured to display Nvidia GPU temperatures (`--enable_gpu` on Linux/Windows).
## Key bindings ## Key bindings
Note that key bindings are generally case-sensitive. Note that key bindings are generally case-sensitive.

View File

@ -74,7 +74,7 @@
# Hides advanced options to stop a process on Unix-like systems. # Hides advanced options to stop a process on Unix-like systems.
#disable_advanced_kill = false #disable_advanced_kill = false
# Shows GPU(s) memory # Shows GPU(s) memory
#enable_gpu_memory = false #enable_gpu = false
# Shows cache and buffer memory # Shows cache and buffer memory
#enable_cache_memory = false #enable_cache_memory = false
# How much data is stored at once in terms of time. # How much data is stored at once in terms of time.
@ -83,7 +83,7 @@
# These are flags around the process widget. # These are flags around the process widget.
#[processes] #[processes]
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State"] #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
@ -103,7 +103,7 @@
#swap_color="LightYellow" #swap_color="LightYellow"
# Represents the colour ARC will use in the memory legend and graph. # Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan" #arc_color="LightCyan"
# Represents the colour the GPU will use in the memory legend and graph. # Represents the colour the GPU will use in the legend and graph.
#gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] #gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"]
# Represents the colour rx will use in the network legend and graph. # Represents the colour rx will use in the network legend and graph.
#rx_color="LightCyan" #rx_color="LightCyan"

View File

@ -60,7 +60,7 @@ pub struct AppConfigFields {
pub use_old_network_legend: bool, pub use_old_network_legend: bool,
pub table_gap: u16, pub table_gap: u16,
pub disable_click: bool, pub disable_click: bool,
pub enable_gpu_memory: bool, pub enable_gpu: bool,
pub enable_cache_memory: bool, pub enable_cache_memory: bool,
pub show_table_scroll_position: bool, pub show_table_scroll_position: bool,
pub is_advanced_kill: bool, pub is_advanced_kill: bool,
@ -1277,6 +1277,30 @@ impl App {
disk.set_index(3); disk.set_index(3);
} }
} }
#[cfg(feature = "gpu")]
'M' => {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self
.states
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetColumn::GpuMem);
}
}
}
#[cfg(feature = "gpu")]
'C' => {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self
.states
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetColumn::GpuUtil);
}
}
}
'?' => { '?' => {
self.help_dialog_state.is_showing_help = true; self.help_dialog_state.is_showing_help = true;
self.is_force_redraw = true; self.is_force_redraw = true;
@ -2702,7 +2726,14 @@ impl App {
{ {
if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y) if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y)
{ {
battery_widget_state.currently_selected_battery_index = itx; if itx >= self.converted_data.battery_data.len() {
// range check to keep within current data
battery_widget_state.currently_selected_battery_index =
self.converted_data.battery_data.len() - 1;
} else {
battery_widget_state.currently_selected_battery_index =
itx;
}
break; break;
} }
} }

View File

@ -2,7 +2,7 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[cfg(target_os = "linux")] #[cfg(any(target_os = "linux", feature = "gpu"))]
use hashbrown::HashMap; use hashbrown::HashMap;
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
use starship_battery::{Battery, Manager}; use starship_battery::{Battery, Manager};
@ -125,6 +125,11 @@ pub struct DataCollector {
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
user_table: processes::UserTable, user_table: processes::UserTable,
#[cfg(feature = "gpu")]
gpu_pids: Option<Vec<HashMap<u32, (u64, u32)>>>,
#[cfg(feature = "gpu")]
gpus_total_mem: Option<u64>,
} }
impl DataCollector { impl DataCollector {
@ -153,6 +158,10 @@ impl DataCollector {
filters, filters,
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
user_table: Default::default(), user_table: Default::default(),
#[cfg(feature = "gpu")]
gpu_pids: None,
#[cfg(feature = "gpu")]
gpus_total_mem: None,
} }
} }
@ -288,18 +297,47 @@ impl DataCollector {
self.update_cpu_usage(); self.update_cpu_usage();
self.update_memory_usage(); self.update_memory_usage();
self.update_processes();
self.update_temps(); self.update_temps();
#[cfg(feature = "battery")]
self.update_batteries();
#[cfg(feature = "gpu")]
self.update_gpus(); // update_gpus before procs for gpu_pids but after temps for appending
self.update_processes();
self.update_network_usage(); self.update_network_usage();
self.update_disks(); self.update_disks();
#[cfg(feature = "battery")]
self.update_batteries();
// Update times for future reference. // Update times for future reference.
self.last_collection_time = self.data.collection_time; self.last_collection_time = self.data.collection_time;
} }
#[cfg(feature = "gpu")]
#[inline]
fn update_gpus(&mut self) {
if self.widgets_to_harvest.use_gpu {
#[cfg(feature = "nvidia")]
if let Some(data) = nvidia::get_nvidia_vecs(
&self.temperature_type,
&self.filters.temp_filter,
&self.widgets_to_harvest,
) {
if let Some(mut temp) = data.temperature {
if let Some(sensors) = &mut self.data.temperature_sensors {
sensors.append(&mut temp);
} else {
self.data.temperature_sensors = Some(temp);
}
}
if let Some(mem) = data.memory {
self.data.gpu = Some(mem);
}
if let Some(proc) = data.procs {
self.gpu_pids = Some(proc.1);
self.gpus_total_mem = Some(proc.0);
}
}
}
}
#[inline] #[inline]
fn update_cpu_usage(&mut self) { fn update_cpu_usage(&mut self) {
if self.widgets_to_harvest.use_cpu { if self.widgets_to_harvest.use_cpu {
@ -365,11 +403,6 @@ impl DataCollector {
{ {
self.data.arc = memory::arc::get_arc_usage(); self.data.arc = memory::arc::get_arc_usage();
} }
#[cfg(feature = "gpu")]
if self.widgets_to_harvest.use_gpu {
self.data.gpu = memory::gpu::get_gpu_mem_usage();
}
} }
} }

View File

@ -1,8 +1,4 @@
//! Data collection for CPU usage and load average. //! Data collection for CPU usage and load average.
//!
//! For CPU usage, Linux, macOS, and Windows are handled by Heim, FreeBSD by sysinfo.
//!
//! For load average, macOS and Linux are supported through Heim, FreeBSD by sysinfo.
pub mod sysinfo; pub mod sysinfo;
pub use self::sysinfo::*; pub use self::sysinfo::*;

View File

@ -15,9 +15,6 @@ cfg_if::cfg_if! {
} }
} }
#[cfg(feature = "gpu")]
pub mod gpu;
#[cfg(feature = "zfs")] #[cfg(feature = "zfs")]
pub mod arc; pub mod arc;

View File

@ -1,47 +0,0 @@
use super::MemHarvest;
/// Return GPU memory usage.
#[cfg(feature = "gpu")]
pub(crate) fn get_gpu_mem_usage() -> Option<Vec<(String, MemHarvest)>> {
// As we add more support, expand on this.
#[cfg(feature = "nvidia")]
get_nvidia_mem_usage()
}
/// Returns the memory usage of NVIDIA cards.
#[inline]
#[cfg(feature = "nvidia")]
fn get_nvidia_mem_usage() -> Option<Vec<(String, MemHarvest)>> {
use crate::data_harvester::nvidia::NVML_DATA;
if let Ok(nvml) = &*NVML_DATA {
if let Ok(num_gpu) = nvml.device_count() {
let mut results = Vec::with_capacity(num_gpu as usize);
for i in 0..num_gpu {
if let Ok(device) = nvml.device_by_index(i) {
if let (Ok(name), Ok(mem)) = (device.name(), device.memory_info()) {
// add device memory in bytes
results.push((
name,
MemHarvest {
total_bytes: mem.total,
used_bytes: mem.used,
use_percent: if mem.total == 0 {
None
} else {
Some(mem.used as f64 / mem.total as f64 * 100.0)
},
},
));
}
}
}
Some(results)
} else {
None
}
} else {
None
}
}

View File

@ -1,4 +1,149 @@
use hashbrown::HashMap;
use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
use nvml_wrapper::enums::device::UsedGpuMemory;
use nvml_wrapper::{error::NvmlError, Nvml}; use nvml_wrapper::{error::NvmlError, Nvml};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::app::Filter;
use crate::app::layout_manager::UsedWidgets;
use crate::data_harvester::memory::MemHarvest;
use crate::data_harvester::temperature::{
convert_temp_unit, is_temp_filtered, TempHarvest, TemperatureType,
};
pub static NVML_DATA: Lazy<Result<Nvml, NvmlError>> = Lazy::new(Nvml::init); pub static NVML_DATA: Lazy<Result<Nvml, NvmlError>> = Lazy::new(Nvml::init);
pub struct GpusData {
pub memory: Option<Vec<(String, MemHarvest)>>,
pub temperature: Option<Vec<TempHarvest>>,
pub procs: Option<(u64, Vec<HashMap<u32, (u64, u32)>>)>,
}
/// Returns the GPU data from NVIDIA cards.
#[inline]
pub fn get_nvidia_vecs(
temp_type: &TemperatureType, filter: &Option<Filter>, widgets_to_harvest: &UsedWidgets,
) -> Option<GpusData> {
if let Ok(nvml) = &*NVML_DATA {
if let Ok(num_gpu) = nvml.device_count() {
let mut temp_vec = Vec::with_capacity(num_gpu as usize);
let mut mem_vec = Vec::with_capacity(num_gpu as usize);
let mut proc_vec = Vec::with_capacity(num_gpu as usize);
let mut total_mem = 0;
for i in 0..num_gpu {
if let Ok(device) = nvml.device_by_index(i) {
if let Ok(name) = device.name() {
if widgets_to_harvest.use_mem {
if let Ok(mem) = device.memory_info() {
mem_vec.push((
name.clone(),
MemHarvest {
total_bytes: mem.total,
used_bytes: mem.used,
use_percent: if mem.total == 0 {
None
} else {
Some(mem.used as f64 / mem.total as f64 * 100.0)
},
},
));
}
}
if widgets_to_harvest.use_temp && is_temp_filtered(filter, &name) {
if let Ok(temperature) = device.temperature(TemperatureSensor::Gpu) {
let temperature = temperature as f32;
let temperature = convert_temp_unit(temperature, temp_type);
temp_vec.push(TempHarvest {
name: name.clone(),
temperature,
});
}
}
}
if widgets_to_harvest.use_proc {
let mut procs = HashMap::new();
if let Ok(gpu_procs) = device.process_utilization_stats(None) {
for proc in gpu_procs {
let pid = proc.pid;
let gpu_util = proc.sm_util + proc.enc_util + proc.dec_util;
procs.insert(pid, (0, gpu_util));
}
}
if let Ok(compute_procs) = device.running_compute_processes() {
for proc in compute_procs {
let pid = proc.pid;
let gpu_mem = match proc.used_gpu_memory {
UsedGpuMemory::Used(val) => val,
UsedGpuMemory::Unavailable => 0,
};
if let Some(prev) = procs.get(&pid) {
procs.insert(pid, (gpu_mem, prev.1));
} else {
procs.insert(pid, (gpu_mem, 0));
}
}
}
// Use the legacy API too but prefer newer API results
if let Ok(graphics_procs) = device.running_graphics_processes_v2() {
for proc in graphics_procs {
let pid = proc.pid;
let gpu_mem = match proc.used_gpu_memory {
UsedGpuMemory::Used(val) => val,
UsedGpuMemory::Unavailable => 0,
};
if let Some(prev) = procs.get(&pid) {
procs.insert(pid, (gpu_mem, prev.1));
} else {
procs.insert(pid, (gpu_mem, 0));
}
}
}
if let Ok(graphics_procs) = device.running_graphics_processes() {
for proc in graphics_procs {
let pid = proc.pid;
let gpu_mem = match proc.used_gpu_memory {
UsedGpuMemory::Used(val) => val,
UsedGpuMemory::Unavailable => 0,
};
if let Some(prev) = procs.get(&pid) {
procs.insert(pid, (gpu_mem, prev.1));
} else {
procs.insert(pid, (gpu_mem, 0));
}
}
}
if !procs.is_empty() {
proc_vec.push(procs);
}
// running total for proc %
if let Ok(mem) = device.memory_info() {
total_mem += mem.total;
}
}
}
}
Some(GpusData {
memory: if !mem_vec.is_empty() {
Some(mem_vec)
} else {
None
},
temperature: if !temp_vec.is_empty() {
Some(temp_vec)
} else {
None
},
procs: if !proc_vec.is_empty() {
Some((total_mem, proc_vec))
} else {
None
},
})
} else {
None
}
} else {
None
}
}

View File

@ -83,6 +83,18 @@ pub struct ProcessHarvest {
/// This is the process' user. /// This is the process' user.
pub user: Cow<'static, str>, pub user: Cow<'static, str>,
/// Gpu memory usage as bytes.
#[cfg(feature = "gpu")]
pub gpu_mem: u64,
/// Gpu memory usage as percentage.
#[cfg(feature = "gpu")]
pub gpu_mem_percent: f32,
/// Gpu utilization as a percentage.
#[cfg(feature = "gpu")]
pub gpu_util: u32,
// TODO: Additional fields // TODO: Additional fields
// pub rss_kb: u64, // pub rss_kb: u64,
// pub virt_kb: u64, // pub virt_kb: u64,
@ -98,6 +110,12 @@ impl ProcessHarvest {
self.total_read_bytes += rhs.total_read_bytes; self.total_read_bytes += rhs.total_read_bytes;
self.total_write_bytes += rhs.total_write_bytes; self.total_write_bytes += rhs.total_write_bytes;
self.time += rhs.time; self.time += rhs.time;
#[cfg(feature = "gpu")]
{
self.gpu_mem += rhs.gpu_mem;
self.gpu_util += rhs.gpu_util;
self.gpu_mem_percent += rhs.gpu_mem_percent;
}
} }
} }

View File

@ -250,6 +250,12 @@ fn read_proc(
uid, uid,
user, user,
time, time,
#[cfg(feature = "gpu")]
gpu_mem: 0,
#[cfg(feature = "gpu")]
gpu_mem_percent: 0.0,
#[cfg(feature = "gpu")]
gpu_util: 0,
}, },
new_process_times, new_process_times,
)) ))
@ -326,7 +332,8 @@ pub(crate) fn linux_process_data(
let pid = process.pid; let pid = process.pid;
let prev_proc_details = pid_mapping.entry(pid).or_default(); let prev_proc_details = pid_mapping.entry(pid).or_default();
if let Ok((process_harvest, new_process_times)) = read_proc( #[allow(unused_mut)]
if let Ok((mut process_harvest, new_process_times)) = read_proc(
prev_proc_details, prev_proc_details,
process, process,
cpu_usage, cpu_usage,
@ -336,6 +343,23 @@ pub(crate) fn linux_process_data(
total_memory, total_memory,
user_table, user_table,
) { ) {
#[cfg(feature = "gpu")]
if let Some(gpus) = &collector.gpu_pids {
gpus.iter().for_each(|gpu| {
// add mem/util for all gpus to pid
if let Some((mem, util)) = gpu.get(&(pid as u32)) {
process_harvest.gpu_mem += mem;
process_harvest.gpu_util += util;
}
});
if let Some(gpu_total_mem) = &collector.gpus_total_mem {
process_harvest.gpu_mem_percent = (process_harvest.gpu_mem as f64
/ *gpu_total_mem as f64
* 100.0)
as f32;
}
}
prev_proc_details.cpu_time = new_process_times; prev_proc_details.cpu_time = new_process_times;
prev_proc_details.total_read_bytes = process_harvest.total_read_bytes; prev_proc_details.total_read_bytes = process_harvest.total_read_bytes;
prev_proc_details.total_write_bytes = process_harvest.total_write_bytes; prev_proc_details.total_write_bytes = process_harvest.total_write_bytes;

View File

@ -97,6 +97,12 @@ pub(crate) trait UnixProcessExt {
}) })
.unwrap_or_else(|| "N/A".into()), .unwrap_or_else(|| "N/A".into()),
time: Duration::from_secs(process_val.run_time()), time: Duration::from_secs(process_val.run_time()),
#[cfg(feature = "gpu")]
gpu_mem: 0,
#[cfg(feature = "gpu")]
gpu_mem_percent: 0.0,
#[cfg(feature = "gpu")]
gpu_util: 0,
}); });
} }

View File

@ -67,6 +67,26 @@ pub fn sysinfo_process_data(
let disk_usage = process_val.disk_usage(); let disk_usage = process_val.disk_usage();
let process_state = (process_val.status().to_string(), 'R'); let process_state = (process_val.status().to_string(), 'R');
#[cfg(feature = "gpu")]
let (gpu_mem, gpu_util, gpu_mem_percent) = {
let mut gpu_mem = 0;
let mut gpu_util = 0;
let mut gpu_mem_percent = 0.0;
if let Some(gpus) = &collector.gpu_pids {
gpus.iter().for_each(|gpu| {
// add mem/util for all gpus to pid
if let Some((mem, util)) = gpu.get(&process_val.pid().as_u32()) {
gpu_mem += mem;
gpu_util += util;
}
});
}
if let Some(gpu_total_mem) = &collector.gpus_total_mem {
gpu_mem_percent = (gpu_mem as f64 / *gpu_total_mem as f64 * 100.0) as f32;
}
(gpu_mem, gpu_util, gpu_mem_percent)
};
process_vector.push(ProcessHarvest { process_vector.push(ProcessHarvest {
pid: process_val.pid().as_u32() as _, pid: process_val.pid().as_u32() as _,
parent_pid: process_val.parent().map(|p| p.as_u32() as _), parent_pid: process_val.parent().map(|p| p.as_u32() as _),
@ -95,6 +115,12 @@ pub fn sysinfo_process_data(
} else { } else {
Duration::from_secs(process_val.run_time()) Duration::from_secs(process_val.run_time())
}, },
#[cfg(feature = "gpu")]
gpu_mem,
#[cfg(feature = "gpu")]
gpu_util,
#[cfg(feature = "gpu")]
gpu_mem_percent,
}); });
} }

View File

@ -13,9 +13,6 @@ cfg_if::cfg_if! {
} }
} }
#[cfg(feature = "nvidia")]
pub mod nvidia;
use crate::app::Filter; use crate::app::Filter;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@ -40,7 +37,15 @@ fn convert_celsius_to_fahrenheit(celsius: f32) -> f32 {
(celsius * (9.0 / 5.0)) + 32.0 (celsius * (9.0 / 5.0)) + 32.0
} }
fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool { pub fn convert_temp_unit(temp: f32, temp_type: &TemperatureType) -> f32 {
match temp_type {
TemperatureType::Celsius => temp,
TemperatureType::Kelvin => convert_celsius_to_kelvin(temp),
TemperatureType::Fahrenheit => convert_celsius_to_fahrenheit(temp),
}
}
pub fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool {
if let Some(filter) = filter { if let Some(filter) = filter {
let mut ret = filter.is_list_ignored; let mut ret = filter.is_list_ignored;
for r in &filter.list { for r in &filter.list {

View File

@ -9,10 +9,7 @@ use anyhow::Result;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use super::{is_temp_filtered, TempHarvest, TemperatureType}; use super::{is_temp_filtered, TempHarvest, TemperatureType};
use crate::app::{ use crate::app::{data_harvester::temperature::convert_temp_unit, Filter};
data_harvester::temperature::{convert_celsius_to_fahrenheit, convert_celsius_to_kelvin},
Filter,
};
const EMPTY_NAME: &str = "Unknown"; const EMPTY_NAME: &str = "Unknown";
@ -31,14 +28,6 @@ fn read_temp(path: &Path) -> Result<f32> {
/ 1_000.0) / 1_000.0)
} }
fn convert_temp_unit(temp: f32, temp_type: &TemperatureType) -> f32 {
match temp_type {
TemperatureType::Celsius => temp,
TemperatureType::Kelvin => convert_celsius_to_kelvin(temp),
TemperatureType::Fahrenheit => convert_celsius_to_fahrenheit(temp),
}
}
/// Get all candidates from hwmon and coretemp. It will also return the number of entries from hwmon. /// Get all candidates from hwmon and coretemp. It will also return the number of entries from hwmon.
fn get_hwmon_candidates() -> (HashSet<PathBuf>, usize) { fn get_hwmon_candidates() -> (HashSet<PathBuf>, usize) {
let mut dirs = HashSet::default(); let mut dirs = HashSet::default();
@ -359,11 +348,6 @@ pub fn get_temperature_data(
add_thermal_zone_temperatures(&mut results.temperatures, temp_type, filter); add_thermal_zone_temperatures(&mut results.temperatures, temp_type, filter);
} }
#[cfg(feature = "nvidia")]
{
super::nvidia::add_nvidia_data(&mut results.temperatures, temp_type, filter)?;
}
Ok(Some(results.temperatures)) Ok(Some(results.temperatures))
} }

View File

@ -1,40 +0,0 @@
use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
use super::{
convert_celsius_to_fahrenheit, convert_celsius_to_kelvin, is_temp_filtered, TempHarvest,
TemperatureType,
};
use crate::app::Filter;
use crate::data_harvester::nvidia::NVML_DATA;
use crate::utils::error;
pub fn add_nvidia_data(
temperature_vec: &mut Vec<TempHarvest>, temp_type: &TemperatureType, filter: &Option<Filter>,
) -> error::Result<()> {
if let Ok(nvml) = &*NVML_DATA {
if let Ok(gpu_num) = nvml.device_count() {
for i in 0..gpu_num {
if let Ok(device) = nvml.device_by_index(i) {
if let (Ok(name), Ok(temperature)) =
(device.name(), device.temperature(TemperatureSensor::Gpu))
{
if is_temp_filtered(filter, &name) {
let temperature = temperature as f32;
let temperature = match temp_type {
TemperatureType::Celsius => temperature,
TemperatureType::Kelvin => convert_celsius_to_kelvin(temperature),
TemperatureType::Fahrenheit => {
convert_celsius_to_fahrenheit(temperature)
}
};
temperature_vec.push(TempHarvest { name, temperature });
}
}
}
}
}
}
Ok(())
}

View File

@ -33,11 +33,6 @@ pub fn get_temperature_data(
} }
} }
#[cfg(feature = "nvidia")]
{
super::nvidia::add_nvidia_data(&mut temperature_vec, temp_type, filter)?;
}
// For RockPro64 boards on FreeBSD, they apparently use "hw.temperature" for sensors. // For RockPro64 boards on FreeBSD, they apparently use "hw.temperature" for sensors.
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
{ {

View File

@ -127,6 +127,44 @@ pub fn parse_query(
Ok(And { lhs, rhs }) Ok(And { lhs, rhs })
} }
#[inline]
fn process_prefix_units(query: &mut VecDeque<String>, value: &mut f64) {
// If no unit, assume base.
//
// Furthermore, base must be PEEKED at initially, and will
// require (likely) prefix_type specific checks
// Lastly, if it *is* a unit, remember to POP!
if let Some(potential_unit) = query.front() {
if potential_unit.eq_ignore_ascii_case("tb") {
*value *= TERA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("tib") {
*value *= TEBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("gb") {
*value *= GIGA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("gib") {
*value *= GIBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("mb") {
*value *= MEGA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("mib") {
*value *= MEBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("kb") {
*value *= KILO_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("kib") {
*value *= KIBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("b") {
query.pop_front();
}
}
}
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> { fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
if let Some(queue_top) = query.pop_front() { if let Some(queue_top) = query.pop_front() {
if inside_quotation { if inside_quotation {
@ -389,47 +427,11 @@ pub fn parse_query(
| PrefixType::Wps | PrefixType::Wps
| PrefixType::TRead | PrefixType::TRead
| PrefixType::TWrite => { | PrefixType::TWrite => {
// If no unit, assume base. process_prefix_units(query, &mut value);
// }
// Furthermore, base must be PEEKED at initially, and will #[cfg(feature = "gpu")]
// require (likely) prefix_type specific checks PrefixType::GMem => {
// Lastly, if it *is* a unit, remember to POP! process_prefix_units(query, &mut value);
if let Some(potential_unit) = query.front() {
if potential_unit.eq_ignore_ascii_case("tb") {
value *= TERA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("tib")
{
value *= TEBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("gb")
{
value *= GIGA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("gib")
{
value *= GIBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("mb")
{
value *= MEGA_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("mib")
{
value *= MEBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("kb")
{
value *= KILO_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("kib")
{
value *= KIBI_LIMIT_F64;
query.pop_front();
} else if potential_unit.eq_ignore_ascii_case("b") {
query.pop_front();
}
}
} }
_ => {} _ => {}
} }
@ -626,6 +628,12 @@ pub enum PrefixType {
State, State,
User, User,
Time, Time,
#[cfg(feature = "gpu")]
PGpu,
#[cfg(feature = "gpu")]
GMem,
#[cfg(feature = "gpu")]
PGMem,
__Nonexhaustive, __Nonexhaustive,
} }
@ -637,32 +645,41 @@ impl std::str::FromStr for PrefixType {
// TODO: Didn't add mem_bytes, total_read, and total_write // TODO: Didn't add mem_bytes, total_read, and total_write
// for now as it causes help to be clogged. // for now as it causes help to be clogged.
let result = if multi_eq_ignore_ascii_case!(s, "cpu" | "cpu%") {
PCpu
} else if multi_eq_ignore_ascii_case!(s, "mem" | "mem%") {
PMem
} else if multi_eq_ignore_ascii_case!(s, "memb") {
MemBytes
} else if multi_eq_ignore_ascii_case!(s, "read" | "r/s" | "rps") {
Rps
} else if multi_eq_ignore_ascii_case!(s, "write" | "w/s" | "wps") {
Wps
} else if multi_eq_ignore_ascii_case!(s, "tread" | "t.read") {
TRead
} else if multi_eq_ignore_ascii_case!(s, "twrite" | "t.write") {
TWrite
} else if multi_eq_ignore_ascii_case!(s, "pid") {
Pid
} else if multi_eq_ignore_ascii_case!(s, "state") {
State
} else if multi_eq_ignore_ascii_case!(s, "user") {
User
} else if multi_eq_ignore_ascii_case!(s, "time") {
Time
} else {
Name
};
let mut result = Name;
if multi_eq_ignore_ascii_case!(s, "cpu" | "cpu%") {
result = PCpu;
} else if multi_eq_ignore_ascii_case!(s, "mem" | "mem%") {
result = PMem;
} else if multi_eq_ignore_ascii_case!(s, "memb") {
result = MemBytes;
} else if multi_eq_ignore_ascii_case!(s, "read" | "r/s" | "rps") {
result = Rps;
} else if multi_eq_ignore_ascii_case!(s, "write" | "w/s" | "wps") {
result = Wps;
} else if multi_eq_ignore_ascii_case!(s, "tread" | "t.read") {
result = TRead;
} else if multi_eq_ignore_ascii_case!(s, "twrite" | "t.write") {
result = TWrite;
} else if multi_eq_ignore_ascii_case!(s, "pid") {
result = Pid;
} else if multi_eq_ignore_ascii_case!(s, "state") {
result = State;
} else if multi_eq_ignore_ascii_case!(s, "user") {
result = User;
} else if multi_eq_ignore_ascii_case!(s, "time") {
result = Time;
}
#[cfg(feature = "gpu")]
{
if multi_eq_ignore_ascii_case!(s, "gmem") {
result = GMem;
} else if multi_eq_ignore_ascii_case!(s, "gmem%") {
result = PGMem;
} else if multi_eq_ignore_ascii_case!(s, "gpu%") {
result = PGpu;
}
}
Ok(result) Ok(result)
} }
} }
@ -801,6 +818,24 @@ impl Prefix {
process.total_write_bytes as f64, process.total_write_bytes as f64,
numerical_query.value, numerical_query.value,
), ),
#[cfg(feature = "gpu")]
PrefixType::PGpu => matches_condition(
&numerical_query.condition,
process.gpu_util,
numerical_query.value,
),
#[cfg(feature = "gpu")]
PrefixType::GMem => matches_condition(
&numerical_query.condition,
process.gpu_mem as f64,
numerical_query.value,
),
#[cfg(feature = "gpu")]
PrefixType::PGMem => matches_condition(
&numerical_query.condition,
process.gpu_mem_percent,
numerical_query.value,
),
_ => true, _ => true,
}, },
ComparableQuery::Time(time_query) => match prefix_type { ComparableQuery::Time(time_query) => match prefix_type {

View File

@ -464,10 +464,10 @@ use CPU (3) as the default instead.
}, },
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
{ {
Arg::new("enable_gpu_memory") Arg::new("enable_gpu")
.long("enable_gpu_memory") .long("enable_gpu")
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.help("Enable collecting and displaying GPU memory usage.") .help("Enable collecting and displaying GPU usage.")
}, },
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {

View File

@ -327,7 +327,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [
"Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart",
]; ];
pub const PROCESS_HELP_TEXT: [&str; 15] = [ pub const PROCESS_HELP_TEXT: [&str; 17] = [
"3 - Process widget", "3 - Process widget",
"dd, F9 Kill the selected process", "dd, F9 Kill the selected process",
"c Sort by CPU usage, press again to reverse", "c Sort by CPU usage, press again to reverse",
@ -343,9 +343,11 @@ pub const PROCESS_HELP_TEXT: [&str; 15] = [
"t, F5 Toggle tree mode", "t, F5 Toggle tree mode",
"+, -, click Collapse/expand a branch while in tree mode", "+, -, click Collapse/expand a branch while in tree mode",
"click on header Sorts the entries by that column, click again to invert the sort", "click on header Sorts the entries by that column, click again to invert the sort",
"C Sort by GPU usage, press again to reverse",
"M Sort by GPU memory usage, press again to reverse",
]; ];
pub const SEARCH_HELP_TEXT: [&str; 48] = [ pub const SEARCH_HELP_TEXT: [&str; 51] = [
"4 - Process search widget", "4 - Process search widget",
"Esc Close the search widget (retains the filter)", "Esc Close the search widget (retains the filter)",
"Ctrl-a Skip to the start of the search query", "Ctrl-a Skip to the start of the search query",
@ -373,6 +375,9 @@ pub const SEARCH_HELP_TEXT: [&str; 48] = [
"twrite, t.write ex: twrite = 1", "twrite, t.write ex: twrite = 1",
"user ex: user = root", "user ex: user = root",
"state ex: state = running", "state ex: state = running",
"gpu% ex: gpu% < 4.2",
"gmem ex: gmem < 100 kb",
"gmem% ex: gmem% < 4.2",
"", "",
"Comparison operators:", "Comparison operators:",
"= ex: cpu = 1", "= ex: cpu = 1",
@ -581,8 +586,8 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
#network_use_log = false #network_use_log = false
# Hides advanced options to stop a process on Unix-like systems. # Hides advanced options to stop a process on Unix-like systems.
#disable_advanced_kill = false #disable_advanced_kill = false
# Shows GPU(s) memory # Shows GPU(s) information
#enable_gpu_memory = false #enable_gpu = false
# Shows cache and buffer memory # Shows cache and buffer memory
#enable_cache_memory = false #enable_cache_memory = false
# How much data is stored at once in terms of time. # How much data is stored at once in terms of time.
@ -591,7 +596,7 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
# These are flags around the process widget. # These are flags around the process widget.
#[processes] #[processes]
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State"] #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
@ -611,7 +616,7 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
#swap_color="LightYellow" #swap_color="LightYellow"
# Represents the colour ARC will use in the memory legend and graph. # Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan" #arc_color="LightCyan"
# Represents the colour the GPU will use in the memory legend and graph. # Represents the colour the GPU will use in the legend and graph.
#gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"] #gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"]
# Represents the colour rx will use in the network legend and graph. # Represents the colour rx will use in the network legend and graph.
#rx_color="LightCyan" #rx_color="LightCyan"

View File

@ -100,7 +100,7 @@ pub struct ConfigFlags {
network_use_bytes: Option<bool>, network_use_bytes: Option<bool>,
network_use_log: Option<bool>, network_use_log: Option<bool>,
network_use_binary_prefix: Option<bool>, network_use_binary_prefix: Option<bool>,
enable_gpu_memory: Option<bool>, enable_gpu: Option<bool>,
enable_cache_memory: Option<bool>, enable_cache_memory: Option<bool>,
retention: Option<StringOrNum>, retention: Option<StringOrNum>,
} }
@ -278,7 +278,7 @@ pub fn build_app(
use_old_network_legend: is_flag_enabled!(use_old_network_legend, matches, config), use_old_network_legend: is_flag_enabled!(use_old_network_legend, matches, config),
table_gap: u16::from(!(is_flag_enabled!(hide_table_gap, matches, config))), table_gap: u16::from(!(is_flag_enabled!(hide_table_gap, matches, config))),
disable_click: is_flag_enabled!(disable_click, matches, config), disable_click: is_flag_enabled!(disable_click, matches, config),
enable_gpu_memory: get_enable_gpu_memory(matches, config), enable_gpu: get_enable_gpu(matches, config),
enable_cache_memory: get_enable_cache_memory(matches, config), enable_cache_memory: get_enable_cache_memory(matches, config),
show_table_scroll_position: is_flag_enabled!(show_table_scroll_position, matches, config), show_table_scroll_position: is_flag_enabled!(show_table_scroll_position, matches, config),
is_advanced_kill, is_advanced_kill,
@ -433,7 +433,7 @@ pub fn build_app(
use_cpu: used_widget_set.get(&Cpu).is_some() || used_widget_set.get(&BasicCpu).is_some(), use_cpu: used_widget_set.get(&Cpu).is_some() || used_widget_set.get(&BasicCpu).is_some(),
use_mem, use_mem,
use_cache: use_mem && get_enable_cache_memory(matches, config), use_cache: use_mem && get_enable_cache_memory(matches, config),
use_gpu: use_mem && get_enable_gpu_memory(matches, config), use_gpu: use_mem && get_enable_gpu(matches, config),
use_net: used_widget_set.get(&Net).is_some() || used_widget_set.get(&BasicNet).is_some(), use_net: used_widget_set.get(&Net).is_some() || used_widget_set.get(&BasicNet).is_some(),
use_proc: used_widget_set.get(&Proc).is_some(), use_proc: used_widget_set.get(&Proc).is_some(),
use_disk: used_widget_set.get(&Disk).is_some(), use_disk: used_widget_set.get(&Disk).is_some(),
@ -742,14 +742,6 @@ fn get_default_widget_and_count(
fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool { fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
{ {
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
if batteries.count() == 0 {
return false;
}
}
}
if matches.get_flag("battery") { if matches.get_flag("battery") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
@ -757,20 +749,28 @@ fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
return battery; return battery;
} }
} }
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
if batteries.count() == 0 {
return false;
}
}
}
} }
false false
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_enable_gpu_memory(matches: &ArgMatches, config: &Config) -> bool { fn get_enable_gpu(matches: &ArgMatches, config: &Config) -> bool {
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
{ {
if matches.get_flag("enable_gpu_memory") { if matches.get_flag("enable_gpu") {
return true; return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(enable_gpu_memory) = flags.enable_gpu_memory { if let Some(enable_gpu) = flags.enable_gpu {
return enable_gpu_memory; return enable_gpu;
} }
} }
} }

View File

@ -91,6 +91,12 @@ fn make_column(column: ProcColumn) -> SortColumn<ProcColumn> {
User => SortColumn::soft(User, Some(0.05)), User => SortColumn::soft(User, Some(0.05)),
State => SortColumn::hard(State, 7), State => SortColumn::hard(State, 7),
Time => SortColumn::new(Time), Time => SortColumn::new(Time),
#[cfg(feature = "gpu")]
GpuMem => SortColumn::new(GpuMem).default_descending(),
#[cfg(feature = "gpu")]
GpuMemPercent => SortColumn::new(GpuMemPercent).default_descending(),
#[cfg(feature = "gpu")]
GpuUtilPercent => SortColumn::new(GpuUtilPercent).default_descending(),
} }
} }
@ -117,6 +123,10 @@ pub enum ProcWidgetColumn {
User, User,
State, State,
Time, Time,
#[cfg(feature = "gpu")]
GpuMem,
#[cfg(feature = "gpu")]
GpuUtil,
} }
impl<'de> Deserialize<'de> for ProcWidgetColumn { impl<'de> Deserialize<'de> for ProcWidgetColumn {
@ -140,6 +150,10 @@ impl<'de> Deserialize<'de> for ProcWidgetColumn {
"state" => Ok(ProcWidgetColumn::State), "state" => Ok(ProcWidgetColumn::State),
"user" => Ok(ProcWidgetColumn::User), "user" => Ok(ProcWidgetColumn::User),
"time" => Ok(ProcWidgetColumn::Time), "time" => Ok(ProcWidgetColumn::Time),
#[cfg(feature = "gpu")]
"gmem" | "gmem%" => Ok(ProcWidgetColumn::GpuMem),
#[cfg(feature = "gpu")]
"gpu%" => Ok(ProcWidgetColumn::GpuUtil),
_ => Err(Error::custom("doesn't match any column type")), _ => Err(Error::custom("doesn't match any column type")),
} }
} }
@ -277,6 +291,16 @@ impl ProcWidgetState {
ProcWidgetColumn::User => User, ProcWidgetColumn::User => User,
ProcWidgetColumn::State => State, ProcWidgetColumn::State => State,
ProcWidgetColumn::Time => Time, ProcWidgetColumn::Time => Time,
#[cfg(feature = "gpu")]
ProcWidgetColumn::GpuMem => {
if mem_vals {
GpuMem
} else {
GpuMemPercent
}
}
#[cfg(feature = "gpu")]
ProcWidgetColumn::GpuUtil => GpuUtilPercent,
}; };
make_column(col) make_column(col)
@ -318,6 +342,10 @@ impl ProcWidgetState {
State => ProcWidgetColumn::State, State => ProcWidgetColumn::State,
User => ProcWidgetColumn::User, User => ProcWidgetColumn::User,
Time => ProcWidgetColumn::Time, Time => ProcWidgetColumn::Time,
#[cfg(feature = "gpu")]
GpuMem | GpuMemPercent => ProcWidgetColumn::GpuMem,
#[cfg(feature = "gpu")]
GpuUtilPercent => ProcWidgetColumn::GpuUtil,
} }
}) })
.collect::<IndexSet<_>>(); .collect::<IndexSet<_>>();
@ -743,6 +771,23 @@ impl ProcWidgetState {
_ => unreachable!(), _ => unreachable!(),
} }
self.sort_table.set_data(self.column_text());
self.force_data_update();
}
}
#[cfg(feature = "gpu")]
if let Some(index) = self.column_mapping.get_index_of(&ProcWidgetColumn::GpuMem) {
if let Some(mem) = self.get_mut_proc_col(index) {
match mem {
ProcColumn::GpuMem => {
*mem = ProcColumn::GpuMemPercent;
}
ProcColumn::GpuMemPercent => {
*mem = ProcColumn::GpuMem;
}
_ => unreachable!(),
}
self.sort_table.set_data(self.column_text()); self.sort_table.set_data(self.column_text());
self.force_data_update(); self.force_data_update();
} }
@ -1029,6 +1074,10 @@ mod test {
num_similar: 0, num_similar: 0,
disabled: false, disabled: false,
time: Duration::from_secs(0), time: Duration::from_secs(0),
#[cfg(feature = "gpu")]
gpu_mem_usage: MemUsage::Percent(1.1),
#[cfg(feature = "gpu")]
gpu_usage: 0,
}; };
let b = ProcWidgetData { let b = ProcWidgetData {

View File

@ -24,6 +24,12 @@ pub enum ProcColumn {
State, State,
User, User,
Time, Time,
#[cfg(feature = "gpu")]
GpuMem,
#[cfg(feature = "gpu")]
GpuMemPercent,
#[cfg(feature = "gpu")]
GpuUtilPercent,
} }
impl<'de> Deserialize<'de> for ProcColumn { impl<'de> Deserialize<'de> for ProcColumn {
@ -47,6 +53,12 @@ impl<'de> Deserialize<'de> for ProcColumn {
"state" => Ok(ProcColumn::State), "state" => Ok(ProcColumn::State),
"user" => Ok(ProcColumn::User), "user" => Ok(ProcColumn::User),
"time" => Ok(ProcColumn::Time), "time" => Ok(ProcColumn::Time),
#[cfg(feature = "gpu")]
"gmem" => Ok(ProcColumn::GpuMem),
#[cfg(feature = "gpu")]
"gmem%" => Ok(ProcColumn::GpuMemPercent),
#[cfg(feature = "gpu")]
"gpu%" => Ok(ProcColumn::GpuUtilPercent),
_ => Err(Error::custom("doesn't match any column type")), _ => Err(Error::custom("doesn't match any column type")),
} }
} }
@ -78,6 +90,12 @@ impl ColumnHeader for ProcColumn {
ProcColumn::State => "State", ProcColumn::State => "State",
ProcColumn::User => "User", ProcColumn::User => "User",
ProcColumn::Time => "Time", ProcColumn::Time => "Time",
#[cfg(feature = "gpu")]
ProcColumn::GpuMem => "GMEM",
#[cfg(feature = "gpu")]
ProcColumn::GpuMemPercent => "GMEM%",
#[cfg(feature = "gpu")]
ProcColumn::GpuUtilPercent => "GPU%",
} }
.into() .into()
} }
@ -98,6 +116,12 @@ impl ColumnHeader for ProcColumn {
ProcColumn::State => "State", ProcColumn::State => "State",
ProcColumn::User => "User", ProcColumn::User => "User",
ProcColumn::Time => "Time", ProcColumn::Time => "Time",
#[cfg(feature = "gpu")]
ProcColumn::GpuMem => "GMEM",
#[cfg(feature = "gpu")]
ProcColumn::GpuMemPercent => "GMEM%",
#[cfg(feature = "gpu")]
ProcColumn::GpuUtilPercent => "GPU%",
} }
.into() .into()
} }
@ -158,6 +182,16 @@ impl SortsRow for ProcColumn {
ProcColumn::Time => { ProcColumn::Time => {
data.sort_by(|a, b| sort_partial_fn(descending)(a.time, b.time)); data.sort_by(|a, b| sort_partial_fn(descending)(a.time, b.time));
} }
#[cfg(feature = "gpu")]
ProcColumn::GpuMem | ProcColumn::GpuMemPercent => {
data.sort_by(|a, b| {
sort_partial_fn(descending)(&a.gpu_mem_usage, &b.gpu_mem_usage)
});
}
#[cfg(feature = "gpu")]
ProcColumn::GpuUtilPercent => {
data.sort_by(|a, b| sort_partial_fn(descending)(a.gpu_usage, b.gpu_usage));
}
} }
} }
} }

View File

@ -181,6 +181,10 @@ pub struct ProcWidgetData {
pub num_similar: u64, pub num_similar: u64,
pub disabled: bool, pub disabled: bool,
pub time: Duration, pub time: Duration,
#[cfg(feature = "gpu")]
pub gpu_mem_usage: MemUsage,
#[cfg(feature = "gpu")]
pub gpu_usage: u32,
} }
impl ProcWidgetData { impl ProcWidgetData {
@ -216,6 +220,14 @@ impl ProcWidgetData {
num_similar: 1, num_similar: 1,
disabled: false, disabled: false,
time: process.time, time: process.time,
#[cfg(feature = "gpu")]
gpu_mem_usage: if is_mem_percent {
MemUsage::Percent(process.gpu_mem_percent)
} else {
MemUsage::Bytes(process.gpu_mem)
},
#[cfg(feature = "gpu")]
gpu_usage: process.gpu_util,
} }
} }
@ -248,6 +260,18 @@ impl ProcWidgetData {
self.wps += other.wps; self.wps += other.wps;
self.total_read += other.total_read; self.total_read += other.total_read;
self.total_write += other.total_write; self.total_write += other.total_write;
#[cfg(feature = "gpu")]
{
self.gpu_mem_usage = match (&self.gpu_mem_usage, &other.gpu_mem_usage) {
(MemUsage::Percent(a), MemUsage::Percent(b)) => MemUsage::Percent(a + b),
(MemUsage::Bytes(a), MemUsage::Bytes(b)) => MemUsage::Bytes(a + b),
(MemUsage::Percent(_), MemUsage::Bytes(_))
| (MemUsage::Bytes(_), MemUsage::Percent(_)) => {
unreachable!("trying to add together two different memory usage types!")
}
};
self.gpu_usage += other.gpu_usage;
}
} }
fn to_string(&self, column: &ProcColumn) -> String { fn to_string(&self, column: &ProcColumn) -> String {
@ -264,6 +288,10 @@ impl ProcWidgetData {
ProcColumn::State => self.process_char.to_string(), ProcColumn::State => self.process_char.to_string(),
ProcColumn::User => self.user.clone(), ProcColumn::User => self.user.clone(),
ProcColumn::Time => format_time(self.time), ProcColumn::Time => format_time(self.time),
#[cfg(feature = "gpu")]
ProcColumn::GpuMem | ProcColumn::GpuMemPercent => self.gpu_mem_usage.to_string(),
#[cfg(feature = "gpu")]
ProcColumn::GpuUtilPercent => format!("{:.1}%", self.gpu_usage),
} }
} }
} }
@ -298,6 +326,10 @@ impl DataToCell<ProcColumn> for ProcWidgetData {
} }
ProcColumn::User => self.user.clone(), ProcColumn::User => self.user.clone(),
ProcColumn::Time => format_time(self.time), ProcColumn::Time => format_time(self.time),
#[cfg(feature = "gpu")]
ProcColumn::GpuMem | ProcColumn::GpuMemPercent => self.gpu_mem_usage.to_string(),
#[cfg(feature = "gpu")]
ProcColumn::GpuUtilPercent => format!("{:.1}%", self.gpu_usage),
}, },
calculated_width, calculated_width,
)) ))

View File

@ -178,10 +178,10 @@ fn test_battery_flag() {
#[cfg_attr(feature = "gpu", ignore)] #[cfg_attr(feature = "gpu", ignore)]
fn test_gpu_flag() { fn test_gpu_flag() {
btm_command() btm_command()
.arg("--enable_gpu_memory") .arg("--enable_gpu")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .stderr(predicate::str::contains(
"unexpected argument '--enable_gpu_memory' found", "unexpected argument '--enable_gpu' found",
)); ));
} }