mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-26 23:24:20 +02:00
refactor: rename files from mod to their directory names
This commit is contained in:
parent
b5e6dea324
commit
189be96622
364
src/app/data_harvester.rs
Normal file
364
src/app/data_harvester.rs
Normal file
@ -0,0 +1,364 @@
|
||||
//! This is the main file to house data collection functions.
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use fxhash::FxHashMap;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
use battery::{Battery, Manager};
|
||||
|
||||
use futures::join;
|
||||
|
||||
use super::{DataFilters, UsedWidgets};
|
||||
|
||||
pub mod batteries;
|
||||
pub mod cpu;
|
||||
pub mod disks;
|
||||
pub mod memory;
|
||||
pub mod network;
|
||||
pub mod processes;
|
||||
pub mod temperature;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Data {
|
||||
pub last_collection_time: Instant,
|
||||
pub cpu: Option<cpu::CpuHarvest>,
|
||||
pub load_avg: Option<cpu::LoadAvgHarvest>,
|
||||
pub memory: Option<memory::MemHarvest>,
|
||||
pub swap: Option<memory::MemHarvest>,
|
||||
pub temperature_sensors: Option<Vec<temperature::TempHarvest>>,
|
||||
pub network: Option<network::NetworkHarvest>,
|
||||
pub list_of_processes: Option<Vec<processes::ProcessHarvest>>,
|
||||
pub disks: Option<Vec<disks::DiskHarvest>>,
|
||||
pub io: Option<disks::IoHarvest>,
|
||||
pub list_of_batteries: Option<Vec<batteries::BatteryHarvest>>,
|
||||
}
|
||||
|
||||
impl Default for Data {
|
||||
fn default() -> Self {
|
||||
Data {
|
||||
last_collection_time: Instant::now(),
|
||||
cpu: None,
|
||||
load_avg: None,
|
||||
memory: None,
|
||||
swap: None,
|
||||
temperature_sensors: None,
|
||||
list_of_processes: None,
|
||||
disks: None,
|
||||
io: None,
|
||||
network: None,
|
||||
list_of_batteries: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.io = None;
|
||||
self.temperature_sensors = None;
|
||||
self.list_of_processes = None;
|
||||
self.disks = None;
|
||||
self.memory = None;
|
||||
self.swap = None;
|
||||
self.cpu = None;
|
||||
self.load_avg = None;
|
||||
|
||||
if let Some(network) = &mut self.network {
|
||||
network.first_run_cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataCollector {
|
||||
pub data: Data,
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
sys: System,
|
||||
previous_cpu_times: Vec<(cpu::PastCpuWork, cpu::PastCpuTotal)>,
|
||||
previous_average_cpu_time: Option<(cpu::PastCpuWork, cpu::PastCpuTotal)>,
|
||||
#[cfg(target_os = "linux")]
|
||||
pid_mapping: FxHashMap<crate::Pid, processes::PrevProcDetails>,
|
||||
#[cfg(target_os = "linux")]
|
||||
prev_idle: f64,
|
||||
#[cfg(target_os = "linux")]
|
||||
prev_non_idle: f64,
|
||||
mem_total_kb: u64,
|
||||
temperature_type: temperature::TemperatureType,
|
||||
use_current_cpu_total: bool,
|
||||
last_collection_time: Instant,
|
||||
total_rx: u64,
|
||||
total_tx: u64,
|
||||
show_average_cpu: bool,
|
||||
widgets_to_harvest: UsedWidgets,
|
||||
battery_manager: Option<Manager>,
|
||||
battery_list: Option<Vec<Battery>>,
|
||||
filters: DataFilters,
|
||||
}
|
||||
|
||||
impl DataCollector {
|
||||
pub fn new(filters: DataFilters) -> Self {
|
||||
DataCollector {
|
||||
data: Data::default(),
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
sys: System::new_with_specifics(sysinfo::RefreshKind::new()),
|
||||
previous_cpu_times: vec![],
|
||||
previous_average_cpu_time: None,
|
||||
#[cfg(target_os = "linux")]
|
||||
pid_mapping: FxHashMap::default(),
|
||||
#[cfg(target_os = "linux")]
|
||||
prev_idle: 0_f64,
|
||||
#[cfg(target_os = "linux")]
|
||||
prev_non_idle: 0_f64,
|
||||
mem_total_kb: 0,
|
||||
temperature_type: temperature::TemperatureType::Celsius,
|
||||
use_current_cpu_total: false,
|
||||
last_collection_time: Instant::now(),
|
||||
total_rx: 0,
|
||||
total_tx: 0,
|
||||
show_average_cpu: false,
|
||||
widgets_to_harvest: UsedWidgets::default(),
|
||||
battery_manager: None,
|
||||
battery_list: None,
|
||||
filters,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
futures::executor::block_on(self.initialize_memory_size());
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
self.sys.refresh_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...
|
||||
if self.widgets_to_harvest.use_temp {
|
||||
self.sys.refresh_components_list();
|
||||
}
|
||||
|
||||
// Refresh network list once...
|
||||
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
|
||||
self.sys.refresh_networks_list();
|
||||
}
|
||||
}
|
||||
|
||||
if self.widgets_to_harvest.use_battery {
|
||||
if let Ok(battery_manager) = Manager::new() {
|
||||
if let Ok(batteries) = battery_manager.batteries() {
|
||||
let battery_list: Vec<Battery> = batteries.filter_map(Result::ok).collect();
|
||||
if !battery_list.is_empty() {
|
||||
self.battery_list = Some(battery_list);
|
||||
self.battery_manager = Some(battery_manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
futures::executor::block_on(self.update_data());
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
|
||||
self.data.cleanup();
|
||||
|
||||
// trace!("Enabled widgets to harvest: {:#?}", self.widgets_to_harvest);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn initialize_memory_size(&mut self) {
|
||||
self.mem_total_kb = if let Ok(mem) = heim::memory::memory().await {
|
||||
mem.total().get::<heim::units::information::kilobyte>()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_collected_data(&mut self, used_widgets: UsedWidgets) {
|
||||
self.widgets_to_harvest = used_widgets;
|
||||
}
|
||||
|
||||
pub fn set_temperature_type(&mut self, temperature_type: temperature::TemperatureType) {
|
||||
self.temperature_type = temperature_type;
|
||||
}
|
||||
|
||||
pub fn set_use_current_cpu_total(&mut self, use_current_cpu_total: bool) {
|
||||
self.use_current_cpu_total = use_current_cpu_total;
|
||||
}
|
||||
|
||||
pub fn set_show_average_cpu(&mut self, show_average_cpu: bool) {
|
||||
self.show_average_cpu = show_average_cpu;
|
||||
}
|
||||
|
||||
pub async fn update_data(&mut self) {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
self.sys.refresh_processes();
|
||||
}
|
||||
if self.widgets_to_harvest.use_temp {
|
||||
self.sys.refresh_components();
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
|
||||
self.sys.refresh_networks();
|
||||
}
|
||||
}
|
||||
|
||||
let current_instant = std::time::Instant::now();
|
||||
|
||||
// CPU
|
||||
if self.widgets_to_harvest.use_cpu {
|
||||
if let Ok(cpu_data) = cpu::get_cpu_data_list(
|
||||
self.show_average_cpu,
|
||||
&mut self.previous_cpu_times,
|
||||
&mut self.previous_average_cpu_time,
|
||||
)
|
||||
.await
|
||||
{
|
||||
self.data.cpu = Some(cpu_data);
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
// Load Average
|
||||
if let Ok(load_avg_data) = cpu::get_load_avg().await {
|
||||
self.data.load_avg = Some(load_avg_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Batteries
|
||||
if let Some(battery_manager) = &self.battery_manager {
|
||||
if let Some(battery_list) = &mut self.battery_list {
|
||||
self.data.list_of_batteries =
|
||||
Some(batteries::refresh_batteries(battery_manager, battery_list));
|
||||
}
|
||||
}
|
||||
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
if let Ok(process_list) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
processes::get_process_data(
|
||||
&mut self.prev_idle,
|
||||
&mut self.prev_non_idle,
|
||||
&mut self.pid_mapping,
|
||||
self.use_current_cpu_total,
|
||||
current_instant
|
||||
.duration_since(self.last_collection_time)
|
||||
.as_secs(),
|
||||
self.mem_total_kb,
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
processes::get_process_data(
|
||||
&self.sys,
|
||||
self.use_current_cpu_total,
|
||||
self.mem_total_kb,
|
||||
)
|
||||
}
|
||||
} {
|
||||
self.data.list_of_processes = Some(process_list);
|
||||
}
|
||||
}
|
||||
|
||||
let network_data_fut = {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
network::get_network_data(
|
||||
&self.sys,
|
||||
self.last_collection_time,
|
||||
&mut self.total_rx,
|
||||
&mut self.total_tx,
|
||||
current_instant,
|
||||
self.widgets_to_harvest.use_net,
|
||||
&self.filters.net_filter,
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
network::get_network_data(
|
||||
self.last_collection_time,
|
||||
&mut self.total_rx,
|
||||
&mut self.total_tx,
|
||||
current_instant,
|
||||
self.widgets_to_harvest.use_net,
|
||||
&self.filters.net_filter,
|
||||
)
|
||||
}
|
||||
};
|
||||
let mem_data_fut = memory::get_mem_data(self.widgets_to_harvest.use_mem);
|
||||
let disk_data_fut = disks::get_disk_usage(
|
||||
self.widgets_to_harvest.use_disk,
|
||||
&self.filters.disk_filter,
|
||||
&self.filters.mount_filter,
|
||||
);
|
||||
let disk_io_usage_fut = disks::get_io_usage(self.widgets_to_harvest.use_disk);
|
||||
let temp_data_fut = {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
temperature::get_temperature_data(
|
||||
&self.sys,
|
||||
&self.temperature_type,
|
||||
self.widgets_to_harvest.use_temp,
|
||||
&self.filters.temp_filter,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
temperature::get_temperature_data(
|
||||
&self.temperature_type,
|
||||
self.widgets_to_harvest.use_temp,
|
||||
&self.filters.temp_filter,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let (net_data, mem_res, disk_res, io_res, temp_res) = join!(
|
||||
network_data_fut,
|
||||
mem_data_fut,
|
||||
disk_data_fut,
|
||||
disk_io_usage_fut,
|
||||
temp_data_fut
|
||||
);
|
||||
|
||||
if let Ok(net_data) = net_data {
|
||||
if let Some(net_data) = &net_data {
|
||||
self.total_rx = net_data.total_rx;
|
||||
self.total_tx = net_data.total_tx;
|
||||
}
|
||||
self.data.network = net_data;
|
||||
}
|
||||
|
||||
if let Ok(memory) = mem_res.0 {
|
||||
self.data.memory = memory;
|
||||
}
|
||||
|
||||
if let Ok(swap) = mem_res.1 {
|
||||
self.data.swap = swap;
|
||||
}
|
||||
|
||||
if let Ok(disks) = disk_res {
|
||||
self.data.disks = disks;
|
||||
}
|
||||
|
||||
if let Ok(io) = io_res {
|
||||
self.data.io = io;
|
||||
}
|
||||
|
||||
if let Ok(temp) = temp_res {
|
||||
self.data.temperature_sensors = temp;
|
||||
}
|
||||
|
||||
// Update time
|
||||
self.data.last_collection_time = current_instant;
|
||||
self.last_collection_time = current_instant;
|
||||
}
|
||||
}
|
10
src/app/data_harvester/batteries.rs
Normal file
10
src/app/data_harvester/batteries.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Data collection for batteries.
|
||||
//!
|
||||
//! For Linux, macOS, Windows, FreeBSD, Dragonfly, and iOS, this is handled by the battery crate.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "ios"))] {
|
||||
pub mod battery;
|
||||
pub use self::battery::*;
|
||||
}
|
||||
}
|
14
src/app/data_harvester/cpu.rs
Normal file
14
src/app/data_harvester/cpu.rs
Normal file
@ -0,0 +1,14 @@
|
||||
//! Data collection for CPU usage and load average.
|
||||
//!
|
||||
//! For CPU usage, Linux, macOS, and Windows are handled by Heim.
|
||||
//!
|
||||
//! For load average, macOS and Linux are supported through Heim.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub type LoadAvgHarvest = [f32; 3];
|
170
src/app/data_harvester/cpu/heim.rs
Normal file
170
src/app/data_harvester/cpu/heim.rs
Normal file
@ -0,0 +1,170 @@
|
||||
//! CPU stats through heim.
|
||||
//! Supports macOS, Linux, and Windows.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub mod linux;
|
||||
pub use linux::*;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
|
||||
pub mod windows_macos;
|
||||
pub use windows_macos::*;
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_family = "unix")] {
|
||||
pub mod unix;
|
||||
pub use unix::*;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CpuData {
|
||||
pub cpu_prefix: String,
|
||||
pub cpu_count: Option<usize>,
|
||||
pub cpu_usage: f64,
|
||||
}
|
||||
|
||||
pub type CpuHarvest = Vec<CpuData>;
|
||||
|
||||
pub type PastCpuWork = f64;
|
||||
pub type PastCpuTotal = f64;
|
||||
|
||||
use futures::StreamExt;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub async fn get_cpu_data_list(
|
||||
show_average_cpu: bool, previous_cpu_times: &mut Vec<(PastCpuWork, PastCpuTotal)>,
|
||||
previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>,
|
||||
) -> crate::error::Result<CpuHarvest> {
|
||||
fn calculate_cpu_usage_percentage(
|
||||
(previous_working_time, previous_total_time): (f64, f64),
|
||||
(current_working_time, current_total_time): (f64, f64),
|
||||
) -> f64 {
|
||||
((if current_working_time > previous_working_time {
|
||||
current_working_time - previous_working_time
|
||||
} else {
|
||||
0.0
|
||||
}) * 100.0)
|
||||
/ (if current_total_time > previous_total_time {
|
||||
current_total_time - previous_total_time
|
||||
} else {
|
||||
1.0
|
||||
})
|
||||
}
|
||||
|
||||
// Get all CPU times...
|
||||
let cpu_times = heim::cpu::times().await?;
|
||||
futures::pin_mut!(cpu_times);
|
||||
|
||||
let mut cpu_deque: VecDeque<CpuData> = if previous_cpu_times.is_empty() {
|
||||
// Must initialize ourselves. Use a very quick timeout to calculate an initial.
|
||||
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
let second_cpu_times = heim::cpu::times().await?;
|
||||
futures::pin_mut!(second_cpu_times);
|
||||
|
||||
let mut new_cpu_times: Vec<(PastCpuWork, PastCpuTotal)> = Vec::new();
|
||||
let mut cpu_deque: VecDeque<CpuData> = VecDeque::new();
|
||||
let mut collected_zip = cpu_times.zip(second_cpu_times).enumerate(); // Gotta move it here, can't on while line.
|
||||
|
||||
while let Some((itx, (past, present))) = collected_zip.next().await {
|
||||
if let (Ok(past), Ok(present)) = (past, present) {
|
||||
let present_times = convert_cpu_times(&present);
|
||||
new_cpu_times.push(present_times);
|
||||
cpu_deque.push_back(CpuData {
|
||||
cpu_prefix: "CPU".to_string(),
|
||||
cpu_count: Some(itx),
|
||||
cpu_usage: calculate_cpu_usage_percentage(
|
||||
convert_cpu_times(&past),
|
||||
present_times,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
new_cpu_times.push((0.0, 0.0));
|
||||
cpu_deque.push_back(CpuData {
|
||||
cpu_prefix: "CPU".to_string(),
|
||||
cpu_count: Some(itx),
|
||||
cpu_usage: 0.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
*previous_cpu_times = new_cpu_times;
|
||||
cpu_deque
|
||||
} else {
|
||||
let (new_cpu_times, cpu_deque): (Vec<(PastCpuWork, PastCpuTotal)>, VecDeque<CpuData>) =
|
||||
cpu_times
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.iter()
|
||||
.zip(&*previous_cpu_times)
|
||||
.enumerate()
|
||||
.map(|(itx, (current_cpu, (past_cpu_work, past_cpu_total)))| {
|
||||
if let Ok(cpu_time) = current_cpu {
|
||||
let present_times = convert_cpu_times(cpu_time);
|
||||
|
||||
(
|
||||
present_times,
|
||||
CpuData {
|
||||
cpu_prefix: "CPU".to_string(),
|
||||
cpu_count: Some(itx),
|
||||
cpu_usage: calculate_cpu_usage_percentage(
|
||||
(*past_cpu_work, *past_cpu_total),
|
||||
present_times,
|
||||
),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(
|
||||
(*past_cpu_work, *past_cpu_total),
|
||||
CpuData {
|
||||
cpu_prefix: "CPU".to_string(),
|
||||
cpu_count: Some(itx),
|
||||
cpu_usage: 0.0,
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.unzip();
|
||||
|
||||
*previous_cpu_times = new_cpu_times;
|
||||
cpu_deque
|
||||
};
|
||||
|
||||
// Get average CPU if needed... and slap it at the top
|
||||
if show_average_cpu {
|
||||
let cpu_time = heim::cpu::time().await?;
|
||||
|
||||
let (cpu_usage, new_average_cpu_time) = if let Some((past_cpu_work, past_cpu_total)) =
|
||||
previous_average_cpu_time
|
||||
{
|
||||
let present_times = convert_cpu_times(&cpu_time);
|
||||
(
|
||||
calculate_cpu_usage_percentage((*past_cpu_work, *past_cpu_total), present_times),
|
||||
present_times,
|
||||
)
|
||||
} else {
|
||||
// Again, we need to do a quick timeout...
|
||||
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
|
||||
let second_cpu_time = heim::cpu::time().await?;
|
||||
|
||||
let present_times = convert_cpu_times(&second_cpu_time);
|
||||
(
|
||||
calculate_cpu_usage_percentage(convert_cpu_times(&cpu_time), present_times),
|
||||
present_times,
|
||||
)
|
||||
};
|
||||
|
||||
*previous_average_cpu_time = Some(new_average_cpu_time);
|
||||
cpu_deque.push_front(CpuData {
|
||||
cpu_prefix: "AVG".to_string(),
|
||||
cpu_count: None,
|
||||
cpu_usage,
|
||||
})
|
||||
}
|
||||
|
||||
// Ok(Vec::from(cpu_deque.drain(0..3).collect::<Vec<_>>())) // For artificially limiting the CPU results
|
||||
|
||||
Ok(Vec::from(cpu_deque))
|
||||
}
|
10
src/app/data_harvester/disks.rs
Normal file
10
src/app/data_harvester/disks.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Data collection for disks (IO, usage, space, etc.).
|
||||
//!
|
||||
//! For Linux, macOS, and Windows, this is handled by heim.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
}
|
||||
}
|
154
src/app/data_harvester/disks/heim.rs
Normal file
154
src/app/data_harvester/disks/heim.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use crate::app::Filter;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub mod linux;
|
||||
pub use linux::*;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
|
||||
pub mod windows_macos;
|
||||
pub use windows_macos::*;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DiskHarvest {
|
||||
pub name: String,
|
||||
pub mount_point: String,
|
||||
pub free_space: Option<u64>,
|
||||
pub used_space: Option<u64>,
|
||||
pub total_space: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IoData {
|
||||
pub read_bytes: u64,
|
||||
pub write_bytes: u64,
|
||||
}
|
||||
|
||||
pub type IoHarvest = std::collections::HashMap<String, Option<IoData>>;
|
||||
|
||||
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut io_hash: std::collections::HashMap<String, Option<IoData>> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
let counter_stream = heim::disk::io_counters().await?;
|
||||
futures::pin_mut!(counter_stream);
|
||||
|
||||
while let Some(io) = counter_stream.next().await {
|
||||
if let Ok(io) = io {
|
||||
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::byte>(),
|
||||
write_bytes: io.write_bytes().get::<heim::units::information::byte>(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(io_hash))
|
||||
}
|
||||
|
||||
pub async fn get_disk_usage(
|
||||
actually_get: bool, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
|
||||
let partitions_stream = heim::disk::partitions_physical().await?;
|
||||
futures::pin_mut!(partitions_stream);
|
||||
|
||||
while let Some(part) = partitions_stream.next().await {
|
||||
if let Ok(partition) = part {
|
||||
let name = get_device_name(&partition);
|
||||
|
||||
let mount_point = (partition
|
||||
.mount_point()
|
||||
.to_str()
|
||||
.unwrap_or("Name Unavailable"))
|
||||
.to_string();
|
||||
|
||||
// Precedence ordering in the case where name and mount filters disagree, "allow" takes precedence over "deny".
|
||||
//
|
||||
// For implementation, we do this as follows:
|
||||
// 1. Is the entry allowed through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
// 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
// 3. Anything else is allowed.
|
||||
|
||||
let filter_check_map = [(disk_filter, &name), (mount_filter, &mount_point)];
|
||||
|
||||
// This represents case 1. That is, if there is a match in an allowing list - if there is, then
|
||||
// immediately allow it!
|
||||
let matches_allow_list = filter_check_map.iter().any(|(filter, text)| {
|
||||
if let Some(filter) = filter {
|
||||
if !filter.is_list_ignored {
|
||||
for r in &filter.list {
|
||||
if r.is_match(text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
let to_keep = if matches_allow_list {
|
||||
true
|
||||
} else {
|
||||
// If it doesn't match an allow list, then check if it is denied.
|
||||
// That is, if it matches in a reject filter, then reject. Otherwise, we always keep it.
|
||||
!filter_check_map.iter().any(|(filter, text)| {
|
||||
if let Some(filter) = filter {
|
||||
if filter.is_list_ignored {
|
||||
for r in &filter.list {
|
||||
if r.is_match(text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
};
|
||||
|
||||
if to_keep {
|
||||
// The usage line can fail in some cases (for example, if you use Void Linux + LUKS,
|
||||
// see https://github.com/ClementTsang/bottom/issues/419 for details). As such, check
|
||||
// it like this instead.
|
||||
if let Ok(usage) = heim::disk::usage(partition.mount_point().to_path_buf()).await {
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: Some(usage.free().get::<heim::units::information::byte>()),
|
||||
used_space: Some(usage.used().get::<heim::units::information::byte>()),
|
||||
total_space: Some(usage.total().get::<heim::units::information::byte>()),
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: None,
|
||||
used_space: None,
|
||||
total_space: None,
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec_disks.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
Ok(Some(vec_disks))
|
||||
}
|
10
src/app/data_harvester/memory.rs
Normal file
10
src/app/data_harvester/memory.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Data collection for memory.
|
||||
//!
|
||||
//! For Linux, macOS, and Windows, this is handled by Heim.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod general;
|
||||
pub use self::general::*;
|
||||
}
|
||||
}
|
30
src/app/data_harvester/network.rs
Normal file
30
src/app/data_harvester/network.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! Data collection for network usage/IO.
|
||||
//!
|
||||
//! For Linux and macOS, this is handled by Heim.
|
||||
//! For Windows, this is handled by sysinfo.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
/// All units in bits.
|
||||
pub struct NetworkHarvest {
|
||||
pub rx: u64,
|
||||
pub tx: u64,
|
||||
pub total_rx: u64,
|
||||
pub total_tx: u64,
|
||||
}
|
||||
|
||||
impl NetworkHarvest {
|
||||
pub fn first_run_cleanup(&mut self) {
|
||||
self.rx = 0;
|
||||
self.tx = 0;
|
||||
}
|
||||
}
|
97
src/app/data_harvester/processes.rs
Normal file
97
src/app/data_harvester/processes.rs
Normal file
@ -0,0 +1,97 @@
|
||||
//! Data collection for processes.
|
||||
//!
|
||||
//! For Linux, this is handled by a custom set of functions.
|
||||
//! For Windows and macOS, this is handled by sysinfo.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub mod linux;
|
||||
pub use self::linux::*;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
pub mod macos;
|
||||
pub use self::macos::*;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
pub mod windows;
|
||||
pub use self::windows::*;
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_family = "unix")] {
|
||||
pub mod unix;
|
||||
pub use self::unix::*;
|
||||
}
|
||||
}
|
||||
|
||||
use crate::Pid;
|
||||
|
||||
// TODO: Add value so we know if it's sorted ascending or descending by default?
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum ProcessSorting {
|
||||
CpuPercent,
|
||||
Mem,
|
||||
MemPercent,
|
||||
Pid,
|
||||
ProcessName,
|
||||
Command,
|
||||
ReadPerSecond,
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
TotalWrite,
|
||||
State,
|
||||
User,
|
||||
Count,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProcessSorting {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match &self {
|
||||
ProcessSorting::CpuPercent => "CPU%",
|
||||
ProcessSorting::MemPercent => "Mem%",
|
||||
ProcessSorting::Mem => "Mem",
|
||||
ProcessSorting::ReadPerSecond => "R/s",
|
||||
ProcessSorting::WritePerSecond => "W/s",
|
||||
ProcessSorting::TotalRead => "T.Read",
|
||||
ProcessSorting::TotalWrite => "T.Write",
|
||||
ProcessSorting::State => "State",
|
||||
ProcessSorting::ProcessName => "Name",
|
||||
ProcessSorting::Command => "Command",
|
||||
ProcessSorting::Pid => "PID",
|
||||
ProcessSorting::Count => "Count",
|
||||
ProcessSorting::User => "User",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProcessSorting {
|
||||
fn default() -> Self {
|
||||
ProcessSorting::CpuPercent
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessHarvest {
|
||||
pub pid: Pid,
|
||||
pub parent_pid: Option<Pid>, // Remember, parent_pid 0 is root...
|
||||
pub cpu_usage_percent: f64,
|
||||
pub mem_usage_percent: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
// pub rss_kb: u64,
|
||||
// pub virt_kb: u64,
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
pub read_bytes_per_sec: u64,
|
||||
pub write_bytes_per_sec: u64,
|
||||
pub total_read_bytes: u64,
|
||||
pub total_write_bytes: u64,
|
||||
pub process_state: String,
|
||||
pub process_state_char: char,
|
||||
|
||||
/// This is the *effective* user ID.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub uid: Option<libc::uid_t>,
|
||||
}
|
73
src/app/data_harvester/temperature.rs
Normal file
73
src/app/data_harvester/temperature.rs
Normal file
@ -0,0 +1,73 @@
|
||||
//! Data collection for temperature metrics.
|
||||
//!
|
||||
//! For Linux and macOS, this is handled by Heim.
|
||||
//! For Windows, this is handled by sysinfo.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
}
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::app::Filter;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct TempHarvest {
|
||||
pub name: String,
|
||||
pub temperature: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TemperatureType {
|
||||
Celsius,
|
||||
Kelvin,
|
||||
Fahrenheit,
|
||||
}
|
||||
|
||||
impl Default for TemperatureType {
|
||||
fn default() -> Self {
|
||||
TemperatureType::Celsius
|
||||
}
|
||||
}
|
||||
|
||||
fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool {
|
||||
if let Some(filter) = filter {
|
||||
if filter.is_list_ignored {
|
||||
let mut ret = true;
|
||||
for r in &filter.list {
|
||||
if r.is_match(text) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) {
|
||||
// By default, sort temperature, then by alphabetically!
|
||||
// TODO: [TEMPS] Allow users to control this.
|
||||
|
||||
// Note we sort in reverse here; we want greater temps to be higher priority.
|
||||
temperature_vec.sort_by(|a, b| match a.temperature.partial_cmp(&b.temperature) {
|
||||
Some(x) => match x {
|
||||
Ordering::Less => Ordering::Greater,
|
||||
Ordering::Greater => Ordering::Less,
|
||||
Ordering::Equal => Ordering::Equal,
|
||||
},
|
||||
None => Ordering::Equal,
|
||||
});
|
||||
|
||||
temperature_vec.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap_or(Ordering::Equal));
|
||||
}
|
210
src/app/widgets.rs
Normal file
210
src/app/widgets.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
event::{EventResult, SelectionAction},
|
||||
layout_manager::BottomWidgetType,
|
||||
},
|
||||
constants,
|
||||
};
|
||||
|
||||
pub mod base;
|
||||
pub use base::*;
|
||||
|
||||
pub mod process;
|
||||
pub use process::*;
|
||||
|
||||
pub mod net;
|
||||
pub use net::*;
|
||||
|
||||
pub mod mem;
|
||||
pub use mem::*;
|
||||
|
||||
pub mod cpu;
|
||||
pub use cpu::*;
|
||||
|
||||
pub mod disk;
|
||||
pub use disk::*;
|
||||
|
||||
pub mod battery;
|
||||
pub use self::battery::*;
|
||||
|
||||
pub mod temp;
|
||||
pub use temp::*;
|
||||
|
||||
/// A trait for things that are drawn with state.
|
||||
#[enum_dispatch]
|
||||
#[allow(unused_variables)]
|
||||
pub trait Component {
|
||||
/// Handles a [`KeyEvent`].
|
||||
///
|
||||
/// Defaults to returning [`EventResult::NoRedraw`], indicating nothing should be done.
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
EventResult::NoRedraw
|
||||
}
|
||||
|
||||
/// Handles a [`MouseEvent`].
|
||||
///
|
||||
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done.
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
EventResult::NoRedraw
|
||||
}
|
||||
|
||||
/// Returns a [`Component`]'s bounding box. Note that these are defined in *global*, *absolute*
|
||||
/// coordinates.
|
||||
fn bounds(&self) -> Rect;
|
||||
|
||||
/// Updates a [`Component`]s bounding box to `new_bounds`.
|
||||
fn set_bounds(&mut self, new_bounds: Rect);
|
||||
}
|
||||
|
||||
/// A trait for actual fully-fledged widgets to be displayed in bottom.
|
||||
#[enum_dispatch]
|
||||
pub trait Widget {
|
||||
/// Updates a [`Widget`] given some data. Defaults to doing nothing.
|
||||
fn update(&mut self) {}
|
||||
|
||||
/// Handles what to do when trying to respond to a widget selection movement to the left.
|
||||
/// Defaults to just moving to the next-possible widget in that direction.
|
||||
fn handle_widget_selection_left(&mut self) -> SelectionAction {
|
||||
SelectionAction::NotHandled
|
||||
}
|
||||
|
||||
/// Handles what to do when trying to respond to a widget selection movement to the right.
|
||||
/// Defaults to just moving to the next-possible widget in that direction.
|
||||
fn handle_widget_selection_right(&mut self) -> SelectionAction {
|
||||
SelectionAction::NotHandled
|
||||
}
|
||||
|
||||
/// Handles what to do when trying to respond to a widget selection movement upward.
|
||||
/// Defaults to just moving to the next-possible widget in that direction.
|
||||
fn handle_widget_selection_up(&mut self) -> SelectionAction {
|
||||
SelectionAction::NotHandled
|
||||
}
|
||||
|
||||
/// Handles what to do when trying to respond to a widget selection movement downward.
|
||||
/// Defaults to just moving to the next-possible widget in that direction.
|
||||
fn handle_widget_selection_down(&mut self) -> SelectionAction {
|
||||
SelectionAction::NotHandled
|
||||
}
|
||||
|
||||
fn get_pretty_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// The "main" widgets that are used by bottom to display information!
|
||||
#[enum_dispatch(Component, Widget)]
|
||||
pub enum TmpBottomWidget {
|
||||
MemGraph,
|
||||
TempTable,
|
||||
DiskTable,
|
||||
CpuGraph,
|
||||
NetGraph,
|
||||
OldNetGraph,
|
||||
ProcessManager,
|
||||
BatteryTable,
|
||||
}
|
||||
|
||||
// ----- Old stuff below -----
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScrollDirection {
|
||||
// UP means scrolling up --- this usually DECREMENTS
|
||||
Up,
|
||||
// DOWN means scrolling down --- this usually INCREMENTS
|
||||
Down,
|
||||
}
|
||||
|
||||
impl Default for ScrollDirection {
|
||||
fn default() -> Self {
|
||||
ScrollDirection::Down
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CursorDirection {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
/// AppScrollWidgetState deals with fields for a scrollable app's current state.
|
||||
#[derive(Default)]
|
||||
pub struct AppScrollWidgetState {
|
||||
pub current_scroll_position: usize,
|
||||
pub previous_scroll_position: usize,
|
||||
pub scroll_direction: ScrollDirection,
|
||||
pub table_state: TableState,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum KillSignal {
|
||||
Cancel,
|
||||
Kill(usize),
|
||||
}
|
||||
|
||||
impl Default for KillSignal {
|
||||
#[cfg(target_family = "unix")]
|
||||
fn default() -> Self {
|
||||
KillSignal::Kill(15)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
fn default() -> Self {
|
||||
KillSignal::Kill(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppDeleteDialogState {
|
||||
pub is_showing_dd: bool,
|
||||
pub selected_signal: KillSignal,
|
||||
/// tl x, tl y, br x, br y, index/signal
|
||||
pub button_positions: Vec<(u16, u16, u16, u16, usize)>,
|
||||
pub keyboard_signal_select: usize,
|
||||
pub last_number_press: Option<Instant>,
|
||||
pub scroll_pos: usize,
|
||||
}
|
||||
|
||||
pub struct AppHelpDialogState {
|
||||
pub is_showing_help: bool,
|
||||
pub scroll_state: ParagraphScrollState,
|
||||
pub index_shortcuts: Vec<u16>,
|
||||
}
|
||||
|
||||
impl Default for AppHelpDialogState {
|
||||
fn default() -> Self {
|
||||
AppHelpDialogState {
|
||||
is_showing_help: false,
|
||||
scroll_state: ParagraphScrollState::default(),
|
||||
index_shortcuts: vec![0; constants::HELP_TEXT.len()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Meant for canvas operations involving table column widths.
|
||||
#[derive(Default)]
|
||||
pub struct CanvasTableWidthState {
|
||||
pub desired_column_widths: Vec<u16>,
|
||||
pub calculated_column_widths: Vec<u16>,
|
||||
}
|
||||
|
||||
pub struct BasicTableWidgetState {
|
||||
// Since this is intended (currently) to only be used for ONE widget, that's
|
||||
// how it's going to be written. If we want to allow for multiple of these,
|
||||
// then we can expand outwards with a normal BasicTableState and a hashmap
|
||||
pub currently_displayed_widget_type: BottomWidgetType,
|
||||
pub currently_displayed_widget_id: u64,
|
||||
pub widget_id: i64,
|
||||
pub left_tlc: Option<(u16, u16)>,
|
||||
pub left_brc: Option<(u16, u16)>,
|
||||
pub right_tlc: Option<(u16, u16)>,
|
||||
pub right_brc: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ParagraphScrollState {
|
||||
pub current_scroll_index: u16,
|
||||
pub max_scroll_index: u16,
|
||||
}
|
16
src/app/widgets/base.rs
Normal file
16
src/app/widgets/base.rs
Normal file
@ -0,0 +1,16 @@
|
||||
//! A collection of basic components.
|
||||
|
||||
pub mod text_table;
|
||||
pub use text_table::TextTable;
|
||||
|
||||
pub mod time_graph;
|
||||
pub use time_graph::TimeGraph;
|
||||
|
||||
pub mod scrollable;
|
||||
pub use scrollable::Scrollable;
|
||||
|
||||
pub mod text_input;
|
||||
pub use text_input::TextInput;
|
||||
|
||||
pub mod carousel;
|
||||
pub use carousel::Carousel;
|
Loading…
x
Reference in New Issue
Block a user