mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-05-04 05:50:42 +00:00
feat: Added priority and nice as columns in process view (#1881)
* basic priority and nice functionality * fix fmt, clippy and tests * trying to fix other platform build errors * fmt fix * trying more fixes * clean project * refactored nice to be for all unix systems * few more places where I had to change cfg for Nice * Fix scheme names issues * fix for schema * fmt * fixed clippy errors * 'nice' fix * fmt * fixed cfg in tests also * trying to fix macos test errors * fix nice value extraction for mac * modularised nice and priority for mac and freebsd * fmt * more multi target to unix * removing unnecesary guards * added safety comments - also reverted string array reference changes utils fix * weird utils clippy error fix * removed unneeded commit * not needed after main merge(?)
This commit is contained in:
Generated
+12
@@ -227,6 +227,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"windows 0.62.0",
|
||||
"winprocinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2840,6 +2841,17 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winprocinfo"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d010942d0ed74baf25362e267b54d4f57b859d0cb9b2dc7040e26333a87e1d8"
|
||||
dependencies = [
|
||||
"ntapi",
|
||||
"regex",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
|
||||
@@ -131,6 +131,7 @@ windows = { version = "0.62.0", features = [
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
winprocinfo = "0.1.2"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dependencies]
|
||||
serde_json = { version = "1.0.145" }
|
||||
|
||||
@@ -147,6 +147,13 @@ pub struct ProcessHarvest {
|
||||
/// The process entry "type".
|
||||
#[cfg(target_os = "linux")]
|
||||
pub process_type: ProcessType,
|
||||
|
||||
/// The nice value (user-settable scheduling hint).
|
||||
#[cfg(unix)]
|
||||
pub nice: i32,
|
||||
|
||||
/// The kernel scheduling priority.
|
||||
pub priority: i32,
|
||||
// TODO: Additional fields
|
||||
// pub rss_kb: u64,
|
||||
// pub virt_kb: u64,
|
||||
|
||||
@@ -285,6 +285,9 @@ fn read_proc(
|
||||
#[cfg(feature = "gpu")]
|
||||
gpu_util: 0,
|
||||
process_type,
|
||||
#[cfg(unix)]
|
||||
nice: stat.nice,
|
||||
priority: stat.priority,
|
||||
},
|
||||
new_process_times,
|
||||
))
|
||||
|
||||
@@ -62,6 +62,13 @@ pub(crate) struct Stat {
|
||||
|
||||
/// Kernel thread
|
||||
pub is_kernel_thread: bool,
|
||||
|
||||
/// The kernel scheduling priority.
|
||||
pub priority: i32,
|
||||
|
||||
/// The nice value (user-settable scheduling hint).
|
||||
#[cfg(unix)]
|
||||
pub nice: i32,
|
||||
}
|
||||
|
||||
impl Stat {
|
||||
@@ -107,11 +114,20 @@ impl Stat {
|
||||
let utime: u64 = next_part(&mut rest)?.parse()?;
|
||||
let stime: u64 = next_part(&mut rest)?.parse()?;
|
||||
|
||||
// Skip 6 fields until starttime (cutime, cstime, priority, nice, num_threads,
|
||||
// itrealvalue).
|
||||
let mut rest = rest.skip(6);
|
||||
let start_time: u64 = next_part(&mut rest)?.parse()?;
|
||||
// cutime
|
||||
let _ = next_part(&mut rest)?;
|
||||
// cstime
|
||||
let _ = next_part(&mut rest)?;
|
||||
// priority
|
||||
let priority: i32 = next_part(&mut rest)?.parse()?;
|
||||
// nice
|
||||
let nice: i32 = next_part(&mut rest)?.parse()?;
|
||||
// num_threads
|
||||
let _ = next_part(&mut rest)?;
|
||||
// itrealvalue
|
||||
let _ = next_part(&mut rest)?;
|
||||
|
||||
let start_time: u64 = next_part(&mut rest)?.parse()?;
|
||||
let vsize: u64 = next_part(&mut rest)?.parse()?;
|
||||
let rss: u64 = next_part(&mut rest)?.parse()?;
|
||||
|
||||
@@ -125,6 +141,8 @@ impl Stat {
|
||||
vsize,
|
||||
start_time,
|
||||
is_kernel_thread,
|
||||
priority,
|
||||
nice,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Process data collection for macOS. Uses sysinfo and custom bindings.
|
||||
|
||||
mod sysctl_bindings;
|
||||
pub mod sysctl_bindings;
|
||||
|
||||
use std::{io, process::Command};
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Shared process data harvesting code from macOS and FreeBSD via sysinfo.
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::collection::processes::macos::sysctl_bindings;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -9,6 +13,58 @@ use sysinfo::{ProcessStatus, System};
|
||||
use super::{ProcessHarvest, process_status_str};
|
||||
use crate::collection::{Pid, error::CollectionResult, processes::UserTable};
|
||||
|
||||
fn get_nice(pid: Pid) -> i32 {
|
||||
// SAFETY: getpriority takes no user pointers; pid is passed as a value
|
||||
// and errors are reported via the return value.
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "freebsd")] {
|
||||
unsafe { libc::getpriority(libc::PRIO_PROCESS, pid) }
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
unsafe { libc::getpriority(libc::PRIO_PROCESS, pid as u32) }
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_priority(pid: Pid) -> i32 {
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
if let Ok(kinfo) = sysctl_bindings::kinfo_process(pid) {
|
||||
kinfo.kp_proc.p_priority as i32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
use libc::{c_int, c_void};
|
||||
use std::{mem, ptr};
|
||||
|
||||
let mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid as c_int];
|
||||
let mut kp: libc::kinfo_proc = unsafe { mem::zeroed() };
|
||||
let mut size = mem::size_of::<libc::kinfo_proc>();
|
||||
|
||||
// SAFETY: sysctl takes the following pointer arguments
|
||||
// - mib is valid for KERN_PROC_PID.
|
||||
// - kp is a properly sized output buffer.
|
||||
// - newp is null for a read-only sysctl.
|
||||
let ret = unsafe {
|
||||
libc::sysctl(
|
||||
mib.as_ptr(),
|
||||
mib.len() as u32,
|
||||
&mut kp as *mut _ as *mut c_void,
|
||||
&mut size,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 0 { kp.ki_pri.pri_level as i32 } else { 0 }
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait UnixProcessExt {
|
||||
fn sysinfo_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64,
|
||||
@@ -69,6 +125,9 @@ pub(crate) trait UnixProcessExt {
|
||||
};
|
||||
let uid = process_val.user_id().map(|u| **u);
|
||||
let pid = process_val.pid().as_u32() as Pid;
|
||||
let nice = get_nice(pid);
|
||||
let priority = get_priority(pid);
|
||||
|
||||
process_vector.push(ProcessHarvest {
|
||||
pid,
|
||||
parent_pid: Self::parent_pid(process_val),
|
||||
@@ -90,11 +149,6 @@ pub(crate) trait UnixProcessExt {
|
||||
uid,
|
||||
user: uid.and_then(|uid| user_table.uid_to_username(uid).ok()),
|
||||
time: if process_val.start_time() == 0 {
|
||||
// Workaround for sysinfo occasionally returning a start time equal to UNIX
|
||||
// epoch, giving a run time in the range of 50+ years. We just
|
||||
// return a time of zero in this case for simplicity.
|
||||
//
|
||||
// TODO: Maybe return an option instead?
|
||||
Duration::ZERO
|
||||
} else {
|
||||
Duration::from_secs(process_val.run_time())
|
||||
@@ -105,6 +159,9 @@ pub(crate) trait UnixProcessExt {
|
||||
gpu_mem_percent: 0.0,
|
||||
#[cfg(feature = "gpu")]
|
||||
gpu_util: 0,
|
||||
#[cfg(unix)]
|
||||
nice,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Process data collection for Windows. Uses sysinfo.
|
||||
|
||||
use std::time::Duration;
|
||||
//! Process data collection for Windows. Uses sysinfo and winprocinfo.
|
||||
|
||||
use super::{ProcessHarvest, process_status_str};
|
||||
use crate::collection::{DataCollector, error::CollectionResult};
|
||||
use std::time::Duration;
|
||||
use winprocinfo;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -90,6 +90,13 @@ pub fn sysinfo_process_data(
|
||||
}
|
||||
(gpu_mem, gpu_util, gpu_mem_percent)
|
||||
};
|
||||
|
||||
let base_priority = winprocinfo::get_proc_info_by_pid(process_val.pid().as_u32())
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|proc| proc.base_priority)
|
||||
.unwrap_or(0);
|
||||
|
||||
process_vector.push(ProcessHarvest {
|
||||
pid: process_val.pid().as_u32() as _,
|
||||
parent_pid: process_val.parent().map(|p| p.as_u32() as _),
|
||||
@@ -127,6 +134,7 @@ pub fn sysinfo_process_data(
|
||||
gpu_util,
|
||||
#[cfg(feature = "gpu")]
|
||||
gpu_mem_percent,
|
||||
priority: base_priority,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -146,17 +146,14 @@ mod test {
|
||||
/// This doesn't do anything if you use something like nextest, which runs
|
||||
/// a test-per-process, but that's fine.
|
||||
fn init_test_logger() {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Once;
|
||||
|
||||
static LOG_INIT: AtomicBool = AtomicBool::new(false);
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
if LOG_INIT.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INIT.store(true, Ordering::SeqCst);
|
||||
super::init_logger(log::LevelFilter::Trace, None)
|
||||
.expect("initializing the logger should succeed");
|
||||
INIT.call_once(|| {
|
||||
super::init_logger(log::LevelFilter::Trace, None)
|
||||
.expect("initializing the logger should succeed");
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "logging")]
|
||||
|
||||
@@ -165,6 +165,9 @@ fn make_column(column: ProcColumn) -> SortColumn<ProcColumn> {
|
||||
User => SortColumn::soft(User, Some(0.05)),
|
||||
State => SortColumn::hard(State, 9),
|
||||
Time => SortColumn::new(Time),
|
||||
Priority => SortColumn::new(Priority).default_descending(),
|
||||
#[cfg(unix)]
|
||||
Nice => SortColumn::new(Nice),
|
||||
#[cfg(feature = "gpu")]
|
||||
GpuMemValue => SortColumn::new(GpuMemValue).default_descending(),
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -198,6 +201,9 @@ pub enum ProcWidgetColumn {
|
||||
User,
|
||||
State,
|
||||
Time,
|
||||
Priority,
|
||||
#[cfg(unix)]
|
||||
Nice,
|
||||
#[cfg(feature = "gpu")]
|
||||
GpuMem,
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -340,6 +346,9 @@ impl ProcWidgetState {
|
||||
ProcWidgetColumn::User => User,
|
||||
ProcWidgetColumn::State => State,
|
||||
ProcWidgetColumn::Time => Time,
|
||||
ProcWidgetColumn::Priority => Priority,
|
||||
#[cfg(unix)]
|
||||
ProcWidgetColumn::Nice => Nice,
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcWidgetColumn::GpuMem => {
|
||||
if mem_as_values {
|
||||
@@ -368,6 +377,9 @@ impl ProcWidgetState {
|
||||
User,
|
||||
State,
|
||||
Time,
|
||||
Priority,
|
||||
#[cfg(unix)]
|
||||
Nice,
|
||||
];
|
||||
|
||||
default_columns.into_iter().map(make_column).collect()
|
||||
@@ -393,6 +405,9 @@ impl ProcWidgetState {
|
||||
State => ProcWidgetColumn::State,
|
||||
User => ProcWidgetColumn::User,
|
||||
Time => ProcWidgetColumn::Time,
|
||||
Priority => ProcWidgetColumn::Priority,
|
||||
#[cfg(unix)]
|
||||
Nice => ProcWidgetColumn::Nice,
|
||||
#[cfg(feature = "gpu")]
|
||||
GpuMemValue | GpuMemPercent => ProcWidgetColumn::GpuMem,
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -1205,6 +1220,9 @@ mod test {
|
||||
gpu_usage: 0,
|
||||
#[cfg(target_os = "linux")]
|
||||
process_type: crate::collection::processes::ProcessType::Regular,
|
||||
#[cfg(unix)]
|
||||
nice: 0,
|
||||
priority: -20,
|
||||
};
|
||||
|
||||
let b = ProcWidgetData {
|
||||
|
||||
@@ -30,6 +30,9 @@ pub enum ProcColumn {
|
||||
State,
|
||||
User,
|
||||
Time,
|
||||
#[cfg(unix)]
|
||||
Nice,
|
||||
Priority,
|
||||
#[cfg(feature = "gpu")]
|
||||
GpuMemValue,
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -63,6 +66,9 @@ impl ProcColumn {
|
||||
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => &["GMem", "GMem%"],
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuUtilPercent => &["GPU%"],
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => &["Nice"],
|
||||
ProcColumn::Priority => &["Priority"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +91,9 @@ impl ColumnHeader for ProcColumn {
|
||||
ProcColumn::State => "State",
|
||||
ProcColumn::User => "User",
|
||||
ProcColumn::Time => "Time",
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => "Nice",
|
||||
ProcColumn::Priority => "Priority",
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuMemValue => "GMem",
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -103,6 +112,9 @@ impl ColumnHeader for ProcColumn {
|
||||
ProcColumn::Pid => "PID(p)".into(),
|
||||
ProcColumn::Name => "Name(n)".into(),
|
||||
ProcColumn::Command => "Command(n)".into(),
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => "Nice".into(),
|
||||
ProcColumn::Priority => "Priority".into(),
|
||||
_ => self.text(),
|
||||
}
|
||||
}
|
||||
@@ -167,6 +179,13 @@ impl SortsRow for ProcColumn {
|
||||
ProcColumn::Time => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(a.time, b.time));
|
||||
}
|
||||
ProcColumn::Priority => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(a.priority, b.priority));
|
||||
}
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(a.nice, b.nice));
|
||||
}
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => {
|
||||
data.sort_by(|a, b| {
|
||||
@@ -230,6 +249,9 @@ impl From<&ProcColumn> for ProcWidgetColumn {
|
||||
ProcColumn::State => ProcWidgetColumn::State,
|
||||
ProcColumn::User => ProcWidgetColumn::User,
|
||||
ProcColumn::Time => ProcWidgetColumn::Time,
|
||||
ProcColumn::Priority => ProcWidgetColumn::Priority,
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => ProcWidgetColumn::Nice,
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuMemPercent | ProcColumn::GpuMemValue => ProcWidgetColumn::GpuMem,
|
||||
#[cfg(feature = "gpu")]
|
||||
|
||||
@@ -218,6 +218,9 @@ pub struct ProcWidgetData {
|
||||
/// The process "type". Used to color things.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub process_type: crate::collection::processes::ProcessType,
|
||||
#[cfg(unix)]
|
||||
pub nice: i32,
|
||||
pub priority: i32,
|
||||
}
|
||||
|
||||
impl ProcWidgetData {
|
||||
@@ -264,6 +267,9 @@ impl ProcWidgetData {
|
||||
gpu_usage: process.gpu_util,
|
||||
#[cfg(target_os = "linux")]
|
||||
process_type: process.process_type,
|
||||
#[cfg(unix)]
|
||||
nice: process.nice,
|
||||
priority: process.priority,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +314,9 @@ impl ProcWidgetData {
|
||||
|
||||
fn to_string(&self, column: &ProcColumn) -> String {
|
||||
match column {
|
||||
&ProcColumn::Priority => self.priority.to_string(),
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => self.nice.to_string(),
|
||||
ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent),
|
||||
ProcColumn::MemValue | ProcColumn::MemPercent => self.mem_usage.to_string(),
|
||||
ProcColumn::VirtualMem => binary_byte_string(self.virtual_mem),
|
||||
@@ -337,12 +346,13 @@ impl DataToCell<ProcColumn> for ProcWidgetData {
|
||||
fn to_cell_text(
|
||||
&self, column: &ProcColumn, calculated_width: NonZeroU16,
|
||||
) -> Option<Cow<'static, str>> {
|
||||
let calculated_width = calculated_width.get();
|
||||
|
||||
// TODO: Optimize the string allocations here...
|
||||
// TODO: Also maybe just pull in the to_string call but add a variable for the
|
||||
// differences.
|
||||
Some(match column {
|
||||
#[cfg(unix)]
|
||||
ProcColumn::Nice => self.nice.to_string().into(),
|
||||
&ProcColumn::Priority => self.priority.to_string().into(),
|
||||
ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent).into(),
|
||||
ProcColumn::MemValue | ProcColumn::MemPercent => self.mem_usage.to_string().into(),
|
||||
ProcColumn::VirtualMem => binary_byte_string(self.virtual_mem).into(),
|
||||
@@ -354,7 +364,7 @@ impl DataToCell<ProcColumn> for ProcWidgetData {
|
||||
ProcColumn::TotalRead => dec_bytes_string(self.total_read).into(),
|
||||
ProcColumn::TotalWrite => dec_bytes_string(self.total_write).into(),
|
||||
ProcColumn::State => {
|
||||
if calculated_width < 8 {
|
||||
if calculated_width.get() < 8 {
|
||||
self.process_char.to_string().into()
|
||||
} else {
|
||||
self.process_state.into()
|
||||
|
||||
@@ -670,6 +670,9 @@ enum PrefixType {
|
||||
State,
|
||||
User,
|
||||
Time,
|
||||
#[cfg(unix)]
|
||||
Nice,
|
||||
Priority,
|
||||
#[cfg(feature = "gpu")]
|
||||
PGpu,
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -711,6 +714,13 @@ impl std::str::FromStr for PrefixType {
|
||||
result = User;
|
||||
} else if multi_eq_ignore_ascii_case!(s, "time") {
|
||||
result = Time;
|
||||
} else if multi_eq_ignore_ascii_case!(s, "nice") {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
result = Nice;
|
||||
}
|
||||
} else if multi_eq_ignore_ascii_case!(s, "priority") {
|
||||
result = Priority;
|
||||
}
|
||||
#[cfg(feature = "gpu")]
|
||||
{
|
||||
@@ -880,6 +890,17 @@ impl Prefix {
|
||||
process.gpu_mem_percent,
|
||||
numerical_query.value,
|
||||
),
|
||||
#[cfg(unix)]
|
||||
PrefixType::Nice => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.nice,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::Priority => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.priority,
|
||||
numerical_query.value,
|
||||
),
|
||||
_ => true,
|
||||
},
|
||||
ComparableQuery::Time(time_query) => match prefix_type {
|
||||
|
||||
Reference in New Issue
Block a user