Skip to content

Commit 81fe184

Browse files
Merge pull request #506 from Bluemangoo/feature/top-tui
top: implement tui
2 parents a556488 + 1b3fdf7 commit 81fe184

File tree

13 files changed

+978
-341
lines changed

13 files changed

+978
-341
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: 130 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -4,90 +4,109 @@
44
// file that was distributed with this source code.
55

66
use crate::picker::sysinfo;
7+
use crate::platform::*;
8+
use crate::tui::stat::{CpuValueMode, TuiStat};
79
use bytesize::ByteSize;
10+
use uu_vmstat::CpuLoad;
811
use uu_w::get_formatted_uptime_procps;
912
use uucore::uptime::{get_formatted_loadavg, get_formatted_nusers, get_formatted_time};
1013

11-
pub(crate) fn header(scale_summary_mem: Option<&String>) -> String {
12-
format!(
13-
"top - {time} {uptime}, {user}, {load_average}\n\
14-
{task}\n\
15-
{cpu}\n\
16-
{memory}",
17-
time = get_formatted_time(),
18-
uptime = uptime(),
19-
user = user(),
20-
load_average = load_average(),
21-
task = task(),
22-
cpu = cpu(),
23-
memory = memory(scale_summary_mem),
24-
)
14+
pub(crate) struct Header {
15+
pub uptime: Uptime,
16+
pub task: Task,
17+
pub cpu: Vec<(String, CpuLoad)>,
18+
pub memory: Memory,
2519
}
2620

27-
#[cfg(target_os = "linux")]
28-
extern "C" {
29-
pub fn sd_booted() -> libc::c_int;
30-
pub fn sd_get_sessions(sessions: *mut *mut *mut libc::c_char) -> libc::c_int;
31-
pub fn sd_session_get_class(
32-
session: *const libc::c_char,
33-
class: *mut *mut libc::c_char,
34-
) -> libc::c_int;
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+
}
30+
31+
pub fn update_cpu(&mut self, stat: &TuiStat) {
32+
self.cpu = cpu(stat);
33+
}
3534
}
3635

37-
fn format_memory(memory_b: u64, unit: u64) -> f64 {
38-
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,
41+
}
42+
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+
}
3952
}
4053

41-
#[inline]
42-
fn uptime() -> String {
43-
get_formatted_uptime_procps().unwrap_or_default()
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,
4460
}
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+
}
4580

46-
#[cfg(target_os = "linux")]
47-
pub fn get_nusers_systemd() -> uucore::error::UResult<usize> {
48-
use std::ffi::CStr;
49-
use std::ptr;
50-
use uucore::error::USimpleError;
51-
use uucore::libc::*;
52-
53-
// SAFETY: sd_booted to check if system is booted with systemd.
54-
unsafe {
55-
// systemd
56-
if sd_booted() > 0 {
57-
let mut sessions_list: *mut *mut c_char = ptr::null_mut();
58-
let mut num_user = 0;
59-
let sessions = sd_get_sessions(&mut sessions_list);
60-
61-
if sessions > 0 {
62-
for i in 0..sessions {
63-
let mut class: *mut c_char = ptr::null_mut();
64-
65-
if sd_session_get_class(
66-
*sessions_list.add(i as usize) as *const c_char,
67-
&mut class,
68-
) < 0
69-
{
70-
continue;
71-
}
72-
if CStr::from_ptr(class).to_str().unwrap().starts_with("user") {
73-
num_user += 1;
74-
}
75-
free(class as *mut c_void);
76-
}
77-
}
78-
79-
for i in 0..sessions {
80-
free(*sessions_list.add(i as usize) as *mut c_void);
81-
}
82-
free(sessions_list as *mut c_void);
83-
84-
return Ok(num_user);
81+
Task {
82+
total: process.len(),
83+
running: running_process,
84+
sleeping: sleeping_process,
85+
stopped: stopped_process,
86+
zombie: zombie_process,
8587
}
8688
}
87-
Err(USimpleError::new(
88-
1,
89-
"could not retrieve number of logged users",
90-
))
89+
}
90+
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,
100+
}
101+
102+
impl Memory {
103+
pub fn new() -> Memory {
104+
get_memory()
105+
}
106+
}
107+
108+
pub(crate) fn format_memory(memory_b: u64, unit: u64) -> f64 {
109+
ByteSize::b(memory_b).0 as f64 / unit as f64
91110
}
92111

