mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-26 14:10:19 +00:00
Added clap support for command line options, as well as tweaked some table placement.
This commit is contained in:
parent
3219abeaa5
commit
6d9ed34dcb
5 changed files with 91 additions and 59 deletions
|
@ -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)
|
||||||
|
|
30
src/app.rs
30
src/app.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
51
src/main.rs
51
src/main.rs
|
@ -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",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue