Skip to content

Commit 7af1de6

Browse files
committed
top: implement basic tui
1 parent 4208162 commit 7af1de6

File tree

10 files changed

+720
-276
lines changed

10 files changed

+720
-276
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/top/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ version.workspace = true
1313
[dependencies]
1414
uucore = { workspace = true, features = ["utmpx", "uptime"] }
1515
clap = { workspace = true }
16+
crossterm = { workspace = true }
1617
libc = { workspace = true }
1718
nix = { workspace = true }
18-
prettytable-rs = { workspace = true }
19+
ratatui = { workspace = true, features = ["crossterm"] }
1920
sysinfo = { workspace = true }
2021
chrono = { workspace = true }
2122
bytesize = { workspace = true }

src/uu/top/src/header.rs

Lines changed: 102 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -5,91 +5,118 @@
55

66
use crate::picker::sysinfo;
77
use crate::platform::*;
8-
use crate::{CpuGraphMode, CpuValueMode, MemoryGraphMode, Settings};
8+
use crate::tui::stat::{CpuValueMode, TuiStat};
99
use bytesize::ByteSize;
1010
use uu_vmstat::CpuLoad;
1111
use uu_w::get_formatted_uptime_procps;
1212
use uucore::uptime::{get_formatted_loadavg, get_formatted_nusers, get_formatted_time};
1313

