1use std::collections::HashMap;
2use std::fmt::Display;
3
4#[derive(Debug, Clone)]
5pub struct FunctionCallStats {
6 pub name: String,
7 pub start: std::time::Instant,
8 pub end: std::time::Instant,
9 pub start_var_env: Option<std::time::Instant>,
10 pub end_var_env: Option<std::time::Instant>,
11}
12
13impl FunctionCallStats {
14 pub fn total_duration_ms(&self) -> f64 {
15 (self.end - self.start).as_secs_f64() * 1_000.0
16 }
17
18 pub fn var_env_duration_ms(&self) -> Option<f64> {
19 self.start_var_env.and_then(|start| {
20 self.end_var_env
21 .map(|end| (end - start).as_secs_f64() * 1_000.0)
22 })
23 }
24
25 pub fn body_duration_ms(&self) -> f64 {
26 let var_env = self.var_env_duration_ms().unwrap_or(0.0);
27 self.total_duration_ms() - var_env
28 }
29}
30
31impl Display for FunctionCallStats {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 if self.start_var_env.is_some() && self.end_var_env.is_some() {
34 write!(
35 f,
36 "FunctionCallStats {{ name: {}, duration: {}ms, var_env_duration: {}ms }}",
37 self.name,
38 self.total_duration_ms(),
39 self.var_env_duration_ms().unwrap()
40 )
41 } else {
42 write!(
43 f,
44 "FunctionCallStats {{ name: {}, duration: {}ms, var_env_duration: None (built-in) }}",
45 self.name,
46 self.total_duration_ms()
47 )
48 }
49 }
50}
51
52#[derive(Debug, Default)]
53pub struct ProfilingSummary {
54 pub total_calls: usize,
55 pub lambda_calls: usize,
56 pub builtin_calls: usize,
57 pub total_time_ms: f64,
58 pub var_env_time_ms: f64,
59 pub body_time_ms: f64,
60 pub calls_by_name: HashMap<String, CallStats>,
61}
62
63#[derive(Debug, Clone)]
64pub struct CallStats {
65 pub count: usize,
66 pub total_ms: f64,
67 pub var_env_ms: f64,
68 pub body_ms: f64,
69 pub avg_ms: f64,
70 pub min_ms: f64,
71 pub max_ms: f64,
72}
73
74impl ProfilingSummary {
75 pub fn from_stats(stats: &[FunctionCallStats]) -> Self {
76 let mut summary = ProfilingSummary {
77 total_calls: stats.len(),
78 ..Default::default()
79 };
80
81 let mut calls_by_name: HashMap<String, Vec<&FunctionCallStats>> = HashMap::new();
82 for stat in stats {
83 calls_by_name
84 .entry(stat.name.clone())
85 .or_default()
86 .push(stat);
87 }
88
89 for (name, calls) in calls_by_name.iter() {
90 let is_lambda = calls.iter().any(|c| c.var_env_duration_ms().is_some());
91 if is_lambda {
92 summary.lambda_calls += calls.len();
93 } else {
94 summary.builtin_calls += calls.len();
95 }
96
97 let mut total_ms = 0.0_f64;
98 let mut var_env_ms = 0.0_f64;
99 let mut body_ms = 0.0_f64;
100 let mut min_ms = f64::INFINITY;
101 let mut max_ms = 0.0_f64;
102
103 for call in calls.iter() {
104 let call_total = call.total_duration_ms();
105 let call_var_env = call.var_env_duration_ms().unwrap_or(0.0);
106 let call_body = call.body_duration_ms();
107
108 total_ms += call_total;
109 var_env_ms += call_var_env;
110 body_ms += call_body;
111 min_ms = min_ms.min(call_total);
112 max_ms = max_ms.max(call_total);
113 }
114
115 let count = calls.len();
116 summary.calls_by_name.insert(
117 name.clone(),
118 CallStats {
119 count,
120 total_ms,
121 var_env_ms,
122 body_ms,
123 avg_ms: total_ms / count as f64,
124 min_ms,
125 max_ms,
126 },
127 );
128 }
129
130 for stats in summary.calls_by_name.values() {
132 summary.total_time_ms += stats.total_ms;
133 summary.var_env_time_ms += stats.var_env_ms;
134 summary.body_time_ms += stats.body_ms;
135 }
136
137 summary
138 }
139
140 pub fn print_summary(&self) {
141 println!("\n=== Performance Profile ===");
142 println!("Total function calls: {}", self.total_calls);
143 println!(" Lambda calls: {}", self.lambda_calls);
144 println!(" Built-in calls: {}", self.builtin_calls);
145 println!();
146 println!("Total time: {:.3}ms", self.total_time_ms);
147 println!(
148 " Variable environment setup: {:.3}ms ({:.1}%)",
149 self.var_env_time_ms,
150 if self.total_time_ms > 0.0 {
151 (self.var_env_time_ms / self.total_time_ms) * 100.0
152 } else {
153 0.0
154 }
155 );
156 println!(
157 " Function body execution: {:.3}ms ({:.1}%)",
158 self.body_time_ms,
159 if self.total_time_ms > 0.0 {
160 (self.body_time_ms / self.total_time_ms) * 100.0
161 } else {
162 0.0
163 }
164 );
165 println!();
166
167 if !self.calls_by_name.is_empty() {
168 println!("Top functions by total time:");
169 let mut sorted: Vec<_> = self.calls_by_name.iter().collect();
170 sorted.sort_by(|a, b| b.1.total_ms.partial_cmp(&a.1.total_ms).unwrap());
171
172 for (name, stats) in sorted.iter().take(10) {
173 let var_env_pct = if stats.total_ms > 0.0 {
174 (stats.var_env_ms / stats.total_ms) * 100.0
175 } else {
176 0.0
177 };
178 println!(
179 " {}: {} calls, {:.3}ms total (avg {:.3}ms, min {:.3}ms, max {:.3}ms)",
180 name, stats.count, stats.total_ms, stats.avg_ms, stats.min_ms, stats.max_ms
181 );
182 if stats.var_env_ms > 0.0 {
183 println!(
184 " └─ var_env: {:.3}ms ({:.1}%), body: {:.3}ms ({:.1}%)",
185 stats.var_env_ms,
186 var_env_pct,
187 stats.body_ms,
188 100.0 - var_env_pct
189 );
190 }
191 }
192 }
193 }
194}