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(())
}
#[cfg(test)]
mod tests {
use super::super::super::aggregate::Aggregated;
use super::super::super::scale::ScaleLadder;
use super::*;
fn full_columns() -> Vec<Column> {
super::super::super::columns::compare_columns_for(
super::super::super::columns::DisplayFormat::Full,
)
}
fn mk_row(group_key: &str, display_key: &str, delta: f64) -> DiffRow {
DiffRow {
group_key: group_key.into(),
thread_count_a: 1,
thread_count_b: 1,
metric_name: "run_time_ns",
metric_ladder: ScaleLadder::Ns,
baseline: Aggregated::Sum(100),
candidate: Aggregated::Sum(200),
delta: Some(delta),
delta_pct: Some(delta / 100.0),
display_key: display_key.into(),
uptime_pct: None,
sort_by_cell: None,
sort_by_delta: None,
}
}
#[test]
fn depth_color_maps_each_depth_to_its_palette_entry() {
use comfy_table::Color;
let cases: &[(usize, Color)] = &[
(0, Color::Green),
(1, Color::Cyan),
(2, Color::DarkGrey),
(3, Color::DarkGrey),
(7, Color::DarkGrey),
(usize::MAX, Color::DarkGrey),
];
for &(depth, expected) in cases {
assert_eq!(
depth_color(depth),
expected,
"depth {depth} must map to {expected:?}",
);
}
}
#[test]
fn build_primary_hier_splits_three_field_group_key() {
let rows = [mk_row("/svc\x00alpha\x00alpha-w", "alpha-w", 10.0)];
let hier = build_primary_hier(rows.iter(), |r: &DiffRow| r.group_key.as_str());
assert_eq!(hier.len(), 1);
assert_eq!(hier[0].cgroup, "/svc");
assert_eq!(hier[0].pcomm, "alpha");
assert_eq!(hier[0].comm, "alpha-w");
}
#[test]
fn build_primary_hier_comm_defaults_to_pcomm_when_two_fields() {
let rows = [mk_row("/svc\x00beta", "beta", 10.0)];
let hier = build_primary_hier(rows.iter(), |r: &DiffRow| r.group_key.as_str());
assert_eq!(hier[0].cgroup, "/svc");
assert_eq!(hier[0].pcomm, "beta");
assert_eq!(
hier[0].comm, "beta",
"comm must default to pcomm for a two-field key",
);
}
#[test]
fn build_primary_hier_empty_key_yields_empty_fields() {
let rows = [mk_row("", "", 10.0)];
let hier = build_primary_hier(rows.iter(), |r: &DiffRow| r.group_key.as_str());
assert_eq!(hier[0].cgroup, "");
assert_eq!(hier[0].pcomm, "");
assert_eq!(hier[0].comm, "");
}
#[test]
fn build_primary_hier_floats_top_mover_cgroup_and_leaf_first() {
let rows = [
mk_row("/cg-b\x00p2\x00p2-w", "p2-w", 999.0),
mk_row("/cg-a\x00p1\x00p1-w", "p1-w", 5.0),
mk_row("/cg-b\x00p1\x00p1-w", "p1-w", 50.0),
];
let hier = build_primary_hier(rows.iter(), |r: &DiffRow| r.group_key.as_str());
let order: Vec<(&str, &str)> = hier.iter().map(|h| (h.cgroup, h.pcomm)).collect();
assert_eq!(
order,
vec![("/cg-b", "p2"), ("/cg-b", "p1"), ("/cg-a", "p1")],
"cgroup with the top mover (/cg-b) sorts first; within it \
the leaf holding the top mover (p2) precedes p1; /cg-a sinks",
);
}
fn render(table: &comfy_table::Table) -> String {
format!("{table}")
}
fn label_indent(out: &str, label: &str) -> usize {
let line = out
.lines()
.find(|l| l.trim() == label)
.unwrap_or_else(|| panic!("no heading row carries label `{label}`:\n{out}"));
line.len() - line.trim_start().len()
}
#[test]
fn emit_cgroup_segments_emits_indented_headings_from_empty() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
let returned = emit_cgroup_segments(&mut table, "/a/b", &[], &cols);
assert_eq!(
returned,
Some(vec!["a", "b"]),
"must return the full split segment vector",
);
let out = render(&table);
assert_eq!(
label_indent(&out, "a"),
1,
"depth-0 segment `a` must render flush-left (1 pad space):\n{out}",
);
assert_eq!(
label_indent(&out, "b"),
3,
"depth-1 segment `b` must render at 1 pad + 2 indent spaces:\n{out}",
);
}
#[test]
fn emit_cgroup_segments_skips_common_prefix() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
let returned = emit_cgroup_segments(&mut table, "/a/c", &["a", "b"], &cols);
assert_eq!(returned, Some(vec!["a", "c"]));
let out = render(&table);
assert_eq!(
label_indent(&out, "c"),
3,
"diverging tail `c` must emit at depth 1 (3 leading spaces):\n{out}",
);
assert!(
!out.lines().any(|l| l.trim() == "a"),
"common ancestor `a` must not re-emit a heading:\n{out}",
);
}
#[test]
fn emit_cgroup_segments_unchanged_returns_none_adds_no_rows() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
let returned = emit_cgroup_segments(&mut table, "/a/b", &["a", "b"], &cols);
assert_eq!(returned, None, "unchanged cgroup must return None");
let out = render(&table);
assert!(
!out.lines().any(|l| l.trim() == "a" || l.trim() == "b"),
"no heading rows may be added for an unchanged cgroup:\n{out}",
);
}
#[test]
fn emit_cgroup_segments_deepening_emits_new_tail() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
let returned = emit_cgroup_segments(&mut table, "/a/b", &["a"], &cols);
assert_eq!(returned, Some(vec!["a", "b"]));
let out = render(&table);
assert_eq!(
label_indent(&out, "b"),
3,
"deepening must emit the new tail segment `b` at depth 1 \
(3 leading spaces):\n{out}",
);
assert!(
!out.lines().any(|l| l.trim() == "a"),
"common ancestor `a` must not re-emit on deepening:\n{out}",
);
}
#[test]
fn emit_pcomm_heading_indents_by_cgroup_depth() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
emit_pcomm_heading(&mut table, "myproc", 1, &cols);
let out = render(&table);
assert_eq!(
label_indent(&out, "myproc"),
3,
"pcomm `myproc` must render at cg_depth=1 (3 leading spaces):\n{out}",
);
}
#[test]
fn emit_pcomm_heading_flush_left_at_depth_zero() {
let cols = full_columns();
let mut table = DisplayOptions::default().new_table();
table.set_header(colored_header_with_sort(&cols, "comm", None));
emit_pcomm_heading(&mut table, "rootproc", 0, &cols);
let out = render(&table);
assert_eq!(
label_indent(&out, "rootproc"),
1,
"pcomm `rootproc` must render flush-left at cg_depth=0 \
(1 pad space):\n{out}",
);
}
fn diff_with(rows: Vec<DiffRow>) -> CtprofDiff {
CtprofDiff {
rows,
..Default::default()
}
}
#[test]
fn write_primary_section_all_renders_full_hierarchy() {
let diff = diff_with(vec![mk_row(
"/svc/api\x00gunicorn\x00gunicorn-w",
"gunicorn-w",
10.0,
)]);
let cols = full_columns();
let mut out = String::new();
write_primary_section(
&mut out,
&diff,
GroupBy::All,
"comm",
&cols,
&DisplayOptions::default(),
&[],
)
.unwrap();
assert!(
out.starts_with("## Primary metrics"),
"All-mode section must lead with the heading:\n{out}",
);
assert!(out.contains("svc"), "cgroup segment `svc` missing:\n{out}");
assert!(out.contains("api"), "cgroup segment `api` missing:\n{out}");
assert!(
out.contains("gunicorn"),
"pcomm/comm `gunicorn` must surface in the tree:\n{out}",
);
assert!(
out.contains("run_time_ns"),
"metric row must render under the tree:\n{out}",
);
}
#[test]
fn write_primary_section_all_pins_fudged_rows_past_limit() {
let normal = mk_row("/cg\x00normalproc\x00normalproc-w", "normalproc-w", 999.0);
let fudged = mk_row(
"/cg\x00fudgedproc\x00fudgedproc-w",
"[fudged: leaf] fudgedproc-w",
1.0,
);
let diff = diff_with(vec![fudged, normal]);
let cols = full_columns();
let display = DisplayOptions {
section_line_limit: 1,
..Default::default()
};
let mut out = String::new();
write_primary_section(&mut out, &diff, GroupBy::All, "comm", &cols, &display, &[]).unwrap();
assert!(
out.contains("[fudged: leaf]"),
"fudged marker must survive truncation:\n{out}",
);
assert!(
out.contains("fudgedproc"),
"fudged comm must render alongside its marker:\n{out}",
);
assert!(
!out.contains("normalproc"),
"the normal row must be dropped once the budget is spent on \
the pinned fudged row:\n{out}",
);
}
#[test]
fn write_primary_section_suppressed_when_neither_section_enabled() {
let diff = diff_with(vec![mk_row("/svc\x00p\x00p-w", "p-w", 10.0)]);
let cols = full_columns();
let display = DisplayOptions {
sections: vec![super::super::super::columns::Section::Derived],
..Default::default()
};
let mut out = String::new();
write_primary_section(&mut out, &diff, GroupBy::All, "comm", &cols, &display, &[]).unwrap();
assert!(
out.is_empty(),
"primary section must emit nothing when its gate is closed:\n{out}",
);
}
#[test]
fn write_primary_section_taskstats_only_keeps_table_drops_primary_rows() {
let diff = diff_with(vec![mk_row("alpha\x00alpha\x00alpha-w", "alpha-w", 10.0)]);
let cols = full_columns();
let display = DisplayOptions {
sections: vec![super::super::super::columns::Section::TaskstatsDelay],
..Default::default()
};
let mut out = String::new();
write_primary_section(
&mut out,
&diff,
GroupBy::Pcomm,
"pcomm",
&cols,
&display,
&[],
)
.unwrap();
assert!(
out.contains("## Primary metrics"),
"taskstats-delay alone must keep the table open:\n{out}",
);
assert!(
!out.contains("run_time_ns"),
"a Section::Primary metric row must be filtered out when only \
taskstats-delay is enabled:\n{out}",
);
}
#[test]
fn write_primary_section_cgroup_emits_parent_heading_and_leaf_cell() {
let r1 = mk_row(
"/system.slice/foo.service",
"/system.slice/foo.service",
10.0,
);
let r2 = mk_row(
"/system.slice/bar.service",
"/system.slice/bar.service",
20.0,
);
let diff = diff_with(vec![r1, r2]);
let cols = full_columns();
let mut out = String::new();
write_primary_section(
&mut out,
&diff,
GroupBy::Cgroup,
"cgroup",
&cols,
&DisplayOptions::default(),
&[],
)
.unwrap();
assert!(
out.contains("## /system.slice"),
"parent sub-heading `## /system.slice` missing:\n{out}",
);
let heading_count = out.matches("## /system.slice").count();
assert_eq!(
heading_count, 1,
"both rows share one parent → exactly one sub-heading, got {heading_count}:\n{out}",
);
assert!(
out.contains("foo.service") && out.contains("bar.service"),
"leaf segments must render as the group cell:\n{out}",
);
}
#[test]
fn write_primary_section_cgroup_one_heading_per_distinct_parent() {
let r1 = mk_row("/a/x.service", "/a/x.service", 10.0);
let r2 = mk_row("/b/y.service", "/b/y.service", 20.0);
let diff = diff_with(vec![r1, r2]);
let cols = full_columns();
let mut out = String::new();
write_primary_section(
&mut out,
&diff,
GroupBy::Cgroup,
"cgroup",
&cols,
&DisplayOptions::default(),
&[],
)
.unwrap();
let a_at = out.find("## /a").expect("`## /a` heading missing");
let b_at = out.find("## /b").expect("`## /b` heading missing");
assert!(
a_at < b_at,
"BTreeMap parent iteration must place `/a` before `/b`:\n{out}",
);
}
}