Added auto updating TUI
This commit is contained in:
parent
20b3aef74a
commit
287f416f0b
2 changed files with 131 additions and 35 deletions
71
src/app.rs
Normal file
71
src/app.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
layout::Constraint,
|
||||||
|
style::{Color, Style},
|
||||||
|
widgets::{Block, Borders, Row, Table, TableState},
|
||||||
|
};
|
||||||
|
use sysinfo::System;
|
||||||
|
|
||||||
|
pub struct SystemStats {
|
||||||
|
pub sys: System,
|
||||||
|
pub cpu_count: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_system() -> SystemStats {
|
||||||
|
let mut sys = System::new_all();
|
||||||
|
sys.refresh_cpu_usage();
|
||||||
|
let cpu_count = sys.cpus().len() as f32;
|
||||||
|
SystemStats { sys, cpu_count }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_ui(f: &mut Frame, sys: &mut System, cpu_count: f32, state: &mut TableState) {
|
||||||
|
let mut processes: Vec<_> = sys.processes().values().collect();
|
||||||
|
processes.sort_by(|a, b| {
|
||||||
|
b.cpu_usage()
|
||||||
|
.partial_cmp(&a.cpu_usage())
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
|
||||||
|
let rows: Vec<Row> = processes
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
let cpu_usage = p.cpu_usage();
|
||||||
|
Row::new(vec![
|
||||||
|
p.pid().to_string(),
|
||||||
|
p.name().to_string_lossy().to_string(),
|
||||||
|
format!("{:.1}%", cpu_usage / cpu_count),
|
||||||
|
format!("{:.1}%", cpu_usage),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let widths = [
|
||||||
|
Constraint::Length(10),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(10),
|
||||||
|
Constraint::Length(15),
|
||||||
|
];
|
||||||
|
|
||||||
|
let table = Table::new(rows, widths)
|
||||||
|
.header(
|
||||||
|
Row::new(vec!["PID", "Name", "CPU %", "CPU Core %"]).style(Style::new().blue().bold()),
|
||||||
|
)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(" Process Manager ")
|
||||||
|
.title_bottom(" Use ↑/↓ to Scroll, 'q' to Quit "),
|
||||||
|
)
|
||||||
|
.row_highlight_style(Style::new().bg(Color::Cyan).fg(Color::Black).bold())
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
|
||||||
|
f.render_stateful_widget(table, f.area(), state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_system_data(sys: &mut System) {
|
||||||
|
sys.refresh_processes_specifics(
|
||||||
|
sysinfo::ProcessesToUpdate::All,
|
||||||
|
true,
|
||||||
|
sysinfo::ProcessRefreshKind::nothing().with_cpu(),
|
||||||
|
);
|
||||||
|
}
|
||||||
93
src/main.rs
93
src/main.rs
|
|
@ -1,41 +1,66 @@
|
||||||
use std::thread;
|
mod app;
|
||||||
use std::time::Duration;
|
|
||||||
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
|
|
||||||
|
|
||||||
fn main() {
|
use crossterm::event::{self, Event, KeyCode};
|
||||||
let mut sys = System::new_all();
|
use ratatui::widgets::TableState;
|
||||||
sys.refresh_cpu_usage();
|
use ratatui::{Terminal, backend::CrosstermBackend};
|
||||||
let cpu_count = sys.cpus().len() as f32;
|
use std::io::{self};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use app::{draw_ui, init_system, refresh_system_data};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
crossterm::execute!(stdout, crossterm::terminal::EnterAlternateScreen)?;
|
||||||
|
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
terminal.clear()?;
|
||||||
|
let mut stats = init_system();
|
||||||
|
let mut table_state = TableState::default();
|
||||||
|
table_state.select(Some(0));
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
let tick_rate = Duration::from_secs(1);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
sys.refresh_processes_specifics(
|
terminal.draw(|f| {
|
||||||
sysinfo::ProcessesToUpdate::All,
|
draw_ui(f, &mut stats.sys, stats.cpu_count, &mut table_state);
|
||||||
true,
|
})?;
|
||||||
sysinfo::ProcessRefreshKind::nothing().with_cpu(),
|
|
||||||
);
|
if last_tick.elapsed() >= tick_rate {
|
||||||
print!("{esc}c", esc = 27 as char);
|
refresh_system_data(&mut stats.sys);
|
||||||
println!(
|
last_tick = Instant::now();
|
||||||
"{:<10} {:<30} {:<10} {:<10}",
|
|
||||||
"PID", "Name", "CPU %", "CPU Core %"
|
|
||||||
);
|
|
||||||
println!("{}", "-".repeat(50));
|
|
||||||
let mut procs: Vec<_> = sys.processes().values().collect();
|
|
||||||
procs.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap());
|
|
||||||
for p in procs.iter() {
|
|
||||||
let normalized_cpu = p.cpu_usage() / cpu_count;
|
|
||||||
let mut name = p.name().to_string_lossy().into_owned();
|
|
||||||
if name.len() > 30 {
|
|
||||||
name.truncate(27);
|
|
||||||
name.push_str("...");
|
|
||||||
}
|
}
|
||||||
println!(
|
|
||||||
"{:<10} {:<30} {:<.2}% {:<.2}%",
|
if event::poll(Duration::from_millis(50))? {
|
||||||
p.pid(),
|
if let Event::Key(key) = event::read()? {
|
||||||
p.name().to_string_lossy(),
|
match key.code {
|
||||||
normalized_cpu,
|
KeyCode::Char('q') => break,
|
||||||
p.cpu_usage(),
|
KeyCode::Down => {
|
||||||
);
|
let i = match table_state.selected() {
|
||||||
|
Some(i) => (i + 1).min(stats.sys.processes().len() - 1),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
table_state.select(Some(i));
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(1000));
|
KeyCode::Up => {
|
||||||
|
let i = match table_state.selected() {
|
||||||
|
Some(i) => i.saturating_sub(1),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
table_state.select(Some(i));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crossterm::execute!(
|
||||||
|
io::stdout(),
|
||||||
|
crossterm::terminal::LeaveAlternateScreen,
|
||||||
|
crossterm::cursor::Show
|
||||||
|
)?;
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue