mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00:00
Tried to fix process cpu usage... and reduce total cpu usage of program.
This commit is contained in:
parent
8ba4674560
commit
939e2d1d77
5 changed files with 163 additions and 81 deletions
24
TODO.md
24
TODO.md
|
@ -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
0
src/error.rs
Normal file
41
src/main.rs
41
src/main.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue