diff options
author | ClementTsang <clementjhtsang@gmail.com> | 2019-09-09 18:34:13 -0400 |
---|---|---|
committer | ClementTsang <clementjhtsang@gmail.com> | 2019-09-09 18:34:13 -0400 |
commit | d9a0d32c1fcba4d0cb9505be3ed9eecf3d06476b (patch) | |
tree | edad17d5c5493b54e34e779ae703ab12a3098e4a | |
parent | ff89f1187f13f2f4568f5677f18a1567dbc73931 (diff) |
Ironed out as many kinks as possible in terms of smoothness.
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/main.rs | 312 | ||||
-rw-r--r-- | src/widgets/mod.rs | 100 | ||||
-rw-r--r-- | src/widgets/processes.rs | 8 |
4 files changed, 249 insertions, 174 deletions
@@ -7,12 +7,15 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.9" crossterm = "0.10.2" futures-preview = "0.3.0-alpha.18" +fern = "0.5" futures-timer = "0.3" futures-util = "0.2.1" heim = "0.0.7" heim-common = "0.0.7" +log = "0.4" sysinfo = "0.9.4" tokio = "0.2.0-alpha.4" diff --git a/src/main.rs b/src/main.rs index f63a7ffa..4c483687 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,9 +11,12 @@ use tui::{ mod widgets; +#[macro_use] +extern crate log; + enum Event<I> { Input(I), - Tick, + Update(widgets::Data), } #[tokio::main] @@ -22,7 +25,10 @@ async fn main() -> Result<(), io::Error> { let backend = CrosstermBackend::with_alternate_screen(screen)?; let mut terminal = Terminal::new(backend)?; - let update_rate_in_milliseconds : u64 = 500; + let tick_rate_in_milliseconds : u64 = 220; + let update_rate_in_milliseconds : u64 = 1000; + + let log = init_logger(); terminal.hide_cursor()?; // Setup input handling @@ -41,35 +47,51 @@ async fn main() -> Result<(), io::Error> { } }); } + + // Event loop + let mut data_state = widgets::DataState::default(); { let tx = tx.clone(); thread::spawn(move || { let tx = tx.clone(); loop { - tx.send(Event::Tick).unwrap(); + futures::executor::block_on(data_state.update_data()); // TODO: Fix + tx.send(Event::Update(data_state.data.clone())).unwrap(); thread::sleep(Duration::from_millis(update_rate_in_milliseconds)); } }); } - let mut app : widgets::App = widgets::App::new("rustop"); - + let mut app = widgets::App::new("rustop"); terminal.clear()?; + let mut app_data = widgets::Data::default(); loop { - if let Ok(recv) = rx.recv() { + if let Ok(recv) = rx.recv_timeout(Duration::from_millis(tick_rate_in_milliseconds)) { match recv { - Event::Input(event) => match event { - KeyEvent::Char(c) => app.on_key(c), - KeyEvent::Left => {} - KeyEvent::Right => {} - KeyEvent::Up => {} - KeyEvent::Down => {} - KeyEvent::Ctrl('c') => break, - _ => {} - }, - Event::Tick => { - app.update_data().await; // TODO: This await is causing slow responsiveness... perhaps make drawing another thread? + Event::Input(event) => { + try_debug(&log, "Input event fired!"); + match event { + KeyEvent::Char(c) => app.on_key(c), + KeyEvent::Left => app.on_left(), + KeyEvent::Right => app.on_right(), + KeyEvent::Up => app.on_up(), + KeyEvent::Down => app.on_down(), + KeyEvent::Ctrl('c') => break, + _ => {} + } + + if app.to_be_resorted { + widgets::processes::sort_processes(&mut app_data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); + app.to_be_resorted = false; + } + try_debug(&log, "Input event complete."); + } + Event::Update(data) => { + try_debug(&log, "Update event fired!"); + app_data = data; + widgets::processes::sort_processes(&mut app_data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); + try_debug(&log, "Update event complete."); } } if app.should_quit { @@ -77,122 +99,152 @@ async fn main() -> Result<(), io::Error> { } } - // Convert data into tui components - let temperature_rows = app.list_of_temperature.iter().map(|sensor| { - Row::StyledData( - vec![sensor.component_name.to_string(), sensor.temperature.to_string() + "C"].into_iter(), // TODO: Change this based on temperature type - Style::default().fg(Color::LightGreen), - ) - }); + // Draw! + draw_data(&mut terminal, &app_data)?; + } - let disk_rows = app.list_of_disks.iter().map(|disk| { - Row::StyledData( - vec![ - disk.name.to_string(), - disk.mount_point.to_string(), - format!("{:.2}%", disk.used_space as f64 / disk.total_space as f64 * 100_f64), - (disk.free_space / 1024).to_string() + "GB", - (disk.total_space / 1024).to_string() + "GB", - ] - .into_iter(), // TODO: Change this based on temperature type - Style::default().fg(Color::LightGreen), - ) - }); + Ok(()) +} - let mem_total_mb = app.memory.mem_total_in_mb as f64; - let process_rows = app.list_of_processes.iter().map(|process| { - Row::StyledData( - vec![ - process.pid.to_string(), - process.command.to_string(), - format!("{:.2}%", process.cpu_usage_percent), - format!("{:.2}%", process.mem_usage_in_mb as f64 / mem_total_mb * 100_f64), - ] - .into_iter(), - Style::default().fg(Color::LightGreen), - ) - }); +fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data : &widgets::Data) -> Result<(), io::Error> { + // Convert data into tui components + let temperature_rows = app_data.list_of_temperature.iter().map(|sensor| { + Row::StyledData( + vec![sensor.component_name.to_string(), sensor.temperature.to_string() + "C"].into_iter(), // TODO: Change this based on temperature type + Style::default().fg(Color::LightGreen), + ) + }); + + let disk_rows = app_data.list_of_disks.iter().map(|disk| { + Row::StyledData( + vec![ + disk.name.to_string(), + disk.mount_point.to_string(), + format!("{:.2}%", disk.used_space as f64 / disk.total_space as f64 * 100_f64), + (disk.free_space / 1024).to_string() + "GB", + (disk.total_space / 1024).to_string() + "GB", + ] + .into_iter(), // TODO: Change this based on temperature type + Style::default().fg(Color::LightGreen), + ) + }); + + let mem_total_mb = app_data.memory.mem_total_in_mb as f64; + let process_rows = app_data.list_of_processes.iter().map(|process| { + Row::StyledData( + vec![ + process.pid.to_string(), + process.command.to_string(), + format!("{:.2}%", process.cpu_usage_percent), + format!("{:.2}%", process.mem_usage_in_mb as f64 / mem_total_mb * 100_f64), + ] + .into_iter(), + Style::default().fg(Color::LightGreen), + ) + }); + + terminal.draw(|mut f| { + let vertical_chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)].as_ref()) + .split(f.size()); + let top_chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(0) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(vertical_chunks[0]); + let middle_chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(0) + .constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref()) + .split(vertical_chunks[1]); + let middle_divided_chunk = Layout::default() + .direction(Direction::Vertical) + .margin(0) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(middle_chunks[0]); + let bottom_chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(0) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(vertical_chunks[2]); + + // Set up blocks and their components + + // CPU usage graph + Chart::default() + .block(Block::default().title("CPU Usage").borders(Borders::ALL)) + .x_axis(Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]).labels(&["0.0", "10.0"])) + .y_axis(Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]).labels(&["0.0", "10.0"])) + .datasets(&[ + Dataset::default() + .name("data1") + .marker(Marker::Dot) + .style(Style::default().fg(Color::Cyan)) + .data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)]), + Dataset::default() + .name("data2") + .marker(Marker::Braille) + .style(Style::default().fg(Color::Magenta)) + .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)]), + ]) + .render(&mut f, top_chunks[0]); + + //Memory usage graph + Block::default().title("Memory Usage").borders(Borders::ALL).render(&mut f, top_chunks[1]); + + // Temperature table + Table::new(["Sensor", "Temperature"].iter(), temperature_rows) + .block(Block::default().title("Temperatures").borders(Borders::ALL)) + .header_style(Style::default().fg(Color::LightBlue)) + .widths(&[25, 25]) + .render(&mut f, middle_divided_chunk[0]); + + // Disk usage table + Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows) + .block(Block::default().title("Disk Usage").borders(Borders::ALL)) + .header_style(Style::default().fg(Color::LightBlue)) + .widths(&[25, 25, 10, 10, 10]) + .render(&mut f, middle_divided_chunk[1]); + + // IO graph + Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_chunks[1]); + + // Network graph + Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]); + + // Processes table + Table::new(["PID", "Command", "CPU%", "Mem%"].iter(), process_rows) + .block(Block::default().title("Processes").borders(Borders::ALL)) + .header_style(Style::default().fg(Color::LightBlue)) + .widths(&[5, 15, 10, 10]) + .render(&mut f, bottom_chunks[1]); + })?; - // Draw! - terminal.draw(|mut f| { - let vertical_chunks = Layout::default() - .direction(Direction::Vertical) - .margin(1) - .constraints([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)].as_ref()) - .split(f.size()); - let top_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(vertical_chunks[0]); - let middle_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref()) - .split(vertical_chunks[1]); - let middle_divided_chunk = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(middle_chunks[0]); - let bottom_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(vertical_chunks[2]); - - // Set up blocks and their components - - // CPU usage graph - Chart::default() - .block(Block::default().title("CPU Usage").borders(Borders::ALL)) - .x_axis(Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]).labels(&["0.0", "10.0"])) - .y_axis(Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]).labels(&["0.0", "10.0"])) - .datasets(&[ - Dataset::default() - .name("data1") - .marker(Marker::Dot) - .style(Style::default().fg(Color::Cyan)) - .data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)]), - Dataset::default() - .name("data2") - .marker(Marker::Braille) - .style(Style::default().fg(Color::Magenta)) - .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)]), - ]) - .render(&mut f, top_chunks[0]); - - //Memory usage graph - Block::default().title("Memory Usage").borders(Borders::ALL).render(&mut f, top_chunks[1]); - - // Temperature table - Table::new(["Sensor", "Temperature"].iter(), temperature_rows) - .block(Block::default().title("Temperatures").borders(Borders::ALL)) - .header_style(Style::default().fg(Color::LightBlue)) - .widths(&[25, 25]) - .render(&mut f, middle_divided_chunk[0]); - - // Disk usage table - Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows) - .block(Block::default().title("Disk Usage").borders(Borders::ALL)) - .header_style(Style::default().fg(Color::LightBlue)) - .widths(&[25, 25, 10, 10, 10]) - .render(&mut f, middle_divided_chunk[1]); - - // IO graph - Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_chunks[1]); - - // Network graph - Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]); - - // Processes table - Table::new(["PID", "Command", "CPU%", "Mem%"].iter(), process_rows) - .block(Block::default().title("Processes").borders(Borders::ALL)) - .header_style(Style::default().fg(Color::LightBlue)) - .widths(&[5, 15, 10, 10]) - .render(&mut f, bottom_chunks[1]); - })?; - } + Ok(()) +} + +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]"), + record.target(), + record.level(), + message + )) + }) + .level(log::LevelFilter::Debug) + .chain(fern::log_file("debug.log")?) + .apply()?; Ok(()) } + +fn try_debug(result_log : &Result<(), fern::InitError>, message : &str) { + if result_log.is_ok() { + debug!("{}", message); + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 36c42cd6..59584abc 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -7,8 +7,23 @@ pub mod temperature; use sysinfo::{System, SystemExt}; +#[allow(dead_code)] pub struct App<'a> { + title : &'a str, pub should_quit : bool, + pub process_sorting_type : processes::ProcessSorting, + pub process_sorting_reverse : bool, + pub to_be_resorted : bool, +} + +fn set_if_valid<T : std::clone::Clone>(result : &Result<T, heim::Error>, value_to_set : &mut T) { + if let Ok(result) = result { + *value_to_set = (*result).clone(); + } +} + +#[derive(Default, Clone)] +pub struct Data { pub list_of_cpu_packages : Vec<cpu::CPUData>, pub list_of_io : Vec<disks::IOData>, pub list_of_physical_io : Vec<disks::IOData>, @@ -18,15 +33,39 @@ pub struct App<'a> { pub network : network::NetworkData, pub list_of_processes : Vec<processes::ProcessData>, pub list_of_disks : Vec<disks::DiskData>, - pub title : &'a str, - process_sorting_type : processes::ProcessSorting, - process_sorting_reverse : bool, +} + +pub struct DataState { + pub data : Data, sys : System, } -fn set_if_valid<T : std::clone::Clone>(result : &Result<T, heim::Error>, value_to_set : &mut T) { - if let Ok(result) = result { - *value_to_set = (*result).clone(); +impl Default for DataState { + fn default() -> Self { + DataState { + data : Data::default(), + sys : System::new(), + } + } +} + +impl DataState { + pub async fn update_data(&mut self) { + 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! + set_if_valid(&network::get_network_data(&self.sys), &mut self.data.network); + set_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... + set_if_valid(&processes::get_sorted_processes_list().await, &mut self.data.list_of_processes); + set_if_valid(&disks::get_disk_usage_list().await, &mut self.data.list_of_disks); + set_if_valid(&disks::get_io_usage_list(false).await, &mut self.data.list_of_io); + set_if_valid(&disks::get_io_usage_list(true).await, &mut self.data.list_of_physical_io); + set_if_valid(&mem::get_mem_data_list().await, &mut self.data.memory); + set_if_valid(&mem::get_swap_data_list().await, &mut self.data.swap); + set_if_valid(&temperature::get_temperature_data().await, &mut self.data.list_of_temperature); } } @@ -35,18 +74,9 @@ impl<'a> App<'a> { App { title, process_sorting_type : processes::ProcessSorting::NAME, // TODO: Change this based on input args... - sys : System::new(), // TODO: Evaluate whether this will cause efficiency issues... - list_of_cpu_packages : Vec::new(), - list_of_disks : Vec::new(), - list_of_physical_io : Vec::new(), - list_of_io : Vec::new(), - list_of_processes : Vec::new(), - list_of_temperature : Vec::new(), - network : network::NetworkData::default(), - memory : mem::MemData::default(), - swap : mem::MemData::default(), should_quit : false, process_sorting_reverse : false, + to_be_resorted : false, } } @@ -54,48 +84,38 @@ impl<'a> App<'a> { match c { 'q' => self.should_quit = true, 'c' => { - self.process_sorting_type = processes::ProcessSorting::CPU; - //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); - // TODO: This CANNOT run while it is updating... + self.process_sorting_type = processes::ProcessSorting::CPU; // TODO: Change this such that reversing can be done by just hitting "c" twice... + self.to_be_resorted = true; } 'm' => { self.process_sorting_type = processes::ProcessSorting::MEM; - //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + self.to_be_resorted = true; } 'p' => { self.process_sorting_type = processes::ProcessSorting::PID; - //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + self.to_be_resorted = true; } 'n' => { self.process_sorting_type = processes::ProcessSorting::NAME; - //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + self.to_be_resorted = true; } 'r' => { self.process_sorting_reverse = !self.process_sorting_reverse; - //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + self.to_be_resorted = true; } _ => {} } } - pub async fn update_data(&mut self) { - self.sys.refresh_system(); - self.sys.refresh_network(); + pub fn on_left(&mut self) { + } - // What we want to do: For timed data, if there is an error, just do not add. For other data, just don't update! - set_if_valid(&network::get_network_data(&self.sys), &mut self.network); - set_if_valid(&cpu::get_cpu_data_list(&self.sys), &mut self.list_of_cpu_packages); + pub fn on_right(&mut self) { + } + + pub fn on_up(&mut self) { + } - // TODO: Joining all futures would be better... - set_if_valid( - &processes::get_sorted_processes_list(&self.process_sorting_type, self.process_sorting_reverse).await, - &mut self.list_of_processes, - ); - set_if_valid(&disks::get_disk_usage_list().await, &mut self.list_of_disks); - set_if_valid(&disks::get_io_usage_list(false).await, &mut self.list_of_io); - set_if_valid(&disks::get_io_usage_list(true).await, &mut self.list_of_physical_io); - set_if_valid(&mem::get_mem_data_list().await, &mut self.memory); - set_if_valid(&mem::get_swap_data_list().await, &mut self.swap); - set_if_valid(&temperature::get_temperature_data().await, &mut self.list_of_temperature); + pub fn on_down(&mut self) { } } diff --git a/src/widgets/processes.rs b/src/widgets/processes.rs index 88942e16..5ea79676 100644 --- a/src/widgets/processes.rs +++ b/src/widgets/processes.rs @@ -4,6 +4,7 @@ use heim_common::{ }; #[allow(dead_code)] +#[derive(Clone)] pub enum ProcessSorting { CPU, MEM, @@ -44,13 +45,13 @@ fn get_ordering<T : std::cmp::PartialOrd>(a_val : T, b_val : T, reverse_order : async fn cpu_usage(process : heim::process::Process) -> heim::process::ProcessResult<(heim::process::Process, heim_common::units::Ratio)> { let usage_1 = process.cpu_usage().await?; - futures_timer::Delay::new(std::time::Duration::from_millis(100)).await?; // TODO: This is causing MASSIVE PROBLEMS WITH THE EVENT LOOP! + futures_timer::Delay::new(std::time::Duration::from_millis(100)).await?; let usage_2 = process.cpu_usage().await?; Ok((process, usage_2 - usage_1)) } -pub async fn get_sorted_processes_list(sorting_method : &ProcessSorting, reverse_order : bool) -> Result<Vec<ProcessData>, heim::Error> { +pub async fn get_sorted_processes_list() -> Result<Vec<ProcessData>, heim::Error> { let mut process_stream = heim::process::processes().map_ok(cpu_usage).try_buffer_unordered(std::usize::MAX); let mut process_vector : Vec<ProcessData> = Vec::new(); @@ -78,12 +79,11 @@ pub async fn get_sorted_processes_list(sorting_method : &ProcessSorting, reverse } } } - sort_processes(&sorting_method, &mut process_vector, reverse_order); Ok(process_vector) } -pub fn sort_processes(sorting_method : &ProcessSorting, process_vector : &mut Vec<ProcessData>, reverse_order : bool) { +pub fn sort_processes(process_vector : &mut Vec<ProcessData>, sorting_method : &ProcessSorting, reverse_order : bool) { match sorting_method { ProcessSorting::CPU => process_vector.sort_by(|a, b| get_ordering(a.cpu_usage_percent, b.cpu_usage_percent, reverse_order)), ProcessSorting::MEM => process_vector.sort_by(|a, b| get_ordering(a.mem_usage_in_mb, b.mem_usage_in_mb, reverse_order)), |