mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-23 12:43:09 +00:00
commit
22cdc005bc
25 changed files with 5814 additions and 5032 deletions
12
README.md
12
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2472
src/app.rs
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
3303
src/canvas.rs
3303
src/canvas.rs
File diff suppressed because it is too large
Load diff
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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##"
|
||||
|
|
|
@ -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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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)
|
||||
}
|
||||
|
|
1449
src/main.rs
1449
src/main.rs
File diff suppressed because it is too large
Load diff
267
src/options.rs
Normal file
267
src/options.rs
Normal 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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue