Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/uu/top/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ version.workspace = true
[dependencies]
uucore = { workspace = true, features = ["utmpx", "uptime"] }
clap = { workspace = true }
crossterm = { workspace = true }
libc = { workspace = true }
nix = { workspace = true }
prettytable-rs = { workspace = true }
ratatui = { workspace = true, features = ["crossterm"] }
sysinfo = { workspace = true }
chrono = { workspace = true }
bytesize = { workspace = true }
Expand Down
341 changes: 130 additions & 211 deletions src/uu/top/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,109 @@
// file that was distributed with this source code.

use crate::picker::sysinfo;
use crate::platform::*;
use crate::tui::stat::{CpuValueMode, TuiStat};
use bytesize::ByteSize;
use uu_vmstat::CpuLoad;
use uu_w::get_formatted_uptime_procps;
use uucore::uptime::{get_formatted_loadavg, get_formatted_nusers, get_formatted_time};

pub(crate) fn header(scale_summary_mem: Option<&String>) -> String {
format!(
"top - {time} {uptime}, {user}, {load_average}\n\
{task}\n\
{cpu}\n\
{memory}",
time = get_formatted_time(),
uptime = uptime(),
user = user(),
load_average = load_average(),
task = task(),
cpu = cpu(),
memory = memory(scale_summary_mem),
)
pub(crate) struct Header {
pub uptime: Uptime,
pub task: Task,
pub cpu: Vec<(String, CpuLoad)>,
pub memory: Memory,
}

#[cfg(target_os = "linux")]
extern "C" {
pub fn sd_booted() -> libc::c_int;
pub fn sd_get_sessions(sessions: *mut *mut *mut libc::c_char) -> libc::c_int;
pub fn sd_session_get_class(
session: *const libc::c_char,
class: *mut *mut libc::c_char,
) -> libc::c_int;
impl Header {
pub fn new(stat: &TuiStat) -> Header {
Header {
uptime: Uptime::new(),
task: Task::new(),
cpu: cpu(stat),
memory: Memory::new(),
}
}

pub fn update_cpu(&mut self, stat: &TuiStat) {
self.cpu = cpu(stat);
}
}

fn format_memory(memory_b: u64, unit: u64) -> f64 {
ByteSize::b(memory_b).0 as f64 / unit as f64
pub(crate) struct Uptime {
pub time: String,
pub uptime: String,
pub user: String,
pub load_average: String,
}

impl Uptime {
pub fn new() -> Uptime {
Uptime {
time: get_formatted_time(),
uptime: get_formatted_uptime_procps().unwrap_or_default(),
user: user(),
load_average: get_formatted_loadavg().unwrap_or_default(),
}
}
}

#[inline]
fn uptime() -> String {
get_formatted_uptime_procps().unwrap_or_default()
pub(crate) struct Task {
pub total: usize,
pub running: usize,
pub sleeping: usize,
pub stopped: usize,
pub zombie: usize,
}
impl Task {
pub fn new() -> Task {
let binding = sysinfo().read().unwrap();

let process = binding.processes();
let mut running_process = 0;
let mut sleeping_process = 0;
let mut stopped_process = 0;
let mut zombie_process = 0;

for (_, process) in process.iter() {
match process.status() {
sysinfo::ProcessStatus::Run => running_process += 1,
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
sysinfo::ProcessStatus::Stop => stopped_process += 1,
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
_ => {}
};
}

#[cfg(target_os = "linux")]
pub fn get_nusers_systemd() -> uucore::error::UResult<usize> {
use std::ffi::CStr;
use std::ptr;
use uucore::error::USimpleError;
use uucore::libc::*;

// SAFETY: sd_booted to check if system is booted with systemd.
unsafe {
// systemd
if sd_booted() > 0 {
let mut sessions_list: *mut *mut c_char = ptr::null_mut();
let mut num_user = 0;
let sessions = sd_get_sessions(&mut sessions_list);

if sessions > 0 {
for i in 0..sessions {
let mut class: *mut c_char = ptr::null_mut();

if sd_session_get_class(
*sessions_list.add(i as usize) as *const c_char,
&mut class,
) < 0
{
continue;
}
if CStr::from_ptr(class).to_str().unwrap().starts_with("user") {
num_user += 1;
}
free(class as *mut c_void);
}
}

for i in 0..sessions {
free(*sessions_list.add(i as usize) as *mut c_void);
}
free(sessions_list as *mut c_void);

return Ok(num_user);
Task {
total: process.len(),
running: running_process,
sleeping: sleeping_process,
stopped: stopped_process,
zombie: zombie_process,
}
}
Err(USimpleError::new(
1,
"could not retrieve number of logged users",
))
}

pub(crate) struct Memory {
pub total: u64,
pub free: u64,
pub used: u64,
pub buff_cache: u64,
pub available: u64,
pub total_swap: u64,
pub free_swap: u64,
pub used_swap: u64,
}

impl Memory {
pub fn new() -> Memory {
get_memory()
}
}

pub(crate) fn format_memory(memory_b: u64, unit: u64) -> f64 {
ByteSize::b(memory_b).0 as f64 / unit as f64
}

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

fn load_average() -> String {
get_formatted_loadavg().unwrap_or_default()
}
fn sum_cpu_loads(cpu_loads: &[uu_vmstat::CpuLoadRaw]) -> uu_vmstat::CpuLoadRaw {
let mut total = uu_vmstat::CpuLoadRaw {
user: 0,
nice: 0,
system: 0,
idle: 0,
io_wait: 0,
hardware_interrupt: 0,
software_interrupt: 0,
steal_time: 0,
guest: 0,
guest_nice: 0,
};

fn task() -> String {
let binding = sysinfo().read().unwrap();

let process = binding.processes();
let mut running_process = 0;
let mut sleeping_process = 0;
let mut stopped_process = 0;
let mut zombie_process = 0;

for (_, process) in process.iter() {
match process.status() {
sysinfo::ProcessStatus::Run => running_process += 1,
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
sysinfo::ProcessStatus::Stop => stopped_process += 1,
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
_ => {}
};
for load in cpu_loads {
total.user += load.user;
total.nice += load.nice;
total.system += load.system;
total.idle += load.idle;
total.io_wait += load.io_wait;
total.hardware_interrupt += load.hardware_interrupt;
total.software_interrupt += load.software_interrupt;
total.steal_time += load.steal_time;
total.guest += load.guest;
total.guest_nice += load.guest_nice;
}

format!(
"Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie",
process.len(),
running_process,
sleeping_process,
stopped_process,
zombie_process,
)
total
}

#[cfg(target_os = "linux")]
fn cpu() -> String {
let cpu_load = uu_vmstat::CpuLoad::current();

format!(
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
cpu_load.user,
cpu_load.system,
cpu_load.nice,
cpu_load.idle,
cpu_load.io_wait,
cpu_load.hardware_interrupt,
cpu_load.software_interrupt,
cpu_load.steal_time,
)
}

#[cfg(target_os = "windows")]
fn cpu() -> String {
use libc::malloc;
use windows_sys::Wdk::System::SystemInformation::NtQuerySystemInformation;

#[repr(C)]
#[derive(Debug)]
struct SystemProcessorPerformanceInformation {
idle_time: i64, // LARGE_INTEGER
kernel_time: i64, // LARGE_INTEGER
user_time: i64, // LARGE_INTEGER
dpc_time: i64, // LARGE_INTEGER
interrupt_time: i64, // LARGE_INTEGER
interrupt_count: u32, // ULONG
}

let n_cpu = sysinfo().read().unwrap().cpus().len();
let mut cpu_load = SystemProcessorPerformanceInformation {
idle_time: 0,
kernel_time: 0,
user_time: 0,
dpc_time: 0,
interrupt_time: 0,
interrupt_count: 0,
};
// 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.
unsafe {
let len = n_cpu * size_of::<SystemProcessorPerformanceInformation>();
let data = malloc(len);
let status = NtQuerySystemInformation(
windows_sys::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation,
data,
(n_cpu * size_of::<SystemProcessorPerformanceInformation>()) as u32,
std::ptr::null_mut(),
);
if status == 0 {
for i in 0..n_cpu {
let cpu = data.add(i * size_of::<SystemProcessorPerformanceInformation>())
as *const SystemProcessorPerformanceInformation;
let cpu = cpu.as_ref().unwrap();
cpu_load.idle_time += cpu.idle_time;
cpu_load.kernel_time += cpu.kernel_time;
cpu_load.user_time += cpu.user_time;
cpu_load.dpc_time += cpu.dpc_time;
cpu_load.interrupt_time += cpu.interrupt_time;
cpu_load.interrupt_count += cpu.interrupt_count;
}
fn cpu(stat: &TuiStat) -> Vec<(String, CpuLoad)> {
let cpu_loads = get_cpu_loads();

match stat.cpu_value_mode {
CpuValueMode::PerCore => cpu_loads
.iter()
.enumerate()
.map(|(nth, cpu_load_raw)| {
let cpu_load = CpuLoad::from_raw(cpu_load_raw);
(format!("Cpu{nth}"), cpu_load)
})
.collect::<Vec<(String, CpuLoad)>>(),
CpuValueMode::Sum => {
let total = sum_cpu_loads(&cpu_loads);
let cpu_load = CpuLoad::from_raw(&total);
vec![(String::from("Cpu(s)"), cpu_load)]
}
}
let total = cpu_load.idle_time
+ cpu_load.kernel_time
+ cpu_load.user_time
+ cpu_load.dpc_time
+ cpu_load.interrupt_time;
format!(
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} id, {:.1} hi, {:.1} si",
cpu_load.user_time as f64 / total as f64 * 100.0,
cpu_load.kernel_time as f64 / total as f64 * 100.0,
cpu_load.idle_time as f64 / total as f64 * 100.0,
cpu_load.interrupt_time as f64 / total as f64 * 100.0,
cpu_load.dpc_time as f64 / total as f64 * 100.0,
)
}

//TODO: Implement for macos
#[cfg(target_os = "macos")]
fn cpu() -> String {
"TODO".into()
}

fn memory(scale_summary_mem: Option<&String>) -> String {
let binding = sysinfo().read().unwrap();
let (unit, unit_name) = match scale_summary_mem {
Some(scale) => match scale.as_str() {
"k" => (bytesize::KIB, "KiB"),
"m" => (bytesize::MIB, "MiB"),
"g" => (bytesize::GIB, "GiB"),
"t" => (bytesize::TIB, "TiB"),
"p" => (bytesize::PIB, "PiB"),
"e" => (1_152_921_504_606_846_976, "EiB"),
_ => (bytesize::MIB, "MiB"),
},
None => (bytesize::MIB, "MiB"),
};

format!(
"{unit_name} Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache\n\
{unit_name} Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem",
format_memory(binding.total_memory(), unit),
format_memory(binding.free_memory(), unit),
format_memory(binding.used_memory(), unit),
format_memory(binding.available_memory() - binding.free_memory(), unit),
format_memory(binding.total_swap(), unit),
format_memory(binding.free_swap(), unit),
format_memory(binding.used_swap(), unit),
format_memory(binding.available_memory(), unit),
unit_name = unit_name
)
}
Loading
Loading