Skip to content

Commit 655dc91

Browse files
committed
top: impl per-core cpu info
1 parent ee3a109 commit 655dc91

File tree

7 files changed

+315
-162
lines changed

7 files changed

+315
-162
lines changed

src/uu/top/src/header.rs

Lines changed: 90 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,36 @@
44
// file that was distributed with this source code.
55

66
use crate::picker::sysinfo;
7+
use crate::platform::*;
8+
use crate::{CpuGraphMode, CpuValueMode, Settings};
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}",
14+
pub(crate) fn header(settings: &Settings) -> String {
15+
let uptime_line = format!(
16+
"top - {time} {uptime}, {user}, {load_average}\n",
1717
time = get_formatted_time(),
1818
uptime = uptime(),
1919
user = user(),
2020
load_average = load_average(),
21-
task = task(),
22-
cpu = cpu(),
23-
memory = memory(scale_summary_mem),
24-
)
25-
}
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+
};
33+
34+
let memory_line = memory(settings.scale_summary_mem.as_ref());
2635

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;
36+
format!("{uptime_line}{task_and_cpu}{memory_line}")
3537
}
3638

3739
fn format_memory(memory_b: u64, unit: u64) -> f64 {
@@ -43,53 +45,6 @@ fn uptime() -> String {
4345
get_formatted_uptime_procps().unwrap_or_default()
4446
}
4547

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);
85-
}
86-
}
87-
Err(USimpleError::new(
88-
1,
89-
"could not retrieve number of logged users",
90-
))
91-
}
92-
9348
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
9449
fn user() -> String {
9550
#[cfg(target_os = "linux")]
@@ -133,91 +88,84 @@ fn task() -> String {
13388
)
13489
}
13590

136-
#[cfg(target_os = "linux")]
137-
fn cpu() -> String {
138-
let cpu_load = uu_vmstat::CpuLoad::current();
91+
fn sum_cpu_loads(cpu_loads: &[uu_vmstat::CpuLoadRaw]) -> uu_vmstat::CpuLoadRaw {
92+
let mut total = uu_vmstat::CpuLoadRaw {
93+
user: 0,
94+
nice: 0,
95+
system: 0,
96+
idle: 0,
97+
io_wait: 0,
98+
hardware_interrupt: 0,
99+
software_interrupt: 0,
100+
steal_time: 0,
101+
guest: 0,
102+
guest_nice: 0,
103+
};
139104

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-
)
105+
for load in cpu_loads {
106+
total.user += load.user;
107+
total.nice += load.nice;
108+
total.system += load.system;
109+
total.idle += load.idle;
110+
total.io_wait += load.io_wait;
111+
total.hardware_interrupt += load.hardware_interrupt;
112+
total.software_interrupt += load.software_interrupt;
113+
total.steal_time += load.steal_time;
114+
total.guest += load.guest;
115+
total.guest_nice += load.guest_nice;
116+
}
117+
118+
total
151119
}
152120

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
121+
fn cpu(settings: &Settings) -> String {
122+
if settings.cpu_graph_mode == CpuGraphMode::Hide {
123+
return String::new();
167124
}
168125

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-
}
126+
let cpu_loads = get_cpu_loads();
127+
128+
match settings.cpu_value_mode {
129+
CpuValueMode::PerCore => {
130+
let lines = cpu_loads
131+
.iter()
132+
.enumerate()
133+
.map(|(nth, cpu_load_raw)| {
134+
let cpu_load = CpuLoad::from_raw(cpu_load_raw);
135+
cpu_line(format!("Cpu{nth}").as_str(), &cpu_load, settings)
136+
})
137+
.collect::<Vec<String>>();
138+
lines.join("\n")
139+
}
140+
CpuValueMode::Sum => {
141+
let total = sum_cpu_loads(&cpu_loads);
142+
let cpu_load = CpuLoad::from_raw(&total);
143+
cpu_line("Cpu", &cpu_load, settings)
200144
}
201145
}
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-
)
215146
}
216147

