Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option to sort cpu history #703

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/content/configuration/command-line-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The following flags can be provided to bottom in the command line to change the
| `--color <COLOR SCHEME>` | Use a color scheme, use --help for supported values. |
| `-C, --config <CONFIG PATH>` | Sets the location of the config file. |
| `-u, --current_usage` | Sets process CPU% to be based on current CPU%. |
| `--sort_cpu_hist` | Rank CPU usage across history. |
| `-t, --default_time_value <MS>` | Default time value for graphs in ms. |
| `--default_widget_count <INT>` | Sets the n'th selected widget type as the default. |
| `--default_widget_type <WIDGET TYPE>` | Sets the default widget type, use --help for more info. |
Expand Down
1 change: 1 addition & 0 deletions docs/content/configuration/config-file/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Most of the [command line flags](../../command-line-flags) have config file equi
| `dot_marker` | Boolean | Uses a dot marker for graphs. |
| `left_legend` | Boolean | Puts the CPU chart legend to the left side. |
| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. |
| `sort_cpu_hist` | Boolean | Rank CPU usage across history. |
| `group_processes` | Boolean | Groups processes with the same name by default. |
| `case_sensitive` | Boolean | Enables case sensitivity by default. |
| `whole_word` | Boolean | Enables whole-word matching by default. |
Expand Down
2 changes: 2 additions & 0 deletions sample_configs/default_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 rank CPU usage across history.
#sort_cpu_hist = false
# Whether to group processes with the same name together by default.
#group_processes = false
# Whether to make process searching case sensitive by default.
Expand Down
1 change: 1 addition & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub struct AppConfigFields {
pub left_legend: bool,
pub show_average_cpu: bool,
pub use_current_cpu_total: bool,
pub sort_cpu_hist: bool,
pub use_basic_mode: bool,
pub default_time_value: u64,
pub time_interval: u64,
Expand Down
1 change: 1 addition & 0 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ fn main() -> Result<()> {
&app.data_collection,
&mut app.canvas_data.cpu_data,
false,
app.app_config_fields.sort_cpu_hist,
);
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
}
Expand Down
23 changes: 21 additions & 2 deletions src/canvas/widgets/cpu_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tui::{
};

const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
const RANK_LEGEND_HEADER: [&str; 2] = ["Rank", "Use%"];
const AVG_POSITION: usize = 1;
const ALL_POSITION: usize = 0;

Expand All @@ -32,6 +33,13 @@ static CPU_LEGEND_HEADER_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
.collect::<Vec<_>>()
});

static RANK_LEGEND_HEADER_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
RANK_LEGEND_HEADER
.iter()
.map(|entry| entry.len() as u16)
.collect::<Vec<_>>()
});

