From 43ac5c339971e27261f87aea79b56d7ac4159cea Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Mon, 16 Sep 2019 16:18:42 -0400 Subject: [PATCH] Added reads and writes for disk. --- TODO.md | 4 ++ src/app.rs | 64 +++++++++++++------- src/app/data_collection.rs | 21 ++++--- src/app/data_collection/disks.rs | 37 +++++++----- src/app/data_collection/processes.rs | 2 + src/canvas.rs | 90 ++++++++++++++++++++++------ src/convert_data.rs | 49 ++++++++++++++- src/main.rs | 71 ++++++++++------------ src/utils/logging.rs | 8 ++- 9 files changed, 242 insertions(+), 104 deletions(-) diff --git a/TODO.md b/TODO.md index 0db24577..f307cef7 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,8 @@ ## After making public +* Header should be clear on current sorting direction! + * 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...? @@ -42,4 +44,6 @@ * Help screen +* Modularity + * Potentially process managing? Depends on the libraries... diff --git a/src/app.rs b/src/app.rs index 2461a188..0b1dc2e7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,19 +12,29 @@ pub enum ApplicationPosition { PROCESS, } +pub enum ScrollDirection { + /// UP means scrolling up --- this usually DECREMENTS + UP, + /// DOWN means scrolling down --- this usually INCREMENTS + DOWN, +} + pub struct App { pub should_quit : bool, pub process_sorting_type : processes::ProcessSorting, pub process_sorting_reverse : bool, pub to_be_resorted : bool, - pub currently_selected_process_position : u64, - pub currently_selected_disk_position : u64, - pub currently_selected_temperature_position : u64, + pub currently_selected_process_position : i64, + pub currently_selected_disk_position : i64, + pub currently_selected_temperature_position : i64, 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? + pub data : data_collection::Data, + pub scroll_direction : ScrollDirection, + pub previous_process_position : i64, } impl App { @@ -42,6 +52,9 @@ impl App { show_average_cpu, current_application_position : ApplicationPosition::PROCESS, current_zoom_level_percent : 100.0, + data : data_collection::Data::default(), + scroll_direction : ScrollDirection::DOWN, + previous_process_position : 0, } } @@ -62,6 +75,7 @@ impl App { } } self.to_be_resorted = true; + self.currently_selected_process_position = 0; } 'm' => { match self.process_sorting_type { @@ -72,6 +86,7 @@ impl App { } } self.to_be_resorted = true; + self.currently_selected_process_position = 0; } 'p' => { match self.process_sorting_type { @@ -82,6 +97,7 @@ impl App { } } self.to_be_resorted = true; + self.currently_selected_process_position = 0; } 'n' => { match self.process_sorting_type { @@ -92,6 +108,7 @@ impl App { } } self.to_be_resorted = true; + self.currently_selected_process_position = 0; } _ => {} } @@ -110,40 +127,43 @@ impl App { } 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), _ => {} } + self.scroll_direction = ScrollDirection::UP; } - 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; + 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), + _ => {} } - // else if self.currently_selected_process_position < // TODO: Need to finish this! This should never go PAST the number of elements + self.scroll_direction = ScrollDirection::DOWN; } - 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; + fn change_process_position(&mut self, num_to_change_by : i64) { + if self.currently_selected_process_position + num_to_change_by >= 0 + && self.currently_selected_process_position + num_to_change_by < self.data.list_of_processes.len() as i64 + { + self.currently_selected_process_position += num_to_change_by; + } + } + + fn change_temp_position(&mut self, num_to_change_by : i64) { + if self.currently_selected_temperature_position + num_to_change_by >= 0 { + self.currently_selected_temperature_position += num_to_change_by; } // 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; + fn change_disk_position(&mut self, num_to_change_by : i64) { + if self.currently_selected_disk_position + num_to_change_by >= 0 { + self.currently_selected_disk_position += num_to_change_by; } // else if self.currently_selected_disk_position < // TODO: Need to finish this! This should never go PAST the number of elements } diff --git a/src/app/data_collection.rs b/src/app/data_collection.rs index 4e45b719..c292fb94 100644 --- a/src/app/data_collection.rs +++ b/src/app/data_collection.rs @@ -80,6 +80,11 @@ impl DataState { self.sys.refresh_system(); self.sys.refresh_network(); + if !cfg!(target_os = "linux") { + // For now, might be just windows tbh + self.sys.refresh_processes(); + } + // 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); @@ -94,7 +99,7 @@ impl DataState { 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); + //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 { @@ -144,13 +149,13 @@ impl DataState { .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::>(); + // 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/disks.rs b/src/app/data_collection/disks.rs index 4e532b8a..10349a61 100644 --- a/src/app/data_collection/disks.rs +++ b/src/app/data_collection/disks.rs @@ -10,7 +10,7 @@ pub struct DiskData { pub total_space : u64, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct IOData { pub mount_point : Box, pub read_bytes : u64, @@ -19,37 +19,46 @@ pub struct IOData { #[derive(Clone)] pub struct IOPackage { - pub io_list : Vec, + pub io_hash : std::collections::HashMap, pub instant : Instant, } +// TODO: This is total --- we have to change the calculation to PER SECOND! pub async fn get_io_usage_list(get_physical : bool) -> Result { - let mut io_list : Vec = Vec::new(); + let mut io_hash : std::collections::HashMap = 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?; - io_list.push(IOData { - mount_point : Box::from(io.device_name().to_str().unwrap_or("Name Unavailable")), - read_bytes : io.read_bytes().get::(), - write_bytes : io.write_bytes().get::(), - }) + let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable"); + io_hash.insert( + mount_point.to_string(), + IOData { + mount_point : Box::from(mount_point), + read_bytes : io.read_bytes().get::(), + write_bytes : io.write_bytes().get::(), + }, + ); } } else { let mut counter_stream = heim::disk::io_counters(); while let Some(io) = counter_stream.next().await { let io = io?; - io_list.push(IOData { - mount_point : Box::from(io.device_name().to_str().unwrap_or("Name Unavailable")), - read_bytes : io.read_bytes().get::(), - write_bytes : io.write_bytes().get::(), - }) + let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable"); + io_hash.insert( + mount_point.to_string(), + IOData { + mount_point : Box::from(mount_point), + read_bytes : io.read_bytes().get::(), + write_bytes : io.write_bytes().get::(), + }, + ); } } Ok(IOPackage { - io_list, + io_hash, instant : Instant::now(), }) } diff --git a/src/app/data_collection/processes.rs b/src/app/data_collection/processes.rs index e688b560..cb9d4516 100644 --- a/src/app/data_collection/processes.rs +++ b/src/app/data_collection/processes.rs @@ -196,6 +196,8 @@ pub async fn get_sorted_processes_list( } else if cfg!(target_os = "windows") { // Windows + + // TODO: DO NOT USE HEIM! let mut process_stream = heim::process::processes().map_ok(non_linux_cpu_usage).try_buffer_unordered(std::usize::MAX); let mut process_vector : Vec = Vec::new(); diff --git a/src/canvas.rs b/src/canvas.rs index c6154351..2233c37f 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -27,7 +27,7 @@ pub struct CanvasData { pub cpu_data : Vec<(String, Vec<(f64, f64)>)>, } -pub fn draw_data(terminal : &mut Terminal, app_data : &app::App, canvas_data : &CanvasData) -> error::Result<()> { +pub fn draw_data(terminal : &mut Terminal, app_state : &mut app::App, canvas_data : &CanvasData) -> error::Result<()> { let border_style : Style = Style::default().fg(BORDER_STYLE_COLOUR); let temperature_rows = canvas_data @@ -35,13 +35,9 @@ pub fn draw_data(terminal : &mut Terminal, app_dat .iter() .map(|sensor| Row::StyledData(sensor.iter(), Style::default().fg(TEXT_COLOUR))); let disk_rows = canvas_data.disk_data.iter().map(|disk| Row::StyledData(disk.iter(), Style::default().fg(TEXT_COLOUR))); - let process_rows = canvas_data - .process_data - .iter() - .map(|process| Row::StyledData(process.iter(), Style::default().fg(TEXT_COLOUR))); terminal.draw(|mut f| { - debug!("Drawing!"); + //debug!("Drawing!"); let vertical_chunks = Layout::default() .direction(Direction::Vertical) .margin(1) @@ -51,7 +47,7 @@ pub fn draw_data(terminal : &mut Terminal, app_dat let middle_chunks = Layout::default() .direction(Direction::Horizontal) .margin(0) - .constraints([Constraint::Percentage(65), Constraint::Percentage(35)].as_ref()) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)].as_ref()) .split(vertical_chunks[1]); let middle_divided_chunk_2 = Layout::default() @@ -76,7 +72,7 @@ pub fn draw_data(terminal : &mut Terminal, app_dat for (i, cpu) in canvas_data.cpu_data.iter().enumerate() { let mut avg_cpu_exist_offset = 0; - if app_data.show_average_cpu { + if app_state.show_average_cpu { if i == 0 { // Skip, we want to render the average cpu last! continue; @@ -95,7 +91,7 @@ pub fn draw_data(terminal : &mut Terminal, app_dat ); } - if !canvas_data.cpu_data.is_empty() && app_data.show_average_cpu { + if !canvas_data.cpu_data.is_empty() && app_state.show_average_cpu { dataset_vector.push( Dataset::default() .name(&canvas_data.cpu_data[0].0) @@ -154,14 +150,13 @@ pub fn draw_data(terminal : &mut Terminal, app_dat .block(Block::default().title("Disk Usage").borders(Borders::ALL).border_style(border_style)) .header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD)) .widths(&[ - // 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, + (width * 0.18) as u16, + (width * 0.14) as u16, + (width * 0.13) as u16, + (width * 0.13) as u16, + (width * 0.13) as u16, + (width * 0.13) as u16, + (width * 0.13) as u16, ]) .render(&mut f, middle_divided_chunk_2[1]); } @@ -192,6 +187,65 @@ pub fn draw_data(terminal : &mut Terminal, app_dat // Processes table { let width = f64::from(bottom_chunks[1].width); + + // Admittedly this is kinda a hack... but we need to: + // * Scroll + // * Show/hide elements based on scroll position + // As such, we use a process_counter to know when we've hit the process we've currently scrolled to. We also need to move the list - we can + // do so by hiding some elements! + let num_rows = i64::from(bottom_chunks[1].height) - 3; + let mut process_counter = 0; + + //TODO: Fix this! + let start_position = if num_rows <= app_state.currently_selected_process_position { + match app_state.scroll_direction { + app::ScrollDirection::UP => { + if app_state.previous_process_position - app_state.currently_selected_process_position <= num_rows { + // We don't need to scroll up yet... + debug!("No need to scroll up yet..."); + app_state.previous_process_position + } + else { + // We need to scroll up! + debug!("Scroll up! Scroll up!"); + app_state.previous_process_position = app_state.currently_selected_process_position; + app_state.currently_selected_process_position + } + } + app::ScrollDirection::DOWN => { + app_state.previous_process_position = app_state.currently_selected_process_position - num_rows + 1; + (app_state.currently_selected_process_position - num_rows + 1) + } + } + } + else { + 0 + }; + + debug!( + "START POSN: {}, CURRENT SELECTED POSN: {}, NUM ROWS: {}", + start_position, app_state.currently_selected_process_position, num_rows + ); + + let sliced_vec : Vec> = (&canvas_data.process_data[start_position as usize..]).to_vec(); + + let process_rows = sliced_vec.iter().map(|process| { + Row::StyledData( + process.iter(), + if process_counter == app_state.currently_selected_process_position - start_position { + // TODO: This is what controls the highlighting! + process_counter = -1; + Style::default().fg(Color::Black).bg(Color::Cyan) + } + else { + if process_counter >= 0 { + process_counter += 1; + } + Style::default().fg(TEXT_COLOUR) + }, + ) + }); + Table::new(["PID", "Name", "CPU%", "Mem%"].iter(), process_rows) .block(Block::default().title("Processes").borders(Borders::ALL).border_style(border_style)) .header_style(Style::default().fg(Color::LightBlue)) @@ -200,5 +254,7 @@ pub fn draw_data(terminal : &mut Terminal, app_dat } })?; + //debug!("Finished drawing."); + Ok(()) } diff --git a/src/convert_data.rs b/src/convert_data.rs index e3b70831..d802dfbd 100644 --- a/src/convert_data.rs +++ b/src/convert_data.rs @@ -19,10 +19,51 @@ pub fn update_temp_row(app_data : &data_collection::Data, temp_type : &data_coll sensor_vector } -// TODO: IO count NEEDS TO BE DONE!!!!! pub fn update_disk_row(app_data : &data_collection::Data) -> Vec> { let mut disk_vector : Vec> = Vec::new(); for disk in &app_data.list_of_disks { + let io_activity = if app_data.list_of_io.len() > 2 { + let io_package = &app_data.list_of_io.last().unwrap(); + let prev_io_package = &app_data.list_of_io[app_data.list_of_io.len() - 2]; + + let io_hashmap = &io_package.io_hash; + let prev_io_hashmap = &prev_io_package.io_hash; + let trimmed_mount = &disk.name.to_string().split('/').last().unwrap().to_string(); + let time_difference = io_package.instant.duration_since(prev_io_package.instant).as_secs_f64(); + if io_hashmap.contains_key(trimmed_mount) && prev_io_hashmap.contains_key(trimmed_mount) { + // Ideally change this... + let ele = &io_hashmap[trimmed_mount]; + let prev = &prev_io_hashmap[trimmed_mount]; + let read_bytes_per_sec = ((ele.read_bytes - prev.read_bytes) as f64 / time_difference) as u64; + let write_bytes_per_sec = ((ele.write_bytes - prev.write_bytes) as f64 / time_difference) as u64; + ( + if read_bytes_per_sec < 1024 { + format!("{}B", read_bytes_per_sec) + } + else if read_bytes_per_sec < 1024 * 1024 { + format!("{}KB", read_bytes_per_sec / 1024) + } + else { + format!("{}MB", read_bytes_per_sec / 1024 / 1024) + }, + if write_bytes_per_sec < 1024 { + format!("{}B", write_bytes_per_sec) + } + else if write_bytes_per_sec < 1024 * 1024 { + format!("{}KB", write_bytes_per_sec / 1024) + } + else { + format!("{}MB", write_bytes_per_sec / 1024 / 1024) + }, + ) + } + else { + ("0B".to_string(), "0B".to_string()) + } + } + else { + ("0B".to_string(), "0B".to_string()) + }; disk_vector.push(vec![ disk.name.to_string(), disk.mount_point.to_string(), @@ -39,6 +80,8 @@ pub fn update_disk_row(app_data : &data_collection::Data) -> Vec> { else { (disk.total_space / 1024).to_string() + "GB" }, + io_activity.0, + io_activity.1, ]); } @@ -215,8 +258,8 @@ pub fn convert_network_data_points(network_data : &[data_collection::network::Ne 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); + //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() { diff --git a/src/main.rs b/src/main.rs index d1c3c80f..40b66018 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,7 +113,6 @@ fn main() -> error::Result<()> { _ => {} } } - input.disable_mouse_mode().unwrap(); }); } @@ -142,14 +141,12 @@ fn main() -> error::Result<()> { }); } - 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::KeyInput(event) => { - debug!("Keyboard event fired!"); + // debug!("Keyboard event fired!"); match event { 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? @@ -161,55 +158,46 @@ fn main() -> error::Result<()> { } if app.to_be_resorted { - data_collection::processes::sort_processes(&mut app_data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); - canvas_data.process_data = update_process_row(&app_data); + data_collection::processes::sort_processes(&mut app.data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); + canvas_data.process_data = update_process_row(&app.data); app.to_be_resorted = false; } - debug!("Input event complete."); + // debug!("Input event complete."); } Event::MouseInput(event) => { - debug!("Mouse event fired!"); + // 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::Press(e, _x, _y) => match e { + MouseButton::WheelUp => { + app.decrement_position_count(); } - } - MouseEvent::Hold(_x, _y) => { - debug!("Mouse hold!"); - } - MouseEvent::Release(_x, _y) => { - debug!("Mouse release!"); - } - _ => { - debug!("Mouse unknown event..."); - } + MouseButton::WheelDown => { + app.increment_position_count(); + } + _ => {} + }, + MouseEvent::Hold(_x, _y) => {} + MouseEvent::Release(_x, _y) => {} + _ => {} } } Event::Update(data) => { - debug!("Update event fired!"); - app_data = *data; - data_collection::processes::sort_processes(&mut app_data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); + // debug!("Update event fired!"); + app.data = *data; + data_collection::processes::sort_processes(&mut app.data.list_of_processes, &app.process_sorting_type, app.process_sorting_reverse); // Convert all data into tui components - let network_data = update_network_data_points(&app_data); + let network_data = update_network_data_points(&app.data); canvas_data.network_data_rx = network_data.rx; canvas_data.network_data_tx = network_data.tx; canvas_data.rx_display = network_data.rx_display; canvas_data.tx_display = network_data.tx_display; - canvas_data.disk_data = update_disk_row(&app_data); - canvas_data.temp_sensor_data = update_temp_row(&app_data, &app.temperature_type); - 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.show_average_cpu, &app_data); + canvas_data.disk_data = update_disk_row(&app.data); + canvas_data.temp_sensor_data = update_temp_row(&app.data, &app.temperature_type); + 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.show_average_cpu, &app.data); debug!("Update event complete."); } @@ -219,9 +207,14 @@ fn main() -> error::Result<()> { } } // Draw! - canvas::draw_data(&mut terminal, &app, &canvas_data)?; + if let Err(err) = canvas::draw_data(&mut terminal, &mut app, &canvas_data) { + input().disable_mouse_mode().unwrap(); + error!("{}", err); + return Err(err); + } } + input().disable_mouse_mode().unwrap(); debug!("Terminating."); Ok(()) } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index f3afea42..c9281109 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -10,7 +10,13 @@ pub fn init_logger() -> Result<(), fern::InitError> { )) }) .level(if cfg!(debug_assertions) { log::LevelFilter::Debug } else { log::LevelFilter::Info }) - .chain(fern::log_file("debug.log")?) + .chain(if cfg!(debug_assertions) { + //std::fs::OpenOptions::new().write(true).create(true).append(false).open("debug.log")? + fern::log_file("debug.log")? + } + else { + fern::log_file("logging.log")? + }) .apply()?; Ok(())