diff --git a/README.md b/README.md index 24b4adf4..52d354e2 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,29 @@ Currently, I'm unable to test on MacOS, so I'm not sure how well this will work, ## Usage +### Command line options + +* ``-h``, ``--help`` to show the help screen and exit (basically has all of the below CLI option info). + +* ``-a``, ``--avgcpu`` enables showing the average CPU usage on rustop + +* ``-c``, ``--celsius`` displays the temperature type in Celsius. This is the default. + +* ``-f``, ``--fahrenheit`` displays the temperature type in Fahrenheit. This is the default. + +* ``-k``, ``--kelvin`` displays the temperature type in Kelvin. This is the default. + +* ``-v``, ``--version`` displays the version number and exits. + +* ``-r ``, ``--rate `` will set the refresh rate in *milliseconds*. Pick a range from 250ms to ``UINT_MAX``. Defaults to 1000ms, and higher values may take more resources due to more frequent polling of data, and may be less accurate in some circumstances. + ### Keybinds (some may not be available on certain operating systems) #### General -* ``q``, ``Esc``, or ``Ctrl-C`` to quit +* ``q``, ``Esc``, or ``Ctrl-C`` to quit. -* ``Shift-Up/Shift-k``, ``Shift-Down/Shift-j``, ``Shift-Left/Shift-h``, ``Shift-Right/Shift-l`` to navigate between panels +* ``Up/k``, ``Down/j``, ``Left/h``, ``Right/l`` to navigate between panels. This currently doesn't have much functionality but will change in the future. #### Processes Panel @@ -45,16 +61,51 @@ Currently, I'm unable to test on MacOS, so I'm not sure how well this will work, ### Mouse Actions -* Scrolling either scrolls through the list if the panel is a table (Temperature, Disks, Processes), or zooms in and out if it is a chart +[* Scrolling either scrolls through the list if the panel is a table (Temperature, Disks, Processes), or zooms in and out if it is a chart.]: <> + +* Scrolling currently only scrolls through the list if you are on the Processes panel. This will change in the future. + +## Regarding Process Use Percentage (on Linux) + +I cannot guarantee whether they are 100% accurate. I'm using a technique I found online which seems to be a better indicator of process use percentage at the current time, rather than pulling from, say, ``ps`` (which is average CPU usage over the *entire lifespan* of the process). I found the options from the library I used to get other data (heim) to be a bit too inaccurate in my testing. + +For reference, the formula I am using to calculate CPU process usage is along the lines of: + +```rust +let idle = idle + iowait; +let non_idle = user + nice + system + irq + softirq + steal + guest; + +let total = idle + non_idle; +let prev_total = prev_idle + prev_non_idle; // Both of these values are calculated using the same formula from the previous polling + +let total_delta : f64 = total - prev_total; +let idle_delta : f64 = idle - prev_idle; + +let final_delta : f64 = total_delta - idle_delta; + +//... + +// Get utime and stime from reading /proc//stat +let after_proc_val = utime + stime; +(after_proc_val - before_proc_val) / cpu_usage * 100_f64; //This gives your use percentage. before_proc_val comes from the previous polling +``` + +Any suggestions on more accurate data is welcome! Note all other fields should be accurate. ## Thanks * 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: + * [chrono](https://github.com/chronotope/chrono) * [clap](https://github.com/clap-rs/clap) * [crossterm](https://github.com/TimonPost/crossterm) + * [failure](https://github.com/rust-lang-nursery/failure) + * [fern](https://github.com/daboross/fern) + * [futures-rs](https://github.com/rust-lang-nursery/futures-rs) + * [futures-timer](https://github.com/rustasync/futures-timer) * [heim](https://github.com/heim-rs/heim) + * [log](https://github.com/rust-lang-nursery/log) * [sysinfo](https://github.com/GuillaumeGomez/sysinfo) * [tokio](https://github.com/tokio-rs/tokio) * [tui-rs](https://github.com/fdehau/tui-rs) (note I used a fork due to some issues I faced, you can find that [here](https://github.com/ClementTsang/tui-rs)) diff --git a/TODO.md b/TODO.md index bdfb3caf..d9b07147 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ # To-Do List +Note this will probably migrate to GitHub's native Issues; this was mostly for personal use during early stages. + ## Pre-release (bare minimum) * ~~Get each function working as a POC~~ @@ -14,21 +16,23 @@ ~~* Scrolling in at least processes~~ -* Keybindings +* Keybindings - I want to do at least arrow keys and dd. ~~* Legend gets in the way at too small of a height... maybe modify tui a bit more to fix this.~~ ## After making public +* Scaling in and out (zoom), may need to show zoom levels + +* More keybinds + * Tests * Mouse + key events conflict? Make it so that some events don't clog up the loop if they are not valid keys! * Header should be clear on current sorting direction! -* Scaling in and out (zoom), may need to show zoom levels - -* It would be maybe a good idea to see if we can run the process calculation across ALL cpus...? +* It would be maybe a good idea to see if we can run the process calculation across ALL cpus...? Might be more accurate. * ~~Add custom error because it's really messy~~ Done, but need to implement across rest of app! @@ -52,4 +56,6 @@ * Modularity -*~~Potentially process managing? Depends on the libraries...~~ Done on Linux! +* ~~Potentially process managing? Depends on the libraries...~~ Done on Linux! + +* Probably good to add a "are you sure" to dd-ing... diff --git a/src/app.rs b/src/app.rs index 9331026c..cc05eab7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,8 +3,7 @@ use data_collection::{processes, temperature}; mod process_killer; -#[allow(dead_code)] -// Probably only use the list elements... +#[derive(Clone, Copy)] pub enum ApplicationPosition { CPU, MEM, @@ -74,10 +73,6 @@ impl App { self.awaiting_d = true; } } - 'h' => self.on_right(), - 'j' => self.on_down(), - 'k' => self.on_up(), - 'l' => self.on_left(), 'c' => { // TODO: This should depend on what tile you're on! match self.process_sorting_type { @@ -137,16 +132,51 @@ impl App { Ok(()) } + // For now, these are hard coded --- in the future, they shouldn't be! + // + // General idea for now: + // CPU -(down)> MEM + // MEM -(down)> Network, -(right)> TEMP + // TEMP -(down)> Disk, -(left)> MEM, -(up)> CPU + // Disk -(down)> Processes, -(left)> MEM, -(up)> TEMP + // Network -(up)> MEM, -(right)> PROC + // PROC -(up)> Disk, -(left)> Network pub fn on_left(&mut self) { + self.current_application_position = match self.current_application_position { + ApplicationPosition::PROCESS => ApplicationPosition::NETWORK, + ApplicationPosition::DISK => ApplicationPosition::MEM, + ApplicationPosition::TEMP => ApplicationPosition::MEM, + _ => self.current_application_position, + }; } pub fn on_right(&mut self) { + self.current_application_position = match self.current_application_position { + ApplicationPosition::MEM => ApplicationPosition::TEMP, + ApplicationPosition::NETWORK => ApplicationPosition::PROCESS, + _ => self.current_application_position, + }; } pub fn on_up(&mut self) { + self.current_application_position = match self.current_application_position { + ApplicationPosition::MEM => ApplicationPosition::CPU, + ApplicationPosition::NETWORK => ApplicationPosition::MEM, + ApplicationPosition::PROCESS => ApplicationPosition::DISK, + ApplicationPosition::TEMP => ApplicationPosition::CPU, + ApplicationPosition::DISK => ApplicationPosition::TEMP, + _ => self.current_application_position, + }; } pub fn on_down(&mut self) { + self.current_application_position = match self.current_application_position { + ApplicationPosition::CPU => ApplicationPosition::MEM, + ApplicationPosition::MEM => ApplicationPosition::NETWORK, + ApplicationPosition::TEMP => ApplicationPosition::DISK, + ApplicationPosition::DISK => ApplicationPosition::PROCESS, + _ => self.current_application_position, + }; } pub fn decrement_position_count(&mut self) { diff --git a/src/canvas.rs b/src/canvas.rs index afec5ba0..6cdf276e 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -11,6 +11,7 @@ const COLOUR_LIST : [Color; 6] = [Color::Red, Color::Green, Color::LightYellow, const TEXT_COLOUR : Color = Color::Gray; const GRAPH_COLOUR : Color = Color::Gray; const BORDER_STYLE_COLOUR : Color = Color::Gray; +const HIGHLIGHTED_BORDER_STYLE_COLOUR : Color = Color::LightBlue; const GRAPH_MARKER : Marker = Marker::Braille; #[derive(Default)] @@ -29,6 +30,7 @@ pub struct CanvasData { pub fn draw_data(terminal : &mut Terminal, app_state : &mut app::App, canvas_data : &CanvasData) -> error::Result<()> { let border_style : Style = Style::default().fg(BORDER_STYLE_COLOUR); + let highlighted_border_style : Style = Style::default().fg(HIGHLIGHTED_BORDER_STYLE_COLOUR); let temperature_rows = canvas_data .temp_sensor_data @@ -102,7 +104,15 @@ pub fn draw_data(terminal : &mut Terminal, app_sta } Chart::default() - .block(Block::default().title("CPU Usage").borders(Borders::ALL).border_style(border_style)) + .block( + Block::default() + .title("CPU Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::CPU => highlighted_border_style, + _ => border_style, + }), + ) .x_axis(x_axis) .y_axis(y_axis) .datasets(&dataset_vector) @@ -114,7 +124,15 @@ pub fn draw_data(terminal : &mut Terminal, app_sta let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.5]).labels(&["0%", "100%"]); // Offset as the zero value isn't drawn otherwise... Chart::default() - .block(Block::default().title("Memory Usage").borders(Borders::ALL).border_style(border_style)) + .block( + Block::default() + .title("Memory Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::MEM => highlighted_border_style, + _ => border_style, + }), + ) .x_axis(x_axis) .y_axis(y_axis) .datasets(&[ @@ -136,7 +154,15 @@ pub fn draw_data(terminal : &mut Terminal, app_sta { let width = f64::from(middle_divided_chunk_2[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(match app_state.current_application_position { + app::ApplicationPosition::TEMP => highlighted_border_style, + _ => border_style, + }), + ) .header_style(Style::default().fg(Color::LightBlue)) .widths(&[(width * 0.45) as u16, (width * 0.4) as u16]) .render(&mut f, middle_divided_chunk_2[0]); @@ -144,10 +170,18 @@ pub fn draw_data(terminal : &mut Terminal, app_sta // Disk usage table { - // TODO: We have to dynamically remove some of these table elements based on size... + // TODO: We may have to dynamically remove some of these table elements based on size... let width = f64::from(middle_divided_chunk_2[1].width); Table::new(["Disk", "Mount", "Used", "Total", "Free", "R/s", "W/s"].iter(), disk_rows) - .block(Block::default().title("Disk Usage").borders(Borders::ALL).border_style(border_style)) + .block( + Block::default() + .title("Disk Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::DISK => highlighted_border_style, + _ => border_style, + }), + ) .header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD)) .widths(&[ (width * 0.18).floor() as u16, @@ -166,7 +200,15 @@ pub fn draw_data(terminal : &mut Terminal, app_sta let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 1_000_000.5]).labels(&["0GB", "1GB"]); Chart::default() - .block(Block::default().title("Network").borders(Borders::ALL).border_style(border_style)) + .block( + Block::default() + .title("Network") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::NETWORK => highlighted_border_style, + _ => border_style, + }), + ) .x_axis(x_axis) .y_axis(y_axis) .datasets(&[ @@ -196,7 +238,6 @@ pub fn draw_data(terminal : &mut Terminal, app_sta let num_rows = i64::from(bottom_chunks[1].height) - 3; let mut process_counter = 0; - //TODO: Fix this! let start_position = match app_state.scroll_direction { app::ScrollDirection::DOWN => { if app_state.currently_selected_process_position < num_rows { @@ -251,7 +292,15 @@ pub fn draw_data(terminal : &mut Terminal, app_sta }); 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(match app_state.current_application_position { + app::ApplicationPosition::PROCESS => highlighted_border_style, + _ => border_style, + }), + ) .header_style(Style::default().fg(Color::LightBlue)) .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]); diff --git a/src/main.rs b/src/main.rs index 40b66018..fc753d7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ enum Event { } fn main() -> error::Result<()> { - let _log = utils::logging::init_logger(); // TODO: Error handling + let _log = utils::logging::init_logger(); // TODO: Note this could fail and we wouldn't know... consider whether it is worth dealing with // Parse command line options let matches = clap_app!(app => @@ -42,9 +42,9 @@ fn main() -> error::Result<()> { (version: crate_version!()) (author: "Clement Tsang ") (about: "A graphical top clone.") - (@arg THEME: -t --theme +takes_value "Sets a colour theme.") + //(@arg THEME: -t --theme +takes_value "Sets a colour theme.") (@arg AVG_CPU: -a --avgcpu "Enables showing the average CPU usage.") - (@arg DEBUG: -d --debug "Enables debug mode.") // TODO: This isn't done yet! + //(@arg DEBUG: -d --debug "Enables debug mode.") // TODO: This isn't done yet! (@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.") @@ -52,7 +52,7 @@ fn main() -> error::Result<()> { ) (@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:") + //.after_help("Themes:") // TODO: This and others disabled for now .get_matches(); let update_rate_in_milliseconds : u128 = matches.value_of("RATE").unwrap_or("1000").parse::()?; @@ -149,11 +149,11 @@ fn main() -> error::Result<()> { // debug!("Keyboard event fired!"); match event { KeyEvent::Ctrl('c') | KeyEvent::Esc => break, + KeyEvent::Char('h') | KeyEvent::Left => app.on_left(), + KeyEvent::Char('l') | KeyEvent::Right => app.on_right(), + KeyEvent::Char('k') | KeyEvent::Up => app.on_up(), + KeyEvent::Char('j') | KeyEvent::Down => app.on_down(), KeyEvent::Char(c) => app.on_key(c), // TODO: We can remove the 'q' event and just move it to the quit? - KeyEvent::Left => app.on_left(), - KeyEvent::Right => app.on_right(), - KeyEvent::Up => app.on_up(), - KeyEvent::Down => app.on_down(), _ => {} } diff --git a/tests/arg_rate_tests.rs b/tests/arg_rate_tests.rs index f7ad7819..d2527123 100644 --- a/tests/arg_rate_tests.rs +++ b/tests/arg_rate_tests.rs @@ -1,11 +1,8 @@ -use assert_cmd::prelude::*; // Add methods on commands +use assert_cmd::prelude::*; use predicates::prelude::*; -use std::process::Command; // Run programs // Used for writing assertions +use std::process::Command; //======================RATES======================// -#[test] -fn valid_rate_argument() { -} #[test] fn test_small_rate() -> Result<(), Box> {