Added clap support for command line options, as well as tweaked some table placement.

This commit is contained in:
ClementTsang 2019-09-14 16:46:14 -04:00
parent 3219abeaa5
commit 6d9ed34dcb
5 changed files with 91 additions and 59 deletions

View file

@ -21,6 +21,7 @@ Currently, I'm unable to test on MacOS, so I'm not sure how well this will work,
* As mentioned, this project is very much inspired by both [gotop](https://github.com/cjbassi/gotop) and [gtop](https://github.com/aksakalli/gtop) . * As mentioned, this project is very much inspired by both [gotop](https://github.com/cjbassi/gotop) and [gtop](https://github.com/aksakalli/gtop) .
* This application was written with the following libraries: * This application was written with the following libraries:
* clap
* [crossterm](https://github.com/TimonPost/crossterm) * [crossterm](https://github.com/TimonPost/crossterm)
* [heim](https://github.com/heim-rs/heim) * [heim](https://github.com/heim-rs/heim)
* [sysinfo](https://github.com/GuillaumeGomez/sysinfo) * [sysinfo](https://github.com/GuillaumeGomez/sysinfo)

View file

@ -5,22 +5,14 @@ use std::collections::HashMap;
use sysinfo::{System, SystemExt}; use sysinfo::{System, SystemExt};
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone)] pub struct App {
pub enum TemperatureType {
Celsius,
Kelvin,
Fahrenheit,
}
#[allow(dead_code)]
pub struct App<'a> {
title : &'a str,
pub should_quit : bool, pub should_quit : bool,
pub process_sorting_type : processes::ProcessSorting, pub process_sorting_type : processes::ProcessSorting,
pub process_sorting_reverse : bool, pub process_sorting_reverse : bool,
pub to_be_resorted : bool, pub to_be_resorted : bool,
pub current_selected_process_position : u64, pub current_selected_process_position : u64,
pub temperature_type : TemperatureType, pub temperature_type : data_collection::temperature::TemperatureType,
pub update_rate_in_milliseconds : u64,
} }
fn set_if_valid<T : std::clone::Clone>(result : &Result<T, heim::Error>, value_to_set : &mut T) { fn set_if_valid<T : std::clone::Clone>(result : &Result<T, heim::Error>, value_to_set : &mut T) {
@ -56,7 +48,7 @@ pub struct DataState {
prev_pid_stats : HashMap<String, f64>, // TODO: Purge list? prev_pid_stats : HashMap<String, f64>, // TODO: Purge list?
prev_idle : f64, prev_idle : f64,
prev_non_idle : f64, prev_non_idle : f64,
temperature_type : TemperatureType, temperature_type : data_collection::temperature::TemperatureType,
} }
impl Default for DataState { impl Default for DataState {
@ -69,7 +61,7 @@ impl Default for DataState {
prev_pid_stats : HashMap::new(), prev_pid_stats : HashMap::new(),
prev_idle : 0_f64, prev_idle : 0_f64,
prev_non_idle : 0_f64, prev_non_idle : 0_f64,
temperature_type : TemperatureType::Celsius, temperature_type : data_collection::temperature::TemperatureType::Celsius,
} }
} }
} }
@ -79,7 +71,7 @@ impl DataState {
self.stale_max_seconds = stale_max_seconds; self.stale_max_seconds = stale_max_seconds;
} }
pub fn set_temperature_type(&mut self, temperature_type : TemperatureType) { pub fn set_temperature_type(&mut self, temperature_type : data_collection::temperature::TemperatureType) {
self.temperature_type = temperature_type; self.temperature_type = temperature_type;
} }
@ -108,7 +100,7 @@ impl DataState {
set_if_valid(&disks::get_disk_usage_list().await, &mut self.data.list_of_disks); 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(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().await, &mut self.data.list_of_temperature_sensor); set_if_valid(&temperature::get_temperature_data(&self.temperature_type).await, &mut self.data.list_of_temperature_sensor);
if self.first_run { if self.first_run {
self.data = Data::default(); self.data = Data::default();
@ -170,16 +162,16 @@ impl DataState {
} }
} }
impl<'a> App<'a> { impl App {
pub fn new(title : &str) -> App { pub fn new(temperature_type : data_collection::temperature::TemperatureType, update_rate_in_milliseconds : u64) -> App {
App { App {
title,
process_sorting_type : processes::ProcessSorting::CPU, // TODO: Change this based on input args... basically set this on app creation process_sorting_type : processes::ProcessSorting::CPU, // TODO: Change this based on input args... basically set this on app creation
should_quit : false, should_quit : false,
process_sorting_reverse : true, process_sorting_reverse : true,
to_be_resorted : false, to_be_resorted : false,
current_selected_process_position : 0, current_selected_process_position : 0,
temperature_type : TemperatureType::Celsius, temperature_type,
update_rate_in_milliseconds,
} }
} }

View file

@ -6,7 +6,14 @@ pub struct TempData {
pub temperature : f32, pub temperature : f32,
} }
pub async fn get_temperature_data() -> Result<Vec<TempData>, heim::Error> { #[derive(Clone, Debug)]
pub enum TemperatureType {
Celsius,
Kelvin,
Fahrenheit,
}
pub async fn get_temperature_data(temp_type : &TemperatureType) -> Result<Vec<TempData>, heim::Error> {
let mut temperature_vec : Vec<TempData> = Vec::new(); let mut temperature_vec : Vec<TempData> = Vec::new();
let mut sensor_data = heim::sensors::temperatures(); let mut sensor_data = heim::sensors::temperatures();
@ -14,7 +21,11 @@ pub async fn get_temperature_data() -> Result<Vec<TempData>, heim::Error> {
if let Ok(sensor) = sensor { if let Ok(sensor) = sensor {
temperature_vec.push(TempData { temperature_vec.push(TempData {
component_name : Box::from(sensor.unit()), component_name : Box::from(sensor.unit()),
temperature : sensor.current().get::<thermodynamic_temperature::degree_celsius>(), // TODO: Allow for toggling this! temperature : match temp_type {
TemperatureType::Celsius => sensor.current().get::<thermodynamic_temperature::degree_celsius>(),
TemperatureType::Kelvin => sensor.current().get::<thermodynamic_temperature::kelvin>(),
TemperatureType::Fahrenheit => sensor.current().get::<thermodynamic_temperature::degree_fahrenheit>(),
},
}); });
} }
} }

View file

@ -74,11 +74,6 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
.margin(0) .margin(0)
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref()) .constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref())
.split(bottom_divided_chunk_1[0]); .split(bottom_divided_chunk_1[0]);
let bottom_divided_chunk_1_2 = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref())
.split(bottom_divided_chunk_1[1]);
// Set up blocks and their components // Set up blocks and their components
@ -133,18 +128,14 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
Block::default().title("Network").borders(Borders::ALL).border_style(border_style).render(&mut f, middle_chunks[1]); Block::default().title("Network").borders(Borders::ALL).border_style(border_style).render(&mut f, middle_chunks[1]);
// Temperature table // Temperature table
Table::new(["Sensor", "Temperature"].iter(), temperature_rows) {
let width = f64::from(bottom_divided_chunk_1_1[0].width);
Table::new(["Sensor", "Temp"].iter(), temperature_rows)
.block(Block::default().title("Temperatures").borders(Borders::ALL).border_style(border_style)) .block(Block::default().title("Temperatures").borders(Borders::ALL).border_style(border_style))
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(Color::LightBlue))
.widths(&[15, 5]) .widths(&[(width * 0.45) as u16, (width * 0.4) as u16])
.render(&mut f, bottom_divided_chunk_1_1[0]); .render(&mut f, bottom_divided_chunk_1_1[0]);
}
// Disk usage table
Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
.block(Block::default().title("Disk Usage").borders(Borders::ALL).border_style(border_style))
.header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD))
.widths(&[15, 10, 5, 5, 5])
.render(&mut f, bottom_divided_chunk_1_2[0]);
// Temp graph // Temp graph
Block::default() Block::default()
@ -153,19 +144,25 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
.border_style(border_style) .border_style(border_style)
.render(&mut f, bottom_divided_chunk_1_1[1]); .render(&mut f, bottom_divided_chunk_1_1[1]);
// IO graph // Disk usage table
Block::default() {
.title("IO Usage") let width = f64::from(bottom_divided_chunk_1[1].width);
.borders(Borders::ALL) Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
.border_style(border_style) .block(Block::default().title("Disk Usage").borders(Borders::ALL).border_style(border_style))
.render(&mut f, bottom_divided_chunk_1_2[1]); .header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD))
.widths(&[(width * 0.25) as u16, (width * 0.2) as u16, (width * 0.15) as u16, (width * 0.15) as u16, (width * 0.15) as u16])
.render(&mut f, bottom_divided_chunk_1[1]);
}
// Processes table // Processes table
{
let width = f64::from(bottom_chunks[1].width);
Table::new(["PID", "Name", "CPU%", "Mem%"].iter(), process_rows) Table::new(["PID", "Name", "CPU%", "Mem%"].iter(), process_rows)
.block(Block::default().title("Processes").borders(Borders::ALL).border_style(border_style)) .block(Block::default().title("Processes").borders(Borders::ALL).border_style(border_style))
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(Color::LightBlue))
.widths(&[5, 15, 10, 10]) .widths(&[(width * 0.2) as u16, (width * 0.35) as u16, (width * 0.2) as u16, (width * 0.2) as u16])
.render(&mut f, bottom_chunks[1]); .render(&mut f, bottom_chunks[1]);
}
})?; })?;
Ok(()) Ok(())

