Ironed out as many kinks as possible in terms of smoothness.

This commit is contained in:
ClementTsang 2019-09-09 18:34:13 -04:00
parent ff89f1187f
commit d9a0d32c1f
4 changed files with 250 additions and 175 deletions

View File

@ -7,12 +7,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chrono = "0.4.9"
crossterm = "0.10.2" crossterm = "0.10.2"
futures-preview = "0.3.0-alpha.18" futures-preview = "0.3.0-alpha.18"
fern = "0.5"
futures-timer = "0.3" futures-timer = "0.3"
futures-util = "0.2.1" futures-util = "0.2.1"
heim = "0.0.7" heim = "0.0.7"
heim-common = "0.0.7" heim-common = "0.0.7"
log = "0.4"
sysinfo = "0.9.4" sysinfo = "0.9.4"
tokio = "0.2.0-alpha.4" tokio = "0.2.0-alpha.4"

View File

@ -11,9 +11,12 @@ use tui::{
mod widgets; mod widgets;
#[macro_use]
extern crate log;
enum Event<I> { enum Event<I> {
Input(I), Input(I),
Tick, Update(widgets::Data),
} }
#[tokio::main] #[tokio::main]
@ -22,7 +25,10 @@ async fn main() -> Result<(), io::Error> {
let backend = CrosstermBackend::with_alternate_screen(screen)?; let backend = CrosstermBackend::with_alternate_screen(screen)?;
let mut terminal = Terminal::new(backend)?; 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()?; terminal.hide_cursor()?;
// Setup input handling // 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(); let tx = tx.clone();
thread::spawn(move || { thread::spawn(move || {
let tx = tx.clone(); let tx = tx.clone();
loop { 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)); 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()?; terminal.clear()?;
let mut app_data = widgets::Data::default();
loop { loop {
if let Ok(recv) = rx.recv() { if let Ok(recv) = rx.recv_timeout(Duration::from_millis(tick_rate_in_milliseconds)) {
match recv { match recv {
Event::Input(event) => match event { Event::Input(event) => {
try_debug(&log, "Input event fired!");
match event {
KeyEvent::Char(c) => app.on_key(c), KeyEvent::Char(c) => app.on_key(c),
KeyEvent::Left => {} KeyEvent::Left => app.on_left(),
KeyEvent::Right => {} KeyEvent::Right => app.on_right(),
KeyEvent::Up => {} KeyEvent::Up => app.on_up(),
KeyEvent::Down => {} KeyEvent::Down => app.on_down(),
KeyEvent::Ctrl('c') => break, KeyEvent::Ctrl('c') => break,
_ => {} _ => {}
}, }
Event::Tick => {
app.update_data().await; // TODO: This await is causing slow responsiveness... perhaps make drawing another thread? 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 { if app.should_quit {
@ -77,15 +99,23 @@ async fn main() -> Result<(), io::Error> {
} }
} }
// Draw!
draw_data(&mut terminal, &app_data)?;
}
Ok(())
}
fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data : &widgets::Data) -> Result<(), io::Error> {
// Convert data into tui components // Convert data into tui components
let temperature_rows = app.list_of_temperature.iter().map(|sensor| { let temperature_rows = app_data.list_of_temperature.iter().map(|sensor| {
Row::StyledData( Row::StyledData(
vec![sensor.component_name.to_string(), sensor.temperature.to_string() + "C"].into_iter(), // TODO: Change this based on temperature type 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), Style::default().fg(Color::LightGreen),
) )
}); });
let disk_rows = app.list_of_disks.iter().map(|disk| { let disk_rows = app_data.list_of_disks.iter().map(|disk| {
Row::StyledData( Row::StyledData(
vec![ vec![
disk.name.to_string(), disk.name.to_string(),
@ -99,8 +129,8 @@ async fn main() -> Result<(), io::Error> {
) )
}); });
let mem_total_mb = app.memory.mem_total_in_mb as f64; let mem_total_mb = app_data.memory.mem_total_in_mb as f64;
let process_rows = app.list_of_processes.iter().map(|process| { let process_rows = app_data.list_of_processes.iter().map(|process| {
Row::StyledData( Row::StyledData(
vec![ vec![
process.pid.to_string(), process.pid.to_string(),
@ -113,7 +143,6 @@ async fn main() -> Result<(), io::Error> {
) )
}); });
// Draw!
terminal.draw(|mut f| { terminal.draw(|mut f| {
let vertical_chunks = Layout::default() let vertical_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@ -192,7 +221,30 @@ async fn main() -> Result<(), io::Error> {
.widths(&[5, 15, 10, 10]) .widths(&[5, 15, 10, 10])
.render(&mut f, bottom_chunks[1]); .render(&mut f, bottom_chunks[1]);
})?; })?;
}
Ok(()) 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);
}
}

View File

@ -7,8 +7,23 @@ pub mod temperature;
use sysinfo::{System, SystemExt}; use sysinfo::{System, SystemExt};
#[allow(dead_code)]
pub struct App<'a> { pub struct App<'a> {
title : &'a str,
pub should_quit : bool, 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_cpu_packages : Vec<cpu::CPUData>,
pub list_of_io : Vec<disks::IOData>, pub list_of_io : Vec<disks::IOData>,
pub list_of_physical_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 network : network::NetworkData,
pub list_of_processes : Vec<processes::ProcessData>, pub list_of_processes : Vec<processes::ProcessData>,
pub list_of_disks : Vec<disks::DiskData>, 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, sys : System,
} }
fn set_if_valid<T : std::clone::Clone>(result : &Result<T, heim::Error>, value_to_set : &mut T) { impl Default for DataState {
if let Ok(result) = result { fn default() -> Self {
*value_to_set = (*result).clone(); 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 { App {
title, title,
process_sorting_type : processes::ProcessSorting::NAME, // TODO: Change this based on input args... 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, should_quit : false,
process_sorting_reverse : false, process_sorting_reverse : false,
to_be_resorted : false,
} }
} }
@ -54,48 +84,38 @@ impl<'a> App<'a> {
match c { match c {
'q' => self.should_quit = true, 'q' => self.should_quit = true,
'c' => { 'c' => {
self.process_sorting_type = processes::ProcessSorting::CPU; self.process_sorting_type = processes::ProcessSorting::CPU; // TODO: Change this such that reversing can be done by just hitting "c" twice...
//processes::sort_processes(&self.process_sorting_type, &mut self.list_of_processes, self.process_sorting_reverse); self.to_be_resorted = true;
// TODO: This CANNOT run while it is updating...
} }
'm' => { 'm' => {
self.process_sorting_type = processes::ProcessSorting::MEM; 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' => { 'p' => {
self.process_sorting_type = processes::ProcessSorting::PID; 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' => { 'n' => {
self.process_sorting_type = processes::ProcessSorting::NAME; 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' => { 'r' => {
self.process_sorting_reverse = !self.process_sorting_reverse; 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) { pub fn on_left(&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! pub fn on_right(&mut self) {
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... pub fn on_up(&mut self) {
set_if_valid( }
&processes::get_sorted_processes_list(&self.process_sorting_type, self.process_sorting_reverse).await,
&mut self.list_of_processes, pub fn on_down(&mut self) {
);
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);
} }
} }

View File

@ -4,6 +4,7 @@ use heim_common::{
}; };
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone)]
pub enum ProcessSorting { pub enum ProcessSorting {
CPU, CPU,
MEM, 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)> { 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?; 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?; let usage_2 = process.cpu_usage().await?;
Ok((process, usage_2 - usage_1)) 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_stream = heim::process::processes().map_ok(cpu_usage).try_buffer_unordered(std::usize::MAX);
let mut process_vector : Vec<ProcessData> = Vec::new(); 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) 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 { match sorting_method {
ProcessSorting::CPU => process_vector.sort_by(|a, b| get_ordering(a.cpu_usage_percent, b.cpu_usage_percent, reverse_order)), 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)), ProcessSorting::MEM => process_vector.sort_by(|a, b| get_ordering(a.mem_usage_in_mb, b.mem_usage_in_mb, reverse_order)),