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