1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use std::collections::HashMap;
use crate::ir::{FrameId, ProfileIR};
/// Statistics about a caller of a function
#[derive(Debug, Clone)]
pub struct CallerStats {
/// Frame ID of the caller
pub frame_id: FrameId,
/// Caller function name
pub name: String,
/// Caller source location
pub location: String,
/// Number of times this caller called the target function
pub call_count: u32,
/// Time attributed to calls from this caller (time in target when called from this caller)
pub time: u64,
}
/// Statistics about a callee of a function
#[derive(Debug, Clone)]
pub struct CalleeStats {
/// Frame ID of the callee
pub frame_id: FrameId,
/// Callee function name
pub name: String,
/// Callee source location
pub location: String,
/// Number of times the target function called this callee
pub call_count: u32,
/// Self time of this callee when called by target
pub self_time: u64,
/// Total inclusive time of this callee subtree when called by target
pub total_time: u64,
}
/// Caller/callee analysis for a specific function
#[derive(Debug)]
pub struct CallerCalleeAnalysis {
/// The target function being analyzed
pub target_frame_id: FrameId,
/// Target function name
pub target_name: String,
/// Target function location
pub target_location: String,
/// Self time of the target function
pub target_self_time: u64,
/// Total time of the target function
pub target_total_time: u64,
/// Functions that call this function
pub callers: Vec<CallerStats>,
/// Functions that this function calls
pub callees: Vec<CalleeStats>,
}
/// Analyzer for caller/callee relationships
pub struct CallerCalleeAnalyzer {
/// Maximum number of callers/callees to return
top_n: usize,
}
impl CallerCalleeAnalyzer {
/// Create a new analyzer
pub fn new() -> Self {
Self { top_n: 20 }
}
/// Set maximum number of results
pub fn top_n(mut self, n: usize) -> Self {
self.top_n = n;
self
}
/// Analyze callers and callees for a specific frame
pub fn analyze(
&self,
profile: &ProfileIR,
target_frame_id: FrameId,
) -> Option<CallerCalleeAnalysis> {
let target_frame = profile.get_frame(target_frame_id)?;
let mut caller_times: HashMap<FrameId, u64> = HashMap::new();
let mut caller_counts: HashMap<FrameId, u32> = HashMap::new();
let mut callee_self_times: HashMap<FrameId, u64> = HashMap::new();
let mut callee_total_times: HashMap<FrameId, u64> = HashMap::new();
let mut callee_counts: HashMap<FrameId, u32> = HashMap::new();
let mut target_self_time: u64 = 0;
let mut target_total_time: u64 = 0;
// Analyze caller/callee relationships
for sample in &profile.samples {
let weight = sample.weight;
if let Some(stack) = profile.get_stack(sample.stack_id) {
// Find target in the stack
let target_idx = stack.frames.iter().position(|&f| f == target_frame_id);
if let Some(idx) = target_idx {
target_total_time += weight;
// Check if target is the leaf (self time)
if idx == stack.frames.len() - 1 {
target_self_time += weight;
}
// Record caller (frame before target in stack)
if idx > 0 {
let caller_id = stack.frames[idx - 1];
*caller_times.entry(caller_id).or_default() += weight;
*caller_counts.entry(caller_id).or_default() += 1;
}
// Record callees (frames after target in stack)
if idx < stack.frames.len() - 1 {
// Direct callee
let callee_id = stack.frames[idx + 1];
*callee_counts.entry(callee_id).or_default() += 1;
// Total time for this callee subtree (sample weight)
*callee_total_times.entry(callee_id).or_default() += weight;
// Self time: only when callee is the leaf
if let Some(&leaf_id) = stack.frames.last() {
if leaf_id == callee_id {
*callee_self_times.entry(callee_id).or_default() += weight;
}
}
}
}
}
}
// Build caller stats
let mut callers: Vec<CallerStats> = caller_times
.iter()
.filter_map(|(&fid, &time)| {
let frame = profile.get_frame(fid)?;
Some(CallerStats {
frame_id: fid,
name: frame.display_name().to_string(),
location: frame.location(),
call_count: caller_counts.get(&fid).copied().unwrap_or(0),
time,
})
})
.collect();
callers.sort_by(|a, b| b.time.cmp(&a.time).then_with(|| a.name.cmp(&b.name)));
callers.truncate(self.top_n);
// Build callee stats
let mut callees: Vec<CalleeStats> = callee_total_times
.iter()
.filter_map(|(&fid, &total_time)| {
let frame = profile.get_frame(fid)?;
Some(CalleeStats {
frame_id: fid,
name: frame.display_name().to_string(),
location: frame.location(),
call_count: callee_counts.get(&fid).copied().unwrap_or(0),
self_time: callee_self_times.get(&fid).copied().unwrap_or(0),
total_time,
})
})
.collect();
callees.sort_by(|a, b| {
b.total_time
.cmp(&a.total_time)
.then_with(|| a.name.cmp(&b.name))
});
callees.truncate(self.top_n);
Some(CallerCalleeAnalysis {
target_frame_id,
target_name: target_frame.display_name().to_string(),
target_location: target_frame.location(),
target_self_time,
target_total_time,
callers,
callees,
})
}
/// Find a frame by function name (partial match)
pub fn find_frame_by_name<'a>(
profile: &'a ProfileIR,
name: &str,
) -> Option<&'a crate::ir::Frame> {
profile
.frames
.iter()
.find(|f| f.name.contains(name) || f.display_name().contains(name))
}
}
impl Default for CallerCalleeAnalyzer {
fn default() -> Self {
Self::new()
}
}