use crate::artifact::{ParsedArtifact, SymClass};
use crate::attribute::Attributor;
use crate::dwarf::DwarfResolver;
use crate::util::{leading_ident, split_path, strip_generics};
use serde::{Deserialize, Serialize};
use std::cmp::Reverse;
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
pub struct Node {
pub label: String,
pub path: String,
pub text: u64,
pub data: u64,
pub gzip: u64,
pub count: u32,
#[serde(default, skip_serializing_if = "str::is_empty")]
pub conf: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stat: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loc: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unsafe_n: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dep_kind: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub groups: Vec<Node>,
}
#[derive(Serialize)]
pub struct Meta {
pub tool: String,
pub artifacts: Vec<String>,
pub format: String,
pub target: String,
pub profile: String,
pub total_text: u64,
pub total_data: u64,
pub total_gzip: u64,
pub default_metric: String,
pub api_accurate: bool,
pub invocation: String,
pub cwd: String,
pub generated_unix: u64,
}
struct Tmp {
label: String,
path: String,
text: u64,
data: u64,
gzip: u64,
count: u32,
conf: &'static str,
children: HashMap<String, Tmp>,
}
impl Tmp {
fn new(label: &str, path: String) -> Self {
Tmp {
label: label.to_string(),
path,
text: 0,
data: 0,
gzip: 0,
count: 0,
conf: "",
children: HashMap::new(),
}
}
}
fn path_segments(crate_name: &str, demangled: &str) -> Vec<String> {
let dsegs = split_path(demangled);
if dsegs.is_empty() {
return vec![crate_name.to_string(), demangled.to_string()];
}
let leaf = strip_generics(dsegs.last().unwrap());
let mut out = Vec::with_capacity(dsegs.len() + 1);
out.push(crate_name.to_string());
let start = if leading_ident(&dsegs[0]) == crate_name { 1 } else { 0 };
let module_end = dsegs.len() - 1;
for seg in dsegs.iter().take(module_end).skip(start) {
out.push(strip_generics(seg));
}
out.push(leaf);
out
}
fn insert(root: &mut Tmp, segs: &[String], text: u64, data: u64, gzip: u64, conf: &'static str) {
let mut cur = root;
let mut acc = String::new();
for seg in segs {
if !acc.is_empty() {
acc.push_str("::");
}
acc.push_str(seg);
let path = acc.clone();
cur = cur
.children
.entry(seg.clone())
.or_insert_with(|| Tmp::new(seg, path));
}
cur.text += text;
cur.data += data;
cur.gzip += gzip;
cur.count += 1;
if cur.conf.is_empty() {
cur.conf = conf;
}
}
fn to_node(t: Tmp) -> Node {
let mut groups: Vec<Node> = t.children.into_values().map(to_node).collect();
let mut text = t.text;
let mut data = t.data;
let mut gzip = t.gzip;
let mut count = t.count;
for c in &groups {
text += c.text;
data += c.data;
gzip += c.gzip;
count += c.count;
}
groups.sort_by(|a, b| b.text.cmp(&a.text).then_with(|| a.label.cmp(&b.label)));
Node {
label: t.label,
path: t.path,
text,
data,
gzip,
count,
conf: t.conf.to_string(),
stat: None,
loc: None,
unsafe_n: None,
dep_kind: None,
groups,
}
}
pub fn build_tree(art: &ParsedArtifact, attr: &Attributor, dwarf: Option<&DwarfResolver>) -> Node {
let mut root = Tmp::new(&art.name, art.name.clone());
for sym in &art.symbols {
let a = attr.attribute(&sym.name, sym.address, dwarf);
let segs = path_segments(&a.crate_name, &a.demangled);
let (t, d) = match sym.class {
SymClass::Text => (sym.size, 0),
SymClass::Data => (0, sym.size),
};
insert(&mut root, &segs, t, d, sym.gzip_size, a.confidence.as_str());
}
let slack_text = art.totals.text.saturating_sub(art.totals.text_attributed);
let slack_data = art.totals.data.saturating_sub(art.totals.data_attributed);
if slack_text + slack_data > 0 {
insert(
&mut root,
&["[unattributed]".to_string()],
slack_text,
slack_data,
0,
"unknown",
);
}
to_node(root)
}
pub fn combine(mut nodes: Vec<Node>) -> Node {
if nodes.len() == 1 {
return nodes.pop().unwrap();
}
let (mut text, mut data, mut gzip, mut count) = (0u64, 0u64, 0u64, 0u32);
for n in &nodes {
text += n.text;
data += n.data;
gzip += n.gzip;
count += n.count;
}
nodes.sort_by_key(|n| Reverse(n.text));
Node {
label: "(artifacts)".to_string(),
path: String::new(),
text,
data,
gzip,
count,
conf: String::new(),
stat: None,
loc: None,
unsafe_n: None,
dep_kind: None,
groups: nodes,
}
}
pub fn artifact_nodes(root: &Node) -> Vec<&Node> {
if root.path.is_empty() && !root.groups.is_empty() {
root.groups.iter().collect()
} else {
vec![root]
}
}
pub fn crate_bin_totals(root: &Node) -> HashMap<String, (u64, u64, u64, u32)> {
let mut out: HashMap<String, (u64, u64, u64, u32)> = HashMap::new();
for art in artifact_nodes(root) {
for c in &art.groups {
let e = out.entry(c.label.clone()).or_default();
e.0 += c.text;
e.1 += c.data;
e.2 += c.gzip;
e.3 += c.count;
}
}
out
}
pub fn attach_metrics(root: &mut Node, metrics: &HashMap<String, crate::parity::CrateMetrics>) {
let crate_nodes: Vec<&mut Node> = if root.path.is_empty() && !root.groups.is_empty() {
root.groups.iter_mut().flat_map(|a| a.groups.iter_mut()).collect()
} else {
root.groups.iter_mut().collect()
};
for c in crate_nodes {
if let Some(m) = metrics.get(&c.label) {
c.stat = Some(m.src_bytes);
c.loc = Some(m.loc);
c.unsafe_n = Some(m.unsafe_n);
if !m.dep_kind.is_empty() {
c.dep_kind = Some(m.dep_kind.clone());
}
}
}
}