View file

@ -16,25 +16,56 @@ mod canvas;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
extern crate clap;
enum Event<I> { enum Event<I> {
Input(I), Input(I),
Update(Box<app::Data>), Update(Box<app::Data>),
} }
const STALE_MAX_MILLISECONDS : u64 = 60 * 1000; const STALE_MAX_MILLISECONDS : u64 = 60 * 1000;
const TICK_RATE_IN_MILLISECONDS : u64 = 250;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), io::Error> { async fn main() -> Result<(), io::Error> {
let _log = utils::logging::init_logger(); // TODO: Error handling
let matches = clap_app!(app =>
(name: "rustop")
(version: crate_version!())
(author: "Clement Tsang <clementjhtsang@gmail.com>")
(about: "A graphical top clone.")
(@arg THEME: -t --theme +takes_value "Sets a colour theme.")
(@group TEMPERATURE_TYPE =>
(@arg celsius : -c --celsius "Sets the temperature type to Celsius. This is the default option.")
(@arg fahrenheit : -f --fahrenheit "Sets the temperature type to Fahrenheit.")
(@arg kelvin : -k --kelvin "Sets the temperature type to Kelvin.")
)
(@arg RATE: -r --rate +takes_value "Sets a refresh rate in milliseconds, min is 250ms, defaults to 1000ms. Higher values may take more resources.")
)
.after_help("Themes:")
.get_matches();
let screen = AlternateScreen::to_alternate(true)?; let screen = AlternateScreen::to_alternate(true)?;
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 tick_rate_in_milliseconds : u64 = 250; let update_rate_in_milliseconds : u64 = matches.value_of("rate").unwrap_or("1000").parse::<u64>().unwrap_or(1000);
let update_rate_in_milliseconds : u64 = 500; // TODO: Must set a check to prevent this from going into negatives! let temperature_type = if matches.is_present("fahrenheit") {
app::data_collection::temperature::TemperatureType::Fahrenheit
}
else if matches.is_present("kelvin") {
app::data_collection::temperature::TemperatureType::Kelvin
}
else {
app::data_collection::temperature::TemperatureType::Celsius
};
let mut app = app::App::new("rustop"); info!("Temperature type: {:?}", temperature_type);
let _log = utils::logging::init_logger(); let mut app = app::App::new(temperature_type, if update_rate_in_milliseconds < 250 { 250 } else { update_rate_in_milliseconds });
terminal.hide_cursor()?; terminal.hide_cursor()?;
// Setup input handling // Setup input handling
@ -77,7 +108,7 @@ async fn main() -> Result<(), io::Error> {
let mut canvas_data = canvas::CanvasData::default(); let mut canvas_data = canvas::CanvasData::default();
loop { loop {
if let Ok(recv) = rx.recv_timeout(Duration::from_millis(tick_rate_in_milliseconds)) { if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
match recv { match recv {
Event::Input(event) => { Event::Input(event) => {
debug!("Input event fired!"); debug!("Input event fired!");
@ -125,17 +156,17 @@ async fn main() -> Result<(), io::Error> {
Ok(()) Ok(())
} }
fn update_temp_row(app_data : &app::Data, temp_type : &app::TemperatureType) -> Vec<Vec<String>> { fn update_temp_row(app_data : &app::Data, temp_type : &app::data_collection::temperature::TemperatureType) -> Vec<Vec<String>> {
let mut sensor_vector : Vec<Vec<String>> = Vec::new(); let mut sensor_vector : Vec<Vec<String>> = Vec::new();
for sensor in &app_data.list_of_temperature_sensor { for sensor in &app_data.list_of_temperature_sensor {
sensor_vector.push(vec![ sensor_vector.push(vec![
sensor.component_name.to_string(), sensor.component_name.to_string(),
sensor.temperature.to_string() (sensor.temperature.ceil() as u64).to_string()
+ match temp_type { + match temp_type {
app::TemperatureType::Celsius => "C", app::data_collection::temperature::TemperatureType::Celsius => "C",
app::TemperatureType::Kelvin => "K", app::data_collection::temperature::TemperatureType::Kelvin => "K",
app::TemperatureType::Fahrenheit => "F", app::data_collection::temperature::TemperatureType::Fahrenheit => "F",
}, },
]); ]);
} }