1use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8use super::profile::{LockProfile, Profile, Stack, SyscallProfile};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct CpuDiff {
15 pub baseline_total: u64,
16 pub comparison_total: u64,
17 pub stacks: Vec<StackDiff>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct StackDiff {
23 pub stack: Stack,
24 pub baseline_count: u64,
25 pub comparison_count: u64,
26 pub delta: i64,
28 pub delta_pct: f64,
30}
31
32pub fn diff_cpu(baseline: &Profile, comparison: &Profile) -> CpuDiff {
34 let all_stacks: HashSet<&Stack> = baseline
35 .samples
36 .keys()
37 .chain(comparison.samples.keys())
38 .collect();
39
40 let mut stacks: Vec<StackDiff> = all_stacks
41 .into_iter()
42 .map(|stack| {
43 let b = baseline.samples.get(stack).copied().unwrap_or(0);
44 let c = comparison.samples.get(stack).copied().unwrap_or(0);
45 let delta = c as i64 - b as i64;
46 let delta_pct = if b > 0 {
47 delta as f64 / b as f64 * 100.0
48 } else {
49 0.0
50 };
51 StackDiff {
52 stack: stack.clone(),
53 baseline_count: b,
54 comparison_count: c,
55 delta,
56 delta_pct,
57 }
58 })
59 .collect();
60
61 stacks.sort_by(|a, b| b.delta.unsigned_abs().cmp(&a.delta.unsigned_abs()));
62
63 CpuDiff {
64 baseline_total: baseline.total_samples,
65 comparison_total: comparison.total_samples,
66 stacks,
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SyscallDiff {
75 pub baseline_total: u64,
76 pub comparison_total: u64,
77 pub syscalls: Vec<SyscallStatsDiff>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct SyscallStatsDiff {
82 pub syscall_id: u32,
83 pub name: String,
84 pub baseline_count: u64,
85 pub comparison_count: u64,
86 pub delta_count: i64,
87 pub baseline_avg_ns: f64,
88 pub comparison_avg_ns: f64,
89 pub delta_avg_ns: f64,
90}
91
92pub fn diff_syscall(baseline: &SyscallProfile, comparison: &SyscallProfile) -> SyscallDiff {
94 let all_ids: HashSet<u32> = baseline
95 .syscalls
96 .keys()
97 .chain(comparison.syscalls.keys())
98 .copied()
99 .collect();
100
101 let mut syscalls: Vec<SyscallStatsDiff> = all_ids
102 .into_iter()
103 .map(|id| {
104 let b = baseline.syscalls.get(&id);
105 let c = comparison.syscalls.get(&id);
106
107 let b_count = b.map_or(0, |s| s.count);
108 let c_count = c.map_or(0, |s| s.count);
109 let b_avg = b.map_or(0.0, |s| {
110 if s.count > 0 {
111 s.total_duration_ns as f64 / s.count as f64
112 } else {
113 0.0
114 }
115 });
116 let c_avg = c.map_or(0.0, |s| {
117 if s.count > 0 {
118 s.total_duration_ns as f64 / s.count as f64
119 } else {
120 0.0
121 }
122 });
123 let name = b
124 .map(|s| s.name.clone())
125 .or_else(|| c.map(|s| s.name.clone()))
126 .unwrap_or_default();
127
128 SyscallStatsDiff {
129 syscall_id: id,
130 name,
131 baseline_count: b_count,
132 comparison_count: c_count,
133 delta_count: c_count as i64 - b_count as i64,
134 baseline_avg_ns: b_avg,
135 comparison_avg_ns: c_avg,
136 delta_avg_ns: c_avg - b_avg,
137 }
138 })
139 .collect();
140
141 syscalls.sort_by(|a, b| {
142 b.delta_count
143 .unsigned_abs()
144 .cmp(&a.delta_count.unsigned_abs())
145 });
146
147 SyscallDiff {
148 baseline_total: baseline.total_events,
149 comparison_total: comparison.total_events,
150 syscalls,
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct LockDiff {
159 pub baseline_total: u64,
160 pub comparison_total: u64,
161 pub contentions: Vec<LockContentionDiff>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct LockContentionDiff {
166 pub lock_addr: u64,
167 pub stack: Stack,
168 pub baseline_count: u64,
169 pub comparison_count: u64,
170 pub delta_wait_ns: i64,
171}
172
173pub fn diff_lock(baseline: &LockProfile, comparison: &LockProfile) -> LockDiff {
175 let all_keys: HashSet<&(u64, Stack)> = baseline
176 .contentions
177 .keys()
178 .chain(comparison.contentions.keys())
179 .collect();
180
181 let mut contentions: Vec<LockContentionDiff> = all_keys
182 .into_iter()
183 .map(|key| {
184 let b = baseline.contentions.get(key);
185 let c = comparison.contentions.get(key);
186 let b_wait = b.map_or(0, |s| s.total_wait_ns);
187 let c_wait = c.map_or(0, |s| s.total_wait_ns);
188 LockContentionDiff {
189 lock_addr: key.0,
190 stack: key.1.clone(),
191 baseline_count: b.map_or(0, |s| s.count),
192 comparison_count: c.map_or(0, |s| s.count),
193 delta_wait_ns: c_wait as i64 - b_wait as i64,
194 }
195 })
196 .collect();
197
198 contentions.sort_by(|a, b| {
199 b.delta_wait_ns
200 .unsigned_abs()
201 .cmp(&a.delta_wait_ns.unsigned_abs())
202 });
203
204 LockDiff {
205 baseline_total: baseline.total_events,
206 comparison_total: comparison.total_events,
207 contentions,
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_diff_cpu_basic() {
217 let mut baseline = Profile::new(0, 1000, 10_000_000);
218 let mut comparison = Profile::new(1000, 2000, 10_000_000);
219
220 let stack_a = Stack::from_ips(&[0x1000, 0x2000]);
221 let stack_b = Stack::from_ips(&[0x3000]);
222
223 for _ in 0..10 {
225 baseline.add_sample(stack_a.clone());
226 }
227 for _ in 0..5 {
228 baseline.add_sample(stack_b.clone());
229 }
230
231 for _ in 0..7 {
233 comparison.add_sample(stack_a.clone());
234 }
235 for _ in 0..8 {
236 comparison.add_sample(stack_b.clone());
237 }
238
239 let diff = diff_cpu(&baseline, &comparison);
240 assert_eq!(diff.baseline_total, 15);
241 assert_eq!(diff.comparison_total, 15);
242 assert_eq!(diff.stacks.len(), 2);
243
244 let a_diff = diff.stacks.iter().find(|s| s.stack == stack_a).unwrap();
246 assert_eq!(a_diff.baseline_count, 10);
247 assert_eq!(a_diff.comparison_count, 7);
248 assert_eq!(a_diff.delta, -3);
249 assert!((a_diff.delta_pct - (-30.0)).abs() < 0.01);
250
251 let b_diff = diff.stacks.iter().find(|s| s.stack == stack_b).unwrap();
252 assert_eq!(b_diff.delta, 3);
253 assert!((b_diff.delta_pct - 60.0).abs() < 0.01);
254 }
255
256 #[test]
257 fn test_diff_cpu_new_stack_in_comparison() {
258 let baseline = Profile::new(0, 1000, 10_000_000);
259 let mut comparison = Profile::new(1000, 2000, 10_000_000);
260
261 let stack = Stack::from_ips(&[0x1000]);
262 comparison.add_sample(stack.clone());
263
264 let diff = diff_cpu(&baseline, &comparison);
265 assert_eq!(diff.stacks.len(), 1);
266 assert_eq!(diff.stacks[0].baseline_count, 0);
267 assert_eq!(diff.stacks[0].comparison_count, 1);
268 assert_eq!(diff.stacks[0].delta, 1);
269 assert_eq!(diff.stacks[0].delta_pct, 0.0);
271 }
272
273 #[test]
274 fn test_diff_syscall_basic() {
275 let mut baseline = SyscallProfile::new(0);
276 let mut comparison = SyscallProfile::new(1000);
277
278 for _ in 0..10 {
280 baseline.add_syscall(0, "read", 100, 0);
281 }
282 for _ in 0..20 {
284 comparison.add_syscall(0, "read", 200, 0);
285 }
286
287 let diff = diff_syscall(&baseline, &comparison);
288 assert_eq!(diff.syscalls.len(), 1);
289 let s = &diff.syscalls[0];
290 assert_eq!(s.name, "read");
291 assert_eq!(s.baseline_count, 10);
292 assert_eq!(s.comparison_count, 20);
293 assert_eq!(s.delta_count, 10);
294 assert!((s.baseline_avg_ns - 100.0).abs() < 0.01);
295 assert!((s.comparison_avg_ns - 200.0).abs() < 0.01);
296 assert!((s.delta_avg_ns - 100.0).abs() < 0.01);
297 }
298
299 #[test]
300 fn test_diff_lock_basic() {
301 let mut baseline = LockProfile::new(0);
302 let mut comparison = LockProfile::new(1000);
303
304 let stack = Stack::from_ips(&[0x400000]);
305
306 baseline.add_contention(0x1000, stack.clone(), 500);
307 baseline.add_contention(0x1000, stack.clone(), 300);
308
309 comparison.add_contention(0x1000, stack.clone(), 1000);
310
311 let diff = diff_lock(&baseline, &comparison);
312 assert_eq!(diff.contentions.len(), 1);
313 let c = &diff.contentions[0];
314 assert_eq!(c.lock_addr, 0x1000);
315 assert_eq!(c.baseline_count, 2);
316 assert_eq!(c.comparison_count, 1);
317 assert_eq!(c.delta_wait_ns, 200);
319 }
320}