use std::collections::BTreeMap;
use std::fmt;
use super::super::CTPROF_METRICS;
use super::super::columns::{Column, Section};
use super::super::diff_types::{CtprofDiff, DiffRow};
use super::super::options::GroupBy;
use super::super::render::{
cgroup_parent_leaf, color_diff_cell, colored_header_with_sort, render_diff_row_cells,
};
use super::super::runner::DisplayOptions;
pub(super) fn depth_color(depth: usize) -> comfy_table::Color {
match depth {
0 => comfy_table::Color::Green,
1 => comfy_table::Color::Cyan,
_ => comfy_table::Color::DarkGrey,
}
}
pub(super) fn write_primary_section<W: fmt::Write>(
w: &mut W,
diff: &CtprofDiff,
group_by: GroupBy,
group_header: &'static str,
columns: &[Column],
display: &DisplayOptions,
global_max_widths: &[u16],
) -> fmt::Result {
if !display.is_section_enabled(Section::Primary)
&& !display.is_section_enabled(Section::TaskstatsDelay)
{
return Ok(());
}
let primary_rows: Vec<&DiffRow> = diff
.rows
.iter()
.filter(|row| {
if !display.is_metric_enabled(row.metric_name) {
return false;
}
let metric = CTPROF_METRICS
.iter()
.find(|m| m.name == row.metric_name)
.expect("metric_name comes from CTPROF_METRICS via build_row");
display.is_section_enabled(metric.section)
})
.collect();
if group_by == GroupBy::All {
write_primary_all(
w,
&primary_rows,
columns,
display,
global_max_widths,
diff.sort_metric_name,
)?;
} else if group_by == GroupBy::Cgroup {
write_primary_cgroup(w, &primary_rows, columns, display, diff.sort_metric_name)?;
} else {
write_primary_flat(
w,
&primary_rows,
columns,
display,
group_header,
diff.sort_metric_name,
)?;
}
Ok(())
}
pub(super) struct HierRow<'a, R> {
pub(super) cgroup: &'a str,
pub(super) pcomm: &'a str,
pub(super) comm: &'a str,
pub(super) row: &'a R,
}
pub(super) fn build_primary_hier<'a, R>(
rows: impl IntoIterator<Item = &'a R>,
key_fn: impl Fn(&R) -> &str,
) -> Vec<HierRow<'a, R>>
where
R: 'a,
{
let mut hier: Vec<HierRow<'a, R>> = rows
.into_iter()
.map(|row| {
let mut parts = key_fn(row).splitn(3, '\x00');
let cgroup = parts.next().unwrap_or("");
let pcomm = parts.next().unwrap_or("");
let comm = parts.next().unwrap_or(pcomm);
HierRow {
cgroup,
pcomm,
comm,
row,
}
})
.collect();
let row_rank: BTreeMap<*const R, usize> = hier
.iter()
.enumerate()
.map(|(i, h)| (h.row as *const R, i))
.collect();
let mut leaf_rank: BTreeMap<(&str, &str), usize> = BTreeMap::new();
let mut cg_rank: BTreeMap<&str, usize> = BTreeMap::new();
for h in &hier {
let rank = row_rank[&(h.row as *const R)];
let le = leaf_rank.entry((h.cgroup, h.pcomm)).or_insert(usize::MAX);
if rank < *le {
*le = rank;
}
let ce = cg_rank.entry(h.cgroup).or_insert(usize::MAX);
if rank < *ce {
*ce = rank;
}
}
hier.sort_by(|a, b| {
let cga = cg_rank.get(a.cgroup).copied().unwrap_or(usize::MAX);
let cgb = cg_rank.get(b.cgroup).copied().unwrap_or(usize::MAX);
cga.cmp(&cgb)
.then_with(|| {
let sa = leaf_rank
.get(&(a.cgroup, a.pcomm))
.copied()
.unwrap_or(usize::MAX);
let sb = leaf_rank
.get(&(b.cgroup, b.pcomm))
.copied()
.unwrap_or(usize::MAX);
sa.cmp(&sb)
})
.then_with(|| {
let ra = row_rank[&(a.row as *const R)];
let rb = row_rank[&(b.row as *const R)];
ra.cmp(&rb)
})
});
hier
}
pub(super) fn emit_cgroup_segments<'a>(
table: &mut comfy_table::Table,
cgroup: &'a str,
last_segments: &[&'a str],
columns: &[Column],
) -> Option<Vec<&'a str>> {
let segments: Vec<&str> = cgroup.split('/').filter(|s| !s.is_empty()).collect();
let common = segments
.iter()
.zip(last_segments.iter())
.take_while(|(a, b)| a == b)
.count();
let cg_changed = common < last_segments.len() || segments.len() > last_segments.len();
if cg_changed {
for (depth, seg) in segments.iter().enumerate().skip(common) {
let indent = " ".repeat(depth);
let label = format!("{indent}{seg}");
let heading_cells: Vec<comfy_table::Cell> = columns
.iter()
.map(|c| {
if *c == Column::Group {
comfy_table::Cell::new(&label)
.fg(depth_color(depth))
.add_attribute(comfy_table::Attribute::Bold)
} else {
comfy_table::Cell::new("")
}
})
.collect();
table.add_row(heading_cells);
}
Some(segments)
} else {
None
}
}
pub(super) fn emit_pcomm_heading(
table: &mut comfy_table::Table,
pcomm: &str,
cg_depth: usize,
columns: &[Column],
) {
let indent = " ".repeat(cg_depth);
let label = format!("{indent}{pcomm}");
let heading_cells: Vec<comfy_table::Cell> = columns
.iter()
.map(|c| {
if *c == Column::Group {
comfy_table::Cell::new(&label)
.fg(comfy_table::Color::White)
.add_attribute(comfy_table::Attribute::Bold)
} else {
comfy_table::Cell::new("")
}
})
.collect();
table.add_row(heading_cells);
}
fn write_primary_all<W: fmt::Write>(
w: &mut W,
primary_rows: &[&DiffRow],
columns: &[Column],
display: &DisplayOptions,
global_max_widths: &[u16],
sort_metric_name: Option<&'static str>,
) -> fmt::Result {
let (fudged_rows, normal_rows): (Vec<&DiffRow>, Vec<&DiffRow>) = primary_rows
.iter()
.copied()
.partition(|r| r.display_key.starts_with("[fudged"));
let limited_rows: Vec<&DiffRow> = if display.section_line_limit > 0 {
let mut out: Vec<&DiffRow> = fudged_rows.clone();
let budget = display.section_line_limit.saturating_sub(fudged_rows.len());
out.extend(normal_rows.into_iter().take(budget));
out
} else {
let mut out = fudged_rows;
out.extend(normal_rows);
out
};
let hier = build_primary_hier(limited_rows.iter().copied(), |row: &DiffRow| {
row.group_key.as_str()
});
writeln!(w, "## Primary metrics")?;
let mut last_segments: Vec<&str> = Vec::new();
let mut last_pcomm = "";
let mut table = display.new_constrained_table(global_max_widths);
table.set_header(colored_header_with_sort(columns, "comm", sort_metric_name));
for h in &hier {
if let Some(new_segments) =
emit_cgroup_segments(&mut table, h.cgroup, &last_segments, columns)
{
last_segments = new_segments;
last_pcomm = "";
}
if h.pcomm != last_pcomm {
emit_pcomm_heading(&mut table, h.pcomm, last_segments.len(), columns);
last_pcomm = h.pcomm;
}
let mut string_cells = render_diff_row_cells(h.row, columns);
if let Some(pos) = columns.iter().position(|c| *c == Column::Group) {
let cg_depth = last_segments.len();
let fudge_marker = if h.row.display_key.starts_with("[fudged") {
h.row.display_key.as_str()
} else {
""
};
let fudge_separator = if fudge_marker.is_empty() { "" } else { " " };
string_cells[pos] = format!(
"{} {}{}{}",
" ".repeat(cg_depth + 1),
fudge_marker,
fudge_separator,
h.comm,
);
}
let cells: Vec<comfy_table::Cell> = string_cells
.into_iter()
.zip(columns.iter())
.map(|(s, col)| {
color_diff_cell(
s,
*col,
h.row.delta,
h.row.delta_pct,
h.row.uptime_pct,
h.row.sort_by_delta,
)
})
.collect();
table.add_row(cells);
}
writeln!(w, "{table}")?;
Ok(())
}
fn write_primary_cgroup<W: fmt::Write>(
w: &mut W,
primary_rows: &[&DiffRow],
columns: &[Column],
display: &DisplayOptions,
sort_metric_name: Option<&'static str>,
) -> fmt::Result {
let mut by_parent: BTreeMap<&str, Vec<&DiffRow>> = BTreeMap::new();
for row in primary_rows {
let (parent, _) = cgroup_parent_leaf(&row.display_key);
by_parent.entry(parent).or_default().push(row);
}
for (parent, rows) in &by_parent {
writeln!(w)?;
writeln!(w, "\x1b[1;32m## {parent}\x1b[0m")?;
let mut table = display.new_table();
table.set_header(colored_header_with_sort(
columns,
"cgroup",
sort_metric_name,
));
let cg_limit = if display.section_line_limit > 0 {
&rows[..rows.len().min(display.section_line_limit)]
} else {
&rows[..]
};
for row in cg_limit {
let (_, leaf) = cgroup_parent_leaf(&row.display_key);
let mut string_cells = render_diff_row_cells(row, columns);
if let Some(pos) = columns.iter().position(|c| *c == Column::Group) {
string_cells[pos] = leaf.to_string();
}
let cells: Vec<comfy_table::Cell> = string_cells
.into_iter()
.zip(columns.iter())
.map(|(s, col)| {
color_diff_cell(
s,
*col,
row.delta,
row.delta_pct,
row.uptime_pct,
row.sort_by_delta,
)
})
.collect();
table.add_row(cells);
}
writeln!(w, "{table}")?;
}
Ok(())
}
fn write_primary_flat<W: fmt::Write>(
w: &mut W,
primary_rows: &[&DiffRow],
columns: &[Column],
display: &DisplayOptions,
group_header: &'static str,
sort_metric_name: Option<&'static str>,
) -> fmt::Result {
writeln!(w, "## Primary metrics")?;
let mut table = display.new_table();
table.set_header(colored_header_with_sort(
columns,
group_header,
sort_metric_name,
));
let limit_iter = if display.section_line_limit > 0 {
&primary_rows[..primary_rows.len().min(display.section_line_limit)]
} else {
primary_rows
};
for row in limit_iter {
let string_cells = render_diff_row_cells(row, columns);
let cells: Vec<comfy_table::Cell> = string_cells
.into_iter()
.zip(columns.iter())
.map(|(s, col)| {
color_diff_cell(
s,
*col,
row.delta,
row.delta_pct,
row.uptime_pct,
row.sort_by_delta,
)
})
.collect();
table.add_row(cells);
}
writeln!(w, "{table}")?;
Ok(())
}