feature: Add network interface filtering (#381)

Adds a new option in the config file to filter out network interfaces.  Also add the option to filter by whole words.

Interface follows that of the existing ones:

```toml
[net_filter]
is_list_ignored = false
list = ["virbr0.*"]
regex = true
case_sensitive = false
whole_word = false
```
This commit is contained in:
Clement Tsang 2021-01-01 18:09:28 -05:00 committed by GitHub
parent d8d72d060d
commit 90be9730a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 293 additions and 220 deletions

View File

@ -65,6 +65,7 @@
"hjkl", "hjkl",
"htop", "htop",
"indexmap", "indexmap",
"iwlwifi",
"keybinds", "keybinds",
"le", "le",
"libc", "libc",
@ -112,6 +113,7 @@
"use", "use",
"use curr usage", "use curr usage",
"utime", "utime",
"virbr",
"virt", "virt",
"vsize", "vsize",
"whitespaces", "whitespaces",

View File

@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#379](https://github.com/ClementTsang/bottom/pull/379): Adds `--process_command` flag and corresponding config option to default to showing a process' command. - [#379](https://github.com/ClementTsang/bottom/pull/379): Adds `--process_command` flag and corresponding config option to default to showing a process' command.
- [#381](https://github.com/ClementTsang/bottom/pull/381): Adds a filter in the config file for network interfaces.
## Changes ## Changes
- [#372](https://github.com/ClementTsang/bottom/pull/372): Hides the SWAP graph and legend in normal mode if SWAP is 0. - [#372](https://github.com/ClementTsang/bottom/pull/372): Hides the SWAP graph and legend in normal mode if SWAP is 0.

View File

@ -52,7 +52,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [Config flags](#config-flags) - [Config flags](#config-flags)
- [Theming](#theming) - [Theming](#theming)
- [Layout](#layout) - [Layout](#layout)
- [Disk and temperature filtering](#disk-and-temperature-filtering) - [Disk and temperature filtering](#disk-temperature-and-network-filtering)
- [Battery](#battery) - [Battery](#battery)
- [Compatibility](#compatibility) - [Compatibility](#compatibility)
- [FAQ](#faq) - [FAQ](#faq)
@ -670,9 +670,9 @@ Furthermore, you can have duplicate widgets. This means you could do something l
and get the following CPU donut: and get the following CPU donut:
![CPU donut](./assets/cpu_layout.png) ![CPU donut](./assets/cpu_layout.png)
#### Disk and temperature filtering #### Disk, temperature, and network filtering
You can hide specific disks and temperature sensors by name in the config file via `disk_filter` and `temp_filter` respectively. Regex (`regex = true`) and case-sensitivity (`case_sensitive = true`) are supported, but are off by default. You can hide specific disks, temperature sensors, and networks by name in the config file via `disk_filter`, `temp_filter`, and `net_filter` respectively. Regex (`regex = true`), case-sensitivity (`case_sensitive = true`), and matching only the entire word (`whole_word = true`) are supported, but are off by default.
For example, let's say , given this disk list: For example, let's say , given this disk list:
@ -712,6 +712,20 @@ Now, flipping to `case_sensitive = false` would instead show:
![Temp filter after with case sensitivity off](./assets/temp_filter_post2.png) ![Temp filter after with case sensitivity off](./assets/temp_filter_post2.png)
Lastly, let's say I want to filter out _exactly_ "iwlwifi_1" from my results. I could do:
```toml
[temp_filter]
is_list_ignored = true
list = ["iwlwifi_1"]
case_sensitive = true
whole_word = true
```
This will match the entire word, "iwlwifi_1", and ignore any result that exactly matches it:
![Temp filter after with whole_word](./assets/temp_filter_post3.png)
### Battery ### Battery
You can get battery statistics (charge, time to fill/discharge, consumption in watts, and battery health) via the battery widget. You can get battery statistics (charge, time to fill/discharge, consumption in watts, and battery health) via the battery widget.

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -13,19 +13,3 @@ whole_word = false
regex = true regex = true
default_widget_type = "cpu" default_widget_type = "cpu"
default_widget_count = 1 default_widget_count = 1
[colors]
# Based on gruvbox: https://github.com/morhetz/gruvbox
table_header_color="#458588"
widget_title_color="#cc241d"
cpu_core_colors=["#cc241d", "#98971a", "#d79921", "#458588", "#b16286", "#689d6a", "#fb4934", "#b8bb26", "#fabd2f", "#83a598"]
ram_color="#fb4934"
swap_color="#fabd2f"
rx_color="#458588"
tx_color="#689d6a"
border_color="#ebdbb2"
highlighted_border_color="#fe8019"
text_color="#ebdbb2"
graph_color="#ebdbb2"
selected_text_color="#282828"
selected_bg_color="#458588"

View File

@ -57,12 +57,14 @@ pub struct AppConfigFields {
} }
/// For filtering out information /// For filtering out information
#[derive(Debug, Clone)]
pub struct DataFilters { pub struct DataFilters {
pub disk_filter: Option<Filter>, pub disk_filter: Option<Filter>,
pub temp_filter: Option<Filter>, pub temp_filter: Option<Filter>,
pub net_filter: Option<Filter>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Filter { pub struct Filter {
pub is_list_ignored: bool, pub is_list_ignored: bool,
pub list: Vec<regex::Regex>, pub list: Vec<regex::Regex>,

View File

@ -14,6 +14,8 @@ use crate::app::layout_manager::UsedWidgets;
use futures::join; use futures::join;
use super::DataFilters;
pub mod batteries; pub mod batteries;
pub mod cpu; pub mod cpu;
pub mod disks; pub mod disks;
@ -96,15 +98,15 @@ pub struct DataCollector {
battery_list: Option<Vec<Battery>>, battery_list: Option<Vec<Battery>>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
page_file_size_kb: u64, page_file_size_kb: u64,
filters: DataFilters,
} }
impl Default for DataCollector { impl DataCollector {
fn default() -> Self { pub fn new(filters: DataFilters) -> Self {
// trace!("Creating default data collector...");
DataCollector { DataCollector {
data: Data::default(), data: Data::default(),
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
sys: System::new_with_specifics(sysinfo::RefreshKind::new()), // FIXME: Make this run on only macOS and Windows. sys: System::new_with_specifics(sysinfo::RefreshKind::new()),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
previous_cpu_times: vec![], previous_cpu_times: vec![],
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -132,11 +134,10 @@ impl Default for DataCollector {
// page_file_size_kb // page_file_size_kb
libc::sysconf(libc::_SC_PAGESIZE) as u64 / 1024 libc::sysconf(libc::_SC_PAGESIZE) as u64 / 1024
}, },
filters,
} }
} }
}
impl DataCollector {
pub fn init(&mut self) { pub fn init(&mut self) {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@ -147,6 +148,7 @@ impl DataCollector {
self.sys.refresh_memory(); self.sys.refresh_memory();
self.mem_total_kb = self.sys.get_total_memory(); self.mem_total_kb = self.sys.get_total_memory();
// TODO: Would be good to get this and network list running on a timer instead...?
// Refresh components list once... // Refresh components list once...
if self.widgets_to_harvest.use_temp { if self.widgets_to_harvest.use_temp {
self.sys.refresh_components_list(); self.sys.refresh_components_list();
@ -295,6 +297,7 @@ impl DataCollector {
&mut self.total_tx, &mut self.total_tx,
current_instant, current_instant,
self.widgets_to_harvest.use_net, self.widgets_to_harvest.use_net,
&self.filters.net_filter,
) )
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
@ -305,12 +308,14 @@ impl DataCollector {
&mut self.total_tx, &mut self.total_tx,
current_instant, current_instant,
self.widgets_to_harvest.use_net, self.widgets_to_harvest.use_net,
&self.filters.net_filter,
) )
} }
}; };
let mem_data_fut = mem::get_mem_data(self.widgets_to_harvest.use_mem); let mem_data_fut = mem::get_mem_data(self.widgets_to_harvest.use_mem);
let disk_data_fut = disks::get_disk_usage(self.widgets_to_harvest.use_disk); let disk_data_fut =
let disk_io_usage_fut = disks::get_io_usage(false, self.widgets_to_harvest.use_disk); disks::get_disk_usage(self.widgets_to_harvest.use_disk, &self.filters.disk_filter);
let disk_io_usage_fut = disks::get_io_usage(self.widgets_to_harvest.use_disk);
let temp_data_fut = { let temp_data_fut = {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
@ -318,6 +323,7 @@ impl DataCollector {
&self.sys, &self.sys,
&self.temperature_type, &self.temperature_type,
self.widgets_to_harvest.use_temp, self.widgets_to_harvest.use_temp,
&self.filters.temp_filter,
) )
} }
@ -326,6 +332,7 @@ impl DataCollector {
temperature::get_temperature_data( temperature::get_temperature_data(
&self.temperature_type, &self.temperature_type,
self.widgets_to_harvest.use_temp, self.widgets_to_harvest.use_temp,
&self.filters.temp_filter,
) )
} }
}; };

View File

@ -1,3 +1,5 @@
use crate::app::Filter;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct DiskHarvest { pub struct DiskHarvest {
pub name: String, pub name: String,
@ -15,9 +17,7 @@ pub struct IOData {
pub type IOHarvest = std::collections::HashMap<String, Option<IOData>>; pub type IOHarvest = std::collections::HashMap<String, Option<IOData>>;
pub async fn get_io_usage( pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IOHarvest>> {
get_physical: bool, actually_get: bool,
) -> crate::utils::error::Result<Option<IOHarvest>> {
if !actually_get { if !actually_get {
return Ok(None); return Ok(None);
} }
@ -26,37 +26,23 @@ pub async fn get_io_usage(
let mut io_hash: std::collections::HashMap<String, Option<IOData>> = let mut io_hash: std::collections::HashMap<String, Option<IOData>> =
std::collections::HashMap::new(); std::collections::HashMap::new();
if get_physical {
let physical_counter_stream = heim::disk::io_counters_physical().await?;
futures::pin_mut!(physical_counter_stream);
while let Some(io) = physical_counter_stream.next().await { let counter_stream = heim::disk::io_counters().await?;
if let Ok(io) = io { futures::pin_mut!(counter_stream);
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
Some(IOData {
read_bytes: io.read_bytes().get::<heim::units::information::megabyte>(),
write_bytes: io.write_bytes().get::<heim::units::information::megabyte>(),
}),
);
}
}
} else {
let counter_stream = heim::disk::io_counters().await?;
futures::pin_mut!(counter_stream);
while let Some(io) = counter_stream.next().await { while let Some(io) = counter_stream.next().await {
if let Ok(io) = io { if let Ok(io) = io {
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable"); let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(), // FIXME: [MOUNT POINT] Add the filter here I guess?
Some(IOData {
read_bytes: io.read_bytes().get::<heim::units::information::byte>(), io_hash.insert(
write_bytes: io.write_bytes().get::<heim::units::information::byte>(), mount_point.to_string(),
}), Some(IOData {
); read_bytes: io.read_bytes().get::<heim::units::information::byte>(),
} write_bytes: io.write_bytes().get::<heim::units::information::byte>(),
}),
);
} }
} }
@ -64,7 +50,7 @@ pub async fn get_io_usage(
} }
pub async fn get_disk_usage( pub async fn get_disk_usage(
actually_get: bool, actually_get: bool, name_filter: &Option<Filter>,
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> { ) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
if !actually_get { if !actually_get {
return Ok(None); return Ok(None);
@ -77,26 +63,43 @@ pub async fn get_disk_usage(
futures::pin_mut!(partitions_stream); futures::pin_mut!(partitions_stream);
while let Some(part) = partitions_stream.next().await { while let Some(part) = partitions_stream.next().await {
if let Ok(part) = part { if let Ok(partition) = part {
let partition = part; let name = (partition
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?; .device()
.unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable"))
.to_str()
.unwrap_or("Name Unavailable"))
.to_string();
vec_disks.push(DiskHarvest { let mount_point = (partition
free_space: usage.free().get::<heim::units::information::byte>(), .mount_point()
used_space: usage.used().get::<heim::units::information::byte>(), .to_str()
total_space: usage.total().get::<heim::units::information::byte>(), .unwrap_or("Name Unavailable"))
mount_point: (partition .to_string();
.mount_point()
.to_str() let to_keep = if let Some(filter) = name_filter {
.unwrap_or("Name Unavailable")) let mut ret = filter.is_list_ignored;
.to_string(), for r in &filter.list {
name: (partition if r.is_match(&name) {
.device() ret = !filter.is_list_ignored;
.unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable")) break;
.to_str() }
.unwrap_or("Name Unavailable")) }
.to_string(), ret
}); } else {
true
};
if to_keep {
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
vec_disks.push(DiskHarvest {
free_space: usage.free().get::<heim::units::information::byte>(),
used_space: usage.used().get::<heim::units::information::byte>(),
total_space: usage.total().get::<heim::units::information::byte>(),
mount_point,
name,
});
}
} }
} }

View File

@ -20,6 +20,7 @@ impl NetworkHarvest {
pub async fn get_network_data( pub async fn get_network_data(
sys: &sysinfo::System, prev_net_access_time: Instant, prev_net_rx: &mut u64, sys: &sysinfo::System, prev_net_access_time: Instant, prev_net_rx: &mut u64,
prev_net_tx: &mut u64, curr_time: Instant, actually_get: bool, prev_net_tx: &mut u64, curr_time: Instant, actually_get: bool,
filter: &Option<crate::app::Filter>,
) -> crate::utils::error::Result<Option<NetworkHarvest>> { ) -> crate::utils::error::Result<Option<NetworkHarvest>> {
use sysinfo::{NetworkExt, SystemExt}; use sysinfo::{NetworkExt, SystemExt};
@ -31,52 +32,83 @@ pub async fn get_network_data(
let mut total_tx: u64 = 0; let mut total_tx: u64 = 0;
let networks = sys.get_networks(); let networks = sys.get_networks();
for (_, network) in networks { for (name, network) in networks {
total_rx += network.get_total_received(); let to_keep = if let Some(filter) = filter {
total_tx += network.get_total_transmitted(); let mut ret = filter.is_list_ignored;
} for r in &filter.list {
if r.is_match(&name) {
ret = !filter.is_list_ignored;
break;
}
}
ret
} else {
true
};
let elapsed_time = curr_time.duration_since(prev_net_access_time).as_secs_f64(); if to_keep {
total_rx += network.get_total_received();
let (rx, tx) = if elapsed_time == 0.0 { total_tx += network.get_total_transmitted();
(0, 0) }
} else { }
(
((total_rx.saturating_sub(*prev_net_rx)) as f64 / elapsed_time) as u64, let elapsed_time = curr_time.duration_since(prev_net_access_time).as_secs_f64();
((total_tx.saturating_sub(*prev_net_tx)) as f64 / elapsed_time) as u64,
) let (rx, tx) = if elapsed_time == 0.0 {
}; (0, 0)
} else {
*prev_net_rx = total_rx; (
*prev_net_tx = total_tx; ((total_rx.saturating_sub(*prev_net_rx)) as f64 / elapsed_time) as u64,
Ok(Some(NetworkHarvest { ((total_tx.saturating_sub(*prev_net_tx)) as f64 / elapsed_time) as u64,
rx, )
tx, };
total_rx,
total_tx, *prev_net_rx = total_rx;
})) *prev_net_tx = total_tx;
} Ok(Some(NetworkHarvest {
rx,
#[cfg(not(target_os = "windows"))] tx,
pub async fn get_network_data( total_rx,
prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64, total_tx,
curr_time: Instant, actually_get: bool, }))
) -> crate::utils::error::Result<Option<NetworkHarvest>> { }
use futures::StreamExt;
// FIXME: Eventually make it so that this thing also takes individual usage into account, so we can allow for showing per-interface!
if !actually_get { #[cfg(not(target_os = "windows"))]
return Ok(None); pub async fn get_network_data(
} prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64,
curr_time: Instant, actually_get: bool, filter: &Option<crate::app::Filter>,
let io_data = heim::net::io_counters().await?; ) -> crate::utils::error::Result<Option<NetworkHarvest>> {
futures::pin_mut!(io_data); use futures::StreamExt;
let mut total_rx: u64 = 0;
let mut total_tx: u64 = 0; if !actually_get {
return Ok(None);
while let Some(io) = io_data.next().await { }
if let Ok(io) = io {
total_rx += io.bytes_recv().get::<heim::units::information::byte>(); let io_data = heim::net::io_counters().await?;
total_tx += io.bytes_sent().get::<heim::units::information::byte>(); futures::pin_mut!(io_data);
let mut total_rx: u64 = 0;
let mut total_tx: u64 = 0;
while let Some(io) = io_data.next().await {
if let Ok(io) = io {
let to_keep = if let Some(filter) = filter {
let mut ret = filter.is_list_ignored;
for r in &filter.list {
if r.is_match(&io.interface()) {
ret = !filter.is_list_ignored;
break;
}
}
ret
} else {
true
};
if to_keep {
total_rx += io.bytes_recv().get::<heim::units::information::byte>();
total_tx += io.bytes_sent().get::<heim::units::information::byte>();
}
} }
} }

View File

@ -1,9 +1,10 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::app::Filter;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct TempHarvest { pub struct TempHarvest {
pub component_name: Option<String>, pub name: String,
pub component_label: Option<String>,
pub temperature: f32, pub temperature: f32,
} }
@ -22,7 +23,7 @@ impl Default for TemperatureType {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
pub async fn get_temperature_data( pub async fn get_temperature_data(
sys: &sysinfo::System, temp_type: &TemperatureType, actually_get: bool, sys: &sysinfo::System, temp_type: &TemperatureType, actually_get: bool, filter: &Option<Filter>,
) -> crate::utils::error::Result<Option<Vec<TempHarvest>>> { ) -> crate::utils::error::Result<Option<Vec<TempHarvest>>> {
use sysinfo::{ComponentExt, SystemExt}; use sysinfo::{ComponentExt, SystemExt};
@ -42,17 +43,35 @@ pub async fn get_temperature_data(
let sensor_data = sys.get_components(); let sensor_data = sys.get_components();
for component in sensor_data { for component in sensor_data {
temperature_vec.push(TempHarvest { let name = component.get_label().to_string();
component_name: None,
component_label: Some(component.get_label().to_string()), let to_keep = if let Some(filter) = filter {
temperature: match temp_type { let mut ret = filter.is_list_ignored;
TemperatureType::Celsius => component.get_temperature(), for r in &filter.list {
TemperatureType::Kelvin => convert_celsius_to_kelvin(component.get_temperature()), if r.is_match(&name) {
TemperatureType::Fahrenheit => { ret = !filter.is_list_ignored;
convert_celsius_to_fahrenheit(component.get_temperature()) break;
} }
}, }
}); ret
} else {
true
};
if to_keep {
temperature_vec.push(TempHarvest {
name,
temperature: match temp_type {
TemperatureType::Celsius => component.get_temperature(),
TemperatureType::Kelvin => {
convert_celsius_to_kelvin(component.get_temperature())
}
TemperatureType::Fahrenheit => {
convert_celsius_to_fahrenheit(component.get_temperature())
}
},
});
}
} }
temp_vec_sort(&mut temperature_vec); temp_vec_sort(&mut temperature_vec);
@ -61,7 +80,7 @@ pub async fn get_temperature_data(
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub async fn get_temperature_data( pub async fn get_temperature_data(
temp_type: &TemperatureType, actually_get: bool, temp_type: &TemperatureType, actually_get: bool, filter: &Option<Filter>,
) -> crate::utils::error::Result<Option<Vec<TempHarvest>>> { ) -> crate::utils::error::Result<Option<Vec<TempHarvest>>> {
use futures::StreamExt; use futures::StreamExt;
use heim::units::thermodynamic_temperature; use heim::units::thermodynamic_temperature;
@ -75,26 +94,51 @@ pub async fn get_temperature_data(
let mut sensor_data = heim::sensors::temperatures().boxed_local(); let mut sensor_data = heim::sensors::temperatures().boxed_local();
while let Some(sensor) = sensor_data.next().await { while let Some(sensor) = sensor_data.next().await {
if let Ok(sensor) = sensor { if let Ok(sensor) = sensor {
temperature_vec.push(TempHarvest { let component_name = Some(sensor.unit().to_string());
component_name: Some(sensor.unit().to_string()), let component_label = if let Some(label) = sensor.label() {
component_label: if let Some(label) = sensor.label() { Some(label.to_string())
Some(label.to_string()) } else {
} else { None
None };
},
temperature: match temp_type { let name = match (component_name, component_label) {
TemperatureType::Celsius => sensor (Some(name), Some(label)) => format!("{}: {}", name, label),
.current() (None, Some(label)) => label.to_string(),
.get::<thermodynamic_temperature::degree_celsius>(), (Some(name), None) => name.to_string(),
TemperatureType::Kelvin => { (None, None) => String::default(),
sensor.current().get::<thermodynamic_temperature::kelvin>() };
let to_keep = if let Some(filter) = filter {
let mut ret = filter.is_list_ignored;
for r in &filter.list {
if r.is_match(&name) {
ret = !filter.is_list_ignored;
break;
} }
TemperatureType::Fahrenheit => sensor }
.current() ret
.get::<thermodynamic_temperature::degree_fahrenheit>( } else {
), true
}, };
});
if to_keep {
temperature_vec.push(TempHarvest {
name,
temperature: match temp_type {
TemperatureType::Celsius => sensor
.current()
.get::<thermodynamic_temperature::degree_celsius>(
),
TemperatureType::Kelvin => {
sensor.current().get::<thermodynamic_temperature::kelvin>()
}
TemperatureType::Fahrenheit => sensor
.current()
.get::<thermodynamic_temperature::degree_fahrenheit>(
),
},
});
}
} }
} }
@ -116,9 +160,5 @@ fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) {
None => Ordering::Equal, None => Ordering::Equal,
}); });
temperature_vec.sort_by(|a, b| { temperature_vec.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap_or(Ordering::Equal));
a.component_name
.partial_cmp(&b.component_name)
.unwrap_or(Ordering::Equal)
});
} }

View File

@ -121,6 +121,7 @@ fn main() -> Result<()> {
thread_termination_lock.clone(), thread_termination_lock.clone(),
thread_termination_cvar.clone(), thread_termination_cvar.clone(),
&app.app_config_fields, &app.app_config_fields,
app.filters.clone(),
app.used_widgets.clone(), app.used_widgets.clone(),
); );
@ -192,8 +193,7 @@ fn main() -> Result<()> {
// Disk // Disk
if app.used_widgets.use_disk { if app.used_widgets.use_disk {
app.canvas_data.disk_data = app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
convert_disk_row(&app.data_collection, &app.filters.disk_filter);
} }
// Temperatures // Temperatures

View File

@ -479,12 +479,21 @@ pub const OLD_CONFIG_TEXT: &str = r##"# This is a default config file for bottom
#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] #list = ["/dev/sda\\d+", "/dev/nvme0n1p2"]
#regex = true #regex = true
#case_sensitive = false #case_sensitive = false
#whole_word = false
#[temp_filter] #[temp_filter]
#is_list_ignored = false #is_list_ignored = false
#list = ["cpu", "wifi"] #list = ["cpu", "wifi"]
#regex = false #regex = false
#case_sensitive = false #case_sensitive = false
#whole_word = false
#[net_filter]
#is_list_ignored = false
#list = ["virbr0.*"]
#regex = true
#case_sensitive = false
#whole_word = false
"##; "##;
pub const CONFIG_TOP_HEAD: &str = r##"# This is bottom's config file. pub const CONFIG_TOP_HEAD: &str = r##"# This is bottom's config file.

View File

@ -2,7 +2,7 @@
//! can actually handle. //! can actually handle.
use crate::Pid; use crate::Pid;
use crate::{ use crate::{
app::{data_farmer, data_harvester, App, Filter, ProcWidgetState}, app::{data_farmer, data_harvester, App, ProcWidgetState},
utils::{self, gen_util::*}, utils::{self, gen_util::*},
}; };
use data_harvester::processes::ProcessSorting; use data_harvester::processes::ProcessSorting;
@ -84,45 +84,20 @@ pub struct ConvertedCpuData {
pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> { pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> {
let current_data = &app.data_collection; let current_data = &app.data_collection;
let temp_type = &app.app_config_fields.temperature_type; let temp_type = &app.app_config_fields.temperature_type;
let temp_filter = &app.filters.temp_filter;
let mut sensor_vector: Vec<Vec<String>> = current_data let mut sensor_vector: Vec<Vec<String>> = current_data
.temp_harvest .temp_harvest
.iter() .iter()
.filter_map(|temp_harvest| { .map(|temp_harvest| {
let name = match (&temp_harvest.component_name, &temp_harvest.component_label) { vec![
(Some(name), Some(label)) => format!("{}: {}", name, label), temp_harvest.name.clone(),
(None, Some(label)) => label.to_string(), (temp_harvest.temperature.ceil() as u64).to_string()
(Some(name), None) => name.to_string(), + match temp_type {
(None, None) => String::default(), data_harvester::temperature::TemperatureType::Celsius => "C",
}; data_harvester::temperature::TemperatureType::Kelvin => "K",
data_harvester::temperature::TemperatureType::Fahrenheit => "F",
let to_keep = if let Some(temp_filter) = temp_filter { },
let mut ret = temp_filter.is_list_ignored; ]
for r in &temp_filter.list {
if r.is_match(&name) {
ret = !temp_filter.is_list_ignored;
break;
}
}
ret
} else {
true
};
if to_keep {
Some(vec![
name,
(temp_harvest.temperature.ceil() as u64).to_string()
+ match temp_type {
data_harvester::temperature::TemperatureType::Celsius => "C",
data_harvester::temperature::TemperatureType::Kelvin => "K",
data_harvester::temperature::TemperatureType::Fahrenheit => "F",
},
])
} else {
None
}
}) })
.collect(); .collect();
@ -133,26 +108,12 @@ pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> {
sensor_vector sensor_vector
} }
pub fn convert_disk_row( pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<String>> {
current_data: &data_farmer::DataCollection, disk_filter: &Option<Filter>,
) -> Vec<Vec<String>> {
let mut disk_vector: Vec<Vec<String>> = Vec::new(); let mut disk_vector: Vec<Vec<String>> = Vec::new();
current_data current_data
.disk_harvest .disk_harvest
.iter() .iter()
.filter(|disk_harvest| {
if let Some(disk_filter) = disk_filter {
for r in &disk_filter.list {
if r.is_match(&disk_harvest.name) {
return !disk_filter.is_list_ignored;
}
}
disk_filter.is_list_ignored
} else {
true
}
})
.zip(&current_data.io_labels) .zip(&current_data.io_labels)
.for_each(|(disk, (io_read, io_write))| { .for_each(|(disk, (io_read, io_write))| {
let converted_free_space = get_simple_byte_values(disk.free_space, false); let converted_free_space = get_simple_byte_values(disk.free_space, false);
@ -174,6 +135,10 @@ pub fn convert_disk_row(
]); ]);
}); });
if disk_vector.is_empty() {
disk_vector.push(vec!["No Disks Found".to_string(), "".to_string()]);
}
disk_vector disk_vector
} }

View File

@ -620,7 +620,8 @@ pub fn create_collection_thread(
>, >,
control_receiver: std::sync::mpsc::Receiver<ThreadControlEvent>, control_receiver: std::sync::mpsc::Receiver<ThreadControlEvent>,
termination_ctrl_lock: Arc<Mutex<bool>>, termination_ctrl_cvar: Arc<Condvar>, termination_ctrl_lock: Arc<Mutex<bool>>, termination_ctrl_cvar: Arc<Condvar>,
app_config_fields: &app::AppConfigFields, used_widget_set: UsedWidgets, app_config_fields: &app::AppConfigFields, filters: app::DataFilters,
used_widget_set: UsedWidgets,
) -> std::thread::JoinHandle<()> { ) -> std::thread::JoinHandle<()> {
// trace!("Creating collection thread."); // trace!("Creating collection thread.");
let temp_type = app_config_fields.temperature_type.clone(); let temp_type = app_config_fields.temperature_type.clone();
@ -630,7 +631,7 @@ pub fn create_collection_thread(
thread::spawn(move || { thread::spawn(move || {
// trace!("Spawned collection thread."); // trace!("Spawned collection thread.");
let mut data_state = data_harvester::DataCollector::default(); let mut data_state = data_harvester::DataCollector::new(filters);
// trace!("Created default data state."); // trace!("Created default data state.");
data_state.set_collected_data(used_widget_set); data_state.set_collected_data(used_widget_set);
data_state.set_temperature_type(temp_type); data_state.set_temperature_type(temp_type);

View File

@ -30,6 +30,7 @@ pub struct Config {
pub row: Option<Vec<Row>>, pub row: Option<Vec<Row>>,
pub disk_filter: Option<IgnoreList>, pub disk_filter: Option<IgnoreList>,
pub temp_filter: Option<IgnoreList>, pub temp_filter: Option<IgnoreList>,
pub net_filter: Option<IgnoreList>,
} }
impl Config { impl Config {
@ -216,6 +217,7 @@ pub struct IgnoreList {
pub list: Vec<String>, pub list: Vec<String>,
pub regex: Option<bool>, pub regex: Option<bool>,
pub case_sensitive: Option<bool>, pub case_sensitive: Option<bool>,
pub whole_word: Option<bool>,
} }
pub fn build_app( pub fn build_app(
@ -413,6 +415,8 @@ pub fn build_app(
get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?; get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?;
let temp_filter = let temp_filter =
get_ignore_list(&config.temp_filter).context("Update 'temp_filter' in your config file")?; get_ignore_list(&config.temp_filter).context("Update 'temp_filter' in your config file")?;
let net_filter =
get_ignore_list(&config.net_filter).context("Update 'net_filter' in your config file")?;
// One more thing - we have to update the search settings of our proc_state_map, and create the hashmaps if needed! // One more thing - we have to update the search settings of our proc_state_map, and create the hashmaps if needed!
// Note that if you change your layout, this might not actually match properly... not sure if/where we should deal with that... // Note that if you change your layout, this might not actually match properly... not sure if/where we should deal with that...
@ -472,6 +476,7 @@ pub fn build_app(
.filters(DataFilters { .filters(DataFilters {
disk_filter, disk_filter,
temp_filter, temp_filter,
net_filter,
}) })
.config(config.clone()) .config(config.clone())
.config_path(config_path) .config_path(config_path)
@ -907,17 +912,24 @@ fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Fil
} else { } else {
false false
}; };
let whole_word = if let Some(whole_word) = ignore_list.whole_word {
whole_word
} else {
false
};
let escaped_string: String; let escaped_string: String;
let res = format!( let res = format!(
"{}{}", "{}{}{}{}",
if whole_word { "^" } else { "" },
if use_cs { "" } else { "(?i)" }, if use_cs { "" } else { "(?i)" },
if use_regex { if use_regex {
name name
} else { } else {
escaped_string = regex::escape(name); escaped_string = regex::escape(name);
&escaped_string &escaped_string
} },
if whole_word { "$" } else { "" },
); );
Regex::new(&res) Regex::new(&res)