pub trait CpuGraphWidget {
fn draw_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
Expand Down Expand Up @@ -420,13 +428,19 @@ impl CpuGraphWidget for Painter {
.saturating_sub(start_position);
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;

let sort_cpu_hist = app_state.app_config_fields.sort_cpu_hist;
let legend_header_lens = match sort_cpu_hist {
false => &CPU_LEGEND_HEADER_LENS,
true => &RANK_LEGEND_HEADER_LENS,
};

// Calculate widths
if recalculate_column_widths {
cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&[None, None],
&(CPU_LEGEND_HEADER_LENS
&(legend_header_lens
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
Expand Down Expand Up @@ -506,6 +520,11 @@ impl CpuGraphWidget for Painter {
self.colours.border_style
};

let legend_header = match sort_cpu_hist {
false => &CPU_LEGEND_HEADER,
true => &RANK_LEGEND_HEADER,
};

// Draw
f.render_stateful_widget(
Table::new(cpu_rows)
Expand All @@ -515,7 +534,7 @@ impl CpuGraphWidget for Painter {
.border_style(border_and_title_style),
)
.header(
Row::new(CPU_LEGEND_HEADER.to_vec())
Row::new(legend_header.to_vec())
.style(self.colours.table_header_style)
.bottom_margin(table_gap),
)
Expand Down
6 changes: 6 additions & 0 deletions src/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ 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 sort_cpu_hist = Arg::new("sort_cpu_hist")
.long("sort_cpu_hist")
.help("Rank CPU usage across history.")
.long_help("Rank CPU usage for each timestep across history.");

// TODO: [DEBUG] Add a proper debugging solution.

let disable_click = Arg::new("disable_click")
Expand Down Expand Up @@ -389,6 +394,7 @@ use CPU (3) as the default instead.
.arg(network_use_log)
.arg(network_use_binary_prefix)
.arg(current_usage)
.arg(sort_cpu_hist)
.arg(use_old_network_legend)
.arg(whole_word);

Expand Down
2 changes: 2 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,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 rank CPU usage across history.
#sort_cpu_hist = false
# Whether to group processes with the same name together by default.
#group_processes = false
# Whether to make process searching case sensitive by default.
Expand Down
39 changes: 36 additions & 3 deletions src/data_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<S

pub fn convert_cpu_data_points(
current_data: &data_farmer::DataCollection, existing_cpu_data: &mut Vec<ConvertedCpuData>,
is_frozen: bool,
is_frozen: bool, sort_cpu_hist: bool,
) {
let current_time = if is_frozen {
if let Some(frozen_instant) = current_data.frozen_instant {
Expand Down Expand Up @@ -191,7 +191,11 @@ pub fn convert_cpu_data_points(
.map(|(itx, cpu_usage)| ConvertedCpuData {
cpu_name: if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) {
if let Some(cpu_count) = cpu_harvest.cpu_count {
format!("{}{}", cpu_harvest.cpu_prefix, cpu_count)
let cpu_prefix = match sort_cpu_hist {
false => &cpu_harvest.cpu_prefix,
true => "Rank",
};
format!("{}{}", cpu_prefix, cpu_count)
} else {
cpu_harvest.cpu_prefix.to_string()
}
Expand Down Expand Up @@ -228,9 +232,38 @@ pub fn convert_cpu_data_points(
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();

let mut sorted_cpu_data = Vec::new();
if sort_cpu_hist {
for (itx, cpu) in data.cpu_data.iter().enumerate() {
if let Some(_cpu_data) = existing_cpu_data.get_mut(itx + 1) {
if itx > 0 {
// skip sorting avg
sorted_cpu_data.push(*cpu);
}
}
}

// order cpu data in descending values
sorted_cpu_data.sort_by(|a, b| {
if a < b {
std::cmp::Ordering::Greater
} else if a == b {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Less
}
});
}

for (itx, cpu) in data.cpu_data.iter().enumerate() {
if let Some(cpu_data) = existing_cpu_data.get_mut(itx + 1) {
cpu_data.cpu_data.push((-time_from_start, *cpu));
let mut cpu_value = *cpu;
if sort_cpu_hist && itx > 0 {
// skip sorting avg
cpu_value = sorted_cpu_data[itx - 1];
}
cpu_data.legend_value = format!("{:.0}%", cpu_value.round());
cpu_data.cpu_data.push((-time_from_start, cpu_value));
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ pub fn handle_force_redraws(app: &mut App) {
&app.data_collection,
&mut app.canvas_data.cpu_data,
app.is_frozen,
app.app_config_fields.sort_cpu_hist,
);
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
app.cpu_state.force_update = None;
Expand Down
16 changes: 16 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub struct ConfigFlags {
#[builder(default, setter(strip_option))]
pub current_usage: Option<bool>,

#[builder(default, setter(strip_option))]
pub sort_cpu_hist: Option<bool>,

#[builder(default, setter(strip_option))]
pub group_processes: Option<bool>,

Expand Down Expand Up @@ -417,6 +420,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),
sort_cpu_hist: get_sort_cpu_hist(matches, config),
use_basic_mode,
default_time_value,
time_interval: get_time_interval(matches, config)
Expand Down Expand Up @@ -691,6 +695,18 @@ fn get_use_current_cpu_total(matches: &clap::ArgMatches, config: &Config) -> boo
false
}

fn get_sort_cpu_hist(matches: &clap::ArgMatches, config: &Config) -> bool {
if matches.is_present("sort_cpu_hist") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(sort_cpu_hist) = flags.sort_cpu_hist {
return sort_cpu_hist;
}
}

false
}

fn get_use_basic_mode(matches: &clap::ArgMatches, config: &Config) -> bool {
if matches.is_present("basic") {
return true;
Expand Down