use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MethodCall {
pub path: String,
pub call_count: usize,
pub avg_time_ns: Option<u64>,
pub call_sites: Vec<CallSite>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallSite {
pub file_path: String,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CrateUsage {
pub name: String,
pub is_direct: bool,
pub total_calls: usize,
pub methods: HashMap<String, MethodCall>,
}
impl CrateUsage {
pub fn new(name: String, is_direct: bool) -> Self {
Self {
name,
is_direct,
total_calls: 0,
methods: HashMap::new(),
}
}
pub fn add_call(&mut self, method_path: String, call_site: CallSite) {
self.total_calls += 1;
let method = self.methods.entry(method_path.clone()).or_insert_with(|| {
MethodCall {
path: method_path,
call_count: 0,
avg_time_ns: None,
call_sites: Vec::new(),
}
});
method.call_count += 1;
method.call_sites.push(call_site);
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProjectAnalysis {
pub root_crate: String,
pub crates: HashMap<String, CrateUsage>,
pub total_calls: usize,
}
impl ProjectAnalysis {
pub fn new(root_crate: String) -> Self {
Self {
root_crate,
crates: HashMap::new(),
total_calls: 0,
}
}
pub fn get_or_create_crate(&mut self, name: String, is_direct: bool) -> &mut CrateUsage {
self.crates
.entry(name.clone())
.or_insert_with(|| CrateUsage::new(name, is_direct))
}
pub fn recalculate_totals(&mut self) {
self.total_calls = self.crates.values().map(|c| c.total_calls).sum();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TreeMapRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl TreeMapRect {
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
Self { x, y, width, height }
}
pub fn area(&self) -> f64 {
self.width * self.height
}
pub fn aspect_ratio(&self) -> f64 {
let min = self.width.min(self.height);
let max = self.width.max(self.height);
if min == 0.0 {
f64::INFINITY
} else {
max / min
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TreeMapNode {
pub label: String,
pub value: usize,
pub time_ns: Option<u64>,
pub children: Vec<TreeMapNode>,
pub rect: Option<TreeMapRect>,
}
impl TreeMapNode {
pub fn new_crate(name: String, value: usize, children: Vec<TreeMapNode>) -> Self {
Self {
label: name,
value,
time_ns: None,
children,
rect: None,
}
}
pub fn new_method(path: String, call_count: usize, time_ns: Option<u64>) -> Self {
Self {
label: path,
value: call_count,
time_ns,
children: Vec::new(),
rect: None,
}
}
pub fn from_analysis(analysis: &ProjectAnalysis) -> Vec<TreeMapNode> {
let mut nodes = Vec::new();
for (crate_name, crate_usage) in &analysis.crates {
let method_nodes: Vec<TreeMapNode> = crate_usage
.methods
.values()
.map(|method| {
TreeMapNode::new_method(
method.path.clone(),
method.call_count,
method.avg_time_ns,
)
})
.collect();
nodes.push(TreeMapNode::new_crate(
crate_name.clone(),
crate_usage.total_calls,
method_nodes,
));
}
nodes.sort_by(|a, b| b.value.cmp(&a.value));
nodes
}
}