mirror of
https://github.com/ClementTsang/bottom.git
synced 2024-09-11 11:46:59 +03:00
feature: per-core process cpu usage percentage (#899)
This commit is contained in:
parent
9e4aed7d56
commit
71bc6c940e
@ -17,6 +17,8 @@
|
||||
#left_legend = false
|
||||
# Whether to set CPU% on a process to be based on the total CPU or just current usage.
|
||||
#current_usage = false
|
||||
# Whether to set CPU% on a process to be based on the total CPU or per-core CPU% (not divided by the number of cpus).
|
||||
#per_core_percentage = false
|
||||
# Whether to group processes with the same name together by default.
|
||||
#group_processes = false
|
||||
# Whether to make process searching case sensitive by default.
|
||||
|
@ -57,6 +57,7 @@ pub struct AppConfigFields {
|
||||
pub left_legend: bool,
|
||||
pub show_average_cpu: bool,
|
||||
pub use_current_cpu_total: bool,
|
||||
pub per_core_percentage: bool,
|
||||
pub use_basic_mode: bool,
|
||||
pub default_time_value: u64,
|
||||
pub time_interval: u64,
|
||||
|
@ -112,6 +112,7 @@ pub struct DataCollector {
|
||||
mem_total_kb: u64,
|
||||
temperature_type: temperature::TemperatureType,
|
||||
use_current_cpu_total: bool,
|
||||
per_core_percentage: bool,
|
||||
last_collection_time: Instant,
|
||||
total_rx: u64,
|
||||
total_tx: u64,
|
||||
@ -144,6 +145,7 @@ impl DataCollector {
|
||||
mem_total_kb: 0,
|
||||
temperature_type: temperature::TemperatureType::Celsius,
|
||||
use_current_cpu_total: false,
|
||||
per_core_percentage: false,
|
||||
last_collection_time: Instant::now(),
|
||||
total_rx: 0,
|
||||
total_tx: 0,
|
||||
@ -235,6 +237,10 @@ impl DataCollector {
|
||||
self.use_current_cpu_total = use_current_cpu_total;
|
||||
}
|
||||
|
||||
pub fn set_per_core_percentage(&mut self, per_core_percentage: bool) {
|
||||
self.per_core_percentage = per_core_percentage;
|
||||
}
|
||||
|
||||
pub fn set_show_average_cpu(&mut self, show_average_cpu: bool) {
|
||||
self.show_average_cpu = show_average_cpu;
|
||||
}
|
||||
@ -321,28 +327,39 @@ impl DataCollector {
|
||||
}
|
||||
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
if let Ok(mut process_list) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
processes::get_process_data(
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(logical_count) = heim::cpu::logical_count().await {
|
||||
if let Ok(mut process_list) = processes::get_process_data(
|
||||
&mut self.prev_idle,
|
||||
&mut self.prev_non_idle,
|
||||
&mut self.pid_mapping,
|
||||
self.use_current_cpu_total,
|
||||
self.per_core_percentage,
|
||||
current_instant
|
||||
.duration_since(self.last_collection_time)
|
||||
.as_secs(),
|
||||
self.mem_total_kb,
|
||||
logical_count,
|
||||
&mut self.user_table,
|
||||
)
|
||||
) {
|
||||
// NB: To avoid duplicate sorts on rerenders/events, we sort the processes by PID here.
|
||||
// We also want to avoid re-sorting *again* since this process list is sorted!
|
||||
process_list.sort_unstable_by_key(|p| p.pid);
|
||||
self.data.list_of_processes = Some(process_list);
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
if let Ok(mut process_list) = {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
processes::get_process_data(
|
||||
&self.sys,
|
||||
self.use_current_cpu_total,
|
||||
self.per_core_percentage,
|
||||
self.mem_total_kb,
|
||||
&mut self.user_table,
|
||||
)
|
||||
@ -352,15 +369,14 @@ impl DataCollector {
|
||||
processes::get_process_data(
|
||||
&self.sys,
|
||||
self.use_current_cpu_total,
|
||||
self.per_core_percentage,
|
||||
self.mem_total_kb,
|
||||
)
|
||||
}
|
||||
} {
|
||||
process_list.sort_unstable_by_key(|p| p.pid);
|
||||
self.data.list_of_processes = Some(process_list);
|
||||
}
|
||||
} {
|
||||
// NB: To avoid duplicate sorts on rerenders/events, we sort the processes by PID here.
|
||||
// We also want to avoid re-sorting *again* since this process list is sorted!
|
||||
process_list.sort_unstable_by_key(|p| p.pid);
|
||||
self.data.list_of_processes = Some(process_list);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,11 +25,13 @@ struct ProcessRow {
|
||||
}
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
sys: &System, use_current_cpu_total: bool, per_core_percentage: bool, mem_total_kb: u64,
|
||||
user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
super::macos_freebsd::get_process_data(
|
||||
sys,
|
||||
use_current_cpu_total,
|
||||
per_core_percentage,
|
||||
mem_total_kb,
|
||||
user_table,
|
||||
get_freebsd_process_cpu_usage,
|
||||
|
@ -84,9 +84,10 @@ fn cpu_usage_calculation(prev_idle: &mut f64, prev_non_idle: &mut f64) -> error:
|
||||
}
|
||||
|
||||
/// Returns the usage and a new set of process times. Note: cpu_fraction should be represented WITHOUT the x100 factor!
|
||||
#[inline]
|
||||
fn get_linux_cpu_usage(
|
||||
stat: &Stat, cpu_usage: f64, cpu_fraction: f64, prev_proc_times: u64,
|
||||
use_current_cpu_total: bool,
|
||||
stat: &Stat, cpu_usage: f64, cpu_fraction: f64, prev_proc_times: u64, logical_count: u64,
|
||||
use_current_cpu_total: bool, per_core_percentage: bool,
|
||||
) -> (f64, u64) {
|
||||
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
|
||||
let new_proc_times = stat.utime + stat.stime;
|
||||
@ -94,6 +95,11 @@ fn get_linux_cpu_usage(
|
||||
|
||||
if cpu_usage == 0.0 {
|
||||
(0.0, new_proc_times)
|
||||
} else if per_core_percentage {
|
||||
(
|
||||
diff / cpu_usage * 100_f64 * cpu_fraction * (logical_count as f64),
|
||||
new_proc_times,
|
||||
)
|
||||
} else if use_current_cpu_total {
|
||||
((diff / cpu_usage) * 100.0, new_proc_times)
|
||||
} else {
|
||||
@ -101,10 +107,11 @@ fn get_linux_cpu_usage(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn read_proc(
|
||||
prev_proc: &PrevProcDetails, process: &Process, cpu_usage: f64, cpu_fraction: f64,
|
||||
use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64,
|
||||
user_table: &mut UserTable,
|
||||
use_current_cpu_total: bool, per_core_percentage: bool, time_difference_in_secs: u64,
|
||||
mem_total_kb: u64, logical_count: u64, user_table: &mut UserTable,
|
||||
) -> error::Result<(ProcessHarvest, u64)> {
|
||||
let stat = process.stat()?;
|
||||
let (command, name) = {
|
||||
@ -147,7 +154,9 @@ fn read_proc(
|
||||
cpu_usage,
|
||||
cpu_fraction,
|
||||
prev_proc.cpu_time,
|
||||
logical_count,
|
||||
use_current_cpu_total,
|
||||
per_core_percentage,
|
||||
);
|
||||
let parent_pid = Some(stat.ppid);
|
||||
let mem_usage_bytes = stat.rss_bytes()?;
|
||||
@ -209,10 +218,12 @@ fn read_proc(
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn get_process_data(
|
||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
||||
pid_mapping: &mut FxHashMap<Pid, PrevProcDetails>, use_current_cpu_total: bool,
|
||||
time_difference_in_secs: u64, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
per_core_percentage: bool, time_difference_in_secs: u64, mem_total_kb: u64, logical_count: u64,
|
||||
user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
// TODO: [PROC THREADS] Add threads
|
||||
|
||||
@ -234,8 +245,10 @@ pub fn get_process_data(
|
||||
cpu_usage,
|
||||
cpu_fraction,
|
||||
use_current_cpu_total,
|
||||
per_core_percentage,
|
||||
time_difference_in_secs,
|
||||
mem_total_kb,
|
||||
logical_count,
|
||||
user_table,
|
||||
) {
|
||||
prev_proc_details.cpu_time = new_process_times;
|
||||
|
@ -7,11 +7,13 @@ use crate::{data_harvester::processes::UserTable, Pid};
|
||||
mod sysctl_bindings;
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
sys: &System, use_current_cpu_total: bool, per_core_percentage: bool, mem_total_kb: u64,
|
||||
user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
super::macos_freebsd::get_process_data(
|
||||
sys,
|
||||
use_current_cpu_total,
|
||||
per_core_percentage,
|
||||
mem_total_kb,
|
||||
user_table,
|
||||
get_macos_process_cpu_usage,
|
||||
|
@ -9,8 +9,8 @@ use super::ProcessHarvest;
|
||||
use crate::{data_harvester::processes::UserTable, utils::error::Result, Pid};
|
||||
|
||||
pub fn get_process_data<F>(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
get_process_cpu_usage: F,
|
||||
sys: &System, use_current_cpu_total: bool, per_core_percentage: bool, mem_total_kb: u64,
|
||||
user_table: &mut UserTable, get_process_cpu_usage: F,
|
||||
) -> Result<Vec<ProcessHarvest>>
|
||||
where
|
||||
F: Fn(&[Pid]) -> io::Result<HashMap<Pid, f64>>,
|
||||
@ -18,7 +18,6 @@ where
|
||||
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
||||
let process_hashmap = sys.processes();
|
||||
let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0;
|
||||
let num_processors = sys.cpus().len() as f64;
|
||||
for process_val in process_hashmap.values() {
|
||||
let name = if process_val.name().is_empty() {
|
||||
let process_cmd = process_val.cmd();
|
||||
@ -51,11 +50,10 @@ where
|
||||
|
||||
let pcu = {
|
||||
let usage = process_val.cpu_usage() as f64;
|
||||
let res = usage / num_processors;
|
||||
if res.is_finite() {
|
||||
res
|
||||
} else {
|
||||
if per_core_percentage || sys.cpus().is_empty() {
|
||||
usage
|
||||
} else {
|
||||
usage / (sys.cpus().len() as f64)
|
||||
}
|
||||
};
|
||||
let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 {
|
||||
@ -121,10 +119,10 @@ where
|
||||
let cpu_usages = get_process_cpu_usage(&cpu_usage_unknown_pids)?;
|
||||
for process in &mut process_vector {
|
||||
if cpu_usages.contains_key(&process.pid) {
|
||||
process.cpu_usage_percent = if num_processors == 0.0 {
|
||||
process.cpu_usage_percent = if per_core_percentage || sys.cpus().is_empty() {
|
||||
*cpu_usages.get(&process.pid).unwrap()
|
||||
} else {
|
||||
*cpu_usages.get(&process.pid).unwrap() / num_processors
|
||||
*cpu_usages.get(&process.pid).unwrap() / (sys.cpus().len() as f64)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,11 @@ use sysinfo::{CpuExt, PidExt, ProcessExt, System, SystemExt};
|
||||
use super::ProcessHarvest;
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64,
|
||||
sys: &System, use_current_cpu_total: bool, per_core_percentage: bool, mem_total_kb: u64,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
||||
let process_hashmap = sys.processes();
|
||||
let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0;
|
||||
let num_processors = sys.cpus().len() as f64;
|
||||
for process_val in process_hashmap.values() {
|
||||
let name = if process_val.name().is_empty() {
|
||||
let process_cmd = process_val.cmd();
|
||||
@ -43,11 +42,10 @@ pub fn get_process_data(
|
||||
|
||||
let pcu = {
|
||||
let usage = process_val.cpu_usage() as f64;
|
||||
let res = usage / num_processors;
|
||||
if res.is_finite() {
|
||||
res
|
||||
} else {
|
||||
if per_core_percentage || sys.cpus().is_empty() {
|
||||
usage
|
||||
} else {
|
||||
usage / (sys.cpus().len() as f64)
|
||||
}
|
||||
};
|
||||
let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 {
|
||||
|
@ -135,6 +135,14 @@ pub fn build_app() -> Command<'static> {
|
||||
.help("Sets process CPU% to be based on current CPU%.")
|
||||
.long_help("Sets process CPU% usage to be based on the current system CPU% usage rather than total CPU usage.");
|
||||
|
||||
let per_core_percentage = Arg::new("per_core_percentage")
|
||||
.short('p')
|
||||
.long("per_core_percentage")
|
||||
.help("Sets CPU% to be based on per core CPU%.")
|
||||
.long_help(
|
||||
"Sets CPU% usage to be based on the per core CPU% usage rather than total CPU usage.",
|
||||
);
|
||||
|
||||
// TODO: [DEBUG] Add a proper debugging solution.
|
||||
|
||||
let disable_click = Arg::new("disable_click")
|
||||
@ -397,6 +405,7 @@ use CPU (3) as the default instead.
|
||||
.arg(network_use_log)
|
||||
.arg(network_use_binary_prefix)
|
||||
.arg(current_usage)
|
||||
.arg(per_core_percentage)
|
||||
.arg(use_old_network_legend)
|
||||
.arg(whole_word)
|
||||
.arg(retention);
|
||||
|
@ -492,6 +492,8 @@ pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. A
|
||||
#left_legend = false
|
||||
# Whether to set CPU% on a process to be based on the total CPU or just current usage.
|
||||
#current_usage = false
|
||||
# Whether to set CPU% on a process to be based on the total CPU or per-core CPU% (not divided by the number of cpus).
|
||||
#per_core_percentage = false
|
||||
# Whether to group processes with the same name together by default.
|
||||
#group_processes = false
|
||||
# Whether to make process searching case sensitive by default.
|
||||
|
@ -474,6 +474,7 @@ pub fn create_collection_thread(
|
||||
) -> JoinHandle<()> {
|
||||
let temp_type = app_config_fields.temperature_type;
|
||||
let use_current_cpu_total = app_config_fields.use_current_cpu_total;
|
||||
let per_core_percentage = app_config_fields.per_core_percentage;
|
||||
let show_average_cpu = app_config_fields.show_average_cpu;
|
||||
let update_rate_in_milliseconds = app_config_fields.update_rate_in_milliseconds;
|
||||
|
||||
@ -483,6 +484,7 @@ pub fn create_collection_thread(
|
||||
data_state.set_data_collection(used_widget_set);
|
||||
data_state.set_temperature_type(temp_type);
|
||||
data_state.set_use_current_cpu_total(use_current_cpu_total);
|
||||
data_state.set_per_core_percentage(per_core_percentage);
|
||||
data_state.set_show_average_cpu(show_average_cpu);
|
||||
|
||||
data_state.init();
|
||||
@ -508,6 +510,7 @@ pub fn create_collection_thread(
|
||||
data_state.set_temperature_type(app_config_fields.temperature_type);
|
||||
data_state
|
||||
.set_use_current_cpu_total(app_config_fields.use_current_cpu_total);
|
||||
data_state.set_per_core_percentage(per_core_percentage);
|
||||
data_state.set_show_average_cpu(app_config_fields.show_average_cpu);
|
||||
}
|
||||
ThreadControlEvent::UpdateUsedWidgets(used_widget_set) => {
|
||||
|
@ -62,6 +62,7 @@ pub struct ConfigFlags {
|
||||
pub rate: Option<u64>,
|
||||
pub left_legend: Option<bool>,
|
||||
pub current_usage: Option<bool>,
|
||||
pub per_core_percentage: Option<bool>,
|
||||
pub group_processes: Option<bool>,
|
||||
pub case_sensitive: Option<bool>,
|
||||
pub whole_word: Option<bool>,
|
||||
@ -229,6 +230,7 @@ pub fn build_app(
|
||||
use_dot: get_use_dot(matches, config),
|
||||
left_legend: get_use_left_legend(matches, config),
|
||||
use_current_cpu_total: get_use_current_cpu_total(matches, config),
|
||||
per_core_percentage: get_per_core_percentage(matches, config),
|
||||
use_basic_mode,
|
||||
default_time_value,
|
||||
time_interval: get_time_interval(matches, config, retention_ms)
|
||||
@ -594,6 +596,18 @@ fn get_use_current_cpu_total(matches: &ArgMatches, config: &Config) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_per_core_percentage(matches: &ArgMatches, config: &Config) -> bool {
|
||||
if matches.is_present("per_core_percentage") {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(per_core_percentage) = flags.per_core_percentage {
|
||||
return per_core_percentage;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn get_use_basic_mode(matches: &ArgMatches, config: &Config) -> bool {
|
||||
if matches.is_present("basic") {
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user