profile_inspect/analysis/
caller_callee.rs1use std::collections::HashMap;
2
3use crate::ir::{FrameId, ProfileIR};
4
5#[derive(Debug, Clone)]
7pub struct CallerStats {
8 pub frame_id: FrameId,
10 pub name: String,
12 pub location: String,
14 pub call_count: u32,
16 pub time: u64,
18}
19
20#[derive(Debug, Clone)]
22pub struct CalleeStats {
23 pub frame_id: FrameId,
25 pub name: String,
27 pub location: String,
29 pub call_count: u32,
31 pub self_time: u64,
33 pub total_time: u64,
35}
36
37#[derive(Debug)]
39pub struct CallerCalleeAnalysis {
40 pub target_frame_id: FrameId,
42 pub target_name: String,
44 pub target_location: String,
46 pub target_self_time: u64,
48 pub target_total_time: u64,
50 pub callers: Vec<CallerStats>,
52 pub callees: Vec<CalleeStats>,
54}
55
56pub struct CallerCalleeAnalyzer {
58 top_n: usize,
60}
61
62impl CallerCalleeAnalyzer {
63 pub fn new() -> Self {
65 Self { top_n: 20 }
66 }
67
68 pub fn top_n(mut self, n: usize) -> Self {
70 self.top_n = n;
71 self
72 }
73
74 pub fn analyze(
76 &self,
77 profile: &ProfileIR,
78 target_frame_id: FrameId,
79 ) -> Option<CallerCalleeAnalysis> {
80 let target_frame = profile.get_frame(target_frame_id)?;
81
82 let mut caller_times: HashMap<FrameId, u64> = HashMap::new();
83 let mut caller_counts: HashMap<FrameId, u32> = HashMap::new();
84 let mut callee_self_times: HashMap<FrameId, u64> = HashMap::new();
85 let mut callee_total_times: HashMap<FrameId, u64> = HashMap::new();
86 let mut callee_counts: HashMap<FrameId, u32> = HashMap::new();
87 let mut target_self_time: u64 = 0;
88 let mut target_total_time: u64 = 0;
89
90 for sample in &profile.samples {
92 let weight = sample.weight;
93
94 if let Some(stack) = profile.get_stack(sample.stack_id) {
95 let target_idx = stack.frames.iter().position(|&f| f == target_frame_id);
97
98 if let Some(idx) = target_idx {
99 target_total_time += weight;
100
101 if idx == stack.frames.len() - 1 {
103 target_self_time += weight;
104 }
105
106 if idx > 0 {
108 let caller_id = stack.frames[idx - 1];
109 *caller_times.entry(caller_id).or_default() += weight;
110 *caller_counts.entry(caller_id).or_default() += 1;
111 }
112
113 if idx < stack.frames.len() - 1 {
115 let callee_id = stack.frames[idx + 1];
117 *callee_counts.entry(callee_id).or_default() += 1;
118 *callee_total_times.entry(callee_id).or_default() += weight;
120
121 if let Some(&leaf_id) = stack.frames.last() {
123 if leaf_id == callee_id {
124 *callee_self_times.entry(callee_id).or_default() += weight;
125 }
126 }
127 }
128 }
129 }
130 }
131
132 let mut callers: Vec<CallerStats> = caller_times
134 .iter()
135 .filter_map(|(&fid, &time)| {
136 let frame = profile.get_frame(fid)?;
137 Some(CallerStats {
138 frame_id: fid,
139 name: frame.display_name().to_string(),
140 location: frame.location(),
141 call_count: caller_counts.get(&fid).copied().unwrap_or(0),
142 time,
143 })
144 })
145 .collect();
146
147 callers.sort_by(|a, b| b.time.cmp(&a.time).then_with(|| a.name.cmp(&b.name)));
148 callers.truncate(self.top_n);
149
150 let mut callees: Vec<CalleeStats> = callee_total_times
152 .iter()
153 .filter_map(|(&fid, &total_time)| {
154 let frame = profile.get_frame(fid)?;
155 Some(CalleeStats {
156 frame_id: fid,
157 name: frame.display_name().to_string(),
158 location: frame.location(),
159 call_count: callee_counts.get(&fid).copied().unwrap_or(0),
160 self_time: callee_self_times.get(&fid).copied().unwrap_or(0),
161 total_time,
162 })
163 })
164 .collect();
165
166 callees.sort_by(|a, b| {
167 b.total_time
168 .cmp(&a.total_time)
169 .then_with(|| a.name.cmp(&b.name))
170 });
171 callees.truncate(self.top_n);
172
173 Some(CallerCalleeAnalysis {
174 target_frame_id,
175 target_name: target_frame.display_name().to_string(),
176 target_location: target_frame.location(),
177 target_self_time,
178 target_total_time,
179 callers,
180 callees,
181 })
182 }
183
184 pub fn find_frame_by_name<'a>(
186 profile: &'a ProfileIR,
187 name: &str,
188 ) -> Option<&'a crate::ir::Frame> {
189 profile
190 .frames
191 .iter()
192 .find(|f| f.name.contains(name) || f.display_name().contains(name))
193 }
194}
195
196impl Default for CallerCalleeAnalyzer {
197 fn default() -> Self {
198 Self::new()
199 }
200}