Merge pull request #60 from ClementTsang/simple_mode

Simple mode
This commit is contained in:
Clement Tsang 2020-03-05 00:11:04 -05:00 committed by GitHub
commit 22cdc005bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 5814 additions and 5032 deletions

View file

@ -30,6 +30,8 @@ Features of bottom include:
- Maximizing of widgets of interest to take up the entire window.
- Basic mode
More details about each widget and compatibility can be found [here](./docs/widgets.md).
## Config files
@ -75,7 +77,7 @@ sudo dpkg -i bottom_0.2.2_amd64.deb
### Windows
You can get release versions via [Chocolatey](https://chocolatey.org/packages/bottom/):
You can get release versions via [Chocolatey](https://chocolatey.org/packages/bottom/) (note it may take a while to be available due to moderation/review):
```bash
choco install bottom --version=0.2.1
@ -86,10 +88,10 @@ choco install bottom --version=0.2.1
You can get release versions using Homebrew:
```bash
$ brew tap clementtsang/bottom
$ brew install bottom
brew tap clementtsang/bottom
brew install bottom
# Or
$ brew install clementtsang/bottom/bottom
brew install clementtsang/bottom/bottom
```
## Usage
@ -134,6 +136,8 @@ Run using `btm`.
- `-C`, `--config` takes in a file path leading to a TOML file. If doesn't exist, creates one.
- `-b`, `--basic` will enable basic mode, removing all graphs from the main interface and condensing data.
### Keybindings
#### General

View file

@ -3,7 +3,6 @@ max_width = 100
newline_style = "Unix"
reorder_imports = true
fn_args_layout = "Compressed"
hard_tabs = true
merge_derives = true
reorder_modules = true
tab_spaces = 4

View file

@ -34,6 +34,9 @@
#default_widget = "network_default"
#default_widget = "process_default"
# Basic mode
#basic = true
# These are all the components that support custom theming. Currently, it only
# supports taking in a string representing a hex colour. Note that colour support

2472
src/app.rs

File diff suppressed because it is too large Load diff

View file

@ -23,14 +23,14 @@ pub type JoinedDataPoints = (Value, Vec<(TimeOffset, Value)>);
#[derive(Debug, Default)]
pub struct TimedData {
pub rx_data: JoinedDataPoints,
pub tx_data: JoinedDataPoints,
pub cpu_data: Vec<JoinedDataPoints>,
pub mem_data: JoinedDataPoints,
pub swap_data: JoinedDataPoints,
// Unused for now
// pub io_data : JoinedDataPoints
// pub temp_data: JoinedDataPoints,
pub rx_data: JoinedDataPoints,
pub tx_data: JoinedDataPoints,
pub cpu_data: Vec<JoinedDataPoints>,
pub mem_data: JoinedDataPoints,
pub swap_data: JoinedDataPoints,
// Unused for now
// pub io_data : JoinedDataPoints
// pub temp_data: JoinedDataPoints,
}
/// AppCollection represents the pooled data stored within the main app
@ -44,253 +44,253 @@ pub struct TimedData {
/// not the data collector.
#[derive(Debug)]
pub struct DataCollection {
pub current_instant: Instant,
pub timed_data_vec: Vec<(Instant, TimedData)>,
pub network_harvest: network::NetworkHarvest,
pub memory_harvest: mem::MemHarvest,
pub swap_harvest: mem::MemHarvest,
pub cpu_harvest: cpu::CPUHarvest,
pub process_harvest: Vec<processes::ProcessHarvest>,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IOHarvest,
pub io_labels: Vec<(u64, u64)>,
io_prev: Vec<(u64, u64)>,
pub temp_harvest: Vec<temperature::TempHarvest>,
pub current_instant: Instant,
pub timed_data_vec: Vec<(Instant, TimedData)>,
pub network_harvest: network::NetworkHarvest,
pub memory_harvest: mem::MemHarvest,
pub swap_harvest: mem::MemHarvest,
pub cpu_harvest: cpu::CPUHarvest,
pub process_harvest: Vec<processes::ProcessHarvest>,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IOHarvest,
pub io_labels: Vec<(u64, u64)>,
io_prev: Vec<(u64, u64)>,
pub temp_harvest: Vec<temperature::TempHarvest>,
}
impl Default for DataCollection {
fn default() -> Self {
DataCollection {
current_instant: Instant::now(),
timed_data_vec: Vec::default(),
network_harvest: network::NetworkHarvest::default(),
memory_harvest: mem::MemHarvest::default(),
swap_harvest: mem::MemHarvest::default(),
cpu_harvest: cpu::CPUHarvest::default(),
process_harvest: Vec::default(),
disk_harvest: Vec::default(),
io_harvest: disks::IOHarvest::default(),
io_labels: Vec::default(),
io_prev: Vec::default(),
temp_harvest: Vec::default(),
}
}
fn default() -> Self {
DataCollection {
current_instant: Instant::now(),
timed_data_vec: Vec::default(),
network_harvest: network::NetworkHarvest::default(),
memory_harvest: mem::MemHarvest::default(),
swap_harvest: mem::MemHarvest::default(),
cpu_harvest: cpu::CPUHarvest::default(),
process_harvest: Vec::default(),
disk_harvest: Vec::default(),
io_harvest: disks::IOHarvest::default(),
io_labels: Vec::default(),
io_prev: Vec::default(),
temp_harvest: Vec::default(),
}
}
}
impl DataCollection {
pub fn clean_data(&mut self, max_time_millis: u128) {
let current_time = Instant::now();
pub fn clean_data(&mut self, max_time_millis: u128) {
let current_time = Instant::now();
let mut remove_index = 0;
for entry in &self.timed_data_vec {
if current_time.duration_since(entry.0).as_millis() >= max_time_millis {
remove_index += 1;
} else {
break;
}
}
let mut remove_index = 0;
for entry in &self.timed_data_vec {
if current_time.duration_since(entry.0).as_millis() >= max_time_millis {
remove_index += 1;
} else {
break;
}
}
self.timed_data_vec.drain(0..remove_index);
}
self.timed_data_vec.drain(0..remove_index);
}
pub fn eat_data(&mut self, harvested_data: &Data) {
let harvested_time = harvested_data.last_collection_time;
let mut new_entry = TimedData::default();
pub fn eat_data(&mut self, harvested_data: &Data) {
let harvested_time = harvested_data.last_collection_time;
let mut new_entry = TimedData::default();
// Network
self.eat_network(&harvested_data, harvested_time, &mut new_entry);
// Network
self.eat_network(&harvested_data, harvested_time, &mut new_entry);
// Memory and Swap
self.eat_memory_and_swap(&harvested_data, harvested_time, &mut new_entry);
// Memory and Swap
self.eat_memory_and_swap(&harvested_data, harvested_time, &mut new_entry);
// CPU
self.eat_cpu(&harvested_data, harvested_time, &mut new_entry);
// CPU
self.eat_cpu(&harvested_data, harvested_time, &mut new_entry);
// Temp
self.eat_temp(&harvested_data);
// Temp
self.eat_temp(&harvested_data);
// Disks
self.eat_disks(&harvested_data, harvested_time);
// Disks
self.eat_disks(&harvested_data, harvested_time);
// Processes
self.eat_proc(&harvested_data);
// Processes
self.eat_proc(&harvested_data);
// And we're done eating. Update time and push the new entry!
self.current_instant = harvested_time;
self.timed_data_vec.push((harvested_time, new_entry));
}
// And we're done eating. Update time and push the new entry!
self.current_instant = harvested_time;
self.timed_data_vec.push((harvested_time, new_entry));
}
fn eat_memory_and_swap(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// Memory
let mem_percent = harvested_data.memory.mem_used_in_mb as f64
/ harvested_data.memory.mem_total_in_mb as f64
* 100.0;
let mem_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.mem_data.0, harvested_time, mem_percent)
} else {
Vec::new()
};
let mem_pt = (mem_percent, mem_joining_pts);
new_entry.mem_data = mem_pt;
fn eat_memory_and_swap(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// Memory
let mem_percent = harvested_data.memory.mem_used_in_mb as f64
/ harvested_data.memory.mem_total_in_mb as f64
* 100.0;
let mem_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.mem_data.0, harvested_time, mem_percent)
} else {
Vec::new()
};
let mem_pt = (mem_percent, mem_joining_pts);
new_entry.mem_data = mem_pt;
// Swap
if harvested_data.swap.mem_total_in_mb > 0 {
let swap_percent = harvested_data.swap.mem_used_in_mb as f64
/ harvested_data.swap.mem_total_in_mb as f64
* 100.0;
let swap_joining_pt = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.swap_data.0, harvested_time, swap_percent)
} else {
Vec::new()
};
let swap_pt = (swap_percent, swap_joining_pt);
new_entry.swap_data = swap_pt;
}
// Swap
if harvested_data.swap.mem_total_in_mb > 0 {
let swap_percent = harvested_data.swap.mem_used_in_mb as f64
/ harvested_data.swap.mem_total_in_mb as f64
* 100.0;
let swap_joining_pt = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.swap_data.0, harvested_time, swap_percent)
} else {
Vec::new()
};
let swap_pt = (swap_percent, swap_joining_pt);
new_entry.swap_data = swap_pt;
}
// In addition copy over latest data for easy reference
self.memory_harvest = harvested_data.memory.clone();
self.swap_harvest = harvested_data.swap.clone();
}
// In addition copy over latest data for easy reference
self.memory_harvest = harvested_data.memory.clone();
self.swap_harvest = harvested_data.swap.clone();
}
fn eat_network(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// RX
let logged_rx_val = if harvested_data.network.rx as f64 > 0.0 {
(harvested_data.network.rx as f64).log(2.0)
} else {
0.0
};
fn eat_network(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// RX
let logged_rx_val = if harvested_data.network.rx as f64 > 0.0 {
(harvested_data.network.rx as f64).log(2.0)
} else {
0.0
};
let rx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.rx_data.0, harvested_time, logged_rx_val)
} else {
Vec::new()
};
let rx_pt = (logged_rx_val, rx_joining_pts);
new_entry.rx_data = rx_pt;
let rx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.rx_data.0, harvested_time, logged_rx_val)
} else {
Vec::new()
};
let rx_pt = (logged_rx_val, rx_joining_pts);
new_entry.rx_data = rx_pt;
// TX
let logged_tx_val = if harvested_data.network.tx as f64 > 0.0 {
(harvested_data.network.tx as f64).log(2.0)
} else {
0.0
};
// TX
let logged_tx_val = if harvested_data.network.tx as f64 > 0.0 {
(harvested_data.network.tx as f64).log(2.0)
} else {
0.0
};
let tx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.tx_data.0, harvested_time, logged_tx_val)
} else {
Vec::new()
};
let tx_pt = (logged_tx_val, tx_joining_pts);
new_entry.tx_data = tx_pt;
let tx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.tx_data.0, harvested_time, logged_tx_val)
} else {
Vec::new()
};
let tx_pt = (logged_tx_val, tx_joining_pts);
new_entry.tx_data = tx_pt;
// In addition copy over latest data for easy reference
self.network_harvest = harvested_data.network.clone();
}
// In addition copy over latest data for easy reference
self.network_harvest = harvested_data.network.clone();
}
fn eat_cpu(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// Note this only pre-calculates the data points - the names will be
// within the local copy of cpu_harvest. Since it's all sequential
// it probably doesn't matter anyways.
for (itx, cpu) in harvested_data.cpu.iter().enumerate() {
let cpu_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(
*time,
last_pt.cpu_data[itx].0,
harvested_time,
cpu.cpu_usage,
)
} else {
Vec::new()
};
fn eat_cpu(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
) {
// Note this only pre-calculates the data points - the names will be
// within the local copy of cpu_harvest. Since it's all sequential
// it probably doesn't matter anyways.
for (itx, cpu) in harvested_data.cpu.iter().enumerate() {
let cpu_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(
*time,
last_pt.cpu_data[itx].0,
harvested_time,
cpu.cpu_usage,
)
} else {
Vec::new()
};
let cpu_pt = (cpu.cpu_usage, cpu_joining_pts);
new_entry.cpu_data.push(cpu_pt);
}
let cpu_pt = (cpu.cpu_usage, cpu_joining_pts);
new_entry.cpu_data.push(cpu_pt);
}
self.cpu_harvest = harvested_data.cpu.clone();
}
self.cpu_harvest = harvested_data.cpu.clone();
}
fn eat_temp(&mut self, harvested_data: &Data) {
// TODO: [PO] To implement
self.temp_harvest = harvested_data.temperature_sensors.clone();
}
fn eat_temp(&mut self, harvested_data: &Data) {
// TODO: [PO] To implement
self.temp_harvest = harvested_data.temperature_sensors.clone();
}
fn eat_disks(&mut self, harvested_data: &Data, harvested_time: Instant) {
// TODO: [PO] To implement
fn eat_disks(&mut self, harvested_data: &Data, harvested_time: Instant) {
// TODO: [PO] To implement
let time_since_last_harvest = harvested_time
.duration_since(self.current_instant)
.as_secs_f64();
let time_since_last_harvest = harvested_time
.duration_since(self.current_instant)
.as_secs_f64();
for (itx, device) in harvested_data.disks.iter().enumerate() {
if let Some(trim) = device.name.split('/').last() {
let io_device = harvested_data.io.get(trim);
if let Some(io) = io_device {
let io_r_pt = io.read_bytes;
let io_w_pt = io.write_bytes;
for (itx, device) in harvested_data.disks.iter().enumerate() {
if let Some(trim) = device.name.split('/').last() {
let io_device = harvested_data.io.get(trim);
if let Some(io) = io_device {
let io_r_pt = io.read_bytes;
let io_w_pt = io.write_bytes;
if self.io_labels.len() <= itx {
self.io_prev.push((io_r_pt, io_w_pt));
self.io_labels.push((0, 0));
} else {
let r_rate = ((io_r_pt - self.io_prev[itx].0) as f64
/ time_since_last_harvest)
.round() as u64;
let w_rate = ((io_w_pt - self.io_prev[itx].1) as f64
/ time_since_last_harvest)
.round() as u64;
if self.io_labels.len() <= itx {
self.io_prev.push((io_r_pt, io_w_pt));
self.io_labels.push((0, 0));
} else {
let r_rate = ((io_r_pt - self.io_prev[itx].0) as f64
/ time_since_last_harvest)
.round() as u64;
let w_rate = ((io_w_pt - self.io_prev[itx].1) as f64
/ time_since_last_harvest)
.round() as u64;
self.io_labels[itx] = (r_rate, w_rate);
self.io_prev[itx] = (io_r_pt, io_w_pt);
}
}
}
}
self.io_labels[itx] = (r_rate, w_rate);
self.io_prev[itx] = (io_r_pt, io_w_pt);
}
}
}
}
self.disk_harvest = harvested_data.disks.clone();
self.io_harvest = harvested_data.io.clone();
}
self.disk_harvest = harvested_data.disks.clone();
self.io_harvest = harvested_data.io.clone();
}
fn eat_proc(&mut self, harvested_data: &Data) {
self.process_harvest = harvested_data.list_of_processes.clone();
}
fn eat_proc(&mut self, harvested_data: &Data) {
self.process_harvest = harvested_data.list_of_processes.clone();
}
}
pub fn generate_joining_points(
start_x: Instant, start_y: f64, end_x: Instant, end_y: f64,
start_x: Instant, start_y: f64, end_x: Instant, end_y: f64,
) -> Vec<(TimeOffset, Value)> {
let mut points: Vec<(TimeOffset, Value)> = Vec::new();
let mut points: Vec<(TimeOffset, Value)> = Vec::new();
// Convert time floats first:
let tmp_time_diff = (end_x).duration_since(start_x).as_millis() as f64;
let time_difference = if tmp_time_diff == 0.0 {
0.001
} else {
tmp_time_diff
};
let value_difference = end_y - start_y;
// Convert time floats first:
let tmp_time_diff = (end_x).duration_since(start_x).as_millis() as f64;
let time_difference = if tmp_time_diff == 0.0 {
0.001
} else {
tmp_time_diff
};
let value_difference = end_y - start_y;
// Let's generate... about this many points!
let num_points = std::cmp::min(
std::cmp::max(
(value_difference.abs() / time_difference * 2000.0) as u64,
50,
),
2000,
);
// Let's generate... about this many points!
let num_points = std::cmp::min(
std::cmp::max(
(value_difference.abs() / time_difference * 2000.0) as u64,
50,
),
2000,
);
for itx in (0..num_points).step_by(2) {
points.push((
time_difference - (itx as f64 / num_points as f64 * time_difference),
start_y + (itx as f64 / num_points as f64 * value_difference),
));
}
for itx in (0..num_points).step_by(2) {
points.push((
time_difference - (itx as f64 / num_points as f64 * time_difference),
start_y + (itx as f64 / num_points as f64 * value_difference),
));
}
points
points
}

View file

@ -13,176 +13,176 @@ pub mod temperature;
#[derive(Clone, Debug)]
pub struct Data {
pub cpu: cpu::CPUHarvest,
pub memory: mem::MemHarvest,
pub swap: mem::MemHarvest,
pub temperature_sensors: Vec<temperature::TempHarvest>,
pub network: network::NetworkHarvest,
pub list_of_processes: Vec<processes::ProcessHarvest>,
pub disks: Vec<disks::DiskHarvest>,
pub io: disks::IOHarvest,
pub last_collection_time: Instant,
pub cpu: cpu::CPUHarvest,
pub memory: mem::MemHarvest,
pub swap: mem::MemHarvest,
pub temperature_sensors: Vec<temperature::TempHarvest>,
pub network: network::NetworkHarvest,
pub list_of_processes: Vec<processes::ProcessHarvest>,
pub disks: Vec<disks::DiskHarvest>,
pub io: disks::IOHarvest,
pub last_collection_time: Instant,
}
impl Default for Data {
fn default() -> Self {
Data {
cpu: cpu::CPUHarvest::default(),
memory: mem::MemHarvest::default(),
swap: mem::MemHarvest::default(),
temperature_sensors: Vec::default(),
list_of_processes: Vec::default(),
disks: Vec::default(),
io: disks::IOHarvest::default(),
network: network::NetworkHarvest::default(),
last_collection_time: Instant::now(),
}
}
fn default() -> Self {
Data {
cpu: cpu::CPUHarvest::default(),
memory: mem::MemHarvest::default(),
swap: mem::MemHarvest::default(),
temperature_sensors: Vec::default(),
list_of_processes: Vec::default(),
disks: Vec::default(),
io: disks::IOHarvest::default(),
network: network::NetworkHarvest::default(),
last_collection_time: Instant::now(),
}
}
}
impl Data {
pub fn first_run_cleanup(&mut self) {
self.io = disks::IOHarvest::default();
self.temperature_sensors = Vec::new();
self.list_of_processes = Vec::new();
self.disks = Vec::new();
pub fn first_run_cleanup(&mut self) {
self.io = disks::IOHarvest::default();
self.temperature_sensors = Vec::new();
self.list_of_processes = Vec::new();
self.disks = Vec::new();
self.network.first_run_cleanup();
self.memory = mem::MemHarvest::default();
self.swap = mem::MemHarvest::default();
self.cpu = cpu::CPUHarvest::default();
}
self.network.first_run_cleanup();
self.memory = mem::MemHarvest::default();
self.swap = mem::MemHarvest::default();
self.cpu = cpu::CPUHarvest::default();
}
}
pub struct DataState {
pub data: Data,
sys: System,
prev_pid_stats: HashMap<String, (f64, Instant)>,
prev_idle: f64,
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,
pub data: Data,
sys: System,
prev_pid_stats: HashMap<String, (f64, Instant)>,
prev_idle: f64,
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,
}
impl Default for DataState {
fn default() -> Self {
DataState {
data: Data::default(),
sys: System::new_all(),
prev_pid_stats: HashMap::new(),
prev_idle: 0_f64,
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,
}
}
fn default() -> Self {
DataState {
data: Data::default(),
sys: System::new_all(),
prev_pid_stats: HashMap::new(),
prev_idle: 0_f64,
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,
}
}
}
impl DataState {
pub fn set_temperature_type(&mut self, temperature_type: temperature::TemperatureType) {
self.temperature_type = temperature_type;
}
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_use_current_cpu_total(&mut self, use_current_cpu_total: bool) {
self.use_current_cpu_total = use_current_cpu_total;
}
pub fn init(&mut self) {
self.mem_total_kb = self.sys.get_total_memory();
futures::executor::block_on(self.update_data());
std::thread::sleep(std::time::Duration::from_millis(250));
self.data.first_run_cleanup();
}
pub fn init(&mut self) {
self.mem_total_kb = self.sys.get_total_memory();
futures::executor::block_on(self.update_data());
std::thread::sleep(std::time::Duration::from_millis(250));
self.data.first_run_cleanup();
}
pub async fn update_data(&mut self) {
self.sys.refresh_system();
pub async fn update_data(&mut self) {
self.sys.refresh_system();
if cfg!(not(target_os = "linux")) {
self.sys.refresh_processes();
self.sys.refresh_components();
}
if cfg!(target_os = "windows") {
self.sys.refresh_networks();
}
if cfg!(not(target_os = "linux")) {
self.sys.refresh_processes();
self.sys.refresh_components();
}
if cfg!(target_os = "windows") {
self.sys.refresh_networks();
}
let current_instant = std::time::Instant::now();
let current_instant = std::time::Instant::now();
// CPU
self.data.cpu = cpu::get_cpu_data_list(&self.sys);
// CPU
self.data.cpu = cpu::get_cpu_data_list(&self.sys);
// Processes. This is the longest part of the harvesting process... changing this might be
// good in the future. What was tried already:
// * Splitting the internal part into multiple scoped threads (dropped by ~.01 seconds, but upped usage)
if let Ok(process_list) = processes::get_sorted_processes_list(
&self.sys,
&mut self.prev_idle,
&mut self.prev_non_idle,
&mut self.prev_pid_stats,
self.use_current_cpu_total,
self.mem_total_kb,
current_instant,
) {
self.data.list_of_processes = process_list;
}
// Processes. This is the longest part of the harvesting process... changing this might be
// good in the future. What was tried already:
// * Splitting the internal part into multiple scoped threads (dropped by ~.01 seconds, but upped usage)
if let Ok(process_list) = processes::get_sorted_processes_list(
&self.sys,
&mut self.prev_idle,
&mut self.prev_non_idle,
&mut self.prev_pid_stats,
self.use_current_cpu_total,
self.mem_total_kb,
current_instant,
) {
self.data.list_of_processes = process_list;
}
// ASYNC
let network_data_fut = network::get_network_data(
&self.sys,
self.last_collection_time,
&mut self.total_rx,
&mut self.total_tx,
current_instant,
);
// ASYNC
let network_data_fut = network::get_network_data(
&self.sys,
self.last_collection_time,
&mut self.total_rx,
&mut self.total_tx,
current_instant,
);
let mem_data_fut = mem::get_mem_data_list();
let swap_data_fut = mem::get_swap_data_list();
let disk_data_fut = disks::get_disk_usage_list();
let disk_io_usage_fut = disks::get_io_usage_list(false);
let temp_data_fut = temperature::get_temperature_data(&self.sys, &self.temperature_type);
let mem_data_fut = mem::get_mem_data_list();
let swap_data_fut = mem::get_swap_data_list();
let disk_data_fut = disks::get_disk_usage_list();
let disk_io_usage_fut = disks::get_io_usage_list(false);
let temp_data_fut = temperature::get_temperature_data(&self.sys, &self.temperature_type);
let (net_data, mem_res, swap_res, disk_res, io_res, temp_res) = join!(
network_data_fut,
mem_data_fut,
swap_data_fut,
disk_data_fut,
disk_io_usage_fut,
temp_data_fut
);
let (net_data, mem_res, swap_res, disk_res, io_res, temp_res) = join!(
network_data_fut,
mem_data_fut,
swap_data_fut,
disk_data_fut,
disk_io_usage_fut,
temp_data_fut
);
// After async
self.data.network = net_data;
self.total_rx = self.data.network.total_rx;
self.total_tx = self.data.network.total_tx;
// After async
self.data.network = net_data;
self.total_rx = self.data.network.total_rx;
self.total_tx = self.data.network.total_tx;
if let Ok(memory) = mem_res {
self.data.memory = memory;
}
if let Ok(memory) = mem_res {
self.data.memory = memory;
}
if let Ok(swap) = swap_res {
self.data.swap = swap;
}
if let Ok(swap) = swap_res {
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(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;
}
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;
}
// Update time
self.data.last_collection_time = current_instant;
self.last_collection_time = current_instant;
}
}

View file

@ -2,26 +2,26 @@ use sysinfo::{ProcessorExt, System, SystemExt};
#[derive(Default, Debug, Clone)]
pub struct CPUData {
pub cpu_name: String,
pub cpu_usage: f64,
pub cpu_name: String,
pub cpu_usage: f64,
}
pub type CPUHarvest = Vec<CPUData>;
pub fn get_cpu_data_list(sys: &System) -> CPUHarvest {
let cpu_data = sys.get_processors();
let avg_cpu_usage = sys.get_global_processor_info().get_cpu_usage();
let mut cpu_vec = vec![CPUData {
cpu_name: "AVG".to_string(),
cpu_usage: avg_cpu_usage as f64,
}];
let cpu_data = sys.get_processors();
let avg_cpu_usage = sys.get_global_processor_info().get_cpu_usage();
let mut cpu_vec = vec![CPUData {
cpu_name: "AVG".to_string(),
cpu_usage: avg_cpu_usage as f64,
}];
for cpu in cpu_data {
cpu_vec.push(CPUData {
cpu_name: cpu.get_name().to_uppercase(),
cpu_usage: f64::from(cpu.get_cpu_usage()),
});
}
for cpu in cpu_data {
cpu_vec.push(CPUData {
cpu_name: cpu.get_name().to_uppercase(),
cpu_usage: f64::from(cpu.get_cpu_usage()),
});
}
cpu_vec
cpu_vec
}

View file

@ -3,83 +3,83 @@ use heim::units::information;
#[derive(Debug, Clone, Default)]
pub struct DiskHarvest {
pub name: String,
pub mount_point: String,
pub free_space: u64,
pub used_space: u64,
pub total_space: u64,
pub name: String,
pub mount_point: String,
pub free_space: u64,
pub used_space: u64,
pub total_space: u64,
}
#[derive(Clone, Debug)]
pub struct IOData {
pub read_bytes: u64,
pub write_bytes: u64,
pub read_bytes: u64,
pub write_bytes: u64,
}
pub type IOHarvest = std::collections::HashMap<String, IOData>;
pub async fn get_io_usage_list(get_physical: bool) -> crate::utils::error::Result<IOHarvest> {
let mut io_hash: std::collections::HashMap<String, IOData> = std::collections::HashMap::new();
if get_physical {
let mut physical_counter_stream = heim::disk::io_counters_physical();
while let Some(io) = physical_counter_stream.next().await {
let io = io?;
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
IOData {
read_bytes: io.read_bytes().get::<information::megabyte>(),
write_bytes: io.write_bytes().get::<information::megabyte>(),
},
);
}
} else {
let mut counter_stream = heim::disk::io_counters();
while let Some(io) = counter_stream.next().await {
let io = io?;
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
IOData {
read_bytes: io.read_bytes().get::<information::byte>(),
write_bytes: io.write_bytes().get::<information::byte>(),
},
);
}
}
let mut io_hash: std::collections::HashMap<String, IOData> = std::collections::HashMap::new();
if get_physical {
let mut physical_counter_stream = heim::disk::io_counters_physical();
while let Some(io) = physical_counter_stream.next().await {
let io = io?;
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
IOData {
read_bytes: io.read_bytes().get::<information::megabyte>(),
write_bytes: io.write_bytes().get::<information::megabyte>(),
},
);
}
} else {
let mut counter_stream = heim::disk::io_counters();
while let Some(io) = counter_stream.next().await {
let io = io?;
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
IOData {
read_bytes: io.read_bytes().get::<information::byte>(),
write_bytes: io.write_bytes().get::<information::byte>(),
},
);
}
}
Ok(io_hash)
Ok(io_hash)
}
pub async fn get_disk_usage_list() -> crate::utils::error::Result<Vec<DiskHarvest>> {
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
let mut partitions_stream = heim::disk::partitions_physical();
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
let mut partitions_stream = heim::disk::partitions_physical();
while let Some(part) = partitions_stream.next().await {
if let Ok(part) = part {
let partition = part;
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
while let Some(part) = partitions_stream.next().await {
if let Ok(part) = part {
let partition = part;
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
vec_disks.push(DiskHarvest {
free_space: usage.free().get::<information::byte>(),
used_space: usage.used().get::<information::byte>(),
total_space: usage.total().get::<information::byte>(),
mount_point: (partition
.mount_point()
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
name: (partition
.device()
.unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable"))
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
});
}
}
vec_disks.push(DiskHarvest {
free_space: usage.free().get::<information::byte>(),
used_space: usage.used().get::<information::byte>(),
total_space: usage.total().get::<information::byte>(),
mount_point: (partition
.mount_point()
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
name: (partition
.device()
.unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable"))
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
});
}
}
vec_disks.sort_by(|a, b| a.name.cmp(&b.name));
vec_disks.sort_by(|a, b| a.name.cmp(&b.name));
Ok(vec_disks)
Ok(vec_disks)
}

View file

@ -2,34 +2,34 @@ use heim::units::information;
#[derive(Debug, Clone)]
pub struct MemHarvest {
pub mem_total_in_mb: u64,
pub mem_used_in_mb: u64,
pub mem_total_in_mb: u64,
pub mem_used_in_mb: u64,
}
impl Default for MemHarvest {
fn default() -> Self {
MemHarvest {
mem_total_in_mb: 0,
mem_used_in_mb: 0,
}
}
fn default() -> Self {
MemHarvest {
mem_total_in_mb: 0,
mem_used_in_mb: 0,
}
}
}
pub async fn get_mem_data_list() -> crate::utils::error::Result<MemHarvest> {
let memory = heim::memory::memory().await?;
let memory = heim::memory::memory().await?;
Ok(MemHarvest {
mem_total_in_mb: memory.total().get::<information::megabyte>(),
mem_used_in_mb: memory.total().get::<information::megabyte>()
- memory.available().get::<information::megabyte>(),
})
Ok(MemHarvest {
mem_total_in_mb: memory.total().get::<information::megabyte>(),
mem_used_in_mb: memory.total().get::<information::megabyte>()
- memory.available().get::<information::megabyte>(),
})
}
pub async fn get_swap_data_list() -> crate::utils::error::Result<MemHarvest> {
let memory = heim::memory::swap().await?;
let memory = heim::memory::swap().await?;
Ok(MemHarvest {
mem_total_in_mb: memory.total().get::<information::megabyte>(),
mem_used_in_mb: memory.used().get::<information::megabyte>(),
})
Ok(MemHarvest {
mem_total_in_mb: memory.total().get::<information::megabyte>(),
mem_used_in_mb: memory.used().get::<information::megabyte>(),
})
}

View file

@ -7,53 +7,53 @@ use sysinfo::{NetworkExt, System, SystemExt};
#[derive(Default, Clone, Debug)]
pub struct NetworkHarvest {
pub rx: u64,
pub tx: u64,
pub total_rx: u64,
pub total_tx: u64,
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;
}
pub fn first_run_cleanup(&mut self) {
self.rx = 0;
self.tx = 0;
}
}
pub async fn get_network_data(
sys: &System, prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64,
curr_time: Instant,
sys: &System, prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64,
curr_time: Instant,
) -> NetworkHarvest {
let mut io_data = net::io_counters();
let mut total_rx: u64 = 0;
let mut total_tx: u64 = 0;
let mut io_data = net::io_counters();
let mut total_rx: u64 = 0;
let mut total_tx: u64 = 0;
if cfg!(target_os = "windows") {
let networks = sys.get_networks();
for (_, network) in networks {
total_rx += network.get_total_income();
total_tx += network.get_total_outcome();
}
} else {
while let Some(io) = io_data.next().await {
if let Ok(io) = io {
total_rx += io.bytes_recv().get::<byte>();
total_tx += io.bytes_sent().get::<byte>();
}
}
}
if cfg!(target_os = "windows") {
let networks = sys.get_networks();
for (_, network) in networks {
total_rx += network.get_total_income();
total_tx += network.get_total_outcome();
}
} else {
while let Some(io) = io_data.next().await {
if let Ok(io) = io {
total_rx += io.bytes_recv().get::<byte>();
total_tx += io.bytes_sent().get::<byte>();
}
}
}
let elapsed_time = curr_time.duration_since(prev_net_access_time).as_secs_f64();
let elapsed_time = curr_time.duration_since(prev_net_access_time).as_secs_f64();
let rx = ((total_rx - *prev_net_rx) as f64 / elapsed_time) as u64;
let tx = ((total_tx - *prev_net_tx) as f64 / elapsed_time) as u64;
let rx = ((total_rx - *prev_net_rx) as f64 / elapsed_time) as u64;
let tx = ((total_tx - *prev_net_tx) as f64 / elapsed_time) as u64;
*prev_net_rx = total_rx;
*prev_net_tx = total_tx;
NetworkHarvest {
rx,
tx,
total_rx,
total_tx,
}
*prev_net_rx = total_rx;
*prev_net_tx = total_tx;
NetworkHarvest {
rx,
tx,
total_rx,
total_tx,
}
}

View file

@ -1,7 +1,7 @@
use std::{
collections::{hash_map::RandomState, HashMap},
process::Command,
time::Instant,
collections::{hash_map::RandomState, HashMap},
process::Command,
time::Instant,
};
use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt};
@ -10,277 +10,277 @@ use crate::utils::error;
#[derive(Clone)]
pub enum ProcessSorting {
CPU,
MEM,
PID,
NAME,
CPU,
MEM,
PID,
NAME,
}
impl Default for ProcessSorting {
fn default() -> Self {
ProcessSorting::CPU
}
fn default() -> Self {
ProcessSorting::CPU
}
}
#[derive(Debug, Clone, Default)]
pub struct ProcessHarvest {
pub pid: u32,
pub cpu_usage_percent: f64,
pub mem_usage_percent: f64,
pub name: String,
pub pid: u32,
pub cpu_usage_percent: f64,
pub mem_usage_percent: f64,
pub name: String,
}
fn cpu_usage_calculation(
prev_idle: &mut f64, prev_non_idle: &mut f64,
prev_idle: &mut f64, prev_non_idle: &mut f64,
) -> error::Result<(f64, f64)> {
// From SO answer: https://stackoverflow.com/a/23376195
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push("stat");
// From SO answer: https://stackoverflow.com/a/23376195
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push("stat");
let stat_results = std::fs::read_to_string(path)?;
let first_line: &str;
let stat_results = std::fs::read_to_string(path)?;
let first_line: &str;
let split_results = stat_results.split('\n').collect::<Vec<&str>>();
if split_results.is_empty() {
return Err(error::BottomError::InvalidIO(format!(
"Unable to properly split the stat results; saw {} values, expected at least 1 value.",
split_results.len()
)));
} else {
first_line = split_results[0];
}
let split_results = stat_results.split('\n').collect::<Vec<&str>>();
if split_results.is_empty() {
return Err(error::BottomError::InvalidIO(format!(
"Unable to properly split the stat results; saw {} values, expected at least 1 value.",
split_results.len()
)));
} else {
first_line = split_results[0];
}
let val = first_line.split_whitespace().collect::<Vec<&str>>();
let val = first_line.split_whitespace().collect::<Vec<&str>>();
// SC in case that the parsing will fail due to length:
if val.len() <= 10 {
return Err(error::BottomError::InvalidIO(format!(
"CPU parsing will fail due to too short of a return value; saw {} values, expected 10 values.",
val.len()
)));
}
// SC in case that the parsing will fail due to length:
if val.len() <= 10 {
return Err(error::BottomError::InvalidIO(format!(
"CPU parsing will fail due to too short of a return value; saw {} values, expected 10 values.",
val.len()
)));
}
let user: f64 = val[1].parse::<_>().unwrap_or(0_f64);
let nice: f64 = val[2].parse::<_>().unwrap_or(0_f64);
let system: f64 = val[3].parse::<_>().unwrap_or(0_f64);
let idle: f64 = val[4].parse::<_>().unwrap_or(0_f64);
let iowait: f64 = val[5].parse::<_>().unwrap_or(0_f64);
let irq: f64 = val[6].parse::<_>().unwrap_or(0_f64);
let softirq: f64 = val[7].parse::<_>().unwrap_or(0_f64);
let steal: f64 = val[8].parse::<_>().unwrap_or(0_f64);
let guest: f64 = val[9].parse::<_>().unwrap_or(0_f64);
let user: f64 = val[1].parse::<_>().unwrap_or(0_f64);
let nice: f64 = val[2].parse::<_>().unwrap_or(0_f64);
let system: f64 = val[3].parse::<_>().unwrap_or(0_f64);
let idle: f64 = val[4].parse::<_>().unwrap_or(0_f64);
let iowait: f64 = val[5].parse::<_>().unwrap_or(0_f64);
let irq: f64 = val[6].parse::<_>().unwrap_or(0_f64);
let softirq: f64 = val[7].parse::<_>().unwrap_or(0_f64);
let steal: f64 = val[8].parse::<_>().unwrap_or(0_f64);
let guest: f64 = val[9].parse::<_>().unwrap_or(0_f64);
let idle = idle + iowait;
let non_idle = user + nice + system + irq + softirq + steal + guest;
let idle = idle + iowait;
let non_idle = user + nice + system + irq + softirq + steal + guest;
let total = idle + non_idle;
let prev_total = *prev_idle + *prev_non_idle;
let total = idle + non_idle;
let prev_total = *prev_idle + *prev_non_idle;
let total_delta: f64 = total - prev_total;
let idle_delta: f64 = idle - *prev_idle;
let total_delta: f64 = total - prev_total;
let idle_delta: f64 = idle - *prev_idle;
//debug!("Vangelis function: CPU PERCENT: {}", (total_delta - idle_delta) / total_delta * 100_f64);
//debug!("Vangelis function: CPU PERCENT: {}", (total_delta - idle_delta) / total_delta * 100_f64);
*prev_idle = idle;
*prev_non_idle = non_idle;
*prev_idle = idle;
*prev_non_idle = non_idle;
let result = if total_delta - idle_delta != 0_f64 {
total_delta - idle_delta
} else {
1_f64
};
let result = if total_delta - idle_delta != 0_f64 {
total_delta - idle_delta
} else {
1_f64
};
let cpu_percentage = if total_delta != 0_f64 {
result / total_delta
} else {
0_f64
};
let cpu_percentage = if total_delta != 0_f64 {
result / total_delta
} else {
0_f64
};
Ok((result, cpu_percentage))
Ok((result, cpu_percentage))
}
fn get_process_cpu_stats(pid: u32) -> std::io::Result<f64> {
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push(&pid.to_string());
path.push("stat");
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push(&pid.to_string());
path.push("stat");
let stat_results = std::fs::read_to_string(path)?;
let val = stat_results.split_whitespace().collect::<Vec<&str>>();
let utime = val[13].parse::<f64>().unwrap_or(0_f64);
let stime = val[14].parse::<f64>().unwrap_or(0_f64);
let stat_results = std::fs::read_to_string(path)?;
let val = stat_results.split_whitespace().collect::<Vec<&str>>();
let utime = val[13].parse::<f64>().unwrap_or(0_f64);
let stime = val[14].parse::<f64>().unwrap_or(0_f64);
//debug!("PID: {}, utime: {}, stime: {}", pid, utime, stime);
//debug!("PID: {}, utime: {}, stime: {}", pid, utime, stime);
Ok(utime + stime) // This seems to match top...
Ok(utime + stime) // This seems to match top...
}
/// Note that cpu_fraction should be represented WITHOUT the \times 100 factor!
fn linux_cpu_usage<S: core::hash::BuildHasher>(
pid: u32, cpu_usage: f64, cpu_fraction: f64,
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
curr_time: Instant,
pid: u32, cpu_usage: f64, cpu_fraction: f64,
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
curr_time: Instant,
) -> std::io::Result<f64> {
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
let before_proc_val: f64 = if prev_pid_stats.contains_key(&pid.to_string()) {
prev_pid_stats
.get(&pid.to_string())
.unwrap_or(&(0_f64, curr_time))
.0
} else {
0_f64
};
let after_proc_val = get_process_cpu_stats(pid)?;
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
let before_proc_val: f64 = if prev_pid_stats.contains_key(&pid.to_string()) {
prev_pid_stats
.get(&pid.to_string())
.unwrap_or(&(0_f64, curr_time))
.0
} else {
0_f64
};
let after_proc_val = get_process_cpu_stats(pid)?;
/*debug!(
"PID - {} - Before: {}, After: {}, CPU: {}, Percentage: {}",
pid,
before_proc_val,
after_proc_val,
cpu_usage,
(after_proc_val - before_proc_val) / cpu_usage * 100_f64
);*/
/*debug!(
"PID - {} - Before: {}, After: {}, CPU: {}, Percentage: {}",
pid,
before_proc_val,
after_proc_val,
cpu_usage,
(after_proc_val - before_proc_val) / cpu_usage * 100_f64
);*/
new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time));
new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time));
if use_current_cpu_total {
Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64)
} else {
Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64 * cpu_fraction)
}
if use_current_cpu_total {
Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64)
} else {
Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64 * cpu_fraction)
}
}
fn convert_ps<S: core::hash::BuildHasher>(
process: &str, cpu_usage: f64, cpu_fraction: f64,
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
curr_time: Instant,
process: &str, cpu_usage: f64, cpu_fraction: f64,
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
curr_time: Instant,
) -> std::io::Result<ProcessHarvest> {
if process.trim().to_string().is_empty() {
return Ok(ProcessHarvest {
pid: 0,
name: "".to_string(),
mem_usage_percent: 0.0,
cpu_usage_percent: 0.0,
});
}
if process.trim().to_string().is_empty() {
return Ok(ProcessHarvest {
pid: 0,
name: "".to_string(),
mem_usage_percent: 0.0,
cpu_usage_percent: 0.0,
});
}
let pid = (&process[..11])
.trim()
.to_string()
.parse::<u32>()
.unwrap_or(0);
let name = (&process[11..61]).trim().to_string();
let mem_usage_percent = (&process[62..])
.trim()
.to_string()
.parse::<f64>()
.unwrap_or(0_f64);
let pid = (&process[..11])
.trim()
.to_string()
.parse::<u32>()
.unwrap_or(0);
let name = (&process[11..61]).trim().to_string();
let mem_usage_percent = (&process[62..])
.trim()
.to_string()
.parse::<f64>()
.unwrap_or(0_f64);
let cpu_usage_percent = linux_cpu_usage(
pid,
cpu_usage,
cpu_fraction,
prev_pid_stats,
new_pid_stats,
use_current_cpu_total,
curr_time,
)?;
Ok(ProcessHarvest {
pid,
name,
mem_usage_percent,
cpu_usage_percent,
})
let cpu_usage_percent = linux_cpu_usage(
pid,
cpu_usage,
cpu_fraction,
prev_pid_stats,
new_pid_stats,
use_current_cpu_total,
curr_time,
)?;
Ok(ProcessHarvest {
pid,
name,
mem_usage_percent,
cpu_usage_percent,
})
}
pub fn get_sorted_processes_list(
sys: &System, prev_idle: &mut f64, prev_non_idle: &mut f64,
prev_pid_stats: &mut HashMap<String, (f64, Instant), RandomState>, use_current_cpu_total: bool,
mem_total_kb: u64, curr_time: Instant,
sys: &System, prev_idle: &mut f64, prev_non_idle: &mut f64,
prev_pid_stats: &mut HashMap<String, (f64, Instant), RandomState>, use_current_cpu_total: bool,
mem_total_kb: u64, curr_time: Instant,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
if cfg!(target_os = "linux") {
let ps_result = Command::new("ps")
.args(&["-axo", "pid:10,comm:50,%mem:5", "--noheader"])
.output()?;
let ps_stdout = String::from_utf8_lossy(&ps_result.stdout);
let split_string = ps_stdout.split('\n');
let cpu_calc = cpu_usage_calculation(prev_idle, prev_non_idle);
if let Ok((cpu_usage, cpu_fraction)) = cpu_calc {
let process_stream = split_string.collect::<Vec<&str>>();
if cfg!(target_os = "linux") {
let ps_result = Command::new("ps")
.args(&["-axo", "pid:10,comm:50,%mem:5", "--noheader"])
.output()?;
let ps_stdout = String::from_utf8_lossy(&ps_result.stdout);
let split_string = ps_stdout.split('\n');
let cpu_calc = cpu_usage_calculation(prev_idle, prev_non_idle);
if let Ok((cpu_usage, cpu_fraction)) = cpu_calc {
let process_stream = split_string.collect::<Vec<&str>>();
let mut new_pid_stats: HashMap<String, (f64, Instant), RandomState> = HashMap::new();
let mut new_pid_stats: HashMap<String, (f64, Instant), RandomState> = HashMap::new();
for process in process_stream {
if let Ok(process_object) = convert_ps(
process,
cpu_usage,
cpu_fraction,
&prev_pid_stats,
&mut new_pid_stats,
use_current_cpu_total,
curr_time,
) {
if !process_object.name.is_empty() {
process_vector.push(process_object);
}
}
}
for process in process_stream {
if let Ok(process_object) = convert_ps(
process,
cpu_usage,
cpu_fraction,
&prev_pid_stats,
&mut new_pid_stats,
use_current_cpu_total,
curr_time,
) {
if !process_object.name.is_empty() {
process_vector.push(process_object);
}
}
}
*prev_pid_stats = new_pid_stats;
} else {
error!("Unable to properly parse CPU data in Linux.");
error!("Result: {:?}", cpu_calc.err());
}
} else {
let process_hashmap = sys.get_processes();
let cpu_usage = sys.get_global_processor_info().get_cpu_usage() as f64 / 100.0;
let num_cpus = sys.get_processors().len() as f64;
for process_val in process_hashmap.values() {
let name = if process_val.name().is_empty() {
let process_cmd = process_val.cmd();
if process_cmd.len() > 1 {
process_cmd[0].clone()
} else {
let process_exe = process_val.exe().file_stem();
if let Some(exe) = process_exe {
let process_exe_opt = exe.to_str();
if let Some(exe_name) = process_exe_opt {
exe_name.to_string()
} else {
"".to_string()
}
} else {
"".to_string()
}
}
} else {
process_val.name().to_string()
};
*prev_pid_stats = new_pid_stats;
} else {
error!("Unable to properly parse CPU data in Linux.");
error!("Result: {:?}", cpu_calc.err());
}
} else {
let process_hashmap = sys.get_processes();
let cpu_usage = sys.get_global_processor_info().get_cpu_usage() as f64 / 100.0;
let num_cpus = sys.get_processors().len() as f64;
for process_val in process_hashmap.values() {
let name = if process_val.name().is_empty() {
let process_cmd = process_val.cmd();
if process_cmd.len() > 1 {
process_cmd[0].clone()
} else {
let process_exe = process_val.exe().file_stem();
if let Some(exe) = process_exe {
let process_exe_opt = exe.to_str();
if let Some(exe_name) = process_exe_opt {
exe_name.to_string()
} else {
"".to_string()
}
} else {
"".to_string()
}
}
} else {
process_val.name().to_string()
};
let pcu = if cfg!(target_os = "windows") {
process_val.cpu_usage() as f64
} else {
process_val.cpu_usage() as f64 / num_cpus
};
let process_cpu_usage = if use_current_cpu_total {
pcu / cpu_usage
} else {
pcu
};
let pcu = if cfg!(target_os = "windows") {
process_val.cpu_usage() as f64
} else {
process_val.cpu_usage() as f64 / num_cpus
};
let process_cpu_usage = if use_current_cpu_total {
pcu / cpu_usage
} else {
pcu
};
process_vector.push(ProcessHarvest {
pid: process_val.pid() as u32,
name,
mem_usage_percent: process_val.memory() as f64 * 100.0 / mem_total_kb as f64,
cpu_usage_percent: process_cpu_usage,
});
}
}
process_vector.push(ProcessHarvest {
pid: process_val.pid() as u32,
name,
mem_usage_percent: process_val.memory() as f64 * 100.0 / mem_total_kb as f64,
cpu_usage_percent: process_cpu_usage,
});
}
}
Ok(process_vector)
Ok(process_vector)
}

View file

@ -6,93 +6,93 @@ use sysinfo::{ComponentExt, System, SystemExt};
#[derive(Default, Debug, Clone)]
pub struct TempHarvest {
pub component_name: String,
pub temperature: f32,
pub component_name: String,
pub temperature: f32,
}
#[derive(Clone, Debug)]
pub enum TemperatureType {
Celsius,
Kelvin,
Fahrenheit,
Celsius,
Kelvin,
Fahrenheit,
}
impl Default for TemperatureType {
fn default() -> Self {
TemperatureType::Celsius
}
fn default() -> Self {
TemperatureType::Celsius
}
}
pub async fn get_temperature_data(
sys: &System, temp_type: &TemperatureType,
sys: &System, temp_type: &TemperatureType,
) -> crate::utils::error::Result<Vec<TempHarvest>> {
let mut temperature_vec: Vec<TempHarvest> = Vec::new();
let mut temperature_vec: Vec<TempHarvest> = Vec::new();
if cfg!(target_os = "linux") {
let mut sensor_data = heim::sensors::temperatures();
while let Some(sensor) = sensor_data.next().await {
if let Ok(sensor) = sensor {
temperature_vec.push(TempHarvest {
component_name: sensor.unit().to_string(),
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>(
),
},
});
}
}
} else {
let sensor_data = sys.get_components();
for component in sensor_data {
temperature_vec.push(TempHarvest {
component_name: component.get_label().to_string(),
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())
}
},
});
}
}
if cfg!(target_os = "linux") {
let mut sensor_data = heim::sensors::temperatures();
while let Some(sensor) = sensor_data.next().await {
if let Ok(sensor) = sensor {
temperature_vec.push(TempHarvest {
component_name: sensor.unit().to_string(),
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>(
),
},
});
}
}
} else {
let sensor_data = sys.get_components();
for component in sensor_data {
temperature_vec.push(TempHarvest {
component_name: component.get_label().to_string(),
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())
}
},
});
}
}
// By default, sort temperature, then by alphabetically!
// By default, sort temperature, then by alphabetically!
// 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,
});
// 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.component_name
.partial_cmp(&b.component_name)
.unwrap_or(Ordering::Equal)
});
temperature_vec.sort_by(|a, b| {
a.component_name
.partial_cmp(&b.component_name)
.unwrap_or(Ordering::Equal)
});
Ok(temperature_vec)
Ok(temperature_vec)
}
fn convert_celsius_to_kelvin(celsius: f32) -> f32 {
celsius + 273.15
celsius + 273.15
}
fn convert_celsius_to_fahrenheit(celsius: f32) -> f32 {
(celsius * (9.0 / 5.0)) + 32.0
(celsius * (9.0 / 5.0)) + 32.0
}

View file

@ -3,11 +3,11 @@ use std::process::Command;
// Copied from SO: https://stackoverflow.com/a/55231715
#[cfg(target_os = "windows")]
use winapi::{
shared::{minwindef::DWORD, ntdef::HANDLE},
um::{
processthreadsapi::{OpenProcess, TerminateProcess},
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
},
shared::{minwindef::DWORD, ntdef::HANDLE},
um::{
processthreadsapi::{OpenProcess, TerminateProcess},
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
},
};
/// This file is meant to house (OS specific) implementations on how to kill processes.
@ -18,36 +18,36 @@ struct Process(HANDLE);
#[cfg(target_os = "windows")]
impl Process {
fn open(pid: DWORD) -> Result<Process, String> {
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
if pc.is_null() {
return Err("!OpenProcess".to_string());
}
Ok(Process(pc))
}
fn open(pid: DWORD) -> Result<Process, String> {
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
if pc.is_null() {
return Err("!OpenProcess".to_string());
}
Ok(Process(pc))
}
fn kill(self) -> Result<(), String> {
unsafe { TerminateProcess(self.0, 1) };
Ok(())
}
fn kill(self) -> Result<(), String> {
unsafe { TerminateProcess(self.0, 1) };
Ok(())
}
}
/// Kills a process, given a PID.
pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
Command::new("kill").arg(pid.to_string()).output()?;
} else if cfg!(target_os = "windows") {
#[cfg(target_os = "windows")]
{
let process = Process::open(pid as DWORD)?;
process.kill()?;
}
} else {
return Err(BottomError::GenericError(
"Sorry, support operating systems outside the main three are not implemented yet!"
.to_string(),
));
}
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
Command::new("kill").arg(pid.to_string()).output()?;
} else if cfg!(target_os = "windows") {
#[cfg(target_os = "windows")]
{
let process = Process::open(pid as DWORD)?;
process.kill()?;
}
} else {
return Err(BottomError::GenericError(
"Sorry, support operating systems outside the main three are not implemented yet!"
.to_string(),
));
}
Ok(())
Ok(())
}

File diff suppressed because it is too large Load diff

View file

@ -7,147 +7,147 @@ use crate::{constants::*, utils::error};
mod colour_utils;
pub struct CanvasColours {
pub currently_selected_text_colour: Color,
pub currently_selected_bg_colour: Color,
pub currently_selected_text_style: Style,
pub table_header_style: Style,
pub ram_style: Style,
pub swap_style: Style,
pub rx_style: Style,
pub tx_style: Style,
pub rx_total_style: Style,
pub tx_total_style: Style,
pub avg_colour_style: Style,
pub cpu_colour_styles: Vec<Style>,
pub border_style: Style,
pub highlighted_border_style: Style,
pub text_style: Style,
pub widget_title_style: Style,
pub graph_style: Style,
pub currently_selected_text_colour: Color,
pub currently_selected_bg_colour: Color,
pub currently_selected_text_style: Style,
pub table_header_style: Style,
pub ram_style: Style,
pub swap_style: Style,
pub rx_style: Style,
pub tx_style: Style,
pub total_rx_style: Style,
pub total_tx_style: Style,
pub avg_colour_style: Style,
pub cpu_colour_styles: Vec<Style>,
pub border_style: Style,
pub highlighted_border_style: Style,
pub text_style: Style,
pub widget_title_style: Style,
pub graph_style: Style,
}
impl Default for CanvasColours {
fn default() -> Self {
let text_colour = Color::Gray;
fn default() -> Self {
let text_colour = Color::Gray;
CanvasColours {
currently_selected_text_colour: Color::Black,
currently_selected_bg_colour: Color::Cyan,
currently_selected_text_style: Style::default().fg(Color::Black).bg(Color::Cyan),
table_header_style: Style::default().fg(Color::LightBlue),
ram_style: Style::default().fg(STANDARD_FIRST_COLOUR),
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
rx_style: Style::default().fg(STANDARD_FIRST_COLOUR),
tx_style: Style::default().fg(STANDARD_SECOND_COLOUR),
rx_total_style: Style::default().fg(STANDARD_THIRD_COLOUR),
tx_total_style: Style::default().fg(STANDARD_FOURTH_COLOUR),
avg_colour_style: Style::default().fg(AVG_COLOUR),
cpu_colour_styles: Vec::new(),
border_style: Style::default().fg(text_colour),
highlighted_border_style: Style::default().fg(Color::LightBlue),
text_style: Style::default().fg(text_colour),
widget_title_style: Style::default().fg(text_colour),
graph_style: Style::default().fg(text_colour),
}
}
CanvasColours {
currently_selected_text_colour: Color::Black,
currently_selected_bg_colour: Color::Cyan,
currently_selected_text_style: Style::default().fg(Color::Black).bg(Color::Cyan),
table_header_style: Style::default().fg(Color::LightBlue),
ram_style: Style::default().fg(STANDARD_FIRST_COLOUR),
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
rx_style: Style::default().fg(STANDARD_FIRST_COLOUR),
tx_style: Style::default().fg(STANDARD_SECOND_COLOUR),
total_rx_style: Style::default().fg(STANDARD_THIRD_COLOUR),
total_tx_style: Style::default().fg(STANDARD_FOURTH_COLOUR),
avg_colour_style: Style::default().fg(AVG_COLOUR),
cpu_colour_styles: Vec::new(),
border_style: Style::default().fg(text_colour),
highlighted_border_style: Style::default().fg(Color::LightBlue),
text_style: Style::default().fg(text_colour),
widget_title_style: Style::default().fg(text_colour),
graph_style: Style::default().fg(text_colour),
}
}
}
impl CanvasColours {
pub fn set_text_colour(&mut self, colour: &str) -> error::Result<()> {
self.text_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_text_colour(&mut self, colour: &str) -> error::Result<()> {
self.text_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_border_colour(&mut self, colour: &str) -> error::Result<()> {
self.border_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_border_colour(&mut self, colour: &str) -> error::Result<()> {
self.border_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_highlighted_border_colour(&mut self, colour: &str) -> error::Result<()> {
self.highlighted_border_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_highlighted_border_colour(&mut self, colour: &str) -> error::Result<()> {
self.highlighted_border_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_table_header_colour(&mut self, colour: &str) -> error::Result<()> {
self.table_header_style = get_style_from_config(colour)?.modifier(Modifier::BOLD);
Ok(())
}
pub fn set_table_header_colour(&mut self, colour: &str) -> error::Result<()> {
self.table_header_style = get_style_from_config(colour)?.modifier(Modifier::BOLD);
Ok(())
}
pub fn set_ram_colour(&mut self, colour: &str) -> error::Result<()> {
self.ram_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_ram_colour(&mut self, colour: &str) -> error::Result<()> {
self.ram_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_swap_colour(&mut self, colour: &str) -> error::Result<()> {
self.swap_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_swap_colour(&mut self, colour: &str) -> error::Result<()> {
self.swap_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_rx_colour(&mut self, colour: &str) -> error::Result<()> {
self.rx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_rx_colour(&mut self, colour: &str) -> error::Result<()> {
self.rx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_tx_colour(&mut self, colour: &str) -> error::Result<()> {
self.tx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_tx_colour(&mut self, colour: &str) -> error::Result<()> {
self.tx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> {
self.rx_total_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> {
self.total_rx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> {
self.tx_total_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> {
self.total_tx_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_avg_cpu_colour(&mut self, colour: &str) -> error::Result<()> {
self.avg_colour_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_avg_cpu_colour(&mut self, colour: &str) -> error::Result<()> {
self.avg_colour_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_cpu_colours(&mut self, colours: &[String]) -> error::Result<()> {
let max_amount = std::cmp::min(colours.len(), NUM_COLOURS as usize);
for (itx, colour) in colours.iter().enumerate() {
if itx >= max_amount {
break;
}
self.cpu_colour_styles.push(get_style_from_config(colour)?);
}
Ok(())
}
pub fn set_cpu_colours(&mut self, colours: &[String]) -> error::Result<()> {
let max_amount = std::cmp::min(colours.len(), NUM_COLOURS as usize);
for (itx, colour) in colours.iter().enumerate() {
if itx >= max_amount {
break;
}
self.cpu_colour_styles.push(get_style_from_config(colour)?);
}
Ok(())
}
pub fn generate_remaining_cpu_colours(&mut self) {
let remaining_num_colours = NUM_COLOURS - self.cpu_colour_styles.len() as i32;
self.cpu_colour_styles
.extend(gen_n_styles(remaining_num_colours));
}
pub fn generate_remaining_cpu_colours(&mut self) {
let remaining_num_colours = NUM_COLOURS - self.cpu_colour_styles.len() as i32;
self.cpu_colour_styles
.extend(gen_n_styles(remaining_num_colours));
}
pub fn set_scroll_entry_text_color(&mut self, colour: &str) -> error::Result<()> {
self.currently_selected_text_colour = get_colour_from_config(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
pub fn set_scroll_entry_text_color(&mut self, colour: &str) -> error::Result<()> {
self.currently_selected_text_colour = get_colour_from_config(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
pub fn set_scroll_entry_bg_color(&mut self, colour: &str) -> error::Result<()> {
self.currently_selected_bg_colour = get_colour_from_config(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
pub fn set_scroll_entry_bg_color(&mut self, colour: &str) -> error::Result<()> {
self.currently_selected_bg_colour = get_colour_from_config(colour)?;
self.currently_selected_text_style = Style::default()
.fg(self.currently_selected_text_colour)
.bg(self.currently_selected_bg_colour);
Ok(())
}
pub fn set_widget_title_colour(&mut self, colour: &str) -> error::Result<()> {
self.widget_title_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_widget_title_colour(&mut self, colour: &str) -> error::Result<()> {
self.widget_title_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_graph_colour(&mut self, colour: &str) -> error::Result<()> {
self.graph_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_graph_colour(&mut self, colour: &str) -> error::Result<()> {
self.graph_style = get_style_from_config(colour)?;
Ok(())
}
}

View file

@ -4,7 +4,8 @@ use tui::style::{Color, Style};
use crate::utils::{error, gen_util::*};
const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long)
const GOLDEN_RATIO: f32 = 0.618_034;
// Approx, good enough for use (also Clippy gets mad if it's too long)
pub const STANDARD_FIRST_COLOUR: Color = Color::LightMagenta;
pub const STANDARD_SECOND_COLOUR: Color = Color::LightYellow;
pub const STANDARD_THIRD_COLOUR: Color = Color::LightCyan;
@ -12,181 +13,181 @@ pub const STANDARD_FOURTH_COLOUR: Color = Color::LightGreen;
pub const AVG_COLOUR: Color = Color::Red;
lazy_static! {
static ref COLOR_NAME_LOOKUP_TABLE: HashMap<&'static str, Color> = [
("reset", Color::Reset),
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("gray", Color::Gray),
("darkgray", Color::DarkGray),
("lightred", Color::LightRed),
("lightgreen", Color::LightGreen),
("lightyellow", Color::LightYellow),
("lightblue", Color::LightBlue),
("lightmagenta", Color::LightMagenta),
("lightcyan", Color::LightCyan),
("white", Color::White)
]
.iter()
.copied()
.collect();
static ref COLOR_NAME_LOOKUP_TABLE: HashMap<&'static str, Color> = [
("reset", Color::Reset),
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("gray", Color::Gray),
("darkgray", Color::DarkGray),
("lightred", Color::LightRed),
("lightgreen", Color::LightGreen),
("lightyellow", Color::LightYellow),
("lightblue", Color::LightBlue),
("lightmagenta", Color::LightMagenta),
("lightcyan", Color::LightCyan),
("white", Color::White)
]
.iter()
.copied()
.collect();
}
/// Generates random colours. Strategy found from
/// https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
pub fn gen_n_styles(num_to_gen: i32) -> Vec<Style> {
fn gen_hsv(h: f32) -> f32 {
let new_val = h + GOLDEN_RATIO;
if new_val > 1.0 {
new_val.fract()
} else {
new_val
}
}
/// This takes in an h, s, and v value of range [0, 1]
/// For explanation of what this does, see
/// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative
fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (u8, u8, u8) {
fn hsv_helper(num: u32, hu: f32, sat: f32, val: f32) -> f32 {
let k = (num as f32 + hu * 6.0) % 6.0;
val - val * sat * float_max(float_min(k, float_min(4.1 - k, 1.1)), 0.0)
}
fn gen_hsv(h: f32) -> f32 {
let new_val = h + GOLDEN_RATIO;
if new_val > 1.0 {
new_val.fract()
} else {
new_val
}
}
/// This takes in an h, s, and v value of range [0, 1]
/// For explanation of what this does, see
/// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative
fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (u8, u8, u8) {
fn hsv_helper(num: u32, hu: f32, sat: f32, val: f32) -> f32 {
let k = (num as f32 + hu * 6.0) % 6.0;
val - val * sat * float_max(float_min(k, float_min(4.1 - k, 1.1)), 0.0)
}
(
(hsv_helper(5, hue, saturation, value) * 255.0) as u8,
(hsv_helper(3, hue, saturation, value) * 255.0) as u8,
(hsv_helper(1, hue, saturation, value) * 255.0) as u8,
)
}
(
(hsv_helper(5, hue, saturation, value) * 255.0) as u8,
(hsv_helper(3, hue, saturation, value) * 255.0) as u8,
(hsv_helper(1, hue, saturation, value) * 255.0) as u8,
)
}
// Generate colours
// Why do we need so many colours? Because macOS default terminal
// throws a tantrum if you don't give it supported colours, but so
// does PowerShell with some colours (Magenta and Yellow)!
let mut colour_vec: Vec<Style> = vec![
Style::default().fg(STANDARD_FIRST_COLOUR),
Style::default().fg(STANDARD_SECOND_COLOUR),
Style::default().fg(STANDARD_THIRD_COLOUR),
Style::default().fg(STANDARD_FOURTH_COLOUR),
Style::default().fg(Color::LightBlue),
Style::default().fg(Color::LightRed),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
Style::default().fg(Color::Red),
];
// Generate colours
// Why do we need so many colours? Because macOS default terminal
// throws a tantrum if you don't give it supported colours, but so
// does PowerShell with some colours (Magenta and Yellow)!
let mut colour_vec: Vec<Style> = vec![
Style::default().fg(STANDARD_FIRST_COLOUR),
Style::default().fg(STANDARD_SECOND_COLOUR),
Style::default().fg(STANDARD_THIRD_COLOUR),
Style::default().fg(STANDARD_FOURTH_COLOUR),
Style::default().fg(Color::LightBlue),
Style::default().fg(Color::LightRed),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
Style::default().fg(Color::Red),
];
let mut h: f32 = 0.4; // We don't need random colours... right?
for _i in 0..(num_to_gen - 10) {
h = gen_hsv(h);
let result = hsv_to_rgb(h, 0.5, 0.95);
colour_vec.push(Style::default().fg(Color::Rgb(result.0, result.1, result.2)));
}
let mut h: f32 = 0.4; // We don't need random colours... right?
for _i in 0..(num_to_gen - 10) {
h = gen_hsv(h);
let result = hsv_to_rgb(h, 0.5, 0.95);
colour_vec.push(Style::default().fg(Color::Rgb(result.0, result.1, result.2)));
}
colour_vec
colour_vec
}
pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
fn convert_hex_to_rgb(hex: &str) -> error::Result<(u8, u8, u8)> {
if hex.len() == 7 && &hex[0..1] == "#" {
let r = u8::from_str_radix(&hex[1..3], 16)?;
let g = u8::from_str_radix(&hex[3..5], 16)?;
let b = u8::from_str_radix(&hex[5..7], 16)?;
fn convert_hex_to_rgb(hex: &str) -> error::Result<(u8, u8, u8)> {
if hex.len() == 7 && &hex[0..1] == "#" {
let r = u8::from_str_radix(&hex[1..3], 16)?;
let g = u8::from_str_radix(&hex[3..5], 16)?;
let b = u8::from_str_radix(&hex[5..7], 16)?;
return Ok((r, g, b));
}
return Ok((r, g, b));
}
Err(error::BottomError::GenericError(format!(
"Colour hex {} is not of valid length. It must be a 7 character string of the form \"#112233\".",
hex
)))
}
Err(error::BottomError::GenericError(format!(
"Colour hex {} is not of valid length. It must be a 7 character string of the form \"#112233\".",
hex
)))
}
let rgb = convert_hex_to_rgb(hex)?;
Ok(Color::Rgb(rgb.0, rgb.1, rgb.2))
let rgb = convert_hex_to_rgb(hex)?;
Ok(Color::Rgb(rgb.0, rgb.1, rgb.2))
}
pub fn get_style_from_config(input_val: &str) -> error::Result<Style> {
if input_val.len() > 1 {
if &input_val[0..1] == "#" {
get_style_from_hex(input_val)
} else if input_val.contains(',') {
get_style_from_rgb(input_val)
} else {
get_style_from_color_name(input_val)
}
} else {
Err(error::BottomError::GenericError(format!(
"Colour input {} is not valid.",
input_val
)))
}
if input_val.len() > 1 {
if &input_val[0..1] == "#" {
get_style_from_hex(input_val)
} else if input_val.contains(',') {
get_style_from_rgb(input_val)
} else {
get_style_from_color_name(input_val)
}
} else {
Err(error::BottomError::GenericError(format!(
"Colour input {} is not valid.",
input_val
)))
}
}
pub fn get_colour_from_config(input_val: &str) -> error::Result<Color> {
if input_val.len() > 1 {
if &input_val[0..1] == "#" {
convert_hex_to_color(input_val)
} else if input_val.contains(',') {
convert_rgb_to_color(input_val)
} else {
convert_name_to_color(input_val)
}
} else {
Err(error::BottomError::GenericError(format!(
"Colour input {} is not valid.",
input_val
)))
}
if input_val.len() > 1 {
if &input_val[0..1] == "#" {
convert_hex_to_color(input_val)
} else if input_val.contains(',') {
convert_rgb_to_color(input_val)
} else {
convert_name_to_color(input_val)
}
} else {
Err(error::BottomError::GenericError(format!(
"Colour input {} is not valid.",
input_val
)))
}
}
pub fn get_style_from_hex(hex: &str) -> error::Result<Style> {
Ok(Style::default().fg(convert_hex_to_color(hex)?))
Ok(Style::default().fg(convert_hex_to_color(hex)?))
}
fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
let rgb_list = rgb_str.split(',');
let rgb = rgb_list
.filter_map(|val| {
if let Ok(res) = val.to_string().trim().parse::<u8>() {
Some(res)
} else {
None
}
})
.collect::<Vec<_>>();
if rgb.len() == 3 {
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(error::BottomError::GenericError(format!(
"RGB colour {} is not of valid length. It must be a comma separated value with 3 integers from 0 to 255, like \"255, 0, 155\".",
rgb_str
)))
}
let rgb_list = rgb_str.split(',');
let rgb = rgb_list
.filter_map(|val| {
if let Ok(res) = val.to_string().trim().parse::<u8>() {
Some(res)
} else {
None
}
})
.collect::<Vec<_>>();
if rgb.len() == 3 {
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(error::BottomError::GenericError(format!(
"RGB colour {} is not of valid length. It must be a comma separated value with 3 integers from 0 to 255, like \"255, 0, 155\".",
rgb_str
)))
}
}
pub fn get_style_from_rgb(rgb_str: &str) -> error::Result<Style> {
Ok(Style::default().fg(convert_rgb_to_color(rgb_str)?))
Ok(Style::default().fg(convert_rgb_to_color(rgb_str)?))
}
fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
let color = COLOR_NAME_LOOKUP_TABLE.get(color_name.to_lowercase().as_str());
if let Some(color) = color {
return Ok(*color);
}
let color = COLOR_NAME_LOOKUP_TABLE.get(color_name.to_lowercase().as_str());
if let Some(color) = color {
return Ok(*color);
}
Err(error::BottomError::GenericError(format!(
"Color {} is not a supported config colour. bottom supports the following named colours as strings: \
Err(error::BottomError::GenericError(format!(
"Color {} is not a supported config colour. bottom supports the following named colours as strings: \
Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, \
LightYellow, LightBlue, LightMagenta, LightCyan, White",
color_name
)))
color_name
)))
}
pub fn get_style_from_color_name(color_name: &str) -> error::Result<Style> {
Ok(Style::default().fg(convert_name_to_color(color_name)?))
Ok(Style::default().fg(convert_name_to_color(color_name)?))
}

View file

@ -8,145 +8,154 @@ use crate::app;
/// `width thresholds` and `desired_widths_ratio` should be the same length.
/// Otherwise bad things happen.
pub fn get_variable_intrinsic_widths(
total_width: u16, desired_widths_ratio: &[f64], width_thresholds: &[usize],
total_width: u16, desired_widths_ratio: &[f64], width_thresholds: &[usize],
) -> (Vec<u16>, usize) {
let num_widths = desired_widths_ratio.len();
let mut resulting_widths: Vec<u16> = vec![0; num_widths];
let mut last_index = 0;
let num_widths = desired_widths_ratio.len();
let mut resulting_widths: Vec<u16> = vec![0; num_widths];
let mut last_index = 0;
let mut remaining_width = (total_width - (num_widths as u16 - 1)) as i32; // Required for spaces...
let desired_widths = desired_widths_ratio
.iter()
.map(|&desired_width_ratio| (desired_width_ratio * total_width as f64) as i32)
.collect::<Vec<_>>();
let mut remaining_width = (total_width - (num_widths as u16 - 1)) as i32; // Required for spaces...
let desired_widths = desired_widths_ratio
.iter()
.map(|&desired_width_ratio| (desired_width_ratio * total_width as f64) as i32)
.collect::<Vec<_>>();
for (itx, desired_width) in desired_widths.into_iter().enumerate() {
resulting_widths[itx] = if desired_width < width_thresholds[itx] as i32 {
// Try to take threshold, else, 0
if remaining_width < width_thresholds[itx] as i32 {
0
} else {
remaining_width -= width_thresholds[itx] as i32;
width_thresholds[itx] as u16
}
} else {
// Take as large as possible
if remaining_width < desired_width {
// Check the biggest chunk possible
if remaining_width < width_thresholds[itx] as i32 {
0
} else {
let temp_width = remaining_width;
remaining_width = 0;
temp_width as u16
}
} else {
remaining_width -= desired_width;
desired_width as u16
}
};
for (itx, desired_width) in desired_widths.into_iter().enumerate() {
resulting_widths[itx] = if desired_width < width_thresholds[itx] as i32 {
// Try to take threshold, else, 0
if remaining_width < width_thresholds[itx] as i32 {
0
} else {
remaining_width -= width_thresholds[itx] as i32;
width_thresholds[itx] as u16
}
} else {
// Take as large as possible
if remaining_width < desired_width {
// Check the biggest chunk possible
if remaining_width < width_thresholds[itx] as i32 {
0
} else {
let temp_width = remaining_width;
remaining_width = 0;
temp_width as u16
}
} else {
remaining_width -= desired_width;
desired_width as u16
}
};
if resulting_widths[itx] == 0 {
break;
} else {
last_index += 1;
}
}
if resulting_widths[itx] == 0 {
break;
} else {
last_index += 1;
}
}
// Simple redistribution tactic - if there's any space left, split it evenly amongst all members
if last_index < num_widths {
let for_all_widths = (remaining_width / last_index as i32) as u16;
let mut remainder = remaining_width % last_index as i32;
// Simple redistribution tactic - if there's any space left, split it evenly amongst all members
if last_index < num_widths && last_index != 0 {
let for_all_widths = (remaining_width / last_index as i32) as u16;
let mut remainder = remaining_width % last_index as i32;
for resulting_width in &mut resulting_widths {
*resulting_width += for_all_widths;
if remainder > 0 {
*resulting_width += 1;
remainder -= 1;
}
}
}
for resulting_width in &mut resulting_widths {
*resulting_width += for_all_widths;
if remainder > 0 {
*resulting_width += 1;
remainder -= 1;
}
}
}
(resulting_widths, last_index)
(resulting_widths, last_index)
}
#[allow(dead_code, unused_variables)]
pub fn get_search_start_position(
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
current_cursor_position: usize, is_resized: bool,
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
current_cursor_position: usize, is_resized: bool,
) -> usize {
if is_resized {
*cursor_bar = 0;
}
if is_resized {
*cursor_bar = 0;
}
match cursor_direction {
app::CursorDirection::RIGHT => {
if current_cursor_position < *cursor_bar + num_columns {
// If, using previous_scrolled_position, we can see the element
// (so within that and + num_rows) just reuse the current previously scrolled position
*cursor_bar
} else if current_cursor_position >= num_columns {
// Else if the current position past the last element visible in the list, omit
// until we can see that element
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, if it is not past the last element visible, do not omit anything
0
}
}
app::CursorDirection::LEFT => {
if current_cursor_position <= *cursor_bar {
// If it's past the first element, then show from that element downwards
*cursor_bar = current_cursor_position;
*cursor_bar
} else if current_cursor_position >= *cursor_bar + num_columns {
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, don't change what our start position is from whatever it is set to!
*cursor_bar
}
}
}
match cursor_direction {
app::CursorDirection::RIGHT => {
if current_cursor_position < *cursor_bar + num_columns {
// If, using previous_scrolled_position, we can see the element
// (so within that and + num_rows) just reuse the current previously scrolled position
*cursor_bar
} else if current_cursor_position >= num_columns {
// Else if the current position past the last element visible in the list, omit
// until we can see that element
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, if it is not past the last element visible, do not omit anything
0
}
}
app::CursorDirection::LEFT => {
if current_cursor_position <= *cursor_bar {
// If it's past the first element, then show from that element downwards
*cursor_bar = current_cursor_position;
*cursor_bar
} else if current_cursor_position >= *cursor_bar + num_columns {
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, don't change what our start position is from whatever it is set to!
*cursor_bar
}
}
}
}
pub fn get_start_position(
num_rows: u64, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut u64,
currently_selected_position: u64, is_resized: bool,
num_rows: u64, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut u64,
currently_selected_position: u64, is_resized: bool,
) -> u64 {
if is_resized {
*scroll_position_bar = 0;
}
if is_resized {
*scroll_position_bar = 0;
}
match scroll_direction {
app::ScrollDirection::DOWN => {
if currently_selected_position < *scroll_position_bar + num_rows {
// If, using previous_scrolled_position, we can see the element
// (so within that and + num_rows) just reuse the current previously scrolled position
*scroll_position_bar
} else if currently_selected_position >= num_rows {
// Else if the current position past the last element visible in the list, omit
// until we can see that element
*scroll_position_bar = currently_selected_position - num_rows;
*scroll_position_bar
} else {
// Else, if it is not past the last element visible, do not omit anything
0
}
}
app::ScrollDirection::UP => {
if currently_selected_position <= *scroll_position_bar {
// If it's past the first element, then show from that element downwards
*scroll_position_bar = currently_selected_position;
*scroll_position_bar
} else if currently_selected_position >= *scroll_position_bar + num_rows {
*scroll_position_bar = currently_selected_position - num_rows;
*scroll_position_bar
} else {
// Else, don't change what our start position is from whatever it is set to!
*scroll_position_bar
}
}
}
match scroll_direction {
app::ScrollDirection::DOWN => {
if currently_selected_position < *scroll_position_bar + num_rows {
// If, using previous_scrolled_position, we can see the element
// (so within that and + num_rows) just reuse the current previously scrolled position
*scroll_position_bar
} else if currently_selected_position >= num_rows {
// Else if the current position past the last element visible in the list, omit
// until we can see that element
*scroll_position_bar = currently_selected_position - num_rows;
*scroll_position_bar
} else {
// Else, if it is not past the last element visible, do not omit anything
0
}
}
app::ScrollDirection::UP => {
if currently_selected_position <= *scroll_position_bar {
// If it's past the first element, then show from that element downwards
*scroll_position_bar = currently_selected_position;
*scroll_position_bar
} else if currently_selected_position >= *scroll_position_bar + num_rows {
*scroll_position_bar = currently_selected_position - num_rows;
*scroll_position_bar
} else {
// Else, don't change what our start position is from whatever it is set to!
*scroll_position_bar
}
}
}
}
/// Calculate how many bars are to be
/// drawn within basic mode's components.
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
std::cmp::min(
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
num_bars_available,
)
}

View file

@ -1,6 +1,8 @@
pub const STALE_MAX_MILLISECONDS: u128 = 60 * 1000; // How long to store data.
pub const STALE_MAX_MILLISECONDS: u128 = 60 * 1000;
// How long to store data.
pub const TIME_STARTS_FROM: u64 = 60 * 1000;
pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes
pub const TICK_RATE_IN_MILLISECONDS: u64 = 200;
// How fast the screen refreshes
pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000;
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u128 = 1000;
pub const NUM_COLOURS: i32 = 256;
@ -11,48 +13,48 @@ pub const DEFAULT_WINDOWS_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";
// Help text
pub const GENERAL_HELP_TEXT: [&str; 15] = [
"General Keybindings\n\n",
"q, Ctrl-c Quit bottom\n",
"Esc Close filters, dialog boxes, etc.\n",
"Ctrl-r Reset all data\n",
"f Freeze display\n",
"Ctrl-Arrow Move currently selected widget\n",
"Shift-Arrow Move currently selected widget\n",
"H/J/K/L Move currently selected widget up/down/left/right\n",
"Up, k Move cursor up\n",
"Down, j Move cursor down\n",
"? Open the help screen\n",
"gg Skip to the first entry of a list\n",
"G Skip to the last entry of a list\n",
"Enter Maximize the currently selected widget\n",
"/ Filter out graph lines (only CPU at the moment)\n",
"General Keybindings\n\n",
"q, Ctrl-c Quit bottom\n",
"Esc Close filters, dialog boxes, etc.\n",
"Ctrl-r Reset all data\n",
"f Freeze display\n",
"Ctrl-Arrow Move currently selected widget\n",
"Shift-Arrow Move currently selected widget\n",
"H/J/K/L Move currently selected widget up/down/left/right\n",
"Up, k Move cursor up\n",
"Down, j Move cursor down\n",
"? Open the help screen\n",
"gg Skip to the first entry of a list\n",
"G Skip to the last entry of a list\n",
"Enter Maximize the currently selected widget\n",
"/ Filter out graph lines (only CPU at the moment)\n",
];
pub const PROCESS_HELP_TEXT: [&str; 8] = [
"Process Keybindings\n\n",
"dd Kill the highlighted process\n",
"c Sort by CPU usage\n",
"m Sort by memory usage\n",
"p Sort by PID\n",
"n Sort by process name\n",
"Tab Group together processes with the same name\n",
"Ctrl-f, / Open up the search widget\n",
"Process Keybindings\n\n",
"dd Kill the highlighted process\n",
"c Sort by CPU usage\n",
"m Sort by memory usage\n",
"p Sort by PID\n",
"n Sort by process name\n",
"Tab Group together processes with the same name\n",
"Ctrl-f, / Open up the search widget\n",
];
pub const SEARCH_HELP_TEXT: [&str; 13] = [
"Search Keybindings\n\n",
"Tab Toggle between searching for PID and name.\n",
"Esc Close search widget\n",
"Ctrl-a Skip to the start of search widget\n",
"Ctrl-e Skip to the end of search widget\n",
"Ctrl-u Clear the current search query\n",
"Backspace Delete the character behind the cursor\n",
"Delete Delete the character at the cursor\n",
"Left Move cursor left\n",
"Right Move cursor right\n",
"Alt-c/F1 Toggle whether to ignore case\n",
"Alt-w/F2 Toggle whether to match the whole word\n",
"Alt-r/F3 Toggle whether to use regex\n",
"Search Keybindings\n\n",
"Tab Toggle between searching for PID and name.\n",
"Esc Close search widget\n",
"Ctrl-a Skip to the start of search widget\n",
"Ctrl-e Skip to the end of search widget\n",
"Ctrl-u Clear the current search query\n",
"Backspace Delete the character behind the cursor\n",
"Delete Delete the character at the cursor\n",
"Left Move cursor left\n",
"Right Move cursor right\n",
"Alt-c/F1 Toggle whether to ignore case\n",
"Alt-w/F2 Toggle whether to match the whole word\n",
"Alt-r/F3 Toggle whether to use regex\n",
];
pub const DEFAULT_CONFIG_CONTENT: &str = r##"

View file

@ -6,317 +6,320 @@ use std::collections::HashMap;
use constants::*;
use crate::{
app::{
data_farmer,
data_harvester::{self, processes::ProcessHarvest},
App,
},
constants,
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
app::{
data_farmer,
data_harvester::{self, processes::ProcessHarvest},
App,
},
constants,
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
};
#[derive(Default, Debug)]
pub struct ConvertedNetworkData {
pub rx: Vec<(f64, f64)>,
pub tx: Vec<(f64, f64)>,
pub rx_display: String,
pub tx_display: String,
pub total_rx_display: String,
pub total_tx_display: String,
pub rx: Vec<(f64, f64)>,
pub tx: Vec<(f64, f64)>,
pub rx_display: String,
pub tx_display: String,
pub total_rx_display: String,
pub total_tx_display: String,
}
#[derive(Clone, Default, Debug)]
pub struct ConvertedProcessData {
pub pid: u32,
pub name: String,
pub cpu_usage: f64,
pub mem_usage: f64,
pub group_pids: Vec<u32>,
pub pid: u32,
pub name: String,
pub cpu_usage: f64,
pub mem_usage: f64,
pub group_pids: Vec<u32>,
}
#[derive(Clone, Default, Debug)]
pub struct ConvertedCpuData {
pub cpu_name: String,
pub cpu_data: Vec<(f64, f64)>,
pub cpu_name: String,
/// Tuple is time, value
pub cpu_data: Vec<(f64, f64)>,
}
pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> {
let mut sensor_vector: Vec<Vec<String>> = Vec::new();
let mut sensor_vector: Vec<Vec<String>> = Vec::new();
let current_data = &app.data_collection;
let temp_type = &app.app_config_fields.temperature_type;
let current_data = &app.data_collection;
let temp_type = &app.app_config_fields.temperature_type;
if current_data.temp_harvest.is_empty() {
sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()])
} else {
for sensor in &current_data.temp_harvest {
sensor_vector.push(vec![
sensor.component_name.to_string(),
(sensor.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",
},
]);
}
}
if current_data.temp_harvest.is_empty() {
sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()])
} else {
for sensor in &current_data.temp_harvest {
sensor_vector.push(vec![
sensor.component_name.to_string(),
(sensor.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",
},
]);
}
}
sensor_vector
sensor_vector
}
pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<String>> {
let mut disk_vector: Vec<Vec<String>> = Vec::new();
for (itx, disk) in current_data.disk_harvest.iter().enumerate() {
let io_activity = if current_data.io_labels.len() > itx {
let converted_read = get_simple_byte_values(current_data.io_labels[itx].0, false);
let converted_write = get_simple_byte_values(current_data.io_labels[itx].1, false);
(
format!("{:.*}{}/s", 0, converted_read.0, converted_read.1),
format!("{:.*}{}/s", 0, converted_write.0, converted_write.1),
)
} else {
("0B/s".to_string(), "0B/s".to_string())
};
let mut disk_vector: Vec<Vec<String>> = Vec::new();
for (itx, disk) in current_data.disk_harvest.iter().enumerate() {
let io_activity = if current_data.io_labels.len() > itx {
let converted_read = get_simple_byte_values(current_data.io_labels[itx].0, false);
let converted_write = get_simple_byte_values(current_data.io_labels[itx].1, false);
(
format!("{:.*}{}/s", 0, converted_read.0, converted_read.1),
format!("{:.*}{}/s", 0, converted_write.0, converted_write.1),
)
} else {
("0B/s".to_string(), "0B/s".to_string())
};
let converted_free_space = get_simple_byte_values(disk.free_space, false);
let converted_total_space = get_simple_byte_values(disk.total_space, false);
disk_vector.push(vec![
disk.name.to_string(),
disk.mount_point.to_string(),
format!(
"{:.0}%",
disk.used_space as f64 / disk.total_space as f64 * 100_f64
),
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1),
format!(
"{:.*}{}",
0, converted_total_space.0, converted_total_space.1
),
io_activity.0,
io_activity.1,
]);
}
let converted_free_space = get_simple_byte_values(disk.free_space, false);
let converted_total_space = get_simple_byte_values(disk.total_space, false);
disk_vector.push(vec![
disk.name.to_string(),
disk.mount_point.to_string(),
format!(
"{:.0}%",
disk.used_space as f64 / disk.total_space as f64 * 100_f64
),
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1),
format!(
"{:.*}{}",
0, converted_total_space.0, converted_total_space.1
),
io_activity.0,
io_activity.1,
]);
}
disk_vector
disk_vector
}
pub fn convert_cpu_data_points(
show_avg_cpu: bool, current_data: &data_farmer::DataCollection,
show_avg_cpu: bool, current_data: &data_farmer::DataCollection,
) -> Vec<ConvertedCpuData> {
let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new();
let current_time = current_data.current_instant;
let cpu_listing_offset = if show_avg_cpu { 0 } else { 1 };
let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new();
let current_time = current_data.current_instant;
let cpu_listing_offset = if show_avg_cpu { 0 } else { 1 };
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
for (itx, cpu) in data.cpu_data.iter().enumerate() {
if !show_avg_cpu && itx == 0 {
continue;
}
for (itx, cpu) in data.cpu_data.iter().enumerate() {
if !show_avg_cpu && itx == 0 {
continue;
}
// Check if the vector exists yet
let itx_offset = itx - cpu_listing_offset;
if cpu_data_vector.len() <= itx_offset {
cpu_data_vector.push(ConvertedCpuData::default());
cpu_data_vector[itx_offset].cpu_name =
current_data.cpu_harvest[itx].cpu_name.clone();
}
// Check if the vector exists yet
let itx_offset = itx - cpu_listing_offset;
if cpu_data_vector.len() <= itx_offset {
cpu_data_vector.push(ConvertedCpuData::default());
cpu_data_vector[itx_offset].cpu_name =
current_data.cpu_harvest[itx].cpu_name.clone();
}
//Insert joiner points
for &(joiner_offset, joiner_val) in &cpu.1 {
let offset_time = time_from_start - joiner_offset as f64;
cpu_data_vector[itx_offset]
.cpu_data
.push((offset_time, joiner_val));
}
//Insert joiner points
for &(joiner_offset, joiner_val) in &cpu.1 {
let offset_time = time_from_start - joiner_offset as f64;
cpu_data_vector[itx_offset]
.cpu_data
.push((offset_time, joiner_val));
}
cpu_data_vector[itx_offset]
.cpu_data
.push((time_from_start, cpu.0));
}
}
cpu_data_vector[itx_offset]
.cpu_data
.push((time_from_start, cpu.0));
}
}
cpu_data_vector
cpu_data_vector
}
pub fn convert_mem_data_points(current_data: &data_farmer::DataCollection) -> Vec<(f64, f64)> {
let mut result: Vec<(f64, f64)> = Vec::new();
let current_time = current_data.current_instant;
let mut result: Vec<(f64, f64)> = Vec::new();
let current_time = current_data.current_instant;
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.mem_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
}
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.mem_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
}
result.push((time_from_start, data.mem_data.0));
}
result.push((time_from_start, data.mem_data.0));
}
result
result
}
pub fn convert_swap_data_points(current_data: &data_farmer::DataCollection) -> Vec<(f64, f64)> {
let mut result: Vec<(f64, f64)> = Vec::new();
let current_time = current_data.current_instant;
let mut result: Vec<(f64, f64)> = Vec::new();
let current_time = current_data.current_instant;
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.swap_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
}
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.swap_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
}
result.push((time_from_start, data.swap_data.0));
}
result.push((time_from_start, data.swap_data.0));
}
result
result
}
pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String, String) {
let mem_label = if current_data.memory_harvest.mem_total_in_mb == 0 {
"".to_string()
} else {
"RAM:".to_string()
+ &format!(
"{:3.0}%",
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.memory_harvest.mem_total_in_mb as f64)
.round()
) + &format!(
" {:.1}GB/{:.1}GB",
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
let mem_label = if current_data.memory_harvest.mem_total_in_mb == 0 {
"".to_string()
} else {
"RAM:".to_string()
+ &format!(
"{:3.0}%",
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.memory_harvest.mem_total_in_mb as f64)
.round()
)
+ &format!(
" {:.1}GB/{:.1}GB",
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
let swap_label = if current_data.swap_harvest.mem_total_in_mb == 0 {
"".to_string()
} else {
"SWP:".to_string()
+ &format!(
"{:3.0}%",
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.swap_harvest.mem_total_in_mb as f64)
.round()
) + &format!(
" {:.1}GB/{:.1}GB",
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
let swap_label = if current_data.swap_harvest.mem_total_in_mb == 0 {
"".to_string()
} else {
"SWP:".to_string()
+ &format!(
"{:3.0}%",
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.swap_harvest.mem_total_in_mb as f64)
.round()
)
+ &format!(
" {:.1}GB/{:.1}GB",
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
(mem_label, swap_label)
(mem_label, swap_label)
}
pub fn convert_network_data_points(
current_data: &data_farmer::DataCollection,
current_data: &data_farmer::DataCollection,
) -> ConvertedNetworkData {
let mut rx: Vec<(f64, f64)> = Vec::new();
let mut tx: Vec<(f64, f64)> = Vec::new();
let mut rx: Vec<(f64, f64)> = Vec::new();
let mut tx: Vec<(f64, f64)> = Vec::new();
let current_time = current_data.current_instant;
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
let current_time = current_data.current_instant;
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (TIME_STARTS_FROM as f64
- current_time.duration_since(*time).as_millis() as f64)
.floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.rx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
rx.push((offset_time, joiner_val));
}
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.rx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
rx.push((offset_time, joiner_val));
}
for &(joiner_offset, joiner_val) in &data.tx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
tx.push((offset_time, joiner_val));
}
for &(joiner_offset, joiner_val) in &data.tx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
tx.push((offset_time, joiner_val));
}
rx.push((time_from_start, data.rx_data.0));
tx.push((time_from_start, data.tx_data.0));
}
rx.push((time_from_start, data.rx_data.0));
tx.push((time_from_start, data.tx_data.0));
}
let total_rx_converted_result: (f64, String);
let rx_converted_result: (f64, String);
let total_tx_converted_result: (f64, String);
let tx_converted_result: (f64, String);
let total_rx_converted_result: (f64, String);
let rx_converted_result: (f64, String);
let total_tx_converted_result: (f64, String);
let tx_converted_result: (f64, String);
rx_converted_result = get_exact_byte_values(current_data.network_harvest.rx, false);
total_rx_converted_result = get_exact_byte_values(current_data.network_harvest.total_rx, false);
let rx_display = format!("{:.*}{}", 1, rx_converted_result.0, rx_converted_result.1);
let total_rx_display = format!(
"{:.*}{}",
1, total_rx_converted_result.0, total_rx_converted_result.1
);
rx_converted_result = get_exact_byte_values(current_data.network_harvest.rx, false);
total_rx_converted_result = get_exact_byte_values(current_data.network_harvest.total_rx, false);
let rx_display = format!("{:.*}{}", 1, rx_converted_result.0, rx_converted_result.1);
let total_rx_display = format!(
"{:.*}{}",
1, total_rx_converted_result.0, total_rx_converted_result.1
);
tx_converted_result = get_exact_byte_values(current_data.network_harvest.tx, false);
total_tx_converted_result = get_exact_byte_values(current_data.network_harvest.total_tx, false);
let tx_display = format!("{:.*}{}", 1, tx_converted_result.0, tx_converted_result.1);
let total_tx_display = format!(
"{:.*}{}",
1, total_tx_converted_result.0, total_tx_converted_result.1
);
tx_converted_result = get_exact_byte_values(current_data.network_harvest.tx, false);
total_tx_converted_result = get_exact_byte_values(current_data.network_harvest.total_tx, false);
let tx_display = format!("{:.*}{}", 1, tx_converted_result.0, tx_converted_result.1);
let total_tx_display = format!(
"{:.*}{}",
1, total_tx_converted_result.0, total_tx_converted_result.1
);
ConvertedNetworkData {
rx,
tx,
rx_display,
tx_display,
total_rx_display,
total_tx_display,
}
ConvertedNetworkData {
rx,
tx,
rx_display,
tx_display,
total_rx_display,
total_tx_display,
}
}
pub fn convert_process_data(
current_data: &data_farmer::DataCollection,
current_data: &data_farmer::DataCollection,
) -> (HashMap<u32, ProcessHarvest>, Vec<ConvertedProcessData>) {
let mut single_list: HashMap<u32, ProcessHarvest> = HashMap::new();
let mut single_list: HashMap<u32, ProcessHarvest> = HashMap::new();
// cpu, mem, pids
let mut grouped_hashmap: HashMap<String, (u32, f64, f64, Vec<u32>)> =
std::collections::HashMap::new();
// cpu, mem, pids
let mut grouped_hashmap: HashMap<String, (u32, f64, f64, Vec<u32>)> =
std::collections::HashMap::new();
// Go through every single process in the list... and build a hashmap + single list
for process in &(current_data).process_harvest {
let entry = grouped_hashmap.entry(process.name.clone()).or_insert((
process.pid,
0.0,
0.0,
Vec::new(),
));
// Go through every single process in the list... and build a hashmap + single list
for process in &(current_data).process_harvest {
let entry = grouped_hashmap.entry(process.name.clone()).or_insert((
process.pid,
0.0,
0.0,
Vec::new(),
));
(*entry).1 += process.cpu_usage_percent;
(*entry).2 += process.mem_usage_percent;
(*entry).3.push(process.pid);
(*entry).1 += process.cpu_usage_percent;
(*entry).2 += process.mem_usage_percent;
(*entry).3.push(process.pid);
single_list.insert(process.pid, process.clone());
}
single_list.insert(process.pid, process.clone());
}
let grouped_list: Vec<ConvertedProcessData> = grouped_hashmap
.iter()
.map(|(name, process_details)| {
let p = process_details.clone();
ConvertedProcessData {
pid: p.0,
name: name.to_string(),
cpu_usage: p.1,
mem_usage: p.2,
group_pids: p.3,
}
})
.collect::<Vec<_>>();
let grouped_list: Vec<ConvertedProcessData> = grouped_hashmap
.iter()
.map(|(name, process_details)| {
let p = process_details.clone();
ConvertedProcessData {
pid: p.0,
name: name.to_string(),
cpu_usage: p.1,
mem_usage: p.2,
group_pids: p.3,
}
})
.collect::<Vec<_>>();
(single_list, grouped_list)
(single_list, grouped_list)
}

File diff suppressed because it is too large Load diff

267
src/options.rs Normal file
View file

@ -0,0 +1,267 @@
use serde::Deserialize;
use crate::{
app::{data_harvester, App, WidgetPosition},
constants::*,
utils::error::{self, BottomError},
};
#[derive(Default, Deserialize)]
pub struct Config {
pub flags: Option<ConfigFlags>,
pub colors: Option<ConfigColours>,
}
#[derive(Default, Deserialize)]
pub struct ConfigFlags {
pub avg_cpu: Option<bool>,
pub dot_marker: Option<bool>,
pub temperature_type: Option<String>,
pub rate: Option<u64>,
pub left_legend: Option<bool>,
pub current_usage: Option<bool>,
pub group_processes: Option<bool>,
pub case_sensitive: Option<bool>,
pub whole_word: Option<bool>,
pub regex: Option<bool>,
pub default_widget: Option<String>,
pub show_disabled_data: Option<bool>,
pub basic: Option<bool>,
//disabled_cpu_cores: Option<Vec<u64>>, // TODO: [FEATURE] Enable disabling cores in config/flags
}
#[derive(Default, Deserialize)]
pub struct ConfigColours {
pub table_header_color: Option<String>,
pub avg_cpu_color: Option<String>,
pub cpu_core_colors: Option<Vec<String>>,
pub ram_color: Option<String>,
pub swap_color: Option<String>,
pub rx_color: Option<String>,
pub tx_color: Option<String>,
pub rx_total_color: Option<String>,
pub tx_total_color: Option<String>,
pub border_color: Option<String>,
pub highlighted_border_color: Option<String>,
pub text_color: Option<String>,
pub selected_text_color: Option<String>,
pub selected_bg_color: Option<String>,
pub widget_title_color: Option<String>,
pub graph_color: Option<String>,
}
pub fn get_update_rate_in_milliseconds(
update_rate: &Option<&str>, config: &Config,
) -> error::Result<u128> {
let update_rate_in_milliseconds = if let Some(update_rate) = update_rate {
update_rate.parse::<u128>()?
} else if let Some(flags) = &config.flags {
if let Some(rate) = flags.rate {
rate as u128
} else {
DEFAULT_REFRESH_RATE_IN_MILLISECONDS
}
} else {
DEFAULT_REFRESH_RATE_IN_MILLISECONDS
};
if update_rate_in_milliseconds < 250 {
return Err(BottomError::InvalidArg(
"Please set your update rate to be greater than 250 milliseconds.".to_string(),
));
} else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
return Err(BottomError::InvalidArg(
"Please set your update rate to be less than unsigned INT_MAX.".to_string(),
));
}
Ok(update_rate_in_milliseconds)
}
pub fn get_temperature_option(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<data_harvester::temperature::TemperatureType> {
if matches.is_present("FAHRENHEIT") {
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
} else if matches.is_present("KELVIN") {
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
} else if matches.is_present("CELSIUS") {
return Ok(data_harvester::temperature::TemperatureType::Celsius);
} else if let Some(flags) = &config.flags {
if let Some(temp_type) = &flags.temperature_type {
// Give lowest priority to config.
return match temp_type.as_str() {
"fahrenheit" | "f" => {
Ok(data_harvester::temperature::TemperatureType::Fahrenheit)
}
"kelvin" | "k" => {
Ok(data_harvester::temperature::TemperatureType::Kelvin)
}
"celsius" | "c" => {
Ok(data_harvester::temperature::TemperatureType::Celsius)
}
_ => {
Err(BottomError::ConfigError(
"Invalid temperature type. Please have the value be of the form <kelvin|k|celsius|c|fahrenheit|f>".to_string()
))
}
};
}
}
Ok(data_harvester::temperature::TemperatureType::Celsius)
}
pub fn get_avg_cpu_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("AVG_CPU") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(avg_cpu) = flags.avg_cpu {
return avg_cpu;
}
}
false
}
pub fn get_use_dot_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("DOT_MARKER") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(dot_marker) = flags.dot_marker {
return dot_marker;
}
}
false
}
pub fn get_use_left_legend_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("LEFT_LEGEND") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(left_legend) = flags.left_legend {
return left_legend;
}
}
false
}
pub fn get_use_current_cpu_total_option(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> bool {
if matches.is_present("USE_CURR_USAGE") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(current_usage) = flags.current_usage {
return current_usage;
}
}
false
}
pub fn get_show_disabled_data_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("SHOW_DISABLED_DATA") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(show_disabled_data) = flags.show_disabled_data {
return show_disabled_data;
}
}
false
}
pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("BASIC_MODE") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(basic) = flags.basic {
return basic;
}
}
false
}
pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
if matches.is_present("GROUP_PROCESSES") {
app.toggle_grouping();
} else if let Some(flags) = &config.flags {
if let Some(grouping) = flags.group_processes {
if grouping {
app.toggle_grouping();
}
}
}
}
pub fn enable_app_case_sensitive(
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
if matches.is_present("CASE_SENSITIVE") {
app.process_search_state.search_toggle_ignore_case();
} else if let Some(flags) = &config.flags {
if let Some(case_sensitive) = flags.case_sensitive {
if case_sensitive {
app.process_search_state.search_toggle_ignore_case();
}
}
}
}
pub fn enable_app_match_whole_word(
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
if matches.is_present("WHOLE_WORD") {
app.process_search_state.search_toggle_whole_word();
} else if let Some(flags) = &config.flags {
if let Some(whole_word) = flags.whole_word {
if whole_word {
app.process_search_state.search_toggle_whole_word();
}
}
}
}
pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
if matches.is_present("REGEX_DEFAULT") {
app.process_search_state.search_toggle_regex();
} else if let Some(flags) = &config.flags {
if let Some(regex) = flags.regex {
if regex {
app.process_search_state.search_toggle_regex();
}
}
}
}
pub fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition {
if matches.is_present("CPU_WIDGET") {
return WidgetPosition::Cpu;
} else if matches.is_present("MEM_WIDGET") {
return WidgetPosition::Mem;
} else if matches.is_present("DISK_WIDGET") {
return WidgetPosition::Disk;
} else if matches.is_present("TEMP_WIDGET") {
return WidgetPosition::Temp;
} else if matches.is_present("NET_WIDGET") {
return WidgetPosition::Network;
} else if matches.is_present("PROC_WIDGET") {
return WidgetPosition::Process;
} else if let Some(flags) = &config.flags {
if let Some(default_widget) = &flags.default_widget {
return match default_widget.as_str() {
"cpu_default" => WidgetPosition::Cpu,
"memory_default" => WidgetPosition::Mem,
"processes_default" => WidgetPosition::Process,
"network_default" => WidgetPosition::Network,
"temperature_default" => WidgetPosition::Temp,
"disk_default" => WidgetPosition::Disk,
_ => WidgetPosition::Process,
};
}
}
WidgetPosition::Process
}

View file

@ -6,84 +6,84 @@ pub type Result<T> = result::Result<T, BottomError>;
/// An error that can occur while Bottom runs.
#[derive(Debug)]
pub enum BottomError {
/// An error when there is an IO exception.
InvalidIO(String),
/// An error when there is an invalid argument passed in.
InvalidArg(String),
/// An error when the heim library encounters a problem.
InvalidHeim(String),
/// An error when the Crossterm library encounters a problem.
CrosstermError(String),
/// An error to represent generic errors.
GenericError(String),
/// An error to represent errors with fern.
FernError(String),
/// An error to represent errors with the config.
ConfigError(String),
/// An error when there is an IO exception.
InvalidIO(String),
/// An error when there is an invalid argument passed in.
InvalidArg(String),
/// An error when the heim library encounters a problem.
InvalidHeim(String),
/// An error when the Crossterm library encounters a problem.
CrosstermError(String),
/// An error to represent generic errors.
GenericError(String),
/// An error to represent errors with fern.
FernError(String),
/// An error to represent errors with the config.
ConfigError(String),
}
impl std::fmt::Display for BottomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
BottomError::InvalidIO(ref message) => {
write!(f, "Encountered an IO exception: {}", message)
}
BottomError::InvalidArg(ref message) => write!(f, "Invalid argument: {}", message),
BottomError::InvalidHeim(ref message) => write!(
f,
"Invalid error during data collection due to Heim: {}",
message
),
BottomError::CrosstermError(ref message) => {
write!(f, "Invalid error due to Crossterm: {}", message)
}
BottomError::GenericError(ref message) => write!(f, "{}", message),
BottomError::FernError(ref message) => write!(f, "Invalid fern error: {}", message),
BottomError::ConfigError(ref message) => {
write!(f, "Invalid config file error: {}", message)
}
}
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
BottomError::InvalidIO(ref message) => {
write!(f, "Encountered an IO exception: {}", message)
}
BottomError::InvalidArg(ref message) => write!(f, "Invalid argument: {}", message),
BottomError::InvalidHeim(ref message) => write!(
f,
"Invalid error during data collection due to Heim: {}",
message
),
BottomError::CrosstermError(ref message) => {
write!(f, "Invalid error due to Crossterm: {}", message)
}
BottomError::GenericError(ref message) => write!(f, "{}", message),
BottomError::FernError(ref message) => write!(f, "Invalid fern error: {}", message),
BottomError::ConfigError(ref message) => {
write!(f, "Invalid config file error: {}", message)
}
}
}
}
impl From<std::io::Error> for BottomError {
fn from(err: std::io::Error) -> Self {
BottomError::InvalidIO(err.to_string())
}
fn from(err: std::io::Error) -> Self {
BottomError::InvalidIO(err.to_string())
}
}
impl From<heim::Error> for BottomError {
fn from(err: heim::Error) -> Self {
BottomError::InvalidHeim(err.to_string())
}
fn from(err: heim::Error) -> Self {
BottomError::InvalidHeim(err.to_string())
}
}
impl From<crossterm::ErrorKind> for BottomError {
fn from(err: crossterm::ErrorKind) -> Self {
BottomError::CrosstermError(err.to_string())
}
fn from(err: crossterm::ErrorKind) -> Self {
BottomError::CrosstermError(err.to_string())
}
}
impl From<std::num::ParseIntError> for BottomError {
fn from(err: std::num::ParseIntError) -> Self {
BottomError::InvalidArg(err.to_string())
}
fn from(err: std::num::ParseIntError) -> Self {
BottomError::InvalidArg(err.to_string())
}
}
impl From<std::string::String> for BottomError {
fn from(err: std::string::String) -> Self {
BottomError::GenericError(err)
}
fn from(err: std::string::String) -> Self {
BottomError::GenericError(err)
}
}
impl From<toml::de::Error> for BottomError {
fn from(err: toml::de::Error) -> Self {
BottomError::ConfigError(err.to_string())
}
fn from(err: toml::de::Error) -> Self {
BottomError::ConfigError(err.to_string())
}
}
impl From<fern::InitError> for BottomError {
fn from(err: fern::InitError) -> Self {
BottomError::FernError(err.to_string())
}
fn from(err: fern::InitError) -> Self {
BottomError::FernError(err.to_string())
}
}

View file

@ -1,87 +1,87 @@
use std::cmp::Ordering;
pub fn float_min(a: f32, b: f32) -> f32 {
match a.partial_cmp(&b) {
Some(x) => match x {
Ordering::Greater => b,
Ordering::Less => a,
Ordering::Equal => a,
},
None => a,
}
match a.partial_cmp(&b) {
Some(x) => match x {
Ordering::Greater => b,
Ordering::Less => a,
Ordering::Equal => a,
},
None => a,
}
}
pub fn float_max(a: f32, b: f32) -> f32 {
match a.partial_cmp(&b) {
Some(x) => match x {
Ordering::Greater => a,
Ordering::Less => b,
Ordering::Equal => a,
},
None => a,
}
match a.partial_cmp(&b) {
Some(x) => match x {
Ordering::Greater => a,
Ordering::Less => b,
Ordering::Equal => a,
},
None => a,
}
}
/// Returns a tuple containing the value and the unit. In units of 1024.
/// This only supports up to a tebibyte.
pub fn get_exact_byte_values(bytes: u64, spacing: bool) -> (f64, String) {
match bytes {
b if b < 1024 => (
bytes as f64,
if spacing {
" B".to_string()
} else {
"B".to_string()
},
),
b if b < 1_048_576 => (bytes as f64 / 1024.0, "KiB".to_string()),
b if b < 1_073_741_824 => (bytes as f64 / 1_048_576.0, "MiB".to_string()),
b if b < 1_099_511_627_776 => (bytes as f64 / 1_073_741_824.0, "GiB".to_string()),
_ => (bytes as f64 / 1_099_511_627_776.0, "TiB".to_string()),
}
match bytes {
b if b < 1024 => (
bytes as f64,
if spacing {
" B".to_string()
} else {
"B".to_string()
},
),
b if b < 1_048_576 => (bytes as f64 / 1024.0, "KiB".to_string()),
b if b < 1_073_741_824 => (bytes as f64 / 1_048_576.0, "MiB".to_string()),
b if b < 1_099_511_627_776 => (bytes as f64 / 1_073_741_824.0, "GiB".to_string()),
_ => (bytes as f64 / 1_099_511_627_776.0, "TiB".to_string()),
}
}
/// Returns a tuple containing the value and the unit. In units of 1000.
/// This only supports up to a terabyte. Note the "byte" unit will have a space appended to match the others.
pub fn get_simple_byte_values(bytes: u64, spacing: bool) -> (f64, String) {
match bytes {
b if b < 1000 => (
bytes as f64,
if spacing {
" B".to_string()
} else {
"B".to_string()
},
),
b if b < 1_000_000 => (bytes as f64 / 1000.0, "KB".to_string()),
b if b < 1_000_000_000 => (bytes as f64 / 1_000_000.0, "MB".to_string()),
b if b < 1_000_000_000_000 => (bytes as f64 / 1_000_000_000.0, "GB".to_string()),
_ => (bytes as f64 / 1_000_000_000_000.0, "TB".to_string()),
}
match bytes {
b if b < 1000 => (
bytes as f64,
if spacing {
" B".to_string()
} else {
"B".to_string()
},
),
b if b < 1_000_000 => (bytes as f64 / 1000.0, "KB".to_string()),
b if b < 1_000_000_000 => (bytes as f64 / 1_000_000.0, "MB".to_string()),
b if b < 1_000_000_000_000 => (bytes as f64 / 1_000_000_000.0, "GB".to_string()),
_ => (bytes as f64 / 1_000_000_000_000.0, "TB".to_string()),
}
}
/// Gotta get partial ordering? No problem, here's something to deal with it~
pub fn get_ordering<T: std::cmp::PartialOrd>(
a_val: T, b_val: T, reverse_order: bool,
a_val: T, b_val: T, reverse_order: bool,
) -> std::cmp::Ordering {
match a_val.partial_cmp(&b_val) {
Some(x) => match x {
Ordering::Greater => {
if reverse_order {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
}
Ordering::Less => {
if reverse_order {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
}
Ordering::Equal => Ordering::Equal,
},
None => Ordering::Equal,
}
match a_val.partial_cmp(&b_val) {
Some(x) => match x {
Ordering::Greater => {
if reverse_order {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
}
Ordering::Less => {
if reverse_order {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
}
Ordering::Equal => Ordering::Equal,
},
None => Ordering::Equal,
}
}

View file

@ -1,21 +1,21 @@
pub fn init_logger() -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S:%f]"),
record.target(),
record.level(),
message
))
})
.level(if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.chain(fern::log_file("debug.log")?)
.apply()?;
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S:%f]"),
record.target(),
record.level(),
message
))
})
.level(if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.chain(fern::log_file("debug.log")?)
.apply()?;
Ok(())
Ok(())
}

View file

@ -5,69 +5,97 @@ use predicates::prelude::*;
// These tests are mostly here just to ensure that invalid results will be caught when passing arguments...
// TODO: [TEST] Allow for release testing.
// TODO: [TEST] Allow for release testing. Do this with paths.
//======================RATES======================//
fn get_os_binary_loc() -> String {
if cfg!(target_os = "linux") {
"./target/x86_64-unknown-linux-gnu/debug/btm".to_string()
} else if cfg!(target_os = "windows") {
"./target/x86_64-pc-windows-msvc/debug/btm".to_string()
} else if cfg!(target_os = "macos") {
"./target/x86_64-apple-darwin/debug/btm".to_string()
} else {
"".to_string()
}
if cfg!(target_os = "linux") {
"./target/x86_64-unknown-linux-gnu/debug/btm".to_string()
} else if cfg!(target_os = "windows") {
"./target/x86_64-pc-windows-msvc/debug/btm".to_string()
} else if cfg!(target_os = "macos") {
"./target/x86_64-apple-darwin/debug/btm".to_string()
} else {
"".to_string()
}
}
#[test]
fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("249")
.assert()
.failure()
.stderr(predicate::str::contains("rate to be greater than 250"));
Ok(())
Command::new(get_os_binary_loc())
.arg("-r")
.arg("249")
.assert()
.failure()
.stderr(predicate::str::contains("rate to be greater than 250"));
Ok(())
}
#[test]
fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains(
"rate to be less than unsigned INT_MAX.",
));
Ok(())
Command::new(get_os_binary_loc())
.arg("-r")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains(
"rate to be less than unsigned INT_MAX.",
));
Ok(())
}
#[test]
fn test_negative_rate() -> Result<(), Box<dyn std::error::Error>> {
// This test should auto fail due to how clap works
Command::new(get_os_binary_loc())
.arg("-r")
.arg("-1000")
.assert()
.failure()
.stderr(predicate::str::contains(
"wasn't expected, or isn't valid in this context",
));
// This test should auto fail due to how clap works
Command::new(get_os_binary_loc())
.arg("-r")
.arg("-1000")
.assert()
.failure()
.stderr(predicate::str::contains(
"wasn't expected, or isn't valid in this context",
));
Ok(())
Ok(())
}
#[test]
fn test_invalid_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("100-1000")
.assert()
.failure()
.stderr(predicate::str::contains("invalid digit"));
Command::new(get_os_binary_loc())
.arg("-r")
.arg("100-1000")
.assert()
.failure()
.stderr(predicate::str::contains("invalid digit"));
Ok(())
Ok(())
}
#[test]
fn test_conflicting_temps() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-c")
.arg("-f")
.assert()
.failure()
.stderr(predicate::str::contains(
"cannot be used with one or more of the other specified arguments",
));
Ok(())
}
#[test]
fn test_conflicting_default_widget() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("--cpu_default")
.arg("--disk_default")
.assert()
.failure()
.stderr(predicate::str::contains(
"cannot be used with one or more of the other specified arguments",
));
Ok(())
}