use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use super::profile::{LockProfile, Profile, Stack, SyscallProfile};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuDiff {
pub baseline_total: u64,
pub comparison_total: u64,
pub stacks: Vec<StackDiff>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackDiff {
pub stack: Stack,
pub baseline_count: u64,
pub comparison_count: u64,
pub delta: i64,
pub delta_pct: f64,
}
pub fn diff_cpu(baseline: &Profile, comparison: &Profile) -> CpuDiff {
let all_stacks: HashSet<&Stack> = baseline
.samples
.keys()
.chain(comparison.samples.keys())
.collect();
let mut stacks: Vec<StackDiff> = all_stacks
.into_iter()
.map(|stack| {
let b = baseline.samples.get(stack).copied().unwrap_or(0);
let c = comparison.samples.get(stack).copied().unwrap_or(0);
let delta = c as i64 - b as i64;
let delta_pct = if b > 0 {
delta as f64 / b as f64 * 100.0
} else {
0.0
};
StackDiff {
stack: stack.clone(),
baseline_count: b,
comparison_count: c,
delta,
delta_pct,
}
})
.collect();
stacks.sort_by(|a, b| b.delta.unsigned_abs().cmp(&a.delta.unsigned_abs()));
CpuDiff {
baseline_total: baseline.total_samples,
comparison_total: comparison.total_samples,
stacks,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallDiff {
pub baseline_total: u64,
pub comparison_total: u64,
pub syscalls: Vec<SyscallStatsDiff>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallStatsDiff {
pub syscall_id: u32,
pub name: String,
pub baseline_count: u64,
pub comparison_count: u64,
pub delta_count: i64,
pub baseline_avg_ns: f64,
pub comparison_avg_ns: f64,
pub delta_avg_ns: f64,
}
pub fn diff_syscall(baseline: &SyscallProfile, comparison: &SyscallProfile) -> SyscallDiff {
let all_ids: HashSet<u32> = baseline
.syscalls
.keys()
.chain(comparison.syscalls.keys())
.copied()
.collect();
let mut syscalls: Vec<SyscallStatsDiff> = all_ids
.into_iter()
.map(|id| {
let b = baseline.syscalls.get(&id);
let c = comparison.syscalls.get(&id);
let b_count = b.map_or(0, |s| s.count);
let c_count = c.map_or(0, |s| s.count);
let b_avg = b.map_or(0.0, |s| {
if s.count > 0 {
s.total_duration_ns as f64 / s.count as f64
} else {
0.0
}
});
let c_avg = c.map_or(0.0, |s| {
if s.count > 0 {
s.total_duration_ns as f64 / s.count as f64
} else {
0.0
}
});
let name = b
.map(|s| s.name.clone())
.or_else(|| c.map(|s| s.name.clone()))
.unwrap_or_default();
SyscallStatsDiff {
syscall_id: id,
name,
baseline_count: b_count,
comparison_count: c_count,
delta_count: c_count as i64 - b_count as i64,
baseline_avg_ns: b_avg,
comparison_avg_ns: c_avg,
delta_avg_ns: c_avg - b_avg,
}
})
.collect();
syscalls.sort_by(|a, b| {
b.delta_count
.unsigned_abs()
.cmp(&a.delta_count.unsigned_abs())
});
SyscallDiff {
baseline_total: baseline.total_events,
comparison_total: comparison.total_events,
syscalls,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockDiff {
pub baseline_total: u64,
pub comparison_total: u64,
pub contentions: Vec<LockContentionDiff>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockContentionDiff {
pub lock_addr: u64,
pub stack: Stack,
pub baseline_count: u64,
pub comparison_count: u64,
pub delta_wait_ns: i64,
}
pub fn diff_lock(baseline: &LockProfile, comparison: &LockProfile) -> LockDiff {
let all_keys: HashSet<&(u64, Stack)> = baseline
.contentions
.keys()
.chain(comparison.contentions.keys())
.collect();
let mut contentions: Vec<LockContentionDiff> = all_keys
.into_iter()
.map(|key| {
let b = baseline.contentions.get(key);
let c = comparison.contentions.get(key);
let b_wait = b.map_or(0, |s| s.total_wait_ns);
let c_wait = c.map_or(0, |s| s.total_wait_ns);
LockContentionDiff {
lock_addr: key.0,
stack: key.1.clone(),
baseline_count: b.map_or(0, |s| s.count),
comparison_count: c.map_or(0, |s| s.count),
delta_wait_ns: c_wait as i64 - b_wait as i64,
}
})
.collect();
contentions.sort_by(|a, b| {
b.delta_wait_ns
.unsigned_abs()
.cmp(&a.delta_wait_ns.unsigned_abs())
});
LockDiff {
baseline_total: baseline.total_events,
comparison_total: comparison.total_events,
contentions,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diff_cpu_basic() {
let mut baseline = Profile::new(0, 1000, 10_000_000);
let mut comparison = Profile::new(1000, 2000, 10_000_000);
let stack_a = Stack::from_ips(&[0x1000, 0x2000]);
let stack_b = Stack::from_ips(&[0x3000]);
for _ in 0..10 {
baseline.add_sample(stack_a.clone());
}
for _ in 0..5 {
baseline.add_sample(stack_b.clone());
}
for _ in 0..7 {
comparison.add_sample(stack_a.clone());
}
for _ in 0..8 {
comparison.add_sample(stack_b.clone());
}
let diff = diff_cpu(&baseline, &comparison);
assert_eq!(diff.baseline_total, 15);
assert_eq!(diff.comparison_total, 15);
assert_eq!(diff.stacks.len(), 2);
let a_diff = diff.stacks.iter().find(|s| s.stack == stack_a).unwrap();
assert_eq!(a_diff.baseline_count, 10);
assert_eq!(a_diff.comparison_count, 7);
assert_eq!(a_diff.delta, -3);
assert!((a_diff.delta_pct - (-30.0)).abs() < 0.01);
let b_diff = diff.stacks.iter().find(|s| s.stack == stack_b).unwrap();
assert_eq!(b_diff.delta, 3);
assert!((b_diff.delta_pct - 60.0).abs() < 0.01);
}
#[test]
fn test_diff_cpu_new_stack_in_comparison() {
let baseline = Profile::new(0, 1000, 10_000_000);
let mut comparison = Profile::new(1000, 2000, 10_000_000);
let stack = Stack::from_ips(&[0x1000]);
comparison.add_sample(stack.clone());
let diff = diff_cpu(&baseline, &comparison);
assert_eq!(diff.stacks.len(), 1);
assert_eq!(diff.stacks[0].baseline_count, 0);
assert_eq!(diff.stacks[0].comparison_count, 1);
assert_eq!(diff.stacks[0].delta, 1);
assert_eq!(diff.stacks[0].delta_pct, 0.0);
}
#[test]
fn test_diff_syscall_basic() {
let mut baseline = SyscallProfile::new(0);
let mut comparison = SyscallProfile::new(1000);
for _ in 0..10 {
baseline.add_syscall(0, "read", 100, 0);
}
for _ in 0..20 {
comparison.add_syscall(0, "read", 200, 0);
}
let diff = diff_syscall(&baseline, &comparison);
assert_eq!(diff.syscalls.len(), 1);
let s = &diff.syscalls[0];
assert_eq!(s.name, "read");
assert_eq!(s.baseline_count, 10);
assert_eq!(s.comparison_count, 20);
assert_eq!(s.delta_count, 10);
assert!((s.baseline_avg_ns - 100.0).abs() < 0.01);
assert!((s.comparison_avg_ns - 200.0).abs() < 0.01);
assert!((s.delta_avg_ns - 100.0).abs() < 0.01);
}
#[test]
fn test_diff_lock_basic() {
let mut baseline = LockProfile::new(0);
let mut comparison = LockProfile::new(1000);
let stack = Stack::from_ips(&[0x400000]);
baseline.add_contention(0x1000, stack.clone(), 500);
baseline.add_contention(0x1000, stack.clone(), 300);
comparison.add_contention(0x1000, stack.clone(), 1000);
let diff = diff_lock(&baseline, &comparison);
assert_eq!(diff.contentions.len(), 1);
let c = &diff.contentions[0];
assert_eq!(c.lock_addr, 0x1000);
assert_eq!(c.baseline_count, 2);
assert_eq!(c.comparison_count, 1);
assert_eq!(c.delta_wait_ns, 200);
}
}