Made charting look better, switched back to braille markers (its the only way I could make it look good), and dealt with some issues regarding the display of networking.

This commit is contained in:
ClementTsang 2019-09-15 00:06:57 -04:00
parent 2435b9d90c
commit 282acd1395
7 changed files with 174 additions and 58 deletions

View File

@ -6,7 +6,9 @@
* ~~Refreshing - how are we doing that? Are we allowing individual refresh periods per component?~~
* Write tui display, charting
* ~~Write tui display, charting~~
* Scaling in and out
* Add custom error because it's really messy
@ -22,7 +24,7 @@
* Test for Windows support, mac support
* Efficiency!!! Make sure no wasted hashmaps, use references, etc.
* Efficiency!!!
* Potentially process managing? Depends on the libraries...

View File

@ -103,7 +103,6 @@ impl DataState {
}
// Filter out stale timed entries
// TODO: ideally make this a generic function!
let current_instant = std::time::Instant::now();
self.data.list_of_cpu_packages = self
.data

View File

@ -56,16 +56,18 @@ pub async fn get_disk_usage_list() -> Result<Vec<DiskData>, heim::Error> {
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?;
if let Ok(part) = part {
let partition = part;
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
vec_disks.push(DiskData {
free_space : usage.free().get::<heim_common::units::information::megabyte>(),
used_space : usage.used().get::<heim_common::units::information::megabyte>(),
total_space : usage.total().get::<heim_common::units::information::megabyte>(),
mount_point : Box::from(partition.mount_point().to_str().unwrap_or("Name Unavailable")),
name : Box::from(partition.device().unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable")).to_str().unwrap_or("Name Unavailable")),
});
vec_disks.push(DiskData {
free_space : usage.free().get::<heim_common::units::information::megabyte>(),
used_space : usage.used().get::<heim_common::units::information::megabyte>(),
total_space : usage.total().get::<heim_common::units::information::megabyte>(),
mount_point : Box::from(partition.mount_point().to_str().unwrap_or("Name Unavailable")),
name : Box::from(partition.device().unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable")).to_str().unwrap_or("Name Unavailable")),
});
}
}
vec_disks.sort_by(|a, b| {

View File

@ -39,15 +39,20 @@ fn vangelis_cpu_usage_calculation(prev_idle : &mut f64, prev_non_idle : &mut f64
let first_line = stat_results.split('\n').collect::<Vec<&str>>()[0];
let val = first_line.split_whitespace().collect::<Vec<&str>>();
let user : f64 = val[1].parse::<_>().unwrap_or(-1_f64); // TODO: Better checking
let nice : f64 = val[2].parse::<_>().unwrap_or(-1_f64);
let system : f64 = val[3].parse::<_>().unwrap_or(-1_f64);
let idle : f64 = val[4].parse::<_>().unwrap_or(-1_f64);
let iowait : f64 = val[5].parse::<_>().unwrap_or(-1_f64);
let irq : f64 = val[6].parse::<_>().unwrap_or(-1_f64);
let softirq : f64 = val[7].parse::<_>().unwrap_or(-1_f64);
let steal : f64 = val[8].parse::<_>().unwrap_or(-1_f64);
let guest : f64 = val[9].parse::<_>().unwrap_or(-1_f64);
// SC in case that the parsing will fail due to length:
if val.len() <= 10 {
return Ok(1.0); // TODO: This is not the greatest...
}
let user : f64 = val[1].parse::<_>().unwrap_or(0_f64);
let nice : f64 = val[2].parse::<_>().unwrap_or(0_f64);
let system : f64 = val[3].parse::<_>().unwrap_or(0_f64);
let idle : f64 = val[4].parse::<_>().unwrap_or(0_f64);
let iowait : f64 = val[5].parse::<_>().unwrap_or(0_f64);
let irq : f64 = val[6].parse::<_>().unwrap_or(0_f64);
let softirq : f64 = val[7].parse::<_>().unwrap_or(0_f64);
let steal : f64 = val[8].parse::<_>().unwrap_or(0_f64);
let guest : f64 = val[9].parse::<_>().unwrap_or(0_f64);
let idle = idle + iowait;
let non_idle = user + nice + system + irq + softirq + steal + guest;
@ -204,12 +209,13 @@ pub async fn get_sorted_processes_list(prev_idle : &mut f64, prev_non_idle : &mu
}
}
else if cfg!(target_os = "macos") {
// macOS
dbg!("Mac"); // TODO: Remove
// TODO: macOS
debug!("Mac");
}
else {
dbg!("Else"); // TODO: Remove
// Solaris: https://stackoverflow.com/a/4453581
// TODO: Others?
debug!("Else");
// Solaris: https://stackoverflow.com/a/4453581
}
Ok(process_vector)

View File

@ -7,13 +7,16 @@ use tui::{
use crate::utils::error;
const COLOUR_LIST : [Color; 6] = [Color::LightRed, Color::LightGreen, Color::LightYellow, Color::LightBlue, Color::LightCyan, Color::LightMagenta];
const COLOUR_LIST : [Color; 6] = [Color::Red, Color::Green, Color::LightYellow, Color::LightBlue, Color::LightCyan, Color::LightMagenta];
const TEXT_COLOUR : Color = Color::Gray;
const GRAPH_COLOUR : Color = Color::Gray;
const BORDER_STYLE_COLOUR : Color = Color::Gray;
const GRAPH_MARKER : Marker = Marker::Braille;
#[derive(Default)]
pub struct CanvasData {
pub rx_display : String,
pub tx_display : String,
pub network_data_rx : Vec<(f64, f64)>,
pub network_data_tx : Vec<(f64, f64)>,
pub disk_data : Vec<Vec<String>>,
@ -61,14 +64,14 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
// CPU usage graph
{
let x_axis : Axis<String> = 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.0, 100.0]).labels(&["0%", "50%", "100%"]);
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).labels(&["0%", "100%"]);
let mut dataset_vector : Vec<Dataset> = Vec::new();
for (i, cpu) in canvas_data.cpu_data.iter().enumerate() {
dataset_vector.push(
Dataset::default()
.name(&cpu.0)
.marker(Marker::Dot)
.marker(GRAPH_MARKER)
.style(Style::default().fg(COLOUR_LIST[i % COLOUR_LIST.len()]))
.data(&(cpu.1)),
);
@ -85,20 +88,20 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
//Memory usage graph
{
let x_axis : Axis<String> = 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.0, 100.0]).labels(&["0%", "50%", "100%"]);
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).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))
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&[
Dataset::default()
.name(&("MEM :".to_string() + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(Marker::Dot)
.name(&("RAM:".to_string() + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(GRAPH_MARKER)
.style(Style::default().fg(Color::LightBlue))
.data(&canvas_data.mem_data),
Dataset::default()
.name(&("SWAP:".to_string() + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(Marker::Dot)
.name(&("SWP:".to_string() + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(GRAPH_MARKER)
.style(Style::default().fg(Color::LightYellow))
.data(&canvas_data.swap_data),
])
@ -128,20 +131,20 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
// Network graph
{
let x_axis : Axis<String> = 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.0, 1000.0]).labels(&["0Kb", "1000Kb"]);
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 1_000_000.0]).labels(&["0GB", "1GB"]);
Chart::default()
.block(Block::default().title("Network").borders(Borders::ALL).border_style(border_style))
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&[
Dataset::default()
.name(&("RX:".to_string() + &format!("{:3}%", (canvas_data.network_data_rx.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(Marker::Dot)
.name(&(canvas_data.rx_display))
.marker(GRAPH_MARKER)
.style(Style::default().fg(Color::LightBlue))
.data(&canvas_data.network_data_rx),
Dataset::default()
.name(&("TX:".to_string() + &format!("{:3}%", (canvas_data.network_data_tx.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
.marker(Marker::Dot)
.name(&(canvas_data.tx_display))
.marker(GRAPH_MARKER)
.style(Style::default().fg(Color::LightYellow))
.data(&canvas_data.network_data_tx),
])

View File

@ -41,7 +41,7 @@ fn main() -> error::Result<()> {
(about: "A graphical top clone.")
(@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.")
(@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,11 +52,16 @@ fn main() -> error::Result<()> {
.after_help("Themes:")
.get_matches();
let update_rate_in_milliseconds : u64 = matches.value_of("RATE").unwrap_or("1000").parse::<u64>()?;
let update_rate_in_milliseconds : u128 = matches.value_of("RATE").unwrap_or("1000").parse::<u128>()?;
if update_rate_in_milliseconds < 250 {
return Err(RustopError::InvalidArg {
message : "Please set your rate to be greater than 250 milliseconds.".to_string(),
message : "Please set your update rate to be greater than 250 milliseconds.".to_string(),
});
}
else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
return Err(RustopError::InvalidArg {
message : "Please set your update rate to be less than unsigned INT_MAX.".to_string(),
});
}
@ -72,8 +77,7 @@ fn main() -> error::Result<()> {
let show_average_cpu = matches.is_present("AVG_CPU");
// Create "app" struct, which will control most of the program and store settings/state
// TODO: Error handling here because users may be stupid and pass INT_MAX.
let mut app = app::App::new(show_average_cpu, temperature_type, update_rate_in_milliseconds);
let mut app = app::App::new(show_average_cpu, temperature_type, update_rate_in_milliseconds as u64);
// Set up input handling
let (tx, rx) = mpsc::channel();
@ -104,7 +108,7 @@ fn main() -> error::Result<()> {
loop {
futures::executor::block_on(data_state.update_data());
tx.send(Event::Update(Box::from(data_state.data.clone()))).unwrap();
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
thread::sleep(Duration::from_millis(update_rate_in_milliseconds as u64));
}
});
}
@ -151,6 +155,8 @@ fn main() -> error::Result<()> {
let network_data = update_network_data_points(&app_data);
canvas_data.network_data_rx = network_data.rx;
canvas_data.network_data_tx = network_data.tx;
canvas_data.rx_display = network_data.rx_display;
canvas_data.tx_display = network_data.tx_display;
canvas_data.disk_data = update_disk_row(&app_data);
canvas_data.temp_sensor_data = update_temp_row(&app_data, &app.temperature_type);
canvas_data.process_data = update_process_row(&app_data);
@ -253,8 +259,6 @@ fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data
let mut cpu_collection : Vec<Vec<(f64, f64)>> = Vec::new();
if !app_data.list_of_cpu_packages.is_empty() {
// Initially, populate the cpu_collection. We want to inject elements in between if possible.
// I'm sorry for the if statement but I couldn't be bothered here...
for cpu_num in (if show_avg_cpu { 0 } else { 1 })..app_data.list_of_cpu_packages.last().unwrap().cpu_vec.len() {
let mut this_cpu_data : Vec<(f64, f64)> = Vec::new();
@ -262,10 +266,24 @@ fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data
for data in &app_data.list_of_cpu_packages {
let current_time = std::time::Instant::now();
let current_cpu_usage = data.cpu_vec[cpu_num].cpu_usage;
this_cpu_data.push((
let new_entry = (
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
current_cpu_usage,
));
);
// Now, inject our joining points...
if !this_cpu_data.is_empty() {
let previous_element_data = *(this_cpu_data.last().unwrap());
for idx in 0..100 {
this_cpu_data.push((
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
));
}
}
this_cpu_data.push(new_entry);
}
cpu_collection.push(this_cpu_data);
@ -298,11 +316,23 @@ fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f6
for data in mem_data {
let current_time = std::time::Instant::now();
result.push((
let new_entry = (
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
data.mem_used_in_mb as f64 / data.mem_total_in_mb as f64 * 100_f64,
));
);
// Now, inject our joining points...
if !result.is_empty() {
let previous_element_data = *(result.last().unwrap());
for idx in 0..100 {
result.push((
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
));
}
}
result.push(new_entry);
//debug!("Pushed: ({}, {})", result.last().unwrap().0, result.last().unwrap().1);
}
@ -312,6 +342,8 @@ fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f6
struct ConvertedNetworkData {
rx : Vec<(f64, f64)>,
tx : Vec<(f64, f64)>,
rx_display : String,
tx_display : String,
}
fn update_network_data_points(app_data : &data_collection::Data) -> ConvertedNetworkData {
@ -324,20 +356,80 @@ fn convert_network_data_points(network_data : &[data_collection::network::Networ
for data in network_data {
let current_time = std::time::Instant::now();
rx.push((
let rx_data = (
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
data.rx as f64 / 1024.0,
));
tx.push((
);
let tx_data = (
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
data.tx as f64 / 1024.0,
));
);
// Now, inject our joining points...
if !rx.is_empty() {
let previous_element_data = *(rx.last().unwrap());
for idx in 0..100 {
rx.push((
previous_element_data.0 + ((rx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
previous_element_data.1 + ((rx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
));
}
}
// Now, inject our joining points...
if !tx.is_empty() {
let previous_element_data = *(tx.last().unwrap());
for idx in 0..100 {
tx.push((
previous_element_data.0 + ((tx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
previous_element_data.1 + ((tx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
));
}
}
rx.push(rx_data);
tx.push(tx_data);
debug!("Pushed rx: ({}, {})", rx.last().unwrap().0, rx.last().unwrap().1);
debug!("Pushed tx: ({}, {})", tx.last().unwrap().0, tx.last().unwrap().1);
}
ConvertedNetworkData { rx, tx }
let rx_display = if network_data.is_empty() {
"0B".to_string()
}
else {
let num_bytes = network_data.last().unwrap().rx;
if num_bytes < 1024 {
format!("RX: {:4} B", num_bytes).to_string()
}
else if num_bytes < (1024 * 1024) {
format!("RX: {:4}KB", num_bytes / 1024).to_string()
}
else if num_bytes < (1024 * 1024 * 1024) {
format!("RX: {:4}MB", num_bytes / 1024 / 1024).to_string()
}
else {
format!("RX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
}
};
let tx_display = if network_data.is_empty() {
"0B".to_string()
}
else {
let num_bytes = network_data.last().unwrap().tx;
if num_bytes < 1024 {
format!("TX: {:4} B", num_bytes).to_string()
}
else if num_bytes < (1024 * 1024) {
format!("TX: {:4}KB", num_bytes / 1024).to_string()
}
else if num_bytes < (1024 * 1024 * 1024) {
format!("TX: {:4}MB", num_bytes / 1024 / 1024).to_string()
}
else {
format!("TX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
}
};
ConvertedNetworkData { rx, tx, rx_display, tx_display }
}

View File

@ -2,6 +2,7 @@ use assert_cmd::prelude::*; // Add methods on commands
use predicates::prelude::*;
use std::process::Command; // Run programs // Used for writing assertions
//======================RATES======================//
#[test]
fn valid_rate_argument() {
}
@ -17,6 +18,17 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[test]
fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new("./target/debug/rustop")
.arg("-r")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains("rate to be less than unsigned INT_MAX."));
Ok(())
}
#[test]
fn test_negative_rate() -> Result<(), Box<dyn std::error::Error>> {
// This test should auto fail due to how clap works