use crate::apiusage::DepValue;
use crate::cli::Metric;
use crate::model::{Meta, Node};
use crate::parity::CrateRow;
use crate::util::human_bytes;
use std::cmp::Reverse;
fn metric_of(n: &Node, m: Metric) -> u64 {
match m {
Metric::Text => n.text,
Metric::Data => n.data,
Metric::Gzip => n.gzip,
Metric::Mono => n.count as u64,
Metric::Stat => n.stat.unwrap_or(0),
}
}
pub fn render(root: &Node, meta: &Meta, deps: &[DepValue], crates: &[CrateRow], metric: Metric) {
let total = root
.groups
.iter()
.map(|n| metric_of(n, metric))
.sum::<u64>()
.max(1);
let mut rows: Vec<&Node> = root.groups.iter().collect();
rows.sort_by_key(|n| Reverse(metric_of(n, metric)));
println!(
"\n {:>9} {:>9} {:>9} {:>6} Crate",
"Text", "Data", "Gzip", "%"
);
println!(" {}", "-".repeat(58));
for n in &rows {
let v = metric_of(n, metric);
if v == 0 {
continue;
}
let pct = 100.0 * v as f64 / total as f64;
println!(
" {:>9} {:>9} {:>9} {:>5.1}% {}",
human_bytes(n.text),
human_bytes(n.data),
human_bytes(n.gzip),
pct,
n.label
);
}
println!(" {}", "-".repeat(58));
println!(
" {:>9} {:>9} {:>9} {:>5.1}% {} total",
human_bytes(root.text),
human_bytes(root.data),
human_bytes(root.gzip),
100.0,
root.label
);
println!(
"\n {} · {} · profile {} · sorted by {}\n",
meta.artifacts.join(", "),
meta.format,
meta.profile,
metric.field(),
);
if !crates.is_empty() {
render_crates(crates);
}
if !deps.is_empty() {
render_deps(deps, meta.api_accurate);
}
}
fn render_crates(crates: &[CrateRow]) {
let has_cc = crates.iter().any(|c| c.compile_ms > 0.0);
let cc = if has_cc { "compile, " } else { "" };
println!(" Per-crate metrics (binary size, source, {cc}unsafe, monomorphizations)\n");
if has_cc {
println!(
" {:>9} {:>7} {:>6} {:>7} {:>8} {:>10} crate",
".text", "LOC", "unsafe", "mono", "build", "kind"
);
} else {
println!(
" {:>9} {:>7} {:>6} {:>7} {:>10} crate",
".text", "LOC", "unsafe", "mono", "kind"
);
}
println!(" {}", "-".repeat(if has_cc { 74 } else { 64 }));
for c in crates.iter().take(40) {
if has_cc {
println!(
" {:>9} {:>7} {:>6} {:>7} {:>8} {:>10} {}",
human_bytes(c.text),
c.loc,
c.unsafe_n,
c.mono,
fmt_ms(c.compile_ms),
short_kind(&c.dep_kind),
c.name,
);
} else {
println!(
" {:>9} {:>7} {:>6} {:>7} {:>10} {}",
human_bytes(c.text),
c.loc,
c.unsafe_n,
c.mono,
short_kind(&c.dep_kind),
c.name,
);
}
}
println!();
}
fn fmt_ms(ms: f64) -> String {
if ms <= 0.0 {
"-".to_string()
} else if ms >= 1000.0 {
format!("{:.2}s", ms / 1000.0)
} else {
format!("{:.0}ms", ms)
}
}
fn short_kind(k: &str) -> &str {
match k {
"proc-macro" => "proc",
"workspace" => "ws",
other => other,
}
}
fn render_deps(deps: &[DepValue], accurate: bool) {
println!(" Dependency API usage (how much of each dep's public API you use)\n");
println!(
" {:>7} {:>5} {:>6} {:>9} dependency",
"surface", "used", "ratio", "size"
);
println!(" {}", "-".repeat(64));
for d in deps {
let ratio = match d.ratio {
Some(r) => format!("{:.1}%", 100.0 * r),
None => "-".to_string(),
};
let verdict = if d.is_proc_macro {
" proc-macro (unmeasurable)"
} else if d.flag_unused {
" ⚠ unused?"
} else if d.flag_low_value {
" ⚠ low-value / high-size"
} else if !d.rustdoc_ok {
" (rustdoc unavailable)"
} else if d.ratio.is_none() {
" (no measurable fns)"
} else {
""
};
println!(
" {:>7} {:>5} {:>6} {:>9} {}{}",
d.surface_fns,
d.used_fns,
ratio,
human_bytes(d.size_text + d.size_data),
d.name,
verdict,
);
}
if accurate {
println!("\n usage from an opt-0 build (cross-crate calls not inlined), ratios are trustworthy.");
} else {
println!("\n note: 'used' is a LOWER BOUND, release inlining hides usage. Add --accurate for true ratios.");
}
println!();
}