mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-29 00:24:36 +02:00
Rudimentary charting for cpu and mem.
This commit is contained in:
parent
0d76c49973
commit
2032660230
@ -1,6 +1,6 @@
|
|||||||
# rustop
|
# rustop
|
||||||
|
|
||||||
A [gotop](https://github.com/cjbassi/gotop) clone, written in Rust.
|
A top clone, written in Rust. Inspired by both [gtop](https://github.com/aksakalli/gtop) and [gotop](https://github.com/cjbassi/gotop)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ Currently, I'm unable to test on MacOS, so I'm not sure how well this will work,
|
|||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
* As mentioned, this project is most definitely inspired by [gotop](https://github.com/cjbassi/gotop).
|
* 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:
|
||||||
* [crossterm](https://github.com/TimonPost/crossterm)
|
* [crossterm](https://github.com/TimonPost/crossterm)
|
||||||
|
4
TODO.md
4
TODO.md
@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
* Keybindings
|
* Keybindings
|
||||||
|
|
||||||
|
* FIX PROCESSES AHHHHHH
|
||||||
|
|
||||||
|
* Refactor everything because it's a mess
|
||||||
|
|
||||||
* Test for Windows support, mac support
|
* Test for Windows support, mac support
|
||||||
|
|
||||||
* Efficiency!!! Make sure no wasted hashmaps, use references, etc.
|
* Efficiency!!! Make sure no wasted hashmaps, use references, etc.
|
||||||
|
103
src/main.rs
103
src/main.rs
@ -19,6 +19,8 @@ enum Event<I> {
|
|||||||
Update(Box<widgets::Data>),
|
Update(Box<widgets::Data>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STALE_MAX_SECONDS : u64 = 60;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), io::Error> {
|
async fn main() -> Result<(), io::Error> {
|
||||||
let screen = AlternateScreen::to_alternate(true)?;
|
let screen = AlternateScreen::to_alternate(true)?;
|
||||||
@ -52,6 +54,7 @@ async fn main() -> Result<(), io::Error> {
|
|||||||
|
|
||||||
// Event loop
|
// Event loop
|
||||||
let mut data_state = widgets::DataState::default();
|
let mut data_state = widgets::DataState::default();
|
||||||
|
data_state.set_stale_max_seconds(STALE_MAX_SECONDS);
|
||||||
{
|
{
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
@ -176,11 +179,16 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
|
|||||||
.margin(0)
|
.margin(0)
|
||||||
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref())
|
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref())
|
||||||
.split(vertical_chunks[1]);
|
.split(vertical_chunks[1]);
|
||||||
let middle_divided_chunk = Layout::default()
|
let middle_divided_chunk_1 = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(middle_chunks[0]);
|
.split(middle_chunks[0]);
|
||||||
|
let middle_divided_chunk_2 = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(0)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(middle_chunks[1]);
|
||||||
let bottom_chunks = Layout::default()
|
let bottom_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
@ -190,38 +198,70 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
|
|||||||
// Set up blocks and their components
|
// Set up blocks and their components
|
||||||
|
|
||||||
// CPU usage graph
|
// CPU usage graph
|
||||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]);
|
{
|
||||||
let y_axis : Axis<String> = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 10.0]);
|
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]);
|
||||||
Chart::default()
|
let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]);
|
||||||
.block(Block::default().title("CPU Usage").borders(Borders::ALL))
|
Chart::default()
|
||||||
.x_axis(x_axis)
|
.block(Block::default().title("CPU Usage").borders(Borders::ALL))
|
||||||
.y_axis(y_axis)
|
.x_axis(x_axis)
|
||||||
.datasets(&[Dataset::default()
|
.y_axis(y_axis)
|
||||||
.name("data1")
|
.datasets(&[
|
||||||
.marker(Marker::Dot)
|
Dataset::default()
|
||||||
.style(Style::default().fg(Color::Cyan))
|
.name("CPU0")
|
||||||
.data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)])])
|
.marker(Marker::Braille)
|
||||||
.render(&mut f, top_chunks[0]);
|
.style(Style::default().fg(Color::Cyan))
|
||||||
|
.data(&convert_cpu_data(0, &app_data.list_of_cpu_packages)),
|
||||||
|
Dataset::default()
|
||||||
|
.name("CPU1")
|
||||||
|
.marker(Marker::Braille)
|
||||||
|
.style(Style::default().fg(Color::LightMagenta))
|
||||||
|
.data(&convert_cpu_data(1, &app_data.list_of_cpu_packages)),
|
||||||
|
])
|
||||||
|
.render(&mut f, top_chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
//Memory usage graph
|
//Memory usage graph
|
||||||
Block::default().title("Memory Usage").borders(Borders::ALL).render(&mut f, top_chunks[1]);
|
{
|
||||||
|
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 60.0]);
|
||||||
|
let y_axis = Axis::default().style(Style::default().fg(Color::White)).bounds([0.0, 100.0]).labels(&["0.0", "50.0", "100.0"]);
|
||||||
|
Chart::default()
|
||||||
|
.block(Block::default().title("Memory Usage").borders(Borders::ALL))
|
||||||
|
.x_axis(x_axis)
|
||||||
|
.y_axis(y_axis)
|
||||||
|
.datasets(&[
|
||||||
|
Dataset::default()
|
||||||
|
.name("MEM")
|
||||||
|
.marker(Marker::Braille)
|
||||||
|
.style(Style::default().fg(Color::Cyan))
|
||||||
|
.data(&convert_mem_data(&app_data.memory)),
|
||||||
|
Dataset::default()
|
||||||
|
.name("SWAP")
|
||||||
|
.marker(Marker::Braille)
|
||||||
|
.style(Style::default().fg(Color::LightGreen))
|
||||||
|
.data(&convert_mem_data(&app_data.swap)),
|
||||||
|
])
|
||||||
|
.render(&mut f, top_chunks[1]);
|
||||||
|
}
|
||||||
|
|
||||||
// Temperature table
|
// Temperature table
|
||||||
Table::new(["Sensor", "Temperature"].iter(), temperature_rows)
|
Table::new(["Sensor", "Temperature"].iter(), temperature_rows)
|
||||||
.block(Block::default().title("Temperatures").borders(Borders::ALL))
|
.block(Block::default().title("Temperatures").borders(Borders::ALL))
|
||||||
.header_style(Style::default().fg(Color::LightBlue))
|
.header_style(Style::default().fg(Color::LightBlue))
|
||||||
.widths(&[15, 5])
|
.widths(&[15, 5])
|
||||||
.render(&mut f, middle_divided_chunk[0]);
|
.render(&mut f, middle_divided_chunk_1[0]);
|
||||||
|
|
||||||
// Disk usage table
|
// Disk usage table
|
||||||
Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
|
Table::new(["Disk", "Mount", "Used", "Total", "Free"].iter(), disk_rows)
|
||||||
.block(Block::default().title("Disk Usage").borders(Borders::ALL))
|
.block(Block::default().title("Disk Usage").borders(Borders::ALL))
|
||||||
.header_style(Style::default().fg(Color::LightBlue))
|
.header_style(Style::default().fg(Color::LightBlue))
|
||||||
.widths(&[15, 10, 5, 5, 5])
|
.widths(&[15, 10, 5, 5, 5])
|
||||||
.render(&mut f, middle_divided_chunk[1]);
|
.render(&mut f, middle_divided_chunk_1[1]);
|
||||||
|
|
||||||
|
// Temp graph
|
||||||
|
Block::default().title("Temperatures").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[0]);
|
||||||
|
|
||||||
// IO graph
|
// IO graph
|
||||||
Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_chunks[1]);
|
Block::default().title("IO Usage").borders(Borders::ALL).render(&mut f, middle_divided_chunk_2[1]);
|
||||||
|
|
||||||
// Network graph
|
// Network graph
|
||||||
Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]);
|
Block::default().title("Network").borders(Borders::ALL).render(&mut f, bottom_chunks[0]);
|
||||||
@ -237,6 +277,35 @@ fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, app_data :
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this count, this is for testing, lol
|
||||||
|
fn convert_cpu_data(count : usize, cpu_data : &[widgets::cpu::CPUPackage]) -> Vec<(f64, f64)> {
|
||||||
|
let mut result : Vec<(f64, f64)> = Vec::new();
|
||||||
|
let current_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
for data in cpu_data {
|
||||||
|
result.push((
|
||||||
|
STALE_MAX_SECONDS as f64 - current_time.duration_since(data.instant).as_secs() as f64,
|
||||||
|
f64::from(data.cpu_vec[count + 1].cpu_usage),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_mem_data(mem_data : &[widgets::mem::MemData]) -> Vec<(f64, f64)> {
|
||||||
|
let mut result : Vec<(f64, f64)> = Vec::new();
|
||||||
|
let current_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
for data in mem_data {
|
||||||
|
result.push((
|
||||||
|
STALE_MAX_SECONDS as f64 - current_time.duration_since(data.instant).as_secs() as f64,
|
||||||
|
data.mem_used_in_mb as f64 / data.mem_total_in_mb as f64 * 100_f64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
fn init_logger() -> Result<(), fern::InitError> {
|
fn init_logger() -> Result<(), fern::InitError> {
|
||||||
fern::Dispatch::new()
|
fern::Dispatch::new()
|
||||||
.format(|out, message, record| {
|
.format(|out, message, record| {
|
||||||
|
@ -44,6 +44,7 @@ pub struct Data {
|
|||||||
pub struct DataState {
|
pub struct DataState {
|
||||||
pub data : Data,
|
pub data : Data,
|
||||||
sys : System,
|
sys : System,
|
||||||
|
stale_max_seconds : u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DataState {
|
impl Default for DataState {
|
||||||
@ -51,18 +52,20 @@ impl Default for DataState {
|
|||||||
DataState {
|
DataState {
|
||||||
data : Data::default(),
|
data : Data::default(),
|
||||||
sys : System::new(),
|
sys : System::new(),
|
||||||
|
stale_max_seconds : 60,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataState {
|
impl DataState {
|
||||||
|
pub fn set_stale_max_seconds(&mut self, stale_max_seconds : u64) {
|
||||||
|
self.stale_max_seconds = stale_max_seconds;
|
||||||
|
}
|
||||||
pub async fn update_data(&mut self) {
|
pub async fn update_data(&mut self) {
|
||||||
debug!("Start updating...");
|
debug!("Start updating...");
|
||||||
self.sys.refresh_system();
|
self.sys.refresh_system();
|
||||||
self.sys.refresh_network();
|
self.sys.refresh_network();
|
||||||
|
|
||||||
const STALE_MAX_SECONDS : u64 = 60;
|
|
||||||
|
|
||||||
// What we want to do: For timed data, if there is an error, just do not add. For other data, just don't update!
|
// What we want to do: For timed data, if there is an error, just do not add. For other data, just don't update!
|
||||||
push_if_valid(&network::get_network_data(&self.sys), &mut self.data.network);
|
push_if_valid(&network::get_network_data(&self.sys), &mut self.data.network);
|
||||||
push_if_valid(&cpu::get_cpu_data_list(&self.sys), &mut self.data.list_of_cpu_packages);
|
push_if_valid(&cpu::get_cpu_data_list(&self.sys), &mut self.data.list_of_cpu_packages);
|
||||||
@ -77,14 +80,15 @@ impl DataState {
|
|||||||
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);
|
set_if_valid(&temperature::get_temperature_data().await, &mut self.data.list_of_temperature);
|
||||||
|
|
||||||
// Filter out stale timed entries...
|
// Filter out stale timed entries
|
||||||
|
// TODO: ideally make this a generic function!
|
||||||
let current_instant = std::time::Instant::now();
|
let current_instant = std::time::Instant::now();
|
||||||
self.data.list_of_cpu_packages = self
|
self.data.list_of_cpu_packages = self
|
||||||
.data
|
.data
|
||||||
.list_of_cpu_packages
|
.list_of_cpu_packages
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.data.memory = self
|
self.data.memory = self
|
||||||
@ -92,7 +96,7 @@ impl DataState {
|
|||||||
.memory
|
.memory
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.data.swap = self
|
self.data.swap = self
|
||||||
@ -100,7 +104,7 @@ impl DataState {
|
|||||||
.swap
|
.swap
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.data.network = self
|
self.data.network = self
|
||||||
@ -108,7 +112,7 @@ impl DataState {
|
|||||||
.network
|
.network
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.data.list_of_io = self
|
self.data.list_of_io = self
|
||||||
@ -116,7 +120,7 @@ impl DataState {
|
|||||||
.list_of_io
|
.list_of_io
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.data.list_of_physical_io = self
|
self.data.list_of_physical_io = self
|
||||||
@ -124,7 +128,7 @@ impl DataState {
|
|||||||
.list_of_physical_io
|
.list_of_physical_io
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= STALE_MAX_SECONDS)
|
.filter(|entry| current_instant.duration_since(entry.instant).as_secs() <= self.stale_max_seconds)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
debug!("End updating...");
|
debug!("End updating...");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user