1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct MethodCall {
9 pub path: String,
11 pub call_count: usize,
13 pub avg_time_ns: Option<u64>,
15 pub call_sites: Vec<CallSite>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct CallSite {
22 pub file_path: String,
23 pub line: usize,
24 pub column: usize,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct CrateUsage {
30 pub name: String,
32 pub is_direct: bool,
34 pub total_calls: usize,
36 pub methods: HashMap<String, MethodCall>,
38}
39
40impl CrateUsage {
41 pub fn new(name: String, is_direct: bool) -> Self {
42 Self {
43 name,
44 is_direct,
45 total_calls: 0,
46 methods: HashMap::new(),
47 }
48 }
49
50 pub fn add_call(&mut self, method_path: String, call_site: CallSite) {
52 self.total_calls += 1;
53
54 let method = self.methods.entry(method_path.clone()).or_insert_with(|| {
55 MethodCall {
56 path: method_path,
57 call_count: 0,
58 avg_time_ns: None,
59 call_sites: Vec::new(),
60 }
61 });
62
63 method.call_count += 1;
64 method.call_sites.push(call_site);
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct ProjectAnalysis {
71 pub root_crate: String,
73 pub crates: HashMap<String, CrateUsage>,
75 pub total_calls: usize,
77}
78
79impl ProjectAnalysis {
80 pub fn new(root_crate: String) -> Self {
81 Self {
82 root_crate,
83 crates: HashMap::new(),
84 total_calls: 0,
85 }
86 }
87
88 pub fn get_or_create_crate(&mut self, name: String, is_direct: bool) -> &mut CrateUsage {
90 self.crates
91 .entry(name.clone())
92 .or_insert_with(|| CrateUsage::new(name, is_direct))
93 }
94
95 pub fn recalculate_totals(&mut self) {
97 self.total_calls = self.crates.values().map(|c| c.total_calls).sum();
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq)]
103pub struct TreeMapRect {
104 pub x: f64,
105 pub y: f64,
106 pub width: f64,
107 pub height: f64,
108}
109
110impl TreeMapRect {
111 pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
112 Self { x, y, width, height }
113 }
114
115 pub fn area(&self) -> f64 {
116 self.width * self.height
117 }
118
119 pub fn aspect_ratio(&self) -> f64 {
120 let min = self.width.min(self.height);
121 let max = self.width.max(self.height);
122 if min == 0.0 {
123 f64::INFINITY
124 } else {
125 max / min
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq)]
132pub struct TreeMapNode {
133 pub label: String,
135 pub value: usize,
137 pub time_ns: Option<u64>,
139 pub children: Vec<TreeMapNode>,
141 pub rect: Option<TreeMapRect>,
143}
144
145impl TreeMapNode {
146 pub fn new_crate(name: String, value: usize, children: Vec<TreeMapNode>) -> Self {
147 Self {
148 label: name,
149 value,
150 time_ns: None,
151 children,
152 rect: None,
153 }
154 }
155
156 pub fn new_method(path: String, call_count: usize, time_ns: Option<u64>) -> Self {
157 Self {
158 label: path,
159 value: call_count,
160 time_ns,
161 children: Vec::new(),
162 rect: None,
163 }
164 }
165
166 pub fn from_analysis(analysis: &ProjectAnalysis) -> Vec<TreeMapNode> {
168 let mut nodes = Vec::new();
169
170 for (crate_name, crate_usage) in &analysis.crates {
171 let method_nodes: Vec<TreeMapNode> = crate_usage
172 .methods
173 .values()
174 .map(|method| {
175 TreeMapNode::new_method(
176 method.path.clone(),
177 method.call_count,
178 method.avg_time_ns,
179 )
180 })
181 .collect();
182
183 nodes.push(TreeMapNode::new_crate(
184 crate_name.clone(),
185 crate_usage.total_calls,
186 method_nodes,
187 ));
188 }
189
190 nodes.sort_by(|a, b| b.value.cmp(&a.value));
192 nodes
193 }
194}