diff --git a/src/app.rs b/src/app.rs index 2a54739e..85795451 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,5 @@ pub mod data_collection; -use data_collection::{cpu, disks, mem, network, processes, temperature}; - -use std::collections::HashMap; -use sysinfo::{System, SystemExt}; +use data_collection::{processes, temperature}; #[allow(dead_code)] pub struct App { @@ -11,167 +8,22 @@ pub struct App { pub process_sorting_reverse : bool, pub to_be_resorted : bool, pub current_selected_process_position : u64, - pub temperature_type : data_collection::temperature::TemperatureType, + pub temperature_type : temperature::TemperatureType, pub update_rate_in_milliseconds : u64, -} - -fn set_if_valid(result : &Result, value_to_set : &mut T) { - if let Ok(result) = result { - *value_to_set = (*result).clone(); - } -} - -fn push_if_valid(result : &Result, vector_to_push : &mut Vec) { - if let Ok(result) = result { - vector_to_push.push(result.clone()); - } -} - -#[derive(Default, Clone)] -pub struct Data { - pub list_of_cpu_packages : Vec, - pub list_of_io : Vec, - pub list_of_physical_io : Vec, - pub memory : Vec, - pub swap : Vec, - pub list_of_temperature_sensor : Vec, - pub network : Vec, - pub list_of_processes : Vec, // Only need to keep a list of processes... - pub list_of_disks : Vec, // Only need to keep a list of disks and their data -} - -pub struct DataState { - pub data : Data, - first_run : bool, - sys : System, - stale_max_seconds : u64, - prev_pid_stats : HashMap, // TODO: Purge list? - prev_idle : f64, - prev_non_idle : f64, - temperature_type : data_collection::temperature::TemperatureType, -} - -impl Default for DataState { - fn default() -> Self { - DataState { - data : Data::default(), - first_run : true, - sys : System::new(), - stale_max_seconds : 60, - prev_pid_stats : HashMap::new(), - prev_idle : 0_f64, - prev_non_idle : 0_f64, - temperature_type : data_collection::temperature::TemperatureType::Celsius, - } - } -} - -impl DataState { - pub fn set_stale_max_seconds(&mut self, stale_max_seconds : u64) { - self.stale_max_seconds = stale_max_seconds; - } - - pub fn set_temperature_type(&mut self, temperature_type : data_collection::temperature::TemperatureType) { - self.temperature_type = temperature_type; - } - - pub fn init(&mut self) { - self.sys.refresh_system(); - self.sys.refresh_network(); - } - - pub async fn update_data(&mut self) { - debug!("Start updating..."); - self.sys.refresh_system(); - self.sys.refresh_network(); - - // What we want to do: For timed data, if there is an error, just do not add. For other data, just don't update! - push_if_valid(&network::get_network_data(&self.sys), &mut self.data.network); - push_if_valid(&cpu::get_cpu_data_list(&self.sys), &mut self.data.list_of_cpu_packages); - - // TODO: We can convert this to a multi-threaded task... - push_if_valid(&mem::get_mem_data_list().await, &mut self.data.memory); - push_if_valid(&mem::get_swap_data_list().await, &mut self.data.swap); - set_if_valid( - &processes::get_sorted_processes_list(&mut self.prev_idle, &mut self.prev_non_idle, &mut self.prev_pid_stats).await, - &mut self.data.list_of_processes, - ); - - set_if_valid(&disks::get_disk_usage_list().await, &mut self.data.list_of_disks); - push_if_valid(&disks::get_io_usage_list(false).await, &mut self.data.list_of_io); - push_if_valid(&disks::get_io_usage_list(true).await, &mut self.data.list_of_physical_io); - set_if_valid(&temperature::get_temperature_data(&self.temperature_type).await, &mut self.data.list_of_temperature_sensor); - - if self.first_run { - self.data = Data::default(); - self.first_run = false; - } - - // Filter out stale timed entries - // TODO: ideally make this a generic function! - let current_instant = std::time::Instant::now(); - self.data.list_of_cpu_packages = self - .data - .list_of_cpu_packages - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - self.data.memory = self - .data - .memory - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - self.data.swap = self - .data - .swap - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - self.data.network = self - .data - .network - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - self.data.list_of_io = self - .data - .list_of_io - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - self.data.list_of_physical_io = self - .data - .list_of_physical_io - .iter() - .cloned() - .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) - .collect::>(); - - debug!("End updating..."); - } + pub show_average_cpu : bool, } impl App { - pub fn new(temperature_type : data_collection::temperature::TemperatureType, update_rate_in_milliseconds : u64) -> App { + pub fn new(show_average_cpu : bool, temperature_type : temperature::TemperatureType, update_rate_in_milliseconds : u64) -> App { App { - process_sorting_type : processes::ProcessSorting::CPU, // TODO: Change this based on input args... basically set this on app creation + process_sorting_type : processes::ProcessSorting::CPU, should_quit : false, process_sorting_reverse : true, to_be_resorted : false, current_selected_process_position : 0, temperature_type, update_rate_in_milliseconds, + show_average_cpu, } } diff --git a/src/app/data_collection.rs b/src/app/data_collection.rs index fdbd4bcd..6c041193 100644 --- a/src/app/data_collection.rs +++ b/src/app/data_collection.rs @@ -1,6 +1,156 @@ +use std::collections::HashMap; +use sysinfo::{System, SystemExt}; + pub mod cpu; pub mod disks; pub mod mem; pub mod network; pub mod processes; pub mod temperature; + +fn set_if_valid(result : &Result, value_to_set : &mut T) { + if let Ok(result) = result { + *value_to_set = (*result).clone(); + } +} + +fn push_if_valid(result : &Result, vector_to_push : &mut Vec) { + if let Ok(result) = result { + vector_to_push.push(result.clone()); + } +} + +#[derive(Default, Clone)] +pub struct Data { + pub list_of_cpu_packages : Vec, + pub list_of_io : Vec, + pub list_of_physical_io : Vec, + pub memory : Vec, + pub swap : Vec, + pub list_of_temperature_sensor : Vec, + pub network : Vec, + pub list_of_processes : Vec, // Only need to keep a list of processes... + pub list_of_disks : Vec, // Only need to keep a list of disks and their data +} + +pub struct DataState { + pub data : Data, + first_run : bool, + sys : System, + stale_max_seconds : u64, + prev_pid_stats : HashMap, // TODO: Purge list? + prev_idle : f64, + prev_non_idle : f64, + temperature_type : temperature::TemperatureType, +} + +impl Default for DataState { + fn default() -> Self { + DataState { + data : Data::default(), + first_run : true, + sys : System::new(), + stale_max_seconds : 60, + prev_pid_stats : HashMap::new(), + prev_idle : 0_f64, + prev_non_idle : 0_f64, + temperature_type : temperature::TemperatureType::Celsius, + } + } +} + +impl DataState { + pub fn set_stale_max_seconds(&mut self, stale_max_seconds : u64) { + self.stale_max_seconds = stale_max_seconds; + } + + pub fn set_temperature_type(&mut self, temperature_type : temperature::TemperatureType) { + self.temperature_type = temperature_type; + } + + pub fn init(&mut self) { + self.sys.refresh_system(); + self.sys.refresh_network(); + } + + pub async fn update_data(&mut self) { + debug!("Start updating..."); + self.sys.refresh_system(); + self.sys.refresh_network(); + + // What we want to do: For timed data, if there is an error, just do not add. For other data, just don't update! + push_if_valid(&network::get_network_data(&self.sys), &mut self.data.network); + push_if_valid(&cpu::get_cpu_data_list(&self.sys), &mut self.data.list_of_cpu_packages); + + // TODO: We can convert this to a multi-threaded task... + push_if_valid(&mem::get_mem_data_list().await, &mut self.data.memory); + push_if_valid(&mem::get_swap_data_list().await, &mut self.data.swap); + set_if_valid( + &processes::get_sorted_processes_list(&mut self.prev_idle, &mut self.prev_non_idle, &mut self.prev_pid_stats).await, + &mut self.data.list_of_processes, + ); + + set_if_valid(&disks::get_disk_usage_list().await, &mut self.data.list_of_disks); + push_if_valid(&disks::get_io_usage_list(false).await, &mut self.data.list_of_io); + push_if_valid(&disks::get_io_usage_list(true).await, &mut self.data.list_of_physical_io); + set_if_valid(&temperature::get_temperature_data(&self.temperature_type).await, &mut self.data.list_of_temperature_sensor); + + if self.first_run { + self.data = Data::default(); + self.first_run = false; + } + + // Filter out stale timed entries + // TODO: ideally make this a generic function! + let current_instant = std::time::Instant::now(); + self.data.list_of_cpu_packages = self + .data + .list_of_cpu_packages + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + self.data.memory = self + .data + .memory + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + self.data.swap = self + .data + .swap + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + self.data.network = self + .data + .network + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + self.data.list_of_io = self + .data + .list_of_io + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + self.data.list_of_physical_io = self + .data + .list_of_physical_io + .iter() + .cloned() + .filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds) + .collect::>(); + + debug!("End updating..."); + } +} diff --git a/src/app/data_collection/processes.rs b/src/app/data_collection/processes.rs index 7aed3b70..2ef587bb 100644 --- a/src/app/data_collection/processes.rs +++ b/src/app/data_collection/processes.rs @@ -13,6 +13,12 @@ pub enum ProcessSorting { NAME, } +impl Default for ProcessSorting { + fn default() -> Self { + ProcessSorting::CPU + } +} + // Possible process info struct? #[derive(Clone, Default)] pub struct ProcessData { diff --git a/src/app/data_collection/temperature.rs b/src/app/data_collection/temperature.rs index 19be8871..60f0a25d 100644 --- a/src/app/data_collection/temperature.rs +++ b/src/app/data_collection/temperature.rs @@ -13,6 +13,12 @@ pub enum TemperatureType { Fahrenheit, } +impl Default for TemperatureType { + fn default() -> Self { + TemperatureType::Celsius + } +} + pub async fn get_temperature_data(temp_type : &TemperatureType) -> Result, heim::Error> { let mut temperature_vec : Vec = Vec::new(); diff --git a/src/main.rs b/src/main.rs index ba1144c0..c5516d35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ extern crate clap; enum Event { Input(I), - Update(Box), + Update(Box), } const STALE_MAX_MILLISECONDS : u64 = 60 * 1000; @@ -37,11 +37,11 @@ async fn main() -> Result<(), io::Error> { (author: "Clement Tsang ") (about: "A graphical top clone.") (@arg THEME: -t --theme +takes_value "Sets a colour theme.") + (@arg AVG_CPU: -a --avgcpu "Enables showing the average CPU usage.") (@group TEMPERATURE_TYPE => (@arg celsius : -c --celsius "Sets the temperature type to Celsius. This is the default option.") (@arg fahrenheit : -f --fahrenheit "Sets the temperature type to Fahrenheit.") (@arg kelvin : -k --kelvin "Sets the temperature type to Kelvin.") - ) (@arg RATE: -r --rate +takes_value "Sets a refresh rate in milliseconds, min is 250ms, defaults to 1000ms. Higher values may take more resources.") ) @@ -62,10 +62,9 @@ async fn main() -> Result<(), io::Error> { else { app::data_collection::temperature::TemperatureType::Celsius }; + let show_average_cpu = matches.is_present("AVG_CPU"); - info!("Temperature type: {:?}", temperature_type); - - let mut app = app::App::new(temperature_type, if update_rate_in_milliseconds < 250 { 250 } else { update_rate_in_milliseconds }); + let mut app = app::App::new(show_average_cpu, temperature_type, if update_rate_in_milliseconds < 250 { 250 } else { update_rate_in_milliseconds }); terminal.hide_cursor()?; // Setup input handling @@ -86,7 +85,7 @@ async fn main() -> Result<(), io::Error> { } // Event loop - let mut data_state = app::DataState::default(); + let mut data_state = app::data_collection::DataState::default(); data_state.init(); data_state.set_stale_max_seconds(STALE_MAX_MILLISECONDS); data_state.set_temperature_type(app.temperature_type.clone()); @@ -104,7 +103,7 @@ async fn main() -> Result<(), io::Error> { terminal.clear()?; - let mut app_data = app::Data::default(); + let mut app_data = app::data_collection::Data::default(); let mut canvas_data = canvas::CanvasData::default(); loop { @@ -140,7 +139,7 @@ async fn main() -> Result<(), io::Error> { canvas_data.process_data = update_process_row(&app_data); canvas_data.mem_data = update_mem_data_points(&app_data); canvas_data.swap_data = update_swap_data_points(&app_data); - canvas_data.cpu_data = update_cpu_data_points(&app_data); + canvas_data.cpu_data = update_cpu_data_points(app.show_average_cpu, &app_data); debug!("Update event complete."); } @@ -156,7 +155,7 @@ async fn main() -> Result<(), io::Error> { Ok(()) } -fn update_temp_row(app_data : &app::Data, temp_type : &app::data_collection::temperature::TemperatureType) -> Vec> { +fn update_temp_row(app_data : &app::data_collection::Data, temp_type : &app::data_collection::temperature::TemperatureType) -> Vec> { let mut sensor_vector : Vec> = Vec::new(); for sensor in &app_data.list_of_temperature_sensor { @@ -174,7 +173,7 @@ fn update_temp_row(app_data : &app::Data, temp_type : &app::data_collection::tem sensor_vector } -fn update_disk_row(app_data : &app::Data) -> Vec> { +fn update_disk_row(app_data : &app::data_collection::Data) -> Vec> { let mut disk_vector : Vec> = Vec::new(); for disk in &app_data.list_of_disks { disk_vector.push(vec![ @@ -189,7 +188,7 @@ fn update_disk_row(app_data : &app::Data) -> Vec> { disk_vector } -fn update_process_row(app_data : &app::Data) -> Vec> { +fn update_process_row(app_data : &app::data_collection::Data) -> Vec> { let mut process_vector : Vec> = Vec::new(); for process in &app_data.list_of_processes { @@ -220,15 +219,15 @@ fn update_process_row(app_data : &app::Data) -> Vec> { process_vector } -fn update_cpu_data_points(app_data : &app::Data) -> Vec<(String, Vec<(f64, f64)>)> { +fn update_cpu_data_points(show_avg_cpu : bool, app_data : &app::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::new(); if !app_data.list_of_cpu_packages.is_empty() { // Initially, populate the cpu_collection. We want to inject elements in between if possible. - for cpu_num in 1..app_data.list_of_cpu_packages.last().unwrap().cpu_vec.len() { - // TODO: 1 to skip total cpu? Or no? + // 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 { @@ -246,8 +245,9 @@ fn update_cpu_data_points(app_data : &app::Data) -> Vec<(String, Vec<(f64, f64)> // Finally, add it all onto the end for (i, data) in cpu_collection.iter().enumerate() { cpu_data_vector.push(( - // + 1 to skip total CPU... - (&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i + 1].cpu_name)).to_string() + " " + &format!("{:3}%", (data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)), + // + 1 to skip total CPU if show_avg_cpu is false + (&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i + if show_avg_cpu { 0 } else { 1 }].cpu_name)).to_string() + + " " + &format!("{:3}%", (data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)), data.clone(), )) } @@ -256,11 +256,11 @@ fn update_cpu_data_points(app_data : &app::Data) -> Vec<(String, Vec<(f64, f64)> cpu_data_vector } -fn update_mem_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { +fn update_mem_data_points(app_data : &app::data_collection::Data) -> Vec<(f64, f64)> { convert_mem_data(&app_data.memory) } -fn update_swap_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { +fn update_swap_data_points(app_data : &app::data_collection::Data) -> Vec<(f64, f64)> { convert_mem_data(&app_data.swap) }