217-
//TODO: Implement for macos
218-
#[cfg(target_os = "macos")]
219-
fn cpu() -> String {
220-
"TODO".into()
148+
fn cpu_line(tag: &str, cpu_load: &CpuLoad, settings: &Settings) -> String {
149+
if settings.cpu_graph_mode == CpuGraphMode::Hide {
150+
return String::new();
151+
}
152+
153+
if settings.cpu_graph_mode == CpuGraphMode::Sum {
154+
return format!(
155+
"%{tag:<6}: {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
156+
cpu_load.user,
157+
cpu_load.system,
158+
cpu_load.nice,
159+
cpu_load.idle,
160+
cpu_load.io_wait,
161+
cpu_load.hardware_interrupt,
162+
cpu_load.software_interrupt,
163+
cpu_load.steal_time,
164+
);
165+
}
166+
167+
// TODO: render colored bar chart or block chart
168+
format!("%{tag:<6}: {:>5.1}/{:<5.1}", cpu_load.user, cpu_load.system)
221169
}
222170

223171
fn memory(scale_summary_mem: Option<&String>) -> String {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
#![allow(unused)]
7+
8+
pub fn get_cpu_loads() -> Vec<uu_vmstat::CpuLoadRaw> {
9+
vec![]
10+
}

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use std::str::FromStr;
7+
8+
extern "C" {
9+
pub fn sd_booted() -> libc::c_int;
10+
pub fn sd_get_sessions(sessions: *mut *mut *mut libc::c_char) -> libc::c_int;
11+
pub fn sd_session_get_class(
12+
session: *const libc::c_char,
13+
class: *mut *mut libc::c_char,
14+
) -> libc::c_int;
15+
}
16+
17+
pub fn get_nusers_systemd() -> uucore::error::UResult<usize> {
18+
use std::ffi::CStr;
19+
use std::ptr;
20+
use uucore::error::USimpleError;
21+
use uucore::libc::*;
22+
23+
// SAFETY: sd_booted to check if system is booted with systemd.
24+
unsafe {
25+
// systemd
26+
if sd_booted() > 0 {
27+
let mut sessions_list: *mut *mut c_char = ptr::null_mut();
28+
let mut num_user = 0;
29+
let sessions = sd_get_sessions(&mut sessions_list);
30+
31+
if sessions > 0 {
32+
for i in 0..sessions {
33+
let mut class: *mut c_char = ptr::null_mut();
34+
35+
if sd_session_get_class(
36+
*sessions_list.add(i as usize) as *const c_char,
37+
&mut class,
38+
) < 0
39+
{
40+
continue;
41+
}
42+
if CStr::from_ptr(class).to_str().unwrap().starts_with("user") {
43+
num_user += 1;
44+
}
45+
free(class as *mut c_void);
46+
}
47+
}
48+
49+
for i in 0..sessions {
50+
free(*sessions_list.add(i as usize) as *mut c_void);
51+
}
52+
free(sessions_list as *mut c_void);
53+
54+
return Ok(num_user);
55+
}
56+
}
57+
Err(USimpleError::new(
58+
1,
59+
"could not retrieve number of logged users",
60+
))
61+
}
62+
63+
pub fn get_cpu_loads() -> Vec<uu_vmstat::CpuLoadRaw> {
64+
let mut cpu_loads = Vec::new();
65+
66+
let file = std::fs::File::open(std::path::Path::new("/proc/stat")).unwrap(); // do not use `parse_proc_file` here because only one line is used
67+
let content = std::io::read_to_string(file).unwrap();
68+
69+
for line in content.lines() {
70+
let tag = line.split_whitespace().next().unwrap();
71+
if tag != "cpu" && tag.starts_with("cpu") {
72+
let load = uu_vmstat::CpuLoadRaw::from_str(line.strip_prefix(tag).unwrap()).unwrap();
73+
cpu_loads.push(load);
74+
}
75+
}
76+
77+
cpu_loads
78+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
#[cfg(target_os = "linux")]
7+
pub mod linux;
8+
#[cfg(target_os = "windows")]
9+
pub mod windows;
10+
11+
pub mod fallback;
12+
13+
#[cfg(target_os = "linux")]
14+
pub use linux::{get_cpu_loads, get_nusers_systemd};
15+
#[cfg(target_os = "windows")]
16+
pub use windows::get_cpu_loads;
17+
18+
#[allow(unused)]
19+
pub use fallback::*;

0 commit comments

Comments
 (0)