mirror of
https://github.com/ClementTsang/bottom.git
synced 2024-09-11 11:46:59 +03:00
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:
parent
d8d72d060d
commit
90be9730a6
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -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",
|
||||||
|
@ -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.
|
||||||
|
20
README.md
20
README.md
@ -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.
|
||||||
|
BIN
assets/temp_filter_post3.png
Normal file
BIN
assets/temp_filter_post3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
@ -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"
|
|
||||||
|
@ -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>,
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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(¤t_data.io_labels)
|
.zip(¤t_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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user