93112
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
@@ -100,152 +119,52 @@ fn user() -> String {
100119
get_formatted_nusers()
101120
}
102121

103-
fn load_average() -> String {
104-
get_formatted_loadavg().unwrap_or_default()
105-
}
122+
fn sum_cpu_loads(cpu_loads: &[uu_vmstat::CpuLoadRaw]) -> uu_vmstat::CpuLoadRaw {
123+
let mut total = uu_vmstat::CpuLoadRaw {
124+
user: 0,
125+
nice: 0,
126+
system: 0,
127+
idle: 0,
128+
io_wait: 0,
129+
hardware_interrupt: 0,
130+
software_interrupt: 0,
131+
steal_time: 0,
132+
guest: 0,
133+
guest_nice: 0,
134+
};
106135

107-
fn task() -> String {
108-
let binding = sysinfo().read().unwrap();
109-
110-
let process = binding.processes();
111-
let mut running_process = 0;
112-
let mut sleeping_process = 0;
113-
let mut stopped_process = 0;
114-
let mut zombie_process = 0;
115-
116-
for (_, process) in process.iter() {
117-
match process.status() {
118-
sysinfo::ProcessStatus::Run => running_process += 1,
119-
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
120-
sysinfo::ProcessStatus::Stop => stopped_process += 1,
121-
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
122-
_ => {}
123-
};
136+
for load in cpu_loads {
137+
total.user += load.user;
138+
total.nice += load.nice;
139+
total.system += load.system;
140+
total.idle += load.idle;
141+
total.io_wait += load.io_wait;
142+
total.hardware_interrupt += load.hardware_interrupt;
143+
total.software_interrupt += load.software_interrupt;
144+
total.steal_time += load.steal_time;
145+
total.guest += load.guest;
146+
total.guest_nice += load.guest_nice;
124147
}
125148

126-
format!(
127-
"Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie",
128-
process.len(),
129-
running_process,
130-
sleeping_process,
131-
stopped_process,
132-
zombie_process,
133-
)
149+
total
134150
}
135151

136-
#[cfg(target_os = "linux")]
137-
fn cpu() -> String {
138-
let cpu_load = uu_vmstat::CpuLoad::current();
139-
140-
format!(
141-
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
142-
cpu_load.user,
143-
cpu_load.system,
144-
cpu_load.nice,
145-
cpu_load.idle,
146-
cpu_load.io_wait,
147-
cpu_load.hardware_interrupt,
148-
cpu_load.software_interrupt,
149-
cpu_load.steal_time,
150-
)
151-
}
152-
153-
#[cfg(target_os = "windows")]
154-
fn cpu() -> String {
155-
use libc::malloc;
156-
use windows_sys::Wdk::System::SystemInformation::NtQuerySystemInformation;
157-
158-
#[repr(C)]
159-
#[derive(Debug)]
160-
struct SystemProcessorPerformanceInformation {
161-
idle_time: i64, // LARGE_INTEGER
162-
kernel_time: i64, // LARGE_INTEGER
163-
user_time: i64, // LARGE_INTEGER
164-
dpc_time: i64, // LARGE_INTEGER
165-
interrupt_time: i64, // LARGE_INTEGER
166-
interrupt_count: u32, // ULONG
167-
}
168-
169-
let n_cpu = sysinfo().read().unwrap().cpus().len();
170-
let mut cpu_load = SystemProcessorPerformanceInformation {
171-
idle_time: 0,
172-
kernel_time: 0,
173-
user_time: 0,
174-
dpc_time: 0,
175-
interrupt_time: 0,
176-
interrupt_count: 0,
177-
};
178-
// SAFETY: malloc is safe to use here. We free the memory after we are done with it. If action fails, all "time" will be 0.
179-
unsafe {
180-
let len = n_cpu * size_of::<SystemProcessorPerformanceInformation>();
181-
let data = malloc(len);
182-
let status = NtQuerySystemInformation(
183-
windows_sys::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation,
184-
data,
185-
(n_cpu * size_of::<SystemProcessorPerformanceInformation>()) as u32,
186-
std::ptr::null_mut(),
187-
);
188-
if status == 0 {
189-
for i in 0..n_cpu {
190-
let cpu = data.add(i * size_of::<SystemProcessorPerformanceInformation>())
191-
as *const SystemProcessorPerformanceInformation;
192-
let cpu = cpu.as_ref().unwrap();
193-
cpu_load.idle_time += cpu.idle_time;
194-
cpu_load.kernel_time += cpu.kernel_time;
195-
cpu_load.user_time += cpu.user_time;
196-
cpu_load.dpc_time += cpu.dpc_time;
197-
cpu_load.interrupt_time += cpu.interrupt_time;
198-
cpu_load.interrupt_count += cpu.interrupt_count;
199-
}
152+
fn cpu(stat: &TuiStat) -> Vec<(String, CpuLoad)> {
153+
let cpu_loads = get_cpu_loads();
154+
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)>>(),
164+
CpuValueMode::Sum => {
165+
let total = sum_cpu_loads(&cpu_loads);
166+
let cpu_load = CpuLoad::from_raw(&total);
167+
vec![(String::from("Cpu(s)"), cpu_load)]
200168
}
201169
}
202-
let total = cpu_load.idle_time
203-
+ cpu_load.kernel_time
204-
+ cpu_load.user_time
205-
+ cpu_load.dpc_time
206-
+ cpu_load.interrupt_time;
207-
format!(
208-
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} id, {:.1} hi, {:.1} si",
209-
cpu_load.user_time as f64 / total as f64 * 100.0,
210-
cpu_load.kernel_time as f64 / total as f64 * 100.0,
211-
cpu_load.idle_time as f64 / total as f64 * 100.0,
212-
cpu_load.interrupt_time as f64 / total as f64 * 100.0,
213-
cpu_load.dpc_time as f64 / total as f64 * 100.0,
214-
)
215-
}
216-
217-
//TODO: Implement for macos
218-
#[cfg(target_os = "macos")]
219-
fn cpu() -> String {
220-
"TODO".into()
221-
}
222-
223-
fn memory(scale_summary_mem: Option<&String>) -> String {
224-
let binding = sysinfo().read().unwrap();
225-
let (unit, unit_name) = match scale_summary_mem {
226-
Some(scale) => match scale.as_str() {
227-
"k" => (bytesize::KIB, "KiB"),
228-
"m" => (bytesize::MIB, "MiB"),
229-
"g" => (bytesize::GIB, "GiB"),
230-
"t" => (bytesize::TIB, "TiB"),
231-
"p" => (bytesize::PIB, "PiB"),
232-
"e" => (1_152_921_504_606_846_976, "EiB"),
233-
_ => (bytesize::MIB, "MiB"),
234-
},
235-
None => (bytesize::MIB, "MiB"),
236-
};
237-
238-
format!(
239-
"{unit_name} Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache\n\
240-
{unit_name} Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem",
241-
format_memory(binding.total_memory(), unit),
242-
format_memory(binding.free_memory(), unit),
243-
format_memory(binding.used_memory(), unit),
244-
format_memory(binding.available_memory() - binding.free_memory(), unit),
245-
format_memory(binding.total_swap(), unit),
246-
format_memory(binding.free_swap(), unit),
247-
format_memory(binding.used_swap(), unit),
248-
format_memory(binding.available_memory(), unit),
249-
unit_name = unit_name
250-
)
251170
}

0 commit comments

Comments
 (0)