Tried to fix process cpu usage... and reduce total cpu usage of program.

This commit is contained in:
ClementTsang 2019-09-10 18:22:34 -04:00
parent 8ba4674560
commit 939e2d1d77
5 changed files with 163 additions and 81 deletions

24
TODO.md
View file

@ -8,30 +8,14 @@
* Write tui display, charting
* Add custom error because it's really messy
* Keybindings
* Test for Windows support
* Test for Windows support, mac support
* Efficiency!!! Make sure no wasted hashmaps, use references, etc.
## Planned features: (copy of gotop)
* CPU usage monitor
* Total disk usage
* Memory usage
* Temperature
* Processes
* Network usage
## Other possible features
* Potentially process managing? Depends on the libraries...
* Rearranging?
* Filtering in processes along with sorting
* Filtering in processes (ie: search)

0
src/error.rs Normal file
View file

View file

@ -25,7 +25,7 @@ async fn main() -> Result<(), io::Error> {
let backend = CrosstermBackend::with_alternate_screen(screen)?;
let mut terminal = Terminal::new(backend)?;
let tick_rate_in_milliseconds : u64 = 220;
let tick_rate_in_milliseconds : u64 = 250;
let update_rate_in_milliseconds : u64 = 1000;
let log = init_logger();
@ -129,14 +129,13 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
)
});
let mem_total_mb = app_data.memory.mem_total_in_mb as f64;
let process_rows = app_data.list_of_processes.iter().map(|process| {
Row::StyledData(
vec![
process.pid.to_string(),
process.command.to_string(),
format!("{:.2}%", process.cpu_usage_percent),
format!("{:.2}%", process.mem_usage_in_mb as f64 / mem_total_mb * 100_f64),
format!("{:.2}%", process.mem_usage_percent),
]
.into_iter(),
Style::default().fg(Color::LightGreen),
@ -198,14 +197,14 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
Table::new(["Sensor", "Temperature"].iter(), temperature_rows)
.block(Block::default().title("Temperatures").borders(Borders::ALL))
.header_style(Style::default().fg(Color::LightBlue))
.widths(&[25, 25])
.widths(&[15, 5])
.render(&mut f, middle_divided_chunk[0]);
// Disk usage table
Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
.block(Block::default().title("Disk Usage").borders(Borders::ALL))
.header_style(Style::default().fg(Color::LightBlue))
.widths(&[25, 25, 10, 10, 10])
.widths(&[15, 10, 5, 5, 5])
.render(&mut f, middle_divided_chunk[1]);
// IO graph
@ -215,7 +214,7 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]);
// Processes table
Table::new(["PID", "Command", "CPU%", "Mem%"].iter(), process_rows)
Table::new(["PID", "Name", "CPU%", "Mem%"].iter(), process_rows)
.block(Block::default().title("Processes").borders(Borders::ALL))
.header_style(Style::default().fg(Color::LightBlue))
.widths(&[5, 15, 10, 10])
@ -226,25 +225,27 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
}
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()?;
if cfg!(debug_assertions) {
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() {
if cfg!(debug_assertions) && result_log.is_ok() {
debug!("{}", message);
}
}

View file

@ -51,6 +51,7 @@ impl Default for DataState {
impl DataState {
pub async fn update_data(&mut self) {
debug!("Start updating...");
self.sys.refresh_system();
self.sys.refresh_network();
@ -59,13 +60,15 @@ impl DataState {
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(&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(&processes::get_sorted_processes_list(self.data.memory.mem_total_in_mb).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);
debug!("End updating...");
}
}
@ -73,9 +76,9 @@ impl<'a> App<'a> {
pub fn new(title : &str) -> App {
App {
title,
process_sorting_type : processes::ProcessSorting::NAME, // TODO: Change this based on input args...
process_sorting_type : processes::ProcessSorting::CPU, // TODO: Change this based on input args... basically set this on app creation
should_quit : false,
process_sorting_reverse : false,
process_sorting_reverse : true,
to_be_resorted : false,
}
}
@ -83,24 +86,48 @@ impl<'a> App<'a> {
pub fn on_key(&mut self, c : char) {
match c {
'q' => self.should_quit = true,
'h' => self.on_right(),
'j' => self.on_down(),
'k' => self.on_up(),
'l' => self.on_left(),
'c' => {
self.process_sorting_type = processes::ProcessSorting::CPU; // TODO: Change this such that reversing can be done by just hitting "c" twice...
match self.process_sorting_type {
processes::ProcessSorting::CPU => self.process_sorting_reverse = !self.process_sorting_reverse,
_ => {
self.process_sorting_type = processes::ProcessSorting::CPU;
self.process_sorting_reverse = true;
}
}
self.to_be_resorted = true;
}
'm' => {
self.process_sorting_type = processes::ProcessSorting::MEM;
match self.process_sorting_type {
processes::ProcessSorting::MEM => self.process_sorting_reverse = !self.process_sorting_reverse,
_ => {
self.process_sorting_type = processes::ProcessSorting::MEM;
self.process_sorting_reverse = true;
}
}
self.to_be_resorted = true;
}
'p' => {
self.process_sorting_type = processes::ProcessSorting::PID;
match self.process_sorting_type {
processes::ProcessSorting::PID => self.process_sorting_reverse = !self.process_sorting_reverse,
_ => {
self.process_sorting_type = processes::ProcessSorting::PID;
self.process_sorting_reverse = false;
}
}
self.to_be_resorted = true;
}
'n' => {
self.process_sorting_type = processes::ProcessSorting::NAME;
self.to_be_resorted = true;
}
'r' => {
self.process_sorting_reverse = !self.process_sorting_reverse;
match self.process_sorting_type {
processes::ProcessSorting::NAME => self.process_sorting_reverse = !self.process_sorting_reverse,
_ => {
self.process_sorting_type = processes::ProcessSorting::NAME;
self.process_sorting_reverse = false;
}
}
self.to_be_resorted = true;
}
_ => {}

View file

@ -2,6 +2,7 @@ use heim_common::{
prelude::{StreamExt, TryStreamExt},
units,
};
use std::process::Command;
#[allow(dead_code)]
#[derive(Clone)]
@ -17,7 +18,7 @@ pub enum ProcessSorting {
pub struct ProcessData {
pub pid : u32,
pub cpu_usage_percent : f64,
pub mem_usage_in_mb : u64,
pub mem_usage_percent : f64,
pub command : String,
}
@ -43,51 +44,120 @@ 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 non_linux_cpu_usage(process : heim::process::Process) -> heim::process::ProcessResult<(heim::process::Process, heim_common::units::Ratio)> {
let usage_1 = process.cpu_usage().await?;
futures_timer::Delay::new(std::time::Duration::from_millis(150)).await?;
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await?;
let usage_2 = process.cpu_usage().await?;
Ok((process, usage_2 - usage_1))
}
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);
fn get_process_cpu_stats(pid : u32) -> std::io::Result<f64> {
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push(&pid.to_string());
path.push("stat");
let stat_results = std::fs::read_to_string(path)?;
let val = stat_results.split_whitespace().collect::<Vec<&str>>();
Ok(val[13].parse::<f64>().unwrap_or(0_f64) + val[14].parse::<f64>().unwrap_or(0_f64))
}
fn get_cpu_use_val() -> std::io::Result<f64> {
let mut path = std::path::PathBuf::new();
path.push("/proc");
path.push("stat");
let stat_results = std::fs::read_to_string(path)?;
let first_line = stat_results.split('\n').collect::<Vec<&str>>()[0];
let val = first_line.split_whitespace().collect::<Vec<&str>>();
Ok(val[0].parse::<f64>().unwrap_or(0_f64) + val[1].parse::<f64>().unwrap_or(0_f64) + val[2].parse::<f64>().unwrap_or(0_f64) + val[3].parse::<f64>().unwrap_or(0_f64))
}
async fn linux_cpu_usage(pid : u32) -> std::io::Result<f64> {
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
let before_proc_val = get_process_cpu_stats(pid)?;
let before_cpu_val = get_cpu_use_val()?;
futures_timer::Delay::new(std::time::Duration::from_millis(1000)).await.unwrap();
let after_proc_val = get_process_cpu_stats(pid)?;
let after_cpu_val = get_cpu_use_val()?;
Ok((after_proc_val - before_proc_val) / (after_cpu_val - before_cpu_val) * 100_f64)
}
async fn convert_ps(process : &str) -> std::io::Result<ProcessData> {
let mut result = process.split_whitespace();
let pid = result.next().unwrap_or("").parse::<u32>().unwrap_or(0);
Ok(ProcessData {
pid,
command : result.next().unwrap_or("").to_string(),
mem_usage_percent : result.next().unwrap_or("").parse::<f64>().unwrap_or(0_f64),
cpu_usage_percent : linux_cpu_usage(pid).await?,
})
}
pub async fn get_sorted_processes_list(total_mem : u64) -> Result<Vec<ProcessData>, heim::Error> {
let mut process_vector : Vec<ProcessData> = Vec::new();
while let Some(process) = process_stream.next().await {
if let Ok(process) = process {
let (process, cpu_usage) = process;
let mem_measurement = process.memory().await;
if let Ok(mem_measurement) = mem_measurement {
/*
// Unsure whether I want to implement this by grouping together process names...?
let mut process_info = process_hashmap.entry(command_name.to_string()).or_insert(ProcessInfo {
command : command_name,
pid : process.pid() as u32,
cpu_usage_percent : cpu_usage.get::<units::ratio::percent>(),
mem_usage_in_mb : mem_measurement.rss().get::<units::information::megabyte>(),
});
*/
process_vector.push(ProcessData {
command : process.name().await.unwrap_or_else(|_| "".to_string()),
pid : process.pid() as u32,
cpu_usage_percent : f64::from(cpu_usage.get::<units::ratio::percent>()),
mem_usage_in_mb : mem_measurement.rss().get::<units::information::megabyte>(),
});
if cfg!(target_os = "linux") {
// Linux specific - this is a massive pain... ugh.
let ps_result = Command::new("ps").args(&["-axo", "pid,comm,%mem", "--noheader"]).output().expect("Failed to execute.");
let ps_stdout = String::from_utf8_lossy(&ps_result.stdout);
let split_string = ps_stdout.split('\n');
let mut process_stream = futures::stream::iter::<_>(split_string.collect::<Vec<&str>>()).map(convert_ps).buffer_unordered(std::usize::MAX);
while let Some(process) = process_stream.next().await {
if let Ok(process) = process {
process_vector.push(process);
}
}
}
else if cfg!(target_os = "windows") {
// Windows
let mut process_stream = heim::process::processes().map_ok(non_linux_cpu_usage).try_buffer_unordered(std::usize::MAX);
let mut process_vector : Vec<ProcessData> = Vec::new();
while let Some(process) = process_stream.next().await {
if let Ok(process) = process {
let (process, cpu_usage) = process;
let mem_measurement = process.memory().await;
if let Ok(mem_measurement) = mem_measurement {
process_vector.push(ProcessData {
command : process.name().await.unwrap_or_else(|_| "".to_string()),
pid : process.pid() as u32,
cpu_usage_percent : f64::from(cpu_usage.get::<units::ratio::percent>()),
mem_usage_percent : mem_measurement.rss().get::<units::information::megabyte>() as f64 / total_mem as f64 * 100_f64,
});
}
}
}
}
else if cfg!(target_os = "macos") {
// macOS
dbg!("Mac"); // TODO: Remove
}
else {
dbg!("Else"); // TODO: Remove
}
Ok(process_vector)
}
pub fn sort_processes(process_vector : &mut Vec<ProcessData>, sorting_method : &ProcessSorting, reverse_order : bool) {
match sorting_method {
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::PID => process_vector.sort_by(|a, b| get_ordering(a.pid, b.pid, reverse_order)),
// Always sort alphabetically first!
ProcessSorting::CPU => {
process_vector.sort_by(|a, b| get_ordering(&a.command, &b.command, false));
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.command, &b.command, false));
process_vector.sort_by(|a, b| get_ordering(a.mem_usage_percent, b.mem_usage_percent, reverse_order));
}
ProcessSorting::PID => {
process_vector.sort_by(|a, b| get_ordering(&a.command, &b.command, false));
process_vector.sort_by(|a, b| get_ordering(a.pid, b.pid, reverse_order));
}
ProcessSorting::NAME => process_vector.sort_by(|a, b| get_ordering(&a.command, &b.command, reverse_order)),
}
}