diff --git a/sample_configs/default_config.toml b/sample_configs/default_config.toml index 7ead3315..a2eabb38 100644 --- a/sample_configs/default_config.toml +++ b/sample_configs/default_config.toml @@ -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. diff --git a/src/app.rs b/src/app.rs index 34696d2c..5b75e50f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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, diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 25913077..1d28a020 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -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); } } diff --git a/src/app/data_harvester/processes/freebsd.rs b/src/app/data_harvester/processes/freebsd.rs index 49816bc2..cbcb5eb1 100644 --- a/src/app/data_harvester/processes/freebsd.rs +++ b/src/app/data_harvester/processes/freebsd.rs @@ -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> { super::macos_freebsd::get_process_data( sys, use_current_cpu_total, + per_core_percentage, mem_total_kb, user_table, get_freebsd_process_cpu_usage, diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index 69a19cc5..d024389d 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -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, 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> { // 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; diff --git a/src/app/data_harvester/processes/macos.rs b/src/app/data_harvester/processes/macos.rs index d0847a1b..c3570290 100644 --- a/src/app/data_harvester/processes/macos.rs +++ b/src/app/data_harvester/processes/macos.rs @@ -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> { super::macos_freebsd::get_process_data( sys, use_current_cpu_total, + per_core_percentage, mem_total_kb, user_table, get_macos_process_cpu_usage, diff --git a/src/app/data_harvester/processes/macos_freebsd.rs b/src/app/data_harvester/processes/macos_freebsd.rs index a4ac1e2d..3f2e340d 100644 --- a/src/app/data_harvester/processes/macos_freebsd.rs +++ b/src/app/data_harvester/processes/macos_freebsd.rs @@ -9,8 +9,8 @@ use super::ProcessHarvest; use crate::{data_harvester::processes::UserTable, utils::error::Result, Pid}; pub fn get_process_data( - 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> where F: Fn(&[Pid]) -> io::Result>, @@ -18,7 +18,6 @@ where let mut process_vector: Vec = 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) }; } } diff --git a/src/app/data_harvester/processes/windows.rs b/src/app/data_harvester/processes/windows.rs index 55b59086..d92f2032 100644 --- a/src/app/data_harvester/processes/windows.rs +++ b/src/app/data_harvester/processes/windows.rs @@ -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> { let mut process_vector: Vec = 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 { diff --git a/src/clap.rs b/src/clap.rs index 043b79b3..b7b9f9a6 100644 --- a/src/clap.rs +++ b/src/clap.rs @@ -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); diff --git a/src/constants.rs b/src/constants.rs index ceee7c4e..ab5a0f0b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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. diff --git a/src/lib.rs b/src/lib.rs index c6b220b9..b23adcdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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) => { diff --git a/src/options.rs b/src/options.rs index f2d8c0a8..b0dd974c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -62,6 +62,7 @@ pub struct ConfigFlags { pub rate: Option, pub left_legend: Option, pub current_usage: Option, + pub per_core_percentage: Option, pub group_processes: Option, pub case_sensitive: Option, pub whole_word: Option, @@ -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;