Added scrolling event, need to implement across processes now.
This commit is contained in:
parent
05d4e82153
commit
1a4a261db6
34
TODO.md
34
TODO.md
|
@ -1,5 +1,7 @@
|
|||
# To-Do List
|
||||
|
||||
## Pre-release (bare minimum)
|
||||
|
||||
* ~~Get each function working as a POC~~
|
||||
|
||||
* ~~Separate each component for readability, finalize project structure~~
|
||||
|
@ -8,24 +10,36 @@
|
|||
|
||||
* ~~Write tui display, charting~~
|
||||
|
||||
* Scaling in and out
|
||||
* ~~FIX PROCESSES AHHHHHH~~
|
||||
|
||||
* Add custom error because it's really messy
|
||||
|
||||
* Remove any ``unwrap()``.
|
||||
|
||||
* Scrolling event
|
||||
* Scrolling in at least processes
|
||||
|
||||
* Keybindings
|
||||
|
||||
~~* FIX PROCESSES AHHHHHH~~
|
||||
## After making public
|
||||
|
||||
* Scaling in and out (zoom), may need to show zoom levels
|
||||
|
||||
* It would be maybe a good idea to see if we can run the process calculation across ALL cpus...?
|
||||
|
||||
* ~~Add custom error because it's really messy~~ Done, but need to implement across rest of app!
|
||||
|
||||
* Remove any ``unwrap()``, ensure no crashing!
|
||||
|
||||
* Scrolling event in lists
|
||||
|
||||
* Switching between panels
|
||||
|
||||
* Truncate columns if needed for tables
|
||||
|
||||
* Refactor everything because it's a mess
|
||||
|
||||
* Test for Windows support, mac support
|
||||
* Test for Windows support, mac support, other. May be doable, depends on sysinfo and how much I know about other OSes probably.
|
||||
|
||||
* Efficiency!!!
|
||||
|
||||
* Potentially process managing? Depends on the libraries...
|
||||
* Filtering in processes (that is, allow searching)
|
||||
|
||||
* Filtering in processes (ie: search)
|
||||
* Help screen
|
||||
|
||||
* Potentially process managing? Depends on the libraries...
|
||||
|
|
62
src/app.rs
62
src/app.rs
|
@ -1,15 +1,30 @@
|
|||
pub mod data_collection;
|
||||
use data_collection::{processes, temperature};
|
||||
|
||||
#[allow(dead_code)]
|
||||
// Probably only use the list elements...
|
||||
pub enum ApplicationPosition {
|
||||
CPU,
|
||||
MEM,
|
||||
DISK,
|
||||
TEMP,
|
||||
NETWORK,
|
||||
PROCESS,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub should_quit : bool,
|
||||
pub process_sorting_type : processes::ProcessSorting,
|
||||
pub process_sorting_reverse : bool,
|
||||
pub to_be_resorted : bool,
|
||||
pub current_selected_process_position : u64,
|
||||
pub currently_selected_process_position : u64,
|
||||
pub currently_selected_disk_position : u64,
|
||||
pub currently_selected_temperature_position : u64,
|
||||
pub temperature_type : temperature::TemperatureType,
|
||||
pub update_rate_in_milliseconds : u64,
|
||||
pub show_average_cpu : bool,
|
||||
pub current_application_position : ApplicationPosition,
|
||||
pub current_zoom_level_percent : f64, // Make at most 200, least 50?
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -19,10 +34,14 @@ impl App {
|
|||
should_quit : false,
|
||||
process_sorting_reverse : true,
|
||||
to_be_resorted : false,
|
||||
current_selected_process_position : 0,
|
||||
currently_selected_process_position : 0,
|
||||
currently_selected_disk_position : 0,
|
||||
currently_selected_temperature_position : 0,
|
||||
temperature_type,
|
||||
update_rate_in_milliseconds,
|
||||
show_average_cpu,
|
||||
current_application_position : ApplicationPosition::PROCESS,
|
||||
current_zoom_level_percent : 100.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,4 +108,43 @@ impl App {
|
|||
|
||||
pub fn on_down(&mut self) {
|
||||
}
|
||||
|
||||
pub fn decrement_position_count(&mut self) {
|
||||
match self.current_application_position {
|
||||
ApplicationPosition::PROCESS => self.change_process_position(1),
|
||||
ApplicationPosition::TEMP => self.change_temp_position(1),
|
||||
ApplicationPosition::DISK => self.change_disk_position(1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_position_count(&mut self) {
|
||||
match self.current_application_position {
|
||||
ApplicationPosition::PROCESS => self.change_process_position(-1),
|
||||
ApplicationPosition::TEMP => self.change_temp_position(-1),
|
||||
ApplicationPosition::DISK => self.change_disk_position(-1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_process_position(&mut self, num_to_change_by : i32) {
|
||||
if self.currently_selected_process_position + num_to_change_by as u64 > 0 {
|
||||
self.currently_selected_process_position += num_to_change_by as u64;
|
||||
}
|
||||
// else if self.currently_selected_process_position < // TODO: Need to finish this! This should never go PAST the number of elements
|
||||
}
|
||||
|
||||
fn change_temp_position(&mut self, num_to_change_by : i32) {
|
||||
if self.currently_selected_temperature_position + num_to_change_by as u64 > 0 {
|
||||
self.currently_selected_temperature_position += num_to_change_by as u64;
|
||||
}
|
||||
// else if self.currently_selected_temperature_position < // TODO: Need to finish this! This should never go PAST the number of elements
|
||||
}
|
||||
|
||||
fn change_disk_position(&mut self, num_to_change_by : i32) {
|
||||
if self.currently_selected_disk_position + num_to_change_by as u64 > 0 {
|
||||
self.currently_selected_disk_position += num_to_change_by as u64;
|
||||
}
|
||||
// else if self.currently_selected_disk_position < // TODO: Need to finish this! This should never go PAST the number of elements
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ use heim_common::{
|
|||
};
|
||||
use std::{collections::HashMap, process::Command};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub enum ProcessSorting {
|
||||
CPU,
|
||||
|
@ -37,6 +36,9 @@ fn vangelis_cpu_usage_calculation(prev_idle : &mut f64, prev_non_idle : &mut f64
|
|||
|
||||
let stat_results = std::fs::read_to_string(path)?;
|
||||
let first_line = stat_results.split('\n').collect::<Vec<&str>>()[0];
|
||||
|
||||
// TODO: Consider grabbing by number of threads instead, and summing the total?
|
||||
// ie: 4 threads, so: (prev - curr) / cpu_0 + ... + (prev - curr) / cpu_n instead? This might be how top does it?
|
||||
let val = first_line.split_whitespace().collect::<Vec<&str>>();
|
||||
|
||||
// SC in case that the parsing will fail due to length:
|
||||
|
@ -167,7 +169,9 @@ fn convert_ps(process : &str, cpu_usage_percentage : f64, prev_pid_stats : &mut
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn get_sorted_processes_list(prev_idle : &mut f64, prev_non_idle : &mut f64, prev_pid_stats : &mut HashMap<String, f64>) -> Result<Vec<ProcessData>, heim::Error> {
|
||||
pub async fn get_sorted_processes_list(
|
||||
prev_idle : &mut f64, prev_non_idle : &mut f64, prev_pid_stats : &mut std::collections::HashMap<String, f64>,
|
||||
) -> Result<Vec<ProcessData>, heim::Error> {
|
||||
let mut process_vector : Vec<ProcessData> = Vec::new();
|
||||
|
||||
if cfg!(target_os = "linux") {
|
||||
|
|
|
@ -70,7 +70,7 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_dat
|
|||
// CPU usage graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).labels(&["0%", "100%"]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.5]).labels(&["0%", "100%"]);
|
||||
|
||||
let mut dataset_vector : Vec<Dataset> = Vec::new();
|
||||
|
||||
|
@ -116,7 +116,7 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_dat
|
|||
//Memory usage graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).labels(&["0%", "100%"]); // Offset as the zero value isn't drawn otherwise...
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.5]).labels(&["0%", "100%"]); // Offset as the zero value isn't drawn otherwise...
|
||||
Chart::default()
|
||||
.block(Block::default().title("Memory Usage").borders(Borders::ALL).border_style(border_style))
|
||||
.x_axis(x_axis)
|
||||
|
@ -148,16 +148,20 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_dat
|
|||
|
||||
// Disk usage table
|
||||
{
|
||||
// TODO: We have to dynamically remove some of these table elements based on size...
|
||||
let width = f64::from(middle_divided_chunk_2[1].width);
|
||||
Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
|
||||
Table::new(["Disk", "Mount", "Used", "Total", "Free", "R/s", "W/s"].iter(), disk_rows)
|
||||
.block(Block::default().title("Disk Usage").borders(Borders::ALL).border_style(border_style))
|
||||
.header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD))
|
||||
.widths(&[
|
||||
(width * 0.25) as u16,
|
||||
(width * 0.2) as u16,
|
||||
(width * 0.15) as u16,
|
||||
(width * 0.15) as u16,
|
||||
(width * 0.15) as u16,
|
||||
// Must make sure these are NEVER zero! It will fail to display! Seems to only be this...
|
||||
(width * 0.2) as u16 + 1,
|
||||
(width * 0.2) as u16 + 1,
|
||||
(width * 0.1) as u16 + 1,
|
||||
(width * 0.1) as u16 + 1,
|
||||
(width * 0.1) as u16 + 1,
|
||||
(width * 0.1) as u16 + 1,
|
||||
(width * 0.1) as u16 + 1,
|
||||
])
|
||||
.render(&mut f, middle_divided_chunk_2[1]);
|
||||
}
|
||||
|
@ -165,7 +169,7 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_dat
|
|||
// Network graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 1_000_000.0]).labels(&["0GB", "1GB"]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 1_000_000.5]).labels(&["0GB", "1GB"]);
|
||||
Chart::default()
|
||||
.block(Block::default().title("Network").borders(Borders::ALL).border_style(border_style))
|
||||
.x_axis(x_axis)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// TODO: Store like three minutes of data, then change how much is shown based on scaling!
|
||||
pub const STALE_MAX_MILLISECONDS : u64 = 60 * 1000; // We wish to store at most 60 seconds worth of data. This may change in the future, or be configurable.
|
||||
pub const TICK_RATE_IN_MILLISECONDS : u64 = 200; // We use this as it's a good value to work with.
|
|
@ -0,0 +1,260 @@
|
|||
use crate::{app::data_collection, constants};
|
||||
use constants::*;
|
||||
|
||||
pub fn update_temp_row(app_data : &data_collection::Data, temp_type : &data_collection::temperature::TemperatureType) -> Vec<Vec<String>> {
|
||||
let mut sensor_vector : Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for sensor in &app_data.list_of_temperature_sensor {
|
||||
sensor_vector.push(vec![
|
||||
sensor.component_name.to_string(),
|
||||
(sensor.temperature.ceil() as u64).to_string()
|
||||
+ match temp_type {
|
||||
data_collection::temperature::TemperatureType::Celsius => "C",
|
||||
data_collection::temperature::TemperatureType::Kelvin => "K",
|
||||
data_collection::temperature::TemperatureType::Fahrenheit => "F",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
sensor_vector
|
||||
}
|
||||
|
||||
// TODO: IO count NEEDS TO BE DONE!!!!!
|
||||
pub fn update_disk_row(app_data : &data_collection::Data) -> Vec<Vec<String>> {
|
||||
let mut disk_vector : Vec<Vec<String>> = Vec::new();
|
||||
for disk in &app_data.list_of_disks {
|
||||
disk_vector.push(vec![
|
||||
disk.name.to_string(),
|
||||
disk.mount_point.to_string(),
|
||||
format!("{:.1}%", disk.used_space as f64 / disk.total_space as f64 * 100_f64),
|
||||
if disk.free_space < 1024 {
|
||||
disk.free_space.to_string() + "MB"
|
||||
}
|
||||
else {
|
||||
(disk.free_space / 1024).to_string() + "GB"
|
||||
},
|
||||
if disk.total_space < 1024 {
|
||||
disk.total_space.to_string() + "MB"
|
||||
}
|
||||
else {
|
||||
(disk.total_space / 1024).to_string() + "GB"
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
disk_vector
|
||||
}
|
||||
|
||||
pub fn update_process_row(app_data : &data_collection::Data) -> Vec<Vec<String>> {
|
||||
let mut process_vector : Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for process in &app_data.list_of_processes {
|
||||
process_vector.push(vec![
|
||||
process.pid.to_string(),
|
||||
process.command.to_string(),
|
||||
format!("{:.1}%", process.cpu_usage_percent),
|
||||
format!(
|
||||
"{:.1}%",
|
||||
if let Some(mem_usage) = process.mem_usage_percent {
|
||||
mem_usage
|
||||
}
|
||||
else if let Some(mem_usage_in_mb) = process.mem_usage_mb {
|
||||
if let Some(mem_data) = app_data.memory.last() {
|
||||
mem_usage_in_mb as f64 / mem_data.mem_total_in_mb as f64 * 100_f64
|
||||
}
|
||||
else {
|
||||
0_f64
|
||||
}
|
||||
}
|
||||
else {
|
||||
0_f64
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
process_vector
|
||||
}
|
||||
|
||||
pub fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data) -> Vec<(String, Vec<(f64, f64)>)> {
|
||||
let mut cpu_data_vector : Vec<(String, Vec<(f64, f64)>)> = Vec::new();
|
||||
let mut cpu_collection : Vec<Vec<(f64, f64)>> = Vec::new();
|
||||
|
||||
if !app_data.list_of_cpu_packages.is_empty() {
|
||||
// I'm sorry for the if statement but I couldn't be bothered here...
|
||||
for cpu_num in (if show_avg_cpu { 0 } else { 1 })..app_data.list_of_cpu_packages.last().unwrap().cpu_vec.len() {
|
||||
let mut this_cpu_data : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in &app_data.list_of_cpu_packages {
|
||||
let current_time = std::time::Instant::now();
|
||||
let current_cpu_usage = data.cpu_vec[cpu_num].cpu_usage;
|
||||
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
current_cpu_usage,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !this_cpu_data.is_empty() {
|
||||
let previous_element_data = *(this_cpu_data.last().unwrap());
|
||||
for idx in 0..50 {
|
||||
this_cpu_data.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 50.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 50.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
this_cpu_data.push(new_entry);
|
||||
}
|
||||
|
||||
cpu_collection.push(this_cpu_data);
|
||||
}
|
||||
|
||||
// Finally, add it all onto the end
|
||||
for (i, data) in cpu_collection.iter().enumerate() {
|
||||
cpu_data_vector.push((
|
||||
// + 1 to skip total CPU if show_avg_cpu is false
|
||||
format!(
|
||||
"{:4}: ",
|
||||
&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i + if show_avg_cpu { 0 } else { 1 }].cpu_name)
|
||||
)
|
||||
.to_uppercase() + &format!("{:3}%", (data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)),
|
||||
data.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
cpu_data_vector
|
||||
}
|
||||
|
||||
pub fn update_mem_data_points(app_data : &data_collection::Data) -> Vec<(f64, f64)> {
|
||||
convert_mem_data(&app_data.memory)
|
||||
}
|
||||
|
||||
pub fn update_swap_data_points(app_data : &data_collection::Data) -> Vec<(f64, f64)> {
|
||||
convert_mem_data(&app_data.swap)
|
||||
}
|
||||
|
||||
pub fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f64)> {
|
||||
let mut result : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in mem_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.mem_used_in_mb as f64 / data.mem_total_in_mb as f64 * 100_f64,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !result.is_empty() {
|
||||
let previous_element_data = *(result.last().unwrap());
|
||||
for idx in 0..50 {
|
||||
result.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 50.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 50.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
result.push(new_entry);
|
||||
//debug!("Pushed: ({}, {})", result.last().unwrap().0, result.last().unwrap().1);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub struct ConvertedNetworkData {
|
||||
pub rx : Vec<(f64, f64)>,
|
||||
pub tx : Vec<(f64, f64)>,
|
||||
pub rx_display : String,
|
||||
pub tx_display : String,
|
||||
}
|
||||
|
||||
pub fn update_network_data_points(app_data : &data_collection::Data) -> ConvertedNetworkData {
|
||||
convert_network_data_points(&app_data.network)
|
||||
}
|
||||
|
||||
pub fn convert_network_data_points(network_data : &[data_collection::network::NetworkData]) -> ConvertedNetworkData {
|
||||
let mut rx : Vec<(f64, f64)> = Vec::new();
|
||||
let mut tx : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in network_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
let rx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.rx as f64 / 1024.0,
|
||||
);
|
||||
let tx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.tx as f64 / 1024.0,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !rx.is_empty() {
|
||||
let previous_element_data = *(rx.last().unwrap());
|
||||
for idx in 0..50 {
|
||||
rx.push((
|
||||
previous_element_data.0 + ((rx_data.0 - previous_element_data.0) / 50.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((rx_data.1 - previous_element_data.1) / 50.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !tx.is_empty() {
|
||||
let previous_element_data = *(tx.last().unwrap());
|
||||
for idx in 0..50 {
|
||||
tx.push((
|
||||
previous_element_data.0 + ((tx_data.0 - previous_element_data.0) / 50.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((tx_data.1 - previous_element_data.1) / 50.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
rx.push(rx_data);
|
||||
tx.push(tx_data);
|
||||
|
||||
debug!("Pushed rx: ({}, {})", rx.last().unwrap().0, rx.last().unwrap().1);
|
||||
debug!("Pushed tx: ({}, {})", tx.last().unwrap().0, tx.last().unwrap().1);
|
||||
}
|
||||
|
||||
let rx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().rx;
|
||||
if num_bytes < 1024 {
|
||||
format!("RX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("RX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("RX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("RX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
let tx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().tx;
|
||||
if num_bytes < 1024 {
|
||||
format!("TX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("TX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("TX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("TX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
|
||||
ConvertedNetworkData { rx, tx, rx_display, tx_display }
|
||||
}
|
346
src/main.rs
346
src/main.rs
|
@ -7,29 +7,32 @@ extern crate clap;
|
|||
#[macro_use]
|
||||
extern crate failure;
|
||||
|
||||
use crossterm::{input, AlternateScreen, InputEvent, KeyEvent};
|
||||
use crossterm::{input, AlternateScreen, InputEvent, KeyEvent, MouseButton, MouseEvent};
|
||||
use std::{sync::mpsc, thread, time::Duration};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
mod app;
|
||||
use app::data_collection;
|
||||
pub mod app;
|
||||
mod utils {
|
||||
pub mod error;
|
||||
pub mod logging;
|
||||
}
|
||||
use utils::error::{self, RustopError};
|
||||
mod canvas;
|
||||
mod constants;
|
||||
mod convert_data;
|
||||
|
||||
use app::data_collection;
|
||||
use constants::{STALE_MAX_MILLISECONDS, TICK_RATE_IN_MILLISECONDS};
|
||||
use convert_data::*;
|
||||
use utils::error::{self, RustopError};
|
||||
|
||||
// End imports
|
||||
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
enum Event<I, J> {
|
||||
KeyInput(I),
|
||||
MouseInput(J),
|
||||
Update(Box<data_collection::Data>),
|
||||
}
|
||||
|
||||
const STALE_MAX_MILLISECONDS : u64 = 60 * 1000; // We wish to store at most 60 seconds worth of data. This may change in the future, or be configurable.
|
||||
const TICK_RATE_IN_MILLISECONDS : u64 = 200; // We use this as it's a good value to work with.
|
||||
|
||||
fn main() -> error::Result<()> {
|
||||
let _log = utils::logging::init_logger(); // TODO: Error handling
|
||||
|
||||
|
@ -79,20 +82,38 @@ fn main() -> error::Result<()> {
|
|||
// Create "app" struct, which will control most of the program and store settings/state
|
||||
let mut app = app::App::new(show_average_cpu, temperature_type, update_rate_in_milliseconds as u64);
|
||||
|
||||
// Set up up tui and crossterm
|
||||
let screen = AlternateScreen::to_alternate(true)?;
|
||||
let stdout = std::io::stdout();
|
||||
let backend = CrosstermBackend::with_alternate_screen(stdout, screen)?;
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
terminal.clear()?;
|
||||
|
||||
// Set up input handling
|
||||
let (tx, rx) = mpsc::channel();
|
||||
{
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let input = input();
|
||||
input.enable_mouse_mode().unwrap();
|
||||
let reader = input.read_sync();
|
||||
for event in reader {
|
||||
if let InputEvent::Keyboard(key) = event {
|
||||
if tx.send(Event::Input(key.clone())).is_err() {
|
||||
return;
|
||||
match event {
|
||||
InputEvent::Keyboard(key) => {
|
||||
if tx.send(Event::KeyInput(key.clone())).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
InputEvent::Mouse(mouse) => {
|
||||
if tx.send(Event::MouseInput(mouse)).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
input.disable_mouse_mode().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -121,30 +142,21 @@ fn main() -> error::Result<()> {
|
|||
});
|
||||
}
|
||||
|
||||
// Set up up tui and crossterm
|
||||
let screen = AlternateScreen::to_alternate(true)?;
|
||||
let stdout = std::io::stdout();
|
||||
let backend = CrosstermBackend::with_alternate_screen(stdout, screen)?;
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
terminal.clear()?;
|
||||
|
||||
let mut app_data = data_collection::Data::default();
|
||||
let mut canvas_data = canvas::CanvasData::default();
|
||||
|
||||
loop {
|
||||
if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||
match recv {
|
||||
Event::Input(event) => {
|
||||
debug!("Input event fired!");
|
||||
Event::KeyInput(event) => {
|
||||
debug!("Keyboard event fired!");
|
||||
match event {
|
||||
KeyEvent::Char(c) => app.on_key(c),
|
||||
KeyEvent::Ctrl('c') | KeyEvent::Esc => break,
|
||||
KeyEvent::Char(c) => app.on_key(c), // TODO: We can remove the 'q' event and just move it to the quit?
|
||||
KeyEvent::Left => app.on_left(),
|
||||
KeyEvent::Right => app.on_right(),
|
||||
KeyEvent::Up => app.on_up(),
|
||||
KeyEvent::Down => app.on_down(),
|
||||
KeyEvent::Ctrl('c') => break,
|
||||
KeyEvent::Esc => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -155,6 +167,32 @@ fn main() -> error::Result<()> {
|
|||
}
|
||||
debug!("Input event complete.");
|
||||
}
|
||||
Event::MouseInput(event) => {
|
||||
debug!("Mouse event fired!");
|
||||
match event {
|
||||
MouseEvent::Press(e, _x, _y) => {
|
||||
debug!("Mouse press!");
|
||||
match e {
|
||||
MouseButton::WheelUp => {
|
||||
debug!("Wheel up!");
|
||||
}
|
||||
MouseButton::WheelDown => {
|
||||
debug!("Wheel down!");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
MouseEvent::Hold(_x, _y) => {
|
||||
debug!("Mouse hold!");
|
||||
}
|
||||
MouseEvent::Release(_x, _y) => {
|
||||
debug!("Mouse release!");
|
||||
}
|
||||
_ => {
|
||||
debug!("Mouse unknown event...");
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Update(data) => {
|
||||
debug!("Update event fired!");
|
||||
app_data = *data;
|
||||
|
@ -187,261 +225,3 @@ fn main() -> error::Result<()> {
|
|||
debug!("Terminating.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_temp_row(app_data : &data_collection::Data, temp_type : &data_collection::temperature::TemperatureType) -> Vec<Vec<String>> {
|
||||
let mut sensor_vector : Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for sensor in &app_data.list_of_temperature_sensor {
|
||||
sensor_vector.push(vec![
|
||||
sensor.component_name.to_string(),
|
||||
(sensor.temperature.ceil() as u64).to_string()
|
||||
+ match temp_type {
|
||||
data_collection::temperature::TemperatureType::Celsius => "C",
|
||||
data_collection::temperature::TemperatureType::Kelvin => "K",
|
||||
data_collection::temperature::TemperatureType::Fahrenheit => "F",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
sensor_vector
|
||||
}
|
||||
|
||||
// TODO: IO count
|
||||
fn update_disk_row(app_data : &data_collection::Data) -> Vec<Vec<String>> {
|
||||
let mut disk_vector : Vec<Vec<String>> = Vec::new();
|
||||
for disk in &app_data.list_of_disks {
|
||||
disk_vector.push(vec![
|
||||
disk.name.to_string(),
|
||||
disk.mount_point.to_string(),
|
||||
format!("{:.1}%", disk.used_space as f64 / disk.total_space as f64 * 100_f64),
|
||||
if disk.free_space < 1024 {
|
||||
disk.free_space.to_string() + "MB"
|
||||
}
|
||||
else {
|
||||
(disk.free_space / 1024).to_string() + "GB"
|
||||
},
|
||||
if disk.total_space < 1024 {
|
||||
disk.total_space.to_string() + "MB"
|
||||
}
|
||||
else {
|
||||
(disk.total_space / 1024).to_string() + "GB"
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
disk_vector
|
||||
}
|
||||
|
||||
fn update_process_row(app_data : &data_collection::Data) -> Vec<Vec<String>> {
|
||||
let mut process_vector : Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for process in &app_data.list_of_processes {
|
||||
process_vector.push(vec![
|
||||
process.pid.to_string(),
|
||||
process.command.to_string(),
|
||||
format!("{:.1}%", process.cpu_usage_percent),
|
||||
format!(
|
||||
"{:.1}%",
|
||||
if let Some(mem_usage) = process.mem_usage_percent {
|
||||
mem_usage
|
||||
}
|
||||
else if let Some(mem_usage_in_mb) = process.mem_usage_mb {
|
||||
if let Some(mem_data) = app_data.memory.last() {
|
||||
mem_usage_in_mb as f64 / mem_data.mem_total_in_mb as f64 * 100_f64
|
||||
}
|
||||
else {
|
||||
0_f64
|
||||
}
|
||||
}
|
||||
else {
|
||||
0_f64
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
process_vector
|
||||
}
|
||||
|
||||
fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data) -> Vec<(String, Vec<(f64, f64)>)> {
|
||||
let mut cpu_data_vector : Vec<(String, Vec<(f64, f64)>)> = Vec::new();
|
||||
let mut cpu_collection : Vec<Vec<(f64, f64)>> = Vec::new();
|
||||
|
||||
if !app_data.list_of_cpu_packages.is_empty() {
|
||||
// I'm sorry for the if statement but I couldn't be bothered here...
|
||||
for cpu_num in (if show_avg_cpu { 0 } else { 1 })..app_data.list_of_cpu_packages.last().unwrap().cpu_vec.len() {
|
||||
let mut this_cpu_data : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in &app_data.list_of_cpu_packages {
|
||||
let current_time = std::time::Instant::now();
|
||||
let current_cpu_usage = data.cpu_vec[cpu_num].cpu_usage;
|
||||
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
current_cpu_usage,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !this_cpu_data.is_empty() {
|
||||
let previous_element_data = *(this_cpu_data.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
this_cpu_data.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
this_cpu_data.push(new_entry);
|
||||
}
|
||||
|
||||
cpu_collection.push(this_cpu_data);
|
||||
}
|
||||
|
||||
// Finally, add it all onto the end
|
||||
for (i, data) in cpu_collection.iter().enumerate() {
|
||||
cpu_data_vector.push((
|
||||
// + 1 to skip total CPU if show_avg_cpu is false
|
||||
format!(
|
||||
"{:4}: ",
|
||||
&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i + if show_avg_cpu { 0 } else { 1 }].cpu_name)
|
||||
)
|
||||
.to_uppercase() + &format!("{:3}%", (data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)),
|
||||
data.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
cpu_data_vector
|
||||
}
|
||||
|
||||
fn update_mem_data_points(app_data : &data_collection::Data) -> Vec<(f64, f64)> {
|
||||
convert_mem_data(&app_data.memory)
|
||||
}
|
||||
|
||||
fn update_swap_data_points(app_data : &data_collection::Data) -> Vec<(f64, f64)> {
|
||||
convert_mem_data(&app_data.swap)
|
||||
}
|
||||
|
||||
fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f64)> {
|
||||
let mut result : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in mem_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.mem_used_in_mb as f64 / data.mem_total_in_mb as f64 * 100_f64,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !result.is_empty() {
|
||||
let previous_element_data = *(result.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
result.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
result.push(new_entry);
|
||||
//debug!("Pushed: ({}, {})", result.last().unwrap().0, result.last().unwrap().1);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
struct ConvertedNetworkData {
|
||||
rx : Vec<(f64, f64)>,
|
||||
tx : Vec<(f64, f64)>,
|
||||
rx_display : String,
|
||||
tx_display : String,
|
||||
}
|
||||
|
||||
fn update_network_data_points(app_data : &data_collection::Data) -> ConvertedNetworkData {
|
||||
convert_network_data_points(&app_data.network)
|
||||
}
|
||||
|
||||
fn convert_network_data_points(network_data : &[data_collection::network::NetworkData]) -> ConvertedNetworkData {
|
||||
let mut rx : Vec<(f64, f64)> = Vec::new();
|
||||
let mut tx : Vec<(f64, f64)> = Vec::new();
|
||||
|
||||
for data in network_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
let rx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.rx as f64 / 1024.0,
|
||||
);
|
||||
let tx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.tx as f64 / 1024.0,
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !rx.is_empty() {
|
||||
let previous_element_data = *(rx.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
rx.push((
|
||||
previous_element_data.0 + ((rx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((rx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !tx.is_empty() {
|
||||
let previous_element_data = *(tx.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
tx.push((
|
||||
previous_element_data.0 + ((tx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((tx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
rx.push(rx_data);
|
||||
tx.push(tx_data);
|
||||
|
||||
debug!("Pushed rx: ({}, {})", rx.last().unwrap().0, rx.last().unwrap().1);
|
||||
debug!("Pushed tx: ({}, {})", tx.last().unwrap().0, tx.last().unwrap().1);
|
||||
}
|
||||
|
||||
let rx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().rx;
|
||||
if num_bytes < 1024 {
|
||||
format!("RX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("RX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("RX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("RX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
let tx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().tx;
|
||||
if num_bytes < 1024 {
|
||||
format!("TX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("TX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("TX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("TX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
|
||||
ConvertedNetworkData { rx, tx, rx_display, tx_display }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue