From ff89f1187f13f2f4568f5677f18a1567dbc73931 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Mon, 9 Sep 2019 00:09:58 -0400 Subject: [PATCH] Began working on populating fields. --- README.md | 24 +++++- src/main.rs | 146 ++++++++++++++++++++++++++++--------- src/widgets/mem.rs | 12 +-- src/widgets/mod.rs | 96 ++++++++++++------------ src/widgets/processes.rs | 12 +-- src/widgets/temperature.rs | 2 +- 6 files changed, 193 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 04077e39..ff7abe38 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,34 @@ # rustop -A gotop clone, written in Rust. Mostly done in an effort to learn Rust, while also potentially making a tool that people might want to use. +A [gotop](https://github.com/cjbassi/gotop) clone, written in Rust. -## Features +## Installation -* Support for Linux. +### Linux + +TODO: Write + +### Windows + +TODO: Test + +### MacOS + +Currently, I'm unable to test on MacOS, so I'm not sure how well this will work, if at all. I'll try to source MacOS hardware to test this application. ## Thanks * As mentioned, this project is most definitely inspired by [gotop](https://github.com/cjbassi/gotop). * This application was written with the following libraries: + * [crossterm](https://github.com/TimonPost/crossterm) * [heim](https://github.com/heim-rs/heim) * [sysinfo](https://github.com/GuillaumeGomez/sysinfo) - * [termion](https://github.com/redox-os/termion) * [tokio](https://github.com/tokio-rs/tokio) * [tui-rs](https://github.com/fdehau/tui-rs) + +## Why + +I was looking to try writing more things in Rust, and I love the gotop tool. And thus, this project was born. + +Also, yes, I realize that gotop has a Rust rewrite branch... I found out about that when I was nearly ready to release the first version. diff --git a/src/main.rs b/src/main.rs index e7ce7efb..f63a7ffa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,11 @@ +#![feature(async_closure)] use crossterm::{input, AlternateScreen, InputEvent, KeyEvent}; -use std::{ - io::{self, stdin, stdout, Write}, - sync::mpsc, - thread, - time::Duration, -}; +use std::{io, sync::mpsc, thread, time::Duration}; use tui::{ backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, - widgets::{Block, Borders, Widget}, + style::{Color, Style}, + widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, Terminal, }; @@ -24,6 +21,9 @@ async fn main() -> Result<(), io::Error> { let screen = AlternateScreen::to_alternate(true)?; let backend = CrosstermBackend::with_alternate_screen(screen)?; let mut terminal = Terminal::new(backend)?; + + let update_rate_in_milliseconds : u64 = 500; + terminal.hide_cursor()?; // Setup input handling let (tx, rx) = mpsc::channel(); @@ -47,20 +47,78 @@ async fn main() -> Result<(), io::Error> { let tx = tx.clone(); loop { tx.send(Event::Tick).unwrap(); - thread::sleep(Duration::from_millis(250)); + thread::sleep(Duration::from_millis(update_rate_in_milliseconds)); } }); } let mut app : widgets::App = widgets::App::new("rustop"); + terminal.clear()?; loop { + if let Ok(recv) = rx.recv() { + 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? + } + } + if app.should_quit { + break; + } + } + + // 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), + ) + }); + + 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), + ) + }); + + 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), + ) + }); + + // Draw! terminal.draw(|mut f| { let vertical_chunks = Layout::default() .direction(Direction::Vertical) .margin(1) - .constraints([Constraint::Percentage(33), Constraint::Percentage(34), Constraint::Percentage(33)].as_ref()) + .constraints([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)].as_ref()) .split(f.size()); let top_chunks = Layout::default() .direction(Direction::Horizontal) @@ -83,37 +141,57 @@ async fn main() -> Result<(), io::Error> { .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(vertical_chunks[2]); - Block::default().title("CPU Usage").borders(Borders::ALL).render(&mut f, top_chunks[0]); + // 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]); - Block::default().title("Temperatures").borders(Borders::ALL).render(&mut f, middle_divided_chunk[0]); - Block::default().title("Disk Usage").borders(Borders::ALL).render(&mut f, middle_divided_chunk[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]); - Block::default().title("Processes").borders(Borders::ALL).render(&mut f, bottom_chunks[1]); - })?; - // TODO: Ctrl-C? - if let Ok(recv) = rx.recv() { - 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(); - } - } - if app.should_quit { - break; - } - } + // 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(()) diff --git a/src/widgets/mem.rs b/src/widgets/mem.rs index c896351c..3fa03ef9 100644 --- a/src/widgets/mem.rs +++ b/src/widgets/mem.rs @@ -2,16 +2,16 @@ use heim_common::units::information; #[derive(Clone, Default)] pub struct MemData { - pub mem_total : u64, - pub mem_used : u64, + pub mem_total_in_mb : u64, + pub mem_used_in_mb : u64, } pub async fn get_mem_data_list() -> Result { let memory = heim::memory::memory().await?; Ok(MemData { - mem_total : memory.total().get::(), - mem_used : memory.total().get::() - memory.available().get::(), + mem_total_in_mb : memory.total().get::(), + mem_used_in_mb : memory.total().get::() - memory.available().get::(), }) } @@ -19,7 +19,7 @@ pub async fn get_swap_data_list() -> Result { let memory = heim::memory::swap().await?; Ok(MemData { - mem_total : memory.total().get::(), - mem_used : memory.used().get::(), + mem_total_in_mb : memory.total().get::(), + mem_used_in_mb : memory.used().get::(), }) } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index c99832c1..36c42cd6 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -7,7 +7,6 @@ pub mod temperature; use sysinfo::{System, SystemExt}; -#[derive(Default)] pub struct App<'a> { pub should_quit : bool, pub list_of_cpu_packages : Vec, @@ -20,6 +19,9 @@ pub struct App<'a> { pub list_of_processes : Vec, pub list_of_disks : Vec, pub title : &'a str, + process_sorting_type : processes::ProcessSorting, + process_sorting_reverse : bool, + sys : System, } fn set_if_valid(result : &Result, value_to_set : &mut T) { @@ -30,72 +32,70 @@ fn set_if_valid(result : &Result, value_t impl<'a> App<'a> { pub fn new(title : &str) -> App { - let mut app = App::default(); - app.title = title; - app + 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, + } } pub fn on_key(&mut self, c : char) { 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... + } + 'm' => { + self.process_sorting_type = processes::ProcessSorting::MEM; + //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + } + 'p' => { + self.process_sorting_type = processes::ProcessSorting::PID; + //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + } + 'n' => { + self.process_sorting_type = processes::ProcessSorting::NAME; + //processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); + } + '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); + } _ => {} } } pub async fn update_data(&mut self) { - // Initialize - let mut sys = System::new(); - - sys.refresh_system(); - sys.refresh_network(); + 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(&sys), &mut self.network); - set_if_valid(&cpu::get_cpu_data_list(&sys), &mut self.list_of_cpu_packages); + 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); // TODO: Joining all futures would be better... - set_if_valid(&processes::get_sorted_processes_list(processes::ProcessSorting::NAME, false).await, &mut self.list_of_processes); + 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); - - /* - // DEBUG - output results - for process in &list_of_processes { - println!( - "Process: {} with PID {}, CPU: {}%, MEM: {} MB", - process.command, process.pid, process.cpu_usage_percent, process.mem_usage_in_mb, - ); - } - for disk in &list_of_disks { - println!("{} is mounted on {}: {} used.", disk.name, disk.mount_point, disk.used_space as f64 / disk.total_space as f64); - // TODO: Check if this is valid - } - - for io in &list_of_io { - println!("IO counter for {}: {} writes, {} reads.", &io.mount_point, io.write_bytes, io.read_bytes); - } - - for io in &list_of_physical_io { - println!("Physical IO counter for {}: {} writes, {} reads.", &io.mount_point, io.write_bytes, io.read_bytes); - } - - for cpu in &list_of_cpu_packages { - println!("CPU {} has {}% usage!", &cpu.cpu_name, cpu.cpu_usage); - } - - println!("Memory usage: {} out of {} is used", memory.mem_used, memory.mem_total); - - println!("Memory usage: {} out of {} is used", swap.mem_used, swap.mem_total); - - for sensor in &list_of_temperature { - println!("Sensor for {} is at {} degrees Celsius", sensor.component_name, sensor.temperature); - } - - println!("Network: {} rx, {} tx", network.rx, network.tx); - */ } } diff --git a/src/widgets/processes.rs b/src/widgets/processes.rs index 20f6b3b2..88942e16 100644 --- a/src/widgets/processes.rs +++ b/src/widgets/processes.rs @@ -15,7 +15,7 @@ pub enum ProcessSorting { #[derive(Clone, Default)] pub struct ProcessData { pub pid : u32, - pub cpu_usage_percent : f32, + pub cpu_usage_percent : f64, pub mem_usage_in_mb : u64, pub command : String, } @@ -44,13 +44,13 @@ fn get_ordering(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?; + futures_timer::Delay::new(std::time::Duration::from_millis(100)).await?; // TODO: This is causing MASSIVE PROBLEMS WITH THE EVENT LOOP! 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, heim::Error> { +pub async fn get_sorted_processes_list(sorting_method : &ProcessSorting, reverse_order : bool) -> Result, heim::Error> { let mut process_stream = heim::process::processes().map_ok(cpu_usage).try_buffer_unordered(std::usize::MAX); let mut process_vector : Vec = Vec::new(); @@ -72,18 +72,18 @@ pub async fn get_sorted_processes_list(sorting_method : ProcessSorting, reverse_ process_vector.push(ProcessData { command : process.name().await.unwrap_or_else(|_| "".to_string()), pid : process.pid() as u32, - cpu_usage_percent : cpu_usage.get::(), + cpu_usage_percent : f64::from(cpu_usage.get::()), mem_usage_in_mb : mem_measurement.rss().get::(), }); } } } - sort_processes(sorting_method, &mut process_vector, reverse_order); + sort_processes(&sorting_method, &mut process_vector, reverse_order); Ok(process_vector) } -pub fn sort_processes(sorting_method : ProcessSorting, process_vector : &mut Vec, reverse_order : bool) { +pub fn sort_processes(sorting_method : &ProcessSorting, process_vector : &mut Vec, 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)), diff --git a/src/widgets/temperature.rs b/src/widgets/temperature.rs index 6c153aef..f564647f 100644 --- a/src/widgets/temperature.rs +++ b/src/widgets/temperature.rs @@ -14,7 +14,7 @@ pub async fn get_temperature_data() -> Result, heim::Error> { if let Ok(sensor) = sensor { temperature_vec.push(TempData { component_name : Box::from(sensor.unit()), - temperature : sensor.current().get::(), + temperature : sensor.current().get::(), // TODO: Allow for toggling this! }); } }