From ae6cd3aa774e0864fb96548d8e931840d41d64c6 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Wed, 11 Sep 2019 22:10:49 -0400 Subject: [PATCH] Refactoring. Lots of it. --- src/app/mod.rs | 6 +- src/canvas/mod.rs | 148 +++++++++++++++++++++++++++ src/main.rs | 252 ++++++++++++++-------------------------------- 3 files changed, 226 insertions(+), 180 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index c07b3884..bf2d3afd 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -11,6 +11,7 @@ pub struct App<'a> { pub process_sorting_type : processes::ProcessSorting, pub process_sorting_reverse : bool, pub to_be_resorted : bool, + pub current_selected_process_position : u64, } fn set_if_valid(result : &Result, value_to_set : &mut T) { @@ -32,7 +33,7 @@ pub struct Data { pub list_of_physical_io : Vec, pub memory : Vec, pub swap : Vec, - pub list_of_temperature : 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 @@ -90,7 +91,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); - set_if_valid(&temperature::get_temperature_data().await, &mut self.data.list_of_temperature); + set_if_valid(&temperature::get_temperature_data().await, &mut self.data.list_of_temperature_sensor); // Filter out stale timed entries // TODO: ideally make this a generic function! @@ -155,6 +156,7 @@ impl<'a> App<'a> { should_quit : false, process_sorting_reverse : true, to_be_resorted : false, + current_selected_process_position : 0, } } diff --git a/src/canvas/mod.rs b/src/canvas/mod.rs index fb5d23eb..9469cf62 100644 --- a/src/canvas/mod.rs +++ b/src/canvas/mod.rs @@ -1,6 +1,154 @@ +use std::io; use tui::{ layout::{Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, Terminal, }; + +const COLOUR_LIST : [Color; 6] = [Color::LightCyan, Color::LightMagenta, Color::LightRed, Color::LightGreen, Color::LightYellow, Color::LightBlue]; + +#[derive(Default)] +pub struct CanvasData { + pub disk_data : Vec>, + pub temp_sensor_data : Vec>, + pub process_data : Vec>, + pub mem_data : Vec<(f64, f64)>, + pub swap_data : Vec<(f64, f64)>, + pub cpu_data : Vec<(String, Vec<(f64, f64)>)>, +} + +// TODO: Change the error +pub fn draw_data(terminal : &mut Terminal, canvas_data : &CanvasData) -> Result<(), io::Error> { + let temperature_rows = canvas_data.temp_sensor_data.iter().map(|sensor| { + Row::StyledData( + sensor.iter(), // TODO: Change this based on temperature type + Style::default().fg(Color::White), + ) + }); + + let disk_rows = canvas_data.disk_data.iter().map(|disk| { + Row::StyledData( + disk.iter(), // TODO: Change this based on temperature type + Style::default().fg(Color::White), + ) + }); + + let process_rows = canvas_data.process_data.iter().map(|process| Row::StyledData(process.iter(), Style::default().fg(Color::White))); + + // TODO: Convert this into a separate func! + terminal.draw(|mut f| { + let vertical_chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([Constraint::Percentage(35), Constraint::Percentage(30), Constraint::Percentage(35)].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_1 = Layout::default() + .direction(Direction::Vertical) + .margin(0) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(middle_chunks[0]); + let middle_divided_chunk_2 = Layout::default() + .direction(Direction::Vertical) + .margin(0) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(middle_chunks[1]); + 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 + { + debug!("Drawing CPU..."); + let x_axis : Axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]); + let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]); + + let mut dataset_vector : Vec = Vec::new(); + for (i, cpu) in canvas_data.cpu_data.iter().enumerate() { + dataset_vector.push( + Dataset::default() + .name(&cpu.0) + .marker(Marker::Braille) + .style(Style::default().fg(COLOUR_LIST[i % COLOUR_LIST.len()])) + .data(&(cpu.1)), + ); + } + + Chart::default() + .block(Block::default().title("CPU Usage").borders(Borders::ALL)) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&dataset_vector) + .render(&mut f, top_chunks[0]); + } + + //Memory usage graph + { + let x_axis : Axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]); + let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]); + Chart::default() + .block(Block::default().title("Memory Usage").borders(Borders::ALL)) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&[ + Dataset::default() + .name(&("MEM :".to_string() + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)))) + .marker(Marker::Braille) + .style(Style::default().fg(Color::Cyan)) + .data(&canvas_data.mem_data), + Dataset::default() + .name(&("SWAP:".to_string() + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)))) + .marker(Marker::Braille) + .style(Style::default().fg(Color::LightGreen)) + .data(&canvas_data.swap_data), + ]) + .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(&[15, 5]) + .render(&mut f, middle_divided_chunk_1[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(&[15, 10, 5, 5, 5]) + .render(&mut f, middle_divided_chunk_1[1]); + + // Temp graph + Block::default().title("Temperatures").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[0]); + + // IO graph + Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[1]); + + // Network graph + Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]); + + // Processes table + Table::new(["PID", "Name", "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(()) +} diff --git a/src/main.rs b/src/main.rs index 9a578c10..84c380f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,13 @@ #![feature(async_closure)] use crossterm::{input, AlternateScreen, InputEvent, KeyEvent}; use std::{io, sync::mpsc, thread, time::Duration}; -use tui::{ - backend::CrosstermBackend, - layout::{Constraint, Direction, Layout}, - style::{Color, Modifier, Style}, - widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, - Terminal, -}; +use tui::{backend::CrosstermBackend, Terminal}; mod app; use app::data_collection; +mod canvas; + #[macro_use] extern crate log; @@ -72,8 +68,7 @@ async fn main() -> Result<(), io::Error> { terminal.clear()?; let mut app_data = app::Data::default(); - let mut swap_data : Vec<(f64, f64)> = Vec::new(); - let mut mem_data : Vec<(f64, f64)> = Vec::new(); + let mut canvas_data = canvas::CanvasData::default(); loop { if let Ok(recv) = rx.recv_timeout(Duration::from_millis(tick_rate_in_milliseconds)) { @@ -105,8 +100,12 @@ async fn main() -> Result<(), io::Error> { try_debug(&log, "Update event complete."); // Convert all data into tui components - mem_data = update_mem_data_points(&app_data); - swap_data = update_swap_data_points(&app_data); + canvas_data.disk_data = update_disk_row(&app_data); + canvas_data.temp_sensor_data = update_temp_row(&app_data); + 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); } } if app.should_quit { @@ -116,54 +115,70 @@ async fn main() -> Result<(), io::Error> { // Draw! // TODO: We should change this btw! It should not redraw everything on every tick! - draw_data(&mut terminal, &app_data, &mem_data, &swap_data)?; + canvas::draw_data(&mut terminal, &canvas_data)?; } Ok(()) } -fn update_temp_row(app_data : &app::Data) { +fn update_temp_row(app_data : &app::Data) -> Vec> { + let mut sensor_vector : Vec> = Vec::new(); + + for sensor in &app_data.list_of_temperature_sensor { + sensor_vector.push(vec![sensor.component_name.to_string(), sensor.temperature.to_string() + "C"]); + } + + sensor_vector } -fn update_process_row(app_data : &app::Data) { +fn update_disk_row(app_data : &app::Data) -> Vec> { + let mut disk_vector : Vec> = 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), + (disk.free_space / 1024).to_string() + "GB", + (disk.total_space / 1024).to_string() + "GB", + ]); + } + + disk_vector } -fn update_cpu_data_points(app_data : &app::Data) { +fn update_process_row(app_data : &app::Data) -> Vec> { + let mut process_vector : Vec> = 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_mem_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { - convert_mem_data(&app_data.memory) -} - -fn update_swap_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { - convert_mem_data(&app_data.swap) -} - -fn draw_data(terminal : &mut Terminal, app_data : &app::Data, mem_data : &[(f64, f64)], swap_data : &[(f64, f64)]) -> Result<(), io::Error> { - const COLOUR_LIST : [Color; 6] = [Color::LightCyan, Color::LightMagenta, Color::LightRed, Color::LightGreen, Color::LightYellow, Color::LightBlue]; - - 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!("{:.1}%", 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 mut dataset_vector : Vec = Vec::new(); +fn update_cpu_data_points(app_data : &app::Data) -> Vec<(String, Vec<(f64, f64)>)> { + let mut cpu_vector : Vec<(String, Vec<(f64, f64)>)> = Vec::new(); let mut data_vector : Vec> = Vec::new(); if !app_data.list_of_cpu_packages.is_empty() { @@ -179,141 +194,22 @@ fn draw_data(terminal : &mut Terminal, app_data : } for (i, data) in data_vector.iter().enumerate() { - dataset_vector.push( - Dataset::default() - .name(&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i].cpu_name)) - .marker(Marker::Braille) - .style(Style::default().fg(COLOUR_LIST[i % COLOUR_LIST.len()])) - .data(&data), - ) + cpu_vector.push(( + (&*(app_data.list_of_cpu_packages.last().unwrap().cpu_vec[i].cpu_name)).to_string() + " " + &format!("{:.2}", data.last().unwrap_or(&(0_f64, 0_f64)).1.to_string()), + data.clone(), + )) } } - let process_rows = app_data.list_of_processes.iter().map(|process| { - Row::StyledData( - 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 - } - ), - ] - .into_iter(), - Style::default().fg(Color::LightGreen), - ) - }); + cpu_vector +} - // TODO: Convert this into a separate func! - 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_1 = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(middle_chunks[0]); - let middle_divided_chunk_2 = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(middle_chunks[1]); - let bottom_chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(vertical_chunks[2]); +fn update_mem_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { + convert_mem_data(&app_data.memory) +} - // Set up blocks and their components - - // CPU usage graph - { - debug!("Drawing CPU..."); - let x_axis : Axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]); - let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]); - - Chart::default() - .block(Block::default().title("CPU Usage").borders(Borders::ALL)) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&dataset_vector) - .render(&mut f, top_chunks[0]); - } - - //Memory usage graph - { - let x_axis : Axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]); - let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]); - Chart::default() - .block(Block::default().title("Memory Usage").borders(Borders::ALL)) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&[ - Dataset::default().name("MEM").marker(Marker::Braille).style(Style::default().fg(Color::Cyan)).data(&mem_data), - Dataset::default().name("SWAP").marker(Marker::Braille).style(Style::default().fg(Color::LightGreen)).data(&swap_data), - ]) - .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(&[15, 5]) - .render(&mut f, middle_divided_chunk_1[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(&[15, 10, 5, 5, 5]) - .render(&mut f, middle_divided_chunk_1[1]); - - // Temp graph - Block::default().title("Temperatures").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[0]); - - // IO graph - Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[1]); - - // Network graph - Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]); - - // Processes table - Table::new(["PID", "Name", "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 update_swap_data_points(app_data : &app::Data) -> Vec<(f64, f64)> { + convert_mem_data(&app_data.swap) } fn convert_mem_data(mem_data : &[app::data_collection::mem::MemData]) -> Vec<(f64, f64)> {