From 471209f511f9a23edf35529cf04cd6fa23e1b408 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 8 Sep 2019 19:56:23 -0400 Subject: [PATCH] Refactored code such that it fits more of tui-rs' example. --- Cargo.toml | 9 +- src/main.rs | 194 ++++++++++++++++++++++----------------- src/widgets/disks.rs | 18 ++-- src/widgets/mod.rs | 101 +++++++++++++++++++- src/widgets/processes.rs | 10 +- src/window/mod.rs | 52 ----------- 6 files changed, 230 insertions(+), 154 deletions(-) delete mode 100644 src/window/mod.rs diff --git a/Cargo.toml b/Cargo.toml index ab26928e..42cc4b60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +crossterm = "0.10.2" futures-preview = "0.3.0-alpha.18" futures-timer = "0.3" futures-util = "0.2.1" heim = "0.0.7" heim-common = "0.0.7" sysinfo = "0.9.4" -termion = "1.5.3" tokio = "0.2.0-alpha.4" -tui = "0.6.2" + + +[dependencies.tui] +version = "0.6.2" +default-features = false +features = ['crossterm'] diff --git a/src/main.rs b/src/main.rs index 520f473c..e7ce7efb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,92 +1,120 @@ -use sysinfo::{System, SystemExt}; +use crossterm::{input, AlternateScreen, InputEvent, KeyEvent}; +use std::{ + io::{self, stdin, stdout, Write}, + sync::mpsc, + thread, + time::Duration, +}; +use tui::{ + backend::CrosstermBackend, + layout::{Constraint, Direction, Layout}, + widgets::{Block, Borders, Widget}, + Terminal, +}; mod widgets; -use widgets::{cpu, disks, mem, network, processes, temperature}; -mod window; - -fn set_if_valid(result : &Result, value_to_set : &mut T) { - if let Ok(result) = result { - *value_to_set = (*result).clone(); - } +enum Event { + Input(I), + Tick, } #[tokio::main] -async fn main() -> Result<(), std::io::Error> { - // Initialize - let refresh_interval = 1; // TODO: Make changing this possible! - let mut sys = System::new(); - - let mut list_of_cpu_packages : Vec = Vec::new(); - let mut list_of_io : Vec = Vec::new(); - let mut list_of_physical_io : Vec = Vec::new(); - let mut memory : mem::MemData = mem::MemData::default(); - let mut swap : mem::MemData = mem::MemData::default(); - let mut list_of_temperature : Vec = Vec::new(); - let mut network : network::NetworkData = network::NetworkData::default(); - let mut list_of_processes = Vec::new(); - let mut list_of_disks = Vec::new(); - - window::create_terminal()?; - - loop { - sys.refresh_system(); - 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 network); - set_if_valid(&cpu::get_cpu_data_list(&sys), &mut 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 list_of_processes); - set_if_valid(&disks::get_disk_usage_list().await, &mut list_of_disks); - set_if_valid(&disks::get_io_usage_list(false).await, &mut list_of_io); - set_if_valid(&disks::get_io_usage_list(true).await, &mut list_of_physical_io); - set_if_valid(&mem::get_mem_data_list().await, &mut memory); - set_if_valid(&mem::get_swap_data_list().await, &mut swap); - set_if_valid(&temperature::get_temperature_data().await, &mut 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); - */ - - // TODO: Send to drawing module - - // Repeat on interval - std::thread::sleep(std::time::Duration::from_secs(refresh_interval)); +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)?; + terminal.hide_cursor()?; + // Setup input handling + let (tx, rx) = mpsc::channel(); + { + let tx = tx.clone(); + thread::spawn(move || { + let input = input(); + let reader = input.read_sync(); + for event in reader { + if let InputEvent::Keyboard(key) = event { + if tx.send(Event::Input(key.clone())).is_err() { + return; + } + } + } + }); + } + { + let tx = tx.clone(); + thread::spawn(move || { + let tx = tx.clone(); + loop { + tx.send(Event::Tick).unwrap(); + thread::sleep(Duration::from_millis(250)); + } + }); } - // TODO: Exit on quit command/ctrl-c + let mut app : widgets::App = widgets::App::new("rustop"); + terminal.clear()?; + + loop { + 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()) + .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]); + + Block::default().title("CPU Usage").borders(Borders::ALL).render(&mut f, top_chunks[0]); + 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]); + Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_chunks[1]); + + 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; + } + } + } + + Ok(()) } diff --git a/src/widgets/disks.rs b/src/widgets/disks.rs index 7a3ed212..0d8f26c5 100644 --- a/src/widgets/disks.rs +++ b/src/widgets/disks.rs @@ -1,7 +1,7 @@ use heim_common::prelude::StreamExt; #[derive(Clone, Default)] -pub struct DiskInfo { +pub struct DiskData { pub name : Box, pub mount_point : Box, pub free_space : u64, @@ -10,19 +10,19 @@ pub struct DiskInfo { } #[derive(Clone, Default)] -pub struct IOInfo { +pub struct IOData { pub mount_point : Box, pub read_bytes : u64, pub write_bytes : u64, } -pub async fn get_io_usage_list(get_physical : bool) -> Result, heim::Error> { - let mut io_list : Vec = Vec::new(); +pub async fn get_io_usage_list(get_physical : bool) -> Result, heim::Error> { + let mut io_list : Vec = Vec::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(IOInfo { + 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::(), @@ -33,7 +33,7 @@ pub async fn get_io_usage_list(get_physical : bool) -> Result, heim: let mut counter_stream = heim::disk::io_counters(); while let Some(io) = counter_stream.next().await { let io = io?; - io_list.push(IOInfo { + 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::(), @@ -44,15 +44,15 @@ pub async fn get_io_usage_list(get_physical : bool) -> Result, heim: Ok(io_list) } -pub async fn get_disk_usage_list() -> Result, heim::Error> { - let mut vec_disks : Vec = Vec::new(); +pub async fn get_disk_usage_list() -> Result, heim::Error> { + let mut vec_disks : Vec = Vec::new(); let mut partitions_stream = heim::disk::partitions_physical(); while let Some(part) = partitions_stream.next().await { let partition = part?; // TODO: Change this? We don't want to error out immediately... let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?; - vec_disks.push(DiskInfo { + vec_disks.push(DiskData { free_space : usage.free().get::(), used_space : usage.used().get::(), total_space : usage.total().get::(), diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index cf9c9d2a..c99832c1 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,6 +1,101 @@ +pub mod cpu; pub mod disks; -pub mod temperature; +pub mod mem; pub mod network; pub mod processes; -pub mod mem; -pub mod cpu; \ No newline at end of file +pub mod temperature; + +use sysinfo::{System, SystemExt}; + +#[derive(Default)] +pub struct App<'a> { + pub should_quit : bool, + pub list_of_cpu_packages : Vec, + pub list_of_io : Vec, + pub list_of_physical_io : Vec, + pub memory : mem::MemData, + pub swap : mem::MemData, + pub list_of_temperature : Vec, + pub network : network::NetworkData, + pub list_of_processes : Vec, + pub list_of_disks : Vec, + pub title : &'a str, +} + +fn set_if_valid(result : &Result, value_to_set : &mut T) { + if let Ok(result) = result { + *value_to_set = (*result).clone(); + } +} + +impl<'a> App<'a> { + pub fn new(title : &str) -> App { + let mut app = App::default(); + app.title = title; + app + } + + pub fn on_key(&mut self, c : char) { + match c { + 'q' => self.should_quit = true, + _ => {} + } + } + + pub async fn update_data(&mut self) { + // Initialize + let mut sys = System::new(); + + sys.refresh_system(); + 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); + + // 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(&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 b4e90d4d..20f6b3b2 100644 --- a/src/widgets/processes.rs +++ b/src/widgets/processes.rs @@ -13,7 +13,7 @@ pub enum ProcessSorting { // Possible process info struct? #[derive(Clone, Default)] -pub struct ProcessInfo { +pub struct ProcessData { pub pid : u32, pub cpu_usage_percent : f32, pub mem_usage_in_mb : u64, @@ -50,10 +50,10 @@ async fn cpu_usage(process : heim::process::Process) -> heim::process::ProcessRe 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(); + let mut process_vector : Vec = Vec::new(); while let Some(process) = process_stream.next().await { if let Ok(process) = process { let (process, cpu_usage) = process; @@ -69,7 +69,7 @@ pub async fn get_sorted_processes_list(sorting_method : ProcessSorting, reverse_ }); */ - process_vector.push(ProcessInfo { + process_vector.push(ProcessData { command : process.name().await.unwrap_or_else(|_| "".to_string()), pid : process.pid() as u32, cpu_usage_percent : cpu_usage.get::(), @@ -83,7 +83,7 @@ pub async fn get_sorted_processes_list(sorting_method : ProcessSorting, reverse_ 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/window/mod.rs b/src/window/mod.rs deleted file mode 100644 index bfb6a46f..00000000 --- a/src/window/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::io; -use termion::raw::IntoRawMode; -use tui::{ - backend::TermionBackend, - layout::{Constraint, Direction, Layout}, - widgets::{Block, Borders, Widget}, - Terminal, -}; - -pub fn create_terminal() -> Result<(), io::Error> { - let stdout = io::stdout().into_raw_mode()?; - let backend = TermionBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - terminal.clear()?; - 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()) - .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]); - - Block::default().title("CPU Usage").borders(Borders::ALL).render(&mut f, top_chunks[0]); - 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]); - Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_chunks[1]); - - 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]); - }) -}