#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use crate::ported::crt::{ColorElements as CE, ColorScheme, A_BOLD};
use crate::ported::linux::compat::Compat_readfile;
use crate::ported::linux::linuxmachine::LinuxMachine;
use crate::ported::machine::Machine;
use crate::ported::object::{Arg, Object, ObjectClass, Object_isA};
use crate::ported::process::{
spaceship_nullstr, Process, ProcessClass, ProcessField, ProcessFieldData, Process_class,
Process_compare, Process_compareByKey_Base, Process_compareByParent, Process_getPid,
Process_init, Process_rowGetSortKey, Process_rowIsHighlighted, Process_rowIsVisible,
Process_writeField, Tristate, PROCESS_FLAG_CWD, PROCESS_FLAG_IO, PROCESS_FLAG_SCHEDPOL,
};
use crate::ported::richstring::{RichString, RichString_appendAscii, RichString_appendWide};
use crate::ported::row::{
spaceship_number, PercentageAttr, RowClass, Row_fieldWidths, Row_printBytes, Row_printCount,
Row_printKBytes, Row_printNanoseconds, Row_printPercentage, Row_printRate, Row_printTime,
};
use crate::ported::settings::RowField;
use crate::ported::xutils::compareRealNumbers;
use core::any::Any;
use core::ffi::c_void;
use std::ffi::CString;
use std::sync::atomic::Ordering;
const PROCDIR: &str = "/proc";
pub type IOPriority = i32;
const IOPRIO_CLASS_NONE: i32 = 0;
const IOPRIO_CLASS_RT: i32 = 1;
const IOPRIO_CLASS_BE: i32 = 2;
const IOPRIO_CLASS_IDLE: i32 = 3;
const IOPRIO_WHO_PROCESS: i32 = 1;
const IOPRIO_CLASS_SHIFT: i32 = 13;
const IOPRIO_PRIO_MASK: i32 = (1 << IOPRIO_CLASS_SHIFT) - 1;
pub const PROCESS_FLAG_LINUX_IOPRIO: u32 = 0x00000100;
pub const PROCESS_FLAG_LINUX_OPENVZ: u32 = 0x00000200;
pub const PROCESS_FLAG_LINUX_VSERVER: u32 = 0x00000400;
pub const PROCESS_FLAG_LINUX_CGROUP: u32 = 0x00000800;
pub const PROCESS_FLAG_LINUX_OOM: u32 = 0x00001000;
pub const PROCESS_FLAG_LINUX_SMAPS: u32 = 0x00002000;
pub const PROCESS_FLAG_LINUX_CTXT: u32 = 0x00004000;
pub const PROCESS_FLAG_LINUX_SECATTR: u32 = 0x00008000;
pub const PROCESS_FLAG_LINUX_LRS_FIX: u32 = 0x00010000;
pub const PROCESS_FLAG_LINUX_DELAYACCT: u32 = 0x00040000;
pub const PROCESS_FLAG_LINUX_AUTOGROUP: u32 = 0x00080000;
pub const PROCESS_FLAG_LINUX_GPU: u32 = 0x00100000;
pub const PROCESS_FLAG_LINUX_CONTAINER: u32 = 0x00200000;
pub const LAST_PROCESSFIELD: usize = 135;
const fn pfd(
name: &'static str,
title: &'static str,
description: &'static str,
flags: u32,
pidColumn: bool,
defaultSortDesc: bool,
autoWidth: bool,
autoTitleRightAlign: bool,
) -> ProcessFieldData {
ProcessFieldData {
name,
title: Some(title),
description: Some(description),
flags,
pidColumn,
defaultSortDesc,
autoWidth,
autoTitleRightAlign,
}
}
const EMPTY_FIELD: ProcessFieldData = ProcessFieldData {
name: "",
title: None,
description: None,
flags: 0,
pidColumn: false,
defaultSortDesc: false,
autoWidth: false,
autoTitleRightAlign: false,
};
pub static Process_fields: [ProcessFieldData; LAST_PROCESSFIELD] = build_process_fields();
const fn build_process_fields() -> [ProcessFieldData; LAST_PROCESSFIELD] {
use ProcessField as PF;
let mut t = [EMPTY_FIELD; LAST_PROCESSFIELD];
t[PF::PID as usize] = pfd(
"PID",
"PID",
"Process/thread ID",
0,
true,
false,
false,
false,
);
t[PF::COMM as usize] = pfd(
"Command",
"Command ",
"Command line (insert as last column only)",
0,
false,
false,
false,
false,
);
t[PF::STATE as usize] = pfd(
"STATE",
"S ",
"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)",
0,
false,
false,
false,
false,
);
t[PF::PPID as usize] = pfd(
"PPID",
"PPID",
"Parent process ID",
0,
true,
false,
false,
false,
);
t[PF::PGRP as usize] = pfd(
"PGRP",
"PGRP",
"Process group ID",
0,
true,
false,
false,
false,
);
t[PF::SESSION as usize] = pfd(
"SESSION",
"SID",
"Process's session ID",
0,
true,
false,
false,
false,
);
t[PF::TTY as usize] = pfd(
"TTY",
"TTY ",
"Controlling terminal",
0,
false,
false,
false,
false,
);
t[PF::TPGID as usize] = pfd(
"TPGID",
"TPGID",
"Process ID of the fg process group of the controlling terminal",
0,
true,
false,
false,
false,
);
t[PF::MINFLT as usize] = pfd(
"MINFLT",
" MINFLT ",
"Number of minor faults which have not required loading a memory page from disk",
0,
false,
true,
false,
false,
);
t[PF::CMINFLT as usize] = pfd(
"CMINFLT",
" CMINFLT ",
"Children processes' minor faults",
0,
false,
true,
false,
false,
);
t[PF::MAJFLT as usize] = pfd(
"MAJFLT",
" MAJFLT ",
"Number of major faults which have required loading a memory page from disk",
0,
false,
true,
false,
false,
);
t[PF::CMAJFLT as usize] = pfd(
"CMAJFLT",
" CMAJFLT ",
"Children processes' major faults",
0,
false,
true,
false,
false,
);
t[PF::UTIME as usize] = pfd(
"UTIME",
" UTIME+ ",
"User CPU time - time the process spent executing in user mode",
0,
false,
true,
false,
false,
);
t[PF::STIME as usize] = pfd(
"STIME",
" STIME+ ",
"System CPU time - time the kernel spent running system calls for this process",
0,
false,
true,
false,
false,
);
t[PF::CUTIME as usize] = pfd(
"CUTIME",
" CUTIME+ ",
"Children processes' user CPU time",
0,
false,
true,
false,
false,
);
t[PF::CSTIME as usize] = pfd(
"CSTIME",
" CSTIME+ ",
"Children processes' system CPU time",
0,
false,
true,
false,
false,
);
t[PF::PRIORITY as usize] = pfd(
"PRIORITY",
"PRI ",
"Kernel's internal priority for the process",
0,
false,
false,
false,
false,
);
t[PF::NICE as usize] = pfd(
"NICE",
" NI ",
"Nice value (the higher the value, the more it lets other processes take priority)",
0,
false,
false,
false,
false,
);
t[PF::STARTTIME as usize] = pfd(
"STARTTIME",
"START ",
"Time the process was started",
0,
false,
false,
false,
false,
);
t[PF::ELAPSED as usize] = pfd(
"ELAPSED",
"ELAPSED ",
"Time since the process was started",
0,
false,
false,
false,
false,
);
t[PF::PROCESSOR as usize] = pfd(
"PROCESSOR",
"CPU ",
"Id of the CPU the process last executed on",
0,
false,
false,
false,
false,
);
t[PF::M_VIRT as usize] = pfd(
"M_VIRT",
" VIRT ",
"Total program size in virtual memory",
0,
false,
true,
false,
false,
);
t[PF::M_RESIDENT as usize] = pfd(
"M_RESIDENT",
" RES ",
"Resident set size, size of the text and data sections, plus stack usage",
0,
false,
true,
false,
false,
);
t[PF::M_SHARE as usize] = pfd(
"M_SHARE",
" SHR ",
"Size of the process's shared pages",
0,
false,
true,
false,
false,
);
t[PF::M_PRIV as usize] = pfd(
"M_PRIV",
" PRIV ",
"The private memory size of the process - resident set size minus shared memory",
0,
false,
true,
false,
false,
);
t[PF::M_TRS as usize] = pfd(
"M_TRS",
" CODE ",
"Size of the .text segment of the process (CODE)",
0,
false,
true,
false,
false,
);
t[PF::M_DRS as usize] = pfd(
"M_DRS",
" DATA ",
"Size of the .data segment plus stack usage of the process (DATA)",
0,
false,
true,
false,
false,
);
t[PF::M_LRS as usize] = pfd(
"M_LRS",
" LIB ",
"The library size of the process (calculated from memory maps)",
PROCESS_FLAG_LINUX_LRS_FIX,
false,
true,
false,
false,
);
t[PF::ST_UID as usize] = pfd(
"ST_UID",
"UID",
"User ID of the process owner",
0,
false,
false,
false,
false,
);
t[PF::PERCENT_CPU as usize] = pfd(
"PERCENT_CPU",
" CPU%",
"Percentage of the CPU time the process used in the last sampling",
0,
false,
true,
true,
true,
);
t[PF::PERCENT_NORM_CPU as usize] = pfd("PERCENT_NORM_CPU", "NCPU%", "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", 0, false, true, true, false);
t[PF::PERCENT_MEM as usize] = pfd(
"PERCENT_MEM",
"MEM% ",
"Percentage of the memory the process is using, based on resident memory size",
0,
false,
true,
false,
false,
);
t[PF::USER as usize] = pfd(
"USER",
"USER ",
"Username of the process owner (or user ID if name cannot be determined)",
0,
false,
false,
false,
false,
);
t[PF::TIME as usize] = pfd(
"TIME",
" TIME+ ",
"Total time the process has spent in user and system time",
0,
false,
true,
false,
false,
);
t[PF::NLWP as usize] = pfd(
"NLWP",
"NLWP ",
"Number of threads in the process",
0,
false,
true,
false,
false,
);
t[PF::TGID as usize] = pfd(
"TGID",
"TGID",
"Thread group ID (i.e. process ID)",
0,
true,
false,
false,
false,
);
t[PF::RCHAR as usize] = pfd(
"RCHAR",
"RCHAR ",
"Number of bytes the process has read",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::WCHAR as usize] = pfd(
"WCHAR",
"WCHAR ",
"Number of bytes the process has written",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::SYSCR as usize] = pfd(
"SYSCR",
" READ_SYSC ",
"Number of read(2) syscalls for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::SYSCW as usize] = pfd(
"SYSCW",
" WRITE_SYSC ",
"Number of write(2) syscalls for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::RBYTES as usize] = pfd(
"RBYTES",
" IO_R ",
"Bytes of read(2) I/O for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::WBYTES as usize] = pfd(
"WBYTES",
" IO_W ",
"Bytes of write(2) I/O for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::CNCLWB as usize] = pfd(
"CNCLWB",
" IO_C ",
"Bytes of cancelled write(2) I/O",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::IO_READ_RATE as usize] = pfd(
"IO_READ_RATE",
" DISK READ ",
"The I/O rate of read(2) in bytes per second for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::IO_WRITE_RATE as usize] = pfd(
"IO_WRITE_RATE",
" DISK WRITE ",
"The I/O rate of write(2) in bytes per second for the process",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::IO_RATE as usize] = pfd(
"IO_RATE",
" DISK R/W ",
"Total I/O rate in bytes per second",
PROCESS_FLAG_IO,
false,
true,
false,
false,
);
t[PF::CGROUP as usize] = pfd(
"CGROUP",
"CGROUP (raw)",
"Which cgroup the process is in",
PROCESS_FLAG_LINUX_CGROUP,
false,
false,
true,
false,
);
t[PF::CCGROUP as usize] = pfd(
"CCGROUP",
"CGROUP (compressed)",
"Which cgroup the process is in (condensed to essentials)",
PROCESS_FLAG_LINUX_CGROUP,
false,
false,
true,
false,
);
t[PF::CONTAINER as usize] = pfd(
"CONTAINER",
"CONTAINER",
"Name of the container the process is in (guessed by heuristics)",
PROCESS_FLAG_LINUX_CGROUP,
false,
false,
true,
false,
);
t[PF::OOM as usize] = pfd(
"OOM",
" OOM ",
"OOM (Out-of-Memory) killer score",
PROCESS_FLAG_LINUX_OOM,
false,
true,
false,
false,
);
t[PF::IO_PRIORITY as usize] = pfd(
"IO_PRIORITY",
"IO ",
"I/O priority",
PROCESS_FLAG_LINUX_IOPRIO,
false,
false,
false,
false,
);
t[PF::PERCENT_CPU_DELAY as usize] = pfd(
"PERCENT_CPU_DELAY",
"CPUD% ",
"CPU delay %",
PROCESS_FLAG_LINUX_DELAYACCT,
false,
true,
false,
false,
);
t[PF::PERCENT_IO_DELAY as usize] = pfd(
"PERCENT_IO_DELAY",
" IOD% ",
"Block I/O delay %",
PROCESS_FLAG_LINUX_DELAYACCT,
false,
true,
false,
false,
);
t[PF::PERCENT_SWAP_DELAY as usize] = pfd(
"PERCENT_SWAP_DELAY",
"SWPD% ",
"Swapin delay %",
PROCESS_FLAG_LINUX_DELAYACCT,
false,
true,
false,
false,
);
t[PF::M_PSS as usize] = pfd("M_PSS", " PSS ", "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it", PROCESS_FLAG_LINUX_SMAPS, false, true, false, false);
t[PF::M_SWAP as usize] = pfd(
"M_SWAP",
" SWAP ",
"Size of the process's swapped pages",
PROCESS_FLAG_LINUX_SMAPS,
false,
true,
false,
false,
);
t[PF::M_PSSWP as usize] = pfd("M_PSSWP", " PSSWP ", "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", PROCESS_FLAG_LINUX_SMAPS, false, true, false, false);
t[PF::CTXT as usize] = pfd("CTXT", " CTXT ", "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", PROCESS_FLAG_LINUX_CTXT, false, true, false, false);
t[PF::SECATTR as usize] = pfd(
"SECATTR",
"Security Attribute",
"Security attribute of the process (e.g. SELinux or AppArmor)",
PROCESS_FLAG_LINUX_SECATTR,
false,
false,
true,
false,
);
t[PF::PROC_COMM as usize] = pfd(
"COMM",
"COMM ",
"comm string of the process from /proc/[pid]/comm",
0,
false,
false,
false,
false,
);
t[PF::PROC_EXE as usize] = pfd(
"EXE",
"EXE ",
"Basename of exe of the process from /proc/[pid]/exe",
0,
false,
false,
false,
false,
);
t[PF::CWD as usize] = pfd(
"CWD",
"CWD ",
"The current working directory of the process",
PROCESS_FLAG_CWD,
false,
false,
false,
false,
);
t[PF::AUTOGROUP_ID as usize] = pfd(
"AUTOGROUP_ID",
"AGRP",
"The autogroup identifier of the process",
PROCESS_FLAG_LINUX_AUTOGROUP,
false,
false,
false,
false,
);
t[PF::AUTOGROUP_NICE as usize] = pfd("AUTOGROUP_NICE", " ANI", "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", PROCESS_FLAG_LINUX_AUTOGROUP, false, false, false, false);
t[PF::ISCONTAINER as usize] = pfd(
"ISCONTAINER",
"CONT ",
"Whether the process is running inside a child container",
PROCESS_FLAG_LINUX_CONTAINER,
false,
false,
false,
false,
);
t[PF::SCHEDULERPOLICY as usize] = pfd(
"SCHEDULERPOLICY",
"SCHED ",
"Current scheduling policy of the process",
PROCESS_FLAG_SCHEDPOL,
false,
false,
false,
false,
);
t[PF::GPU_TIME as usize] = pfd(
"GPU_TIME",
"GPU_TIME ",
"Total GPU time",
PROCESS_FLAG_LINUX_GPU,
false,
true,
false,
false,
);
t[PF::GPU_PERCENT as usize] = pfd(
"GPU_PERCENT",
" GPU% ",
"Percentage of the GPU time the process used in the last sampling",
PROCESS_FLAG_LINUX_GPU,
false,
true,
false,
false,
);
t
}
#[derive(Debug, Clone, Default)]
pub struct LinuxProcess {
pub super_: Process,
pub ioPriority: IOPriority,
pub cminflt: u64,
pub cmajflt: u64,
pub utime: u64,
pub stime: u64,
pub cutime: u64,
pub cstime: u64,
pub m_share: i64,
pub m_priv: i64,
pub m_pss: i64,
pub m_swap: i64,
pub m_psswp: i64,
pub m_epss: i64,
pub m_trs: i64,
pub m_drs: i64,
pub m_lrs: i64,
pub flags: u64,
pub io_rchar: u64,
pub io_wchar: u64,
pub io_syscr: u64,
pub io_syscw: u64,
pub io_read_bytes: u64,
pub io_write_bytes: u64,
pub io_cancelled_write_bytes: u64,
pub io_last_scan_time_ms: u64,
pub io_rate_read_bps: f64,
pub io_rate_write_bps: f64,
pub cgroup: Option<String>,
pub cgroup_short: Option<String>,
pub container_short: Option<String>,
pub oom: u32,
pub delay_read_time: u64,
pub cpu_delay_total: u64,
pub blkio_delay_total: u64,
pub swapin_delay_total: u64,
pub cpu_delay_percent: f32,
pub blkio_delay_percent: f32,
pub swapin_delay_percent: f32,
pub ctxt_total: u64,
pub ctxt_diff: u64,
pub secattr: Option<String>,
pub last_mlrs_calctime: u64,
pub gpu_time: u64,
pub gpu_percent: f32,
pub gpu_activityMs: u64,
pub autogroup_id: i64,
pub autogroup_nice: i32,
}
pub static LinuxProcess_class: ProcessClass = ProcessClass {
super_: RowClass {
super_: ObjectClass {
extends: Some(&Process_class.super_.super_),
},
isHighlighted: Some(Process_rowIsHighlighted),
isVisible: Some(Process_rowIsVisible),
writeField: Some(LinuxProcess_rowWriteField),
matchesFilter: None,
sortKeyString: Some(Process_rowGetSortKey),
compareByParent: Some(Process_compareByParent),
},
compareByKey: Some(LinuxProcess_compareByKey),
};
impl Object for LinuxProcess {
fn klass(&self) -> &'static ObjectClass {
&LinuxProcess_class.super_.super_
}
fn row_class(&self) -> Option<&'static RowClass> {
Some(&LinuxProcess_class.super_)
}
fn as_row(&self) -> Option<&crate::ported::row::Row> {
Some(&self.super_.super_)
}
fn as_process(&self) -> Option<&Process> {
Some(&self.super_)
}
fn process_class(&self) -> Option<&'static ProcessClass> {
Some(&LinuxProcess_class)
}
fn display(&self, out: &mut RichString) {
crate::ported::row::Row_display(self, out)
}
fn compare(&self, other: &dyn Object) -> i32 {
Process_compare(self, other)
}
}
pub fn LinuxProcess_new(host: *const Machine) -> LinuxProcess {
let mut this = LinuxProcess::default();
Process_init(&mut this.super_, host as *const c_void);
this
}
pub fn Process_delete() {
todo!("port of LinuxProcess.c:119 — pure free() teardown; Rust Drop handles it")
}
fn LinuxProcess_effectiveIOPriority(this: &LinuxProcess) -> i32 {
if (this.ioPriority >> IOPRIO_CLASS_SHIFT) == IOPRIO_CLASS_NONE {
return (IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT) | ((this.super_.nice + 20) / 5);
}
this.ioPriority
}
pub fn LinuxProcess_updateIOPriority(this: &mut LinuxProcess) -> IOPriority {
#[cfg(target_os = "linux")]
let ioprio: IOPriority = unsafe {
libc::syscall(
libc::SYS_ioprio_get as libc::c_long,
IOPRIO_WHO_PROCESS,
Process_getPid(&this.super_),
)
} as i32;
#[cfg(not(target_os = "linux"))]
let ioprio: IOPriority = 0;
this.ioPriority = ioprio;
ioprio
}
fn LinuxProcess_setIOPriority(this: &mut LinuxProcess, ioprio: Arg) -> bool {
let i = match ioprio {
Arg::I(i) => i,
Arg::V(_) => panic!("LinuxProcess_setIOPriority: Arg must carry the priority in arg.i"),
};
#[cfg(target_os = "linux")]
unsafe {
libc::syscall(
libc::SYS_ioprio_set as libc::c_long,
IOPRIO_WHO_PROCESS,
Process_getPid(&this.super_),
i,
);
}
LinuxProcess_updateIOPriority(this) == i
}
pub fn LinuxProcess_rowSetIOPriority(super_: &mut dyn Object, ioprio: Arg) -> bool {
debug_assert!(Object_isA(Some(super_ as &dyn Object), &Process_class));
let p = (super_ as &mut dyn Any)
.downcast_mut::<LinuxProcess>()
.expect("LinuxProcess_rowSetIOPriority: row is not a LinuxProcess");
LinuxProcess_setIOPriority(p, ioprio)
}
pub fn LinuxProcess_isAutogroupEnabled() -> bool {
let mut buf = [0u8; 16];
let path = CString::new(format!("{}/sys/kernel/sched_autogroup_enabled", PROCDIR))
.expect("PROCDIR path contains no interior NUL");
if Compat_readfile(&path, &mut buf) < 0 {
return false;
}
buf[0] == b'1'
}
fn LinuxProcess_changeAutogroupPriorityBy(p: &Process, delta: Arg) -> bool {
use std::io::{Read, Seek, SeekFrom, Write};
let delta_i = match delta {
Arg::I(i) => i,
Arg::V(_) => {
panic!("LinuxProcess_changeAutogroupPriorityBy: Arg must carry the delta in arg.i")
}
};
let pid = Process_getPid(p);
let path = format!("{}/{}/autogroup", PROCDIR, pid);
let mut file = match std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
{
Ok(f) => f,
Err(_) => return false,
};
let mut content = String::new();
if file.read_to_string(&mut content).is_err() {
return false;
}
let parse_nice = || -> Option<i32> {
let mut it = content.split_whitespace();
let tok0 = it.next()?; let _identity: i64 = tok0.strip_prefix("/autogroup-")?.parse().ok()?;
if it.next()? != "nice" {
return None;
}
it.next()?.parse::<i32>().ok()
};
let mut success = false;
if let Some(nice) = parse_nice() {
if file.seek(SeekFrom::Start(0)).is_ok() {
let buffer = format!("{}", nice + delta_i);
success = file
.write(buffer.as_bytes())
.map(|w| w > 0)
.unwrap_or(false);
}
}
success
}
pub fn LinuxProcess_rowChangeAutogroupPriorityBy(super_: &dyn Object, delta: Arg) -> bool {
debug_assert!(Object_isA(Some(super_), &Process_class));
let lp = (super_ as &dyn Any)
.downcast_ref::<LinuxProcess>()
.expect("LinuxProcess_rowChangeAutogroupPriorityBy: row is not a LinuxProcess");
LinuxProcess_changeAutogroupPriorityBy(&lp.super_, delta)
}
fn LinuxProcess_totalIORate(lp: &LinuxProcess) -> f64 {
let mut totalRate = f64::NAN;
if lp.io_rate_read_bps >= 0.0 {
totalRate = lp.io_rate_read_bps;
if lp.io_rate_write_bps >= 0.0 {
totalRate += lp.io_rate_write_bps;
}
} else if lp.io_rate_write_bps >= 0.0 {
totalRate = lp.io_rate_write_bps;
}
totalRate
}
pub fn LinuxProcess_rowWriteField(super_: &dyn Object, str: &mut RichString, field: RowField) {
use ProcessField as PF;
let this = (super_ as &dyn Any)
.downcast_ref::<LinuxProcess>()
.expect("LinuxProcess_rowWriteField: row is not a LinuxProcess");
let host = unsafe { &*(this.super_.super_.host as *const Machine) };
let lhost = unsafe { &*(this.super_.super_.host as *const LinuxMachine) };
let coloring = host
.settings
.as_ref()
.expect("LinuxProcess_rowWriteField: host->settings is NULL")
.highlightMegabytes;
let scheme = ColorScheme::active();
let n = 255usize;
let mut attr = CE::DEFAULT_COLOR.packed(scheme);
let buffer: String;
macro_rules! pct {
($val:expr, $width:expr) => {{
let mut pa = PercentageAttr::Unchanged;
let s = Row_printPercentage($val, n, $width, &mut pa);
match pa {
PercentageAttr::Shadow => attr = CE::PROCESS_SHADOW.packed(scheme),
PercentageAttr::Megabytes => attr = CE::PROCESS_MEGABYTES.packed(scheme),
PercentageAttr::Unchanged => {}
}
s
}};
}
match field {
f if f == PF::CMINFLT as RowField => {
Row_printCount(str, this.cminflt, coloring);
return;
}
f if f == PF::CMAJFLT as RowField => {
Row_printCount(str, this.cmajflt, coloring);
return;
}
f if f == PF::GPU_PERCENT as RowField => {
buffer = pct!(this.gpu_percent, 5);
}
f if f == PF::GPU_TIME as RowField => {
Row_printNanoseconds(str, this.gpu_time, coloring);
return;
}
f if f == PF::M_DRS as RowField => {
Row_printBytes(
str,
(this.m_drs as u64).wrapping_mul(lhost.pageSize as u64),
coloring,
);
return;
}
f if f == PF::M_LRS as RowField => {
if this.m_lrs != 0 {
Row_printBytes(
str,
(this.m_lrs as u64).wrapping_mul(lhost.pageSize as u64),
coloring,
);
return;
}
attr = CE::PROCESS_SHADOW.packed(scheme);
buffer = " N/A ".to_string();
}
f if f == PF::M_TRS as RowField => {
Row_printBytes(
str,
(this.m_trs as u64).wrapping_mul(lhost.pageSize as u64),
coloring,
);
return;
}
f if f == PF::M_SHARE as RowField => {
Row_printBytes(
str,
(this.m_share as u64).wrapping_mul(lhost.pageSize as u64),
coloring,
);
return;
}
f if f == PF::M_PRIV as RowField => {
Row_printKBytes(str, this.m_priv as u64, coloring);
return;
}
f if f == PF::M_PSS as RowField => {
Row_printKBytes(str, this.m_pss as u64, coloring);
return;
}
f if f == PF::M_SWAP as RowField => {
Row_printKBytes(str, this.m_swap as u64, coloring);
return;
}
f if f == PF::M_PSSWP as RowField => {
Row_printKBytes(str, this.m_psswp as u64, coloring);
return;
}
f if f == PF::UTIME as RowField => {
Row_printTime(str, this.utime, coloring);
return;
}
f if f == PF::STIME as RowField => {
Row_printTime(str, this.stime, coloring);
return;
}
f if f == PF::CUTIME as RowField => {
Row_printTime(str, this.cutime, coloring);
return;
}
f if f == PF::CSTIME as RowField => {
Row_printTime(str, this.cstime, coloring);
return;
}
f if f == PF::RCHAR as RowField => {
Row_printBytes(str, this.io_rchar, coloring);
return;
}
f if f == PF::WCHAR as RowField => {
Row_printBytes(str, this.io_wchar, coloring);
return;
}
f if f == PF::SYSCR as RowField => {
Row_printCount(str, this.io_syscr, coloring);
return;
}
f if f == PF::SYSCW as RowField => {
Row_printCount(str, this.io_syscw, coloring);
return;
}
f if f == PF::RBYTES as RowField => {
Row_printBytes(str, this.io_read_bytes, coloring);
return;
}
f if f == PF::WBYTES as RowField => {
Row_printBytes(str, this.io_write_bytes, coloring);
return;
}
f if f == PF::CNCLWB as RowField => {
Row_printBytes(str, this.io_cancelled_write_bytes, coloring);
return;
}
f if f == PF::IO_READ_RATE as RowField => {
Row_printRate(str, this.io_rate_read_bps, coloring);
return;
}
f if f == PF::IO_WRITE_RATE as RowField => {
Row_printRate(str, this.io_rate_write_bps, coloring);
return;
}
f if f == PF::IO_RATE as RowField => {
Row_printRate(str, LinuxProcess_totalIORate(this), coloring);
return;
}
f if f == PF::CGROUP as RowField => {
let w = Row_fieldWidths[PF::CGROUP as usize].load(Ordering::Relaxed) as usize;
let s = this.cgroup.as_deref().unwrap_or("N/A");
let buf = format!("{s:<w$.w$} ");
RichString_appendWide(str, attr, buf.as_bytes());
return;
}
f if f == PF::CCGROUP as RowField => {
let w = Row_fieldWidths[PF::CCGROUP as usize].load(Ordering::Relaxed) as usize;
let s = this
.cgroup_short
.as_deref()
.or(this.cgroup.as_deref())
.unwrap_or("N/A");
let buf = format!("{s:<w$.w$} ");
RichString_appendWide(str, attr, buf.as_bytes());
return;
}
f if f == PF::CONTAINER as RowField => {
let w = Row_fieldWidths[PF::CONTAINER as usize].load(Ordering::Relaxed) as usize;
let s = this.container_short.as_deref().unwrap_or("N/A");
let buf = format!("{s:<w$.w$} ");
RichString_appendWide(str, attr, buf.as_bytes());
return;
}
f if f == PF::OOM as RowField => {
if this.oom == u32::MAX {
attr = CE::PROCESS_SHADOW.packed(scheme);
buffer = " N/A ".to_string();
} else {
buffer = format!("{:>4} ", this.oom);
}
}
f if f == PF::IO_PRIORITY as RowField => {
let klass = this.ioPriority >> IOPRIO_CLASS_SHIFT;
let data = this.ioPriority & IOPRIO_PRIO_MASK;
buffer = if klass == IOPRIO_CLASS_NONE {
format!("B{} ", (this.super_.nice + 20) / 5)
} else if klass == IOPRIO_CLASS_BE {
format!("B{data} ")
} else if klass == IOPRIO_CLASS_RT {
attr = CE::PROCESS_HIGH_PRIORITY.packed(scheme);
format!("R{data} ")
} else if klass == IOPRIO_CLASS_IDLE {
attr = CE::PROCESS_LOW_PRIORITY.packed(scheme);
"id ".to_string()
} else {
"?? ".to_string()
};
}
f if f == PF::PERCENT_CPU_DELAY as RowField => {
buffer = pct!(this.cpu_delay_percent, 5);
}
f if f == PF::PERCENT_IO_DELAY as RowField => {
buffer = pct!(this.blkio_delay_percent, 5);
}
f if f == PF::PERCENT_SWAP_DELAY as RowField => {
buffer = pct!(this.swapin_delay_percent, 5);
}
f if f == PF::CTXT as RowField => {
if this.ctxt_diff > 1000 {
attr |= A_BOLD;
}
buffer = format!("{:>5} ", this.ctxt_diff);
}
f if f == PF::SECATTR as RowField => {
let w = Row_fieldWidths[PF::SECATTR as usize].load(Ordering::Relaxed) as usize;
let s = this.secattr.as_deref().unwrap_or("N/A");
let buf = format!("{s:<w$.w$} ");
RichString_appendWide(str, attr, buf.as_bytes());
return;
}
f if f == PF::AUTOGROUP_ID as RowField => {
if this.autogroup_id != -1 {
buffer = format!("{:>4} ", this.autogroup_id);
} else {
attr = CE::PROCESS_SHADOW.packed(scheme);
buffer = " N/A ".to_string();
}
}
f if f == PF::AUTOGROUP_NICE as RowField => {
if this.autogroup_id != -1 {
buffer = format!("{:>3} ", this.autogroup_nice);
attr = if this.autogroup_nice < 0 {
CE::PROCESS_HIGH_PRIORITY.packed(scheme)
} else if this.autogroup_nice > 0 {
CE::PROCESS_LOW_PRIORITY.packed(scheme)
} else {
CE::PROCESS_SHADOW.packed(scheme)
};
} else {
attr = CE::PROCESS_SHADOW.packed(scheme);
buffer = "N/A ".to_string();
}
}
f if f == PF::ISCONTAINER as RowField => {
buffer = match this.super_.isRunningInContainer {
Tristate::TRI_ON => "YES ".to_string(),
Tristate::TRI_OFF => "NO ".to_string(),
_ => {
attr = CE::PROCESS_SHADOW.packed(scheme);
"N/A ".to_string()
}
};
}
_ => {
Process_writeField(&this.super_, str, field);
return;
}
}
RichString_appendAscii(str, attr, buffer.as_bytes());
}
pub fn LinuxProcess_compareByKey(v1: &dyn Object, v2: &dyn Object, key: RowField) -> i32 {
let p1 = (v1 as &dyn Any)
.downcast_ref::<LinuxProcess>()
.expect("LinuxProcess_compareByKey: v1 is not a LinuxProcess");
let p2 = (v2 as &dyn Any)
.downcast_ref::<LinuxProcess>()
.expect("LinuxProcess_compareByKey: v2 is not a LinuxProcess");
match key {
k if k == ProcessField::M_DRS as RowField => spaceship_number!(p1.m_drs, p2.m_drs),
k if k == ProcessField::M_LRS as RowField => spaceship_number!(p1.m_lrs, p2.m_lrs),
k if k == ProcessField::M_TRS as RowField => spaceship_number!(p1.m_trs, p2.m_trs),
k if k == ProcessField::M_SHARE as RowField => spaceship_number!(p1.m_share, p2.m_share),
k if k == ProcessField::M_PRIV as RowField => spaceship_number!(p1.m_priv, p2.m_priv),
k if k == ProcessField::M_PSS as RowField => spaceship_number!(p1.m_pss, p2.m_pss),
k if k == ProcessField::M_SWAP as RowField => spaceship_number!(p1.m_swap, p2.m_swap),
k if k == ProcessField::M_PSSWP as RowField => spaceship_number!(p1.m_psswp, p2.m_psswp),
k if k == ProcessField::UTIME as RowField => spaceship_number!(p1.utime, p2.utime),
k if k == ProcessField::CUTIME as RowField => spaceship_number!(p1.cutime, p2.cutime),
k if k == ProcessField::STIME as RowField => spaceship_number!(p1.stime, p2.stime),
k if k == ProcessField::CSTIME as RowField => spaceship_number!(p1.cstime, p2.cstime),
k if k == ProcessField::RCHAR as RowField => spaceship_number!(p1.io_rchar, p2.io_rchar),
k if k == ProcessField::WCHAR as RowField => spaceship_number!(p1.io_wchar, p2.io_wchar),
k if k == ProcessField::SYSCR as RowField => spaceship_number!(p1.io_syscr, p2.io_syscr),
k if k == ProcessField::SYSCW as RowField => spaceship_number!(p1.io_syscw, p2.io_syscw),
k if k == ProcessField::RBYTES as RowField => {
spaceship_number!(p1.io_read_bytes, p2.io_read_bytes)
}
k if k == ProcessField::WBYTES as RowField => {
spaceship_number!(p1.io_write_bytes, p2.io_write_bytes)
}
k if k == ProcessField::CNCLWB as RowField => {
spaceship_number!(p1.io_cancelled_write_bytes, p2.io_cancelled_write_bytes)
}
k if k == ProcessField::IO_READ_RATE as RowField => {
compareRealNumbers(p1.io_rate_read_bps, p2.io_rate_read_bps)
}
k if k == ProcessField::IO_WRITE_RATE as RowField => {
compareRealNumbers(p1.io_rate_write_bps, p2.io_rate_write_bps)
}
k if k == ProcessField::IO_RATE as RowField => {
compareRealNumbers(LinuxProcess_totalIORate(p1), LinuxProcess_totalIORate(p2))
}
k if k == ProcessField::CGROUP as RowField => spaceship_nullstr!(
p1.cgroup.as_deref().map(str::as_bytes),
p2.cgroup.as_deref().map(str::as_bytes)
),
k if k == ProcessField::CCGROUP as RowField => spaceship_nullstr!(
p1.cgroup_short.as_deref().map(str::as_bytes),
p2.cgroup_short.as_deref().map(str::as_bytes)
),
k if k == ProcessField::CONTAINER as RowField => spaceship_nullstr!(
p1.container_short.as_deref().map(str::as_bytes),
p2.container_short.as_deref().map(str::as_bytes)
),
k if k == ProcessField::OOM as RowField => spaceship_number!(p1.oom, p2.oom),
k if k == ProcessField::PERCENT_CPU_DELAY as RowField => {
compareRealNumbers(p1.cpu_delay_percent as f64, p2.cpu_delay_percent as f64)
}
k if k == ProcessField::PERCENT_IO_DELAY as RowField => {
compareRealNumbers(p1.blkio_delay_percent as f64, p2.blkio_delay_percent as f64)
}
k if k == ProcessField::PERCENT_SWAP_DELAY as RowField => compareRealNumbers(
p1.swapin_delay_percent as f64,
p2.swapin_delay_percent as f64,
),
k if k == ProcessField::IO_PRIORITY as RowField => spaceship_number!(
LinuxProcess_effectiveIOPriority(p1),
LinuxProcess_effectiveIOPriority(p2)
),
k if k == ProcessField::CTXT as RowField => spaceship_number!(p1.ctxt_diff, p2.ctxt_diff),
k if k == ProcessField::SECATTR as RowField => spaceship_nullstr!(
p1.secattr.as_deref().map(str::as_bytes),
p2.secattr.as_deref().map(str::as_bytes)
),
k if k == ProcessField::AUTOGROUP_ID as RowField => {
spaceship_number!(p1.autogroup_id, p2.autogroup_id)
}
k if k == ProcessField::AUTOGROUP_NICE as RowField => {
spaceship_number!(p1.autogroup_nice, p2.autogroup_nice)
}
k if k == ProcessField::GPU_PERCENT as RowField => {
let r = compareRealNumbers(p1.gpu_percent as f64, p2.gpu_percent as f64);
if r != 0 {
r
} else {
spaceship_number!(p1.gpu_time, p2.gpu_time)
}
}
k if k == ProcessField::GPU_TIME as RowField => spaceship_number!(p1.gpu_time, p2.gpu_time),
k if k == ProcessField::ISCONTAINER as RowField => spaceship_number!(
p1.super_.isRunningInContainer as i32,
p2.super_.isRunningInContainer as i32
),
_ => Process_compareByKey_Base(&p1.super_, &p2.super_, key as RowField),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linuxprocess_new_initializes_base() {
let lp = LinuxProcess_new(core::ptr::null());
assert_eq!(lp.super_.st_uid, u32::MAX);
assert_eq!(lp.super_.cmdlineBasenameEnd, 0);
assert_eq!(lp.ioPriority, 0);
assert_eq!(lp.oom, 0);
assert!(lp.cgroup.is_none());
assert!(Object_isA(Some(&lp as &dyn Object), &LinuxProcess_class));
assert!(Object_isA(Some(&lp as &dyn Object), &Process_class));
}
#[test]
fn effective_io_priority_derives_from_nice_when_none() {
let mut lp = LinuxProcess_new(core::ptr::null());
lp.ioPriority = 0; lp.super_.nice = 0;
let expected = (IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT) | ((0 + 20) / 5);
assert_eq!(LinuxProcess_effectiveIOPriority(&lp), expected);
lp.super_.nice = -20;
let expected = (IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT) | ((-20 + 20) / 5);
assert_eq!(LinuxProcess_effectiveIOPriority(&lp), expected);
}
#[test]
fn effective_io_priority_passthrough_when_classed() {
let mut lp = LinuxProcess_new(core::ptr::null());
lp.ioPriority = (IOPRIO_CLASS_RT << IOPRIO_CLASS_SHIFT) | 4;
lp.super_.nice = 5; assert_eq!(LinuxProcess_effectiveIOPriority(&lp), lp.ioPriority);
}
#[test]
fn total_io_rate_combines_available_rates() {
let mut lp = LinuxProcess_new(core::ptr::null());
lp.io_rate_read_bps = 100.0;
lp.io_rate_write_bps = 50.0;
assert_eq!(LinuxProcess_totalIORate(&lp), 150.0);
lp.io_rate_read_bps = 100.0;
lp.io_rate_write_bps = f64::NAN;
assert_eq!(LinuxProcess_totalIORate(&lp), 100.0);
lp.io_rate_read_bps = f64::NAN;
lp.io_rate_write_bps = 42.0;
assert_eq!(LinuxProcess_totalIORate(&lp), 42.0);
lp.io_rate_read_bps = f64::NAN;
lp.io_rate_write_bps = f64::NAN;
assert!(LinuxProcess_totalIORate(&lp).is_nan());
}
#[test]
fn process_fields_table_matches_c() {
assert_eq!(Process_fields.len(), LAST_PROCESSFIELD);
assert_eq!(LAST_PROCESSFIELD, 135);
let empty = &Process_fields[0];
assert_eq!(empty.name, "");
assert!(empty.title.is_none());
let pid = &Process_fields[ProcessField::PID as usize];
assert_eq!(pid.name, "PID");
assert_eq!(pid.title, Some("PID"));
assert!(pid.pidColumn);
assert!(!pid.defaultSortDesc);
assert_eq!(
Process_fields[ProcessField::COMM as usize].title,
Some("Command ")
);
let cpu = &Process_fields[ProcessField::PERCENT_CPU as usize];
assert!(cpu.defaultSortDesc && cpu.autoWidth && cpu.autoTitleRightAlign);
assert_eq!(
Process_fields[ProcessField::M_LRS as usize].flags,
PROCESS_FLAG_LINUX_LRS_FIX
);
assert_eq!(
Process_fields[ProcessField::RCHAR as usize].flags,
PROCESS_FLAG_IO
);
assert_eq!(
Process_fields[ProcessField::SCHEDULERPOLICY as usize].flags,
PROCESS_FLAG_SCHEDPOL
);
assert_eq!(Process_fields[ProcessField::CTID as usize].name, "");
assert_eq!(Process_fields[ProcessField::VXID as usize].name, "");
}
#[test]
fn compare_by_key_orders_platform_and_delegates_base() {
let mut a = LinuxProcess_new(core::ptr::null());
let mut b = LinuxProcess_new(core::ptr::null());
a.utime = 10;
b.utime = 20;
assert!(
LinuxProcess_compareByKey(
&a as &dyn Object,
&b as &dyn Object,
ProcessField::UTIME as RowField
) < 0
);
assert!(
LinuxProcess_compareByKey(
&b as &dyn Object,
&a as &dyn Object,
ProcessField::UTIME as RowField
) > 0
);
a.gpu_percent = 5.0;
b.gpu_percent = 5.0;
a.gpu_time = 100;
b.gpu_time = 200;
assert!(
LinuxProcess_compareByKey(
&a as &dyn Object,
&b as &dyn Object,
ProcessField::GPU_PERCENT as RowField
) < 0
);
a.super_.super_.id = 7;
b.super_.super_.id = 3;
assert!(
LinuxProcess_compareByKey(
&a as &dyn Object,
&b as &dyn Object,
ProcessField::PID as RowField
) > 0
);
}
#[test]
fn row_write_field_renders_linux_and_delegates() {
use crate::ported::process::ProcessState;
use crate::ported::richstring::{RichString, RichString_size};
use crate::ported::settings::RowField;
use crate::ported::settings::Settings;
let mut machine = Machine::default();
machine.settings = Some(Settings::default());
let mut lp = LinuxProcess_new(core::ptr::null());
lp.super_.super_.host = &machine as *const Machine as *const c_void;
let render = |lp: &LinuxProcess, field: RowField| -> i32 {
let mut rs = RichString::default();
LinuxProcess_rowWriteField(lp as &dyn Object, &mut rs, field);
RichString_size(&rs)
};
lp.oom = u32::MAX;
assert_eq!(render(&lp, ProcessField::OOM as RowField), 5);
lp.oom = 3;
assert_eq!(render(&lp, ProcessField::OOM as RowField), 5);
assert_eq!(render(&lp, ProcessField::ISCONTAINER as RowField), 5);
lp.super_.state = ProcessState::RUNNING;
assert_eq!(render(&lp, ProcessField::STATE as RowField), 2);
}
#[test]
fn display_dispatches_writefield_through_rowclass_vtable() {
use crate::ported::process::ProcessState;
use crate::ported::richstring::{RichString, RichString_size};
use crate::ported::settings::{RowField, ScreenSettings, Settings};
let mut machine = Machine::default();
let mut settings = Settings::default();
settings.screens = vec![ScreenSettings {
fields: vec![
ProcessField::PID as RowField,
ProcessField::STATE as RowField,
],
..Default::default()
}];
machine.settings = Some(settings);
let mut lp = LinuxProcess_new(core::ptr::null());
lp.super_.super_.host = &machine as *const Machine as *const c_void;
lp.super_.state = ProcessState::RUNNING;
let mut out = RichString::default();
(&lp as &dyn Object).display(&mut out);
assert!(RichString_size(&out) >= 8);
}
#[test]
fn process_compare_dispatches_platform_key_and_direction() {
use crate::ported::process::Process_compare;
use crate::ported::settings::{ScreenSettings, Settings};
let mut machine = Machine::default();
let mut settings = Settings::default();
settings.screens = vec![ScreenSettings {
fields: vec![ProcessField::UTIME as RowField],
sortKey: ProcessField::UTIME as RowField,
direction: 1,
..Default::default()
}];
machine.settings = Some(settings);
let mut a = LinuxProcess_new(core::ptr::null());
let mut b = LinuxProcess_new(core::ptr::null());
a.super_.super_.host = &machine as *const Machine as *const c_void;
b.super_.super_.host = &machine as *const Machine as *const c_void;
a.utime = 10;
b.utime = 20;
a.super_.super_.id = 1;
b.super_.super_.id = 2;
assert!(Process_compare(&a as &dyn Object, &b as &dyn Object) < 0);
assert!(Process_compare(&b as &dyn Object, &a as &dyn Object) > 0);
machine.settings.as_mut().unwrap().screens[0].direction = -1;
assert!(Process_compare(&a as &dyn Object, &b as &dyn Object) > 0);
}
}