14-
pub(crate) fn header(settings: &Settings) -> String {
15-
let uptime_line = format!(
16-
"top - {time} {uptime}, {user}, {load_average}\n",
17-
time = get_formatted_time(),
18-
uptime = uptime(),
19-
user = user(),
20-
load_average = load_average(),
21-
);
22-
23-
let task_and_cpu = if settings.cpu_graph_mode == CpuGraphMode::Hide {
24-
String::new()
25-
} else {
26-
format!(
27-
"{task}\n\
28-
{cpu}\n",
29-
task = task(),
30-
cpu = cpu(settings),
31-
)
32-
};
14+
pub(crate) struct Header {
15+
pub uptime: Uptime,
16+
pub task: Task,
17+
pub cpu: Vec<(String, CpuLoad)>,
18+
pub memory: Memory,
19+
}
3320

34-
let memory_line = if settings.memory_graph_mode == MemoryGraphMode::Hide {
35-
String::new()
36-
} else {
37-
memory(settings)
38-
};
21+
impl Header {
22+
pub fn new(stat: &TuiStat) -> Header {
23+
Header {
24+
uptime: Uptime::new(),
25+
task: Task::new(),
26+
cpu: cpu(stat),
27+
memory: Memory::new(),
28+
}
29+
}
3930

40-
format!("{uptime_line}{task_and_cpu}{memory_line}")
31+
pub fn update_cpu(&mut self, stat: &TuiStat) {
32+
self.cpu = cpu(stat);
33+
}
4134
}
4235

43-
fn format_memory(memory_b: u64, unit: u64) -> f64 {
44-
ByteSize::b(memory_b).0 as f64 / unit as f64
36+
pub(crate) struct Uptime {
37+
pub time: String,
38+
pub uptime: String,
39+
pub user: String,
40+
pub load_average: String,
4541
}
4642

47-
#[inline]
48-
fn uptime() -> String {
49-
get_formatted_uptime_procps().unwrap_or_default()
43+
impl Uptime {
44+
pub fn new() -> Uptime {
45+
Uptime {
46+
time: get_formatted_time(),
47+
uptime: get_formatted_uptime_procps().unwrap_or_default(),
48+
user: user(),
49+
load_average: get_formatted_loadavg().unwrap_or_default(),
50+
}
51+
}
5052
}
5153

52-
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
53-
fn user() -> String {
54-
#[cfg(target_os = "linux")]
55-
if let Ok(nusers) = get_nusers_systemd() {
56-
return uucore::uptime::format_nusers(nusers);
54+
pub(crate) struct Task {
55+
pub total: usize,
56+
pub running: usize,
57+
pub sleeping: usize,
58+
pub stopped: usize,
59+
pub zombie: usize,
60+
}
61+
impl Task {
62+
pub fn new() -> Task {
63+
let binding = sysinfo().read().unwrap();
64+
65+
let process = binding.processes();
66+
let mut running_process = 0;
67+
let mut sleeping_process = 0;
68+
let mut stopped_process = 0;
69+
let mut zombie_process = 0;
70+
71+
for (_, process) in process.iter() {
72+
match process.status() {
73+
sysinfo::ProcessStatus::Run => running_process += 1,
74+
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
75+
sysinfo::ProcessStatus::Stop => stopped_process += 1,
76+
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
77+
_ => {}
78+
};
79+
}
80+
81+
Task {
82+
total: process.len(),
83+
running: running_process,
84+
sleeping: sleeping_process,
85+
stopped: stopped_process,
86+
zombie: zombie_process,
87+
}
5788
}
89+
}
5890

59-
get_formatted_nusers()
91+
pub(crate) struct Memory {
92+
pub total: u64,
93+
pub free: u64,
94+
pub used: u64,
95+
pub buff_cache: u64,
96+
pub available: u64,
97+
pub total_swap: u64,
98+
pub free_swap: u64,
99+
pub used_swap: u64,
60100
}
61101

62-
fn load_average() -> String {
63-
get_formatted_loadavg().unwrap_or_default()
102+
impl Memory {
103+
pub fn new() -> Memory {
104+
get_memory()
105+
}
64106
}
65107

66-
fn task() -> String {
67-
let binding = sysinfo().read().unwrap();
68-
69-
let process = binding.processes();
70-
let mut running_process = 0;
71-
let mut sleeping_process = 0;
72-
let mut stopped_process = 0;
73-
let mut zombie_process = 0;
74-
75-
for (_, process) in process.iter() {
76-
match process.status() {
77-
sysinfo::ProcessStatus::Run => running_process += 1,
78-
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
79-
sysinfo::ProcessStatus::Stop => stopped_process += 1,
80-
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
81-
_ => {}
82-
};
108+
pub(crate) fn format_memory(memory_b: u64, unit: u64) -> f64 {
109+
ByteSize::b(memory_b).0 as f64 / unit as f64
110+
}
111+
112+
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
113+
fn user() -> String {
114+
#[cfg(target_os = "linux")]
115+
if let Ok(nusers) = get_nusers_systemd() {
116+
return uucore::uptime::format_nusers(nusers);
83117
}
84118

85-
format!(
86-
"Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie",
87-
process.len(),
88-
running_process,
89-
sleeping_process,
90-
stopped_process,
91-
zombie_process,
92-
)
119+
get_formatted_nusers()
93120
}
94121

95122
fn sum_cpu_loads(cpu_loads: &[uu_vmstat::CpuLoadRaw]) -> uu_vmstat::CpuLoadRaw {
@@ -122,94 +149,22 @@ fn sum_cpu_loads(cpu_loads: &[uu_vmstat::CpuLoadRaw]) -> uu_vmstat::CpuLoadRaw {
122149
total
123150
}
124151

125-
fn cpu(settings: &Settings) -> String {
126-
if settings.cpu_graph_mode == CpuGraphMode::Hide {
127-
return String::new();
128-
}
129-
152+
fn cpu(stat: &TuiStat) -> Vec<(String, CpuLoad)> {
130153
let cpu_loads = get_cpu_loads();
131154

132-
match settings.cpu_value_mode {
133-
CpuValueMode::PerCore => {
134-
let lines = cpu_loads
135-
.iter()
136-
.enumerate()
137-
.map(|(nth, cpu_load_raw)| {
138-
let cpu_load = CpuLoad::from_raw(cpu_load_raw);
139-
cpu_line(format!("Cpu{nth}").as_str(), &cpu_load, settings)
140-
})
141-
.collect::<Vec<String>>();
142-
lines.join("\n")
143-
}
155+
match stat.cpu_value_mode {
156+
CpuValueMode::PerCore => cpu_loads
157+
.iter()
158+
.enumerate()
159+
.map(|(nth, cpu_load_raw)| {
160+
let cpu_load = CpuLoad::from_raw(cpu_load_raw);
161+
(format!("Cpu{nth}"), cpu_load)
162+
})
163+
.collect::<Vec<(String, CpuLoad)>>(),
144164
CpuValueMode::Sum => {
145165
let total = sum_cpu_loads(&cpu_loads);
146166
let cpu_load = CpuLoad::from_raw(&total);
147-
cpu_line("Cpu", &cpu_load, settings)
167+
vec![(String::from("Cpu(s)"), cpu_load)]
148168
}
149169
}
150170
}
151-
152-
fn cpu_line(tag: &str, cpu_load: &CpuLoad, settings: &Settings) -> String {
153-
if settings.cpu_graph_mode == CpuGraphMode::Hide {
154-
return String::new();
155-
}
156-
157-
if settings.cpu_graph_mode == CpuGraphMode::Sum {
158-
return format!(
159-
"%{tag:<6}: {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
160-
cpu_load.user,
161-
cpu_load.system,
162-
cpu_load.nice,
163-
cpu_load.idle,
164-
cpu_load.io_wait,
165-
cpu_load.hardware_interrupt,
166-
cpu_load.software_interrupt,
167-
cpu_load.steal_time,
168-
);
169-
}
170-
171-
// TODO: render colored bar chart or block chart
172-
format!("%{tag:<6}: {:>5.1}/{:<5.1}", cpu_load.user, cpu_load.system)
173-
}
174-
175-
fn memory(settings: &Settings) -> String {
176-
let binding = sysinfo().read().unwrap();
177-
let (unit, unit_name) = match settings.scale_summary_mem.as_ref() {
178-
Some(scale) => match scale.as_str() {
179-
"k" => (bytesize::KIB, "KiB"),
180-
"m" => (bytesize::MIB, "MiB"),
181-
"g" => (bytesize::GIB, "GiB"),
182-
"t" => (bytesize::TIB, "TiB"),
183-
"p" => (bytesize::PIB, "PiB"),
184-
"e" => (1_152_921_504_606_846_976, "EiB"),
185-
_ => (bytesize::MIB, "MiB"),
186-
},
187-
None => (bytesize::MIB, "MiB"),
188-
};
189-
190-
if settings.memory_graph_mode == MemoryGraphMode::Sum {
191-
return format!(
192-
"{unit_name} Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache\n\
193-
{unit_name} Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem",
194-
format_memory(binding.total_memory(), unit),
195-
format_memory(binding.free_memory(), unit),
196-
format_memory(binding.used_memory(), unit),
197-
format_memory(binding.available_memory() - binding.free_memory(), unit),
198-
format_memory(binding.total_swap(), unit),
199-
format_memory(binding.free_swap(), unit),
200-
format_memory(binding.used_swap(), unit),
201-
format_memory(binding.available_memory(), unit),
202-
unit_name = unit_name
203-
);
204-
}
205-
206-
// TODO: render colored bar chart or block chart
207-
format!(
208-
"GiB Mem : {:>5.1}/{:<5.1}\n\
209-
GiB Swap : {:>5.1}/{:<5.1}",
210-
format_memory(binding.used_memory(), unit),
211-
format_memory(binding.total_memory(), unit),
212-
format_memory(binding.used_swap(), unit),
213-
format_memory(binding.total_swap(), unit)
214-
)
215-
}

src/uu/top/src/platform/fallback.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55

66
#![allow(unused)]
77

8+
use crate::header::Memory;
9+
use crate::picker::sysinfo;
10+
811
pub fn get_cpu_loads() -> Vec<uu_vmstat::CpuLoadRaw> {
912
vec![]
1013
}
14+
15+
pub fn get_memory() -> Memory {
16+
let binding = sysinfo().read().unwrap();
17+
18+
Memory {
19+
total: binding.total_memory(),
20+
free: binding.free_memory(),
21+
used: binding.used_memory(),
22+
buff_cache: binding.available_memory() - binding.free_memory(), // TODO: use proper buff/cache instead of available - free
23+
available: binding.available_memory(),
24+
total_swap: binding.total_swap(),
25+
free_swap: binding.free_swap(),
26+
used_swap: binding.used_swap(),
27+
}
28+
}

src/uu/top/src/platform/linux.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6+
use crate::header::Memory;
67
use std::str::FromStr;
78

89
extern "C" {
@@ -76,3 +77,18 @@ pub fn get_cpu_loads() -> Vec<uu_vmstat::CpuLoadRaw> {
7677

7778
cpu_loads
7879
}
80+
81+
pub fn get_memory() -> Memory {
82+
let mem_info = uu_vmstat::Meminfo::current();
83+
84+
Memory {
85+
total: mem_info.mem_total.0,
86+
free: mem_info.mem_free.0,
87+
used: mem_info.mem_total.0 - mem_info.mem_free.0 - mem_info.buffers.0 - mem_info.cached.0,
88+
buff_cache: mem_info.buffers.0 + mem_info.cached.0,
89+
available: mem_info.mem_available.0,
90+
total_swap: mem_info.swap_total.0,
91+
free_swap: mem_info.swap_free.0,
92+
used_swap: mem_info.swap_total.0 - mem_info.swap_free.0,
93+
}
94+
}

src/uu/top/src/platform/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub mod windows;
1111
pub mod fallback;
1212

1313
#[cfg(target_os = "linux")]
14-
pub use linux::{get_cpu_loads, get_nusers_systemd};
14+
pub use linux::{get_cpu_loads, get_memory, get_nusers_systemd};
1515
#[cfg(target_os = "windows")]
1616
pub use windows::get_cpu_loads;
1717

0 commit comments

Comments
 (0)