use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Frame {
pub ip: u64,
pub function: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub module: Option<String>,
}
impl Frame {
pub fn new_unresolved(ip: u64) -> Self {
Self {
ip,
function: None,
file: None,
line: None,
module: None,
}
}
pub fn is_symbolized(&self) -> bool {
self.function.is_some()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Stack {
pub frames: Vec<Frame>,
}
impl Stack {
pub fn from_ips(ips: &[u64]) -> Self {
Self {
frames: ips.iter().map(|&ip| Frame::new_unresolved(ip)).collect(),
}
}
pub fn from_ips_with_symbols(ips: &[u64], symbols: &[Option<String>]) -> Self {
Self {
frames: ips
.iter()
.enumerate()
.map(|(i, &ip)| {
let function = symbols.get(i).and_then(|s| s.clone());
Frame {
ip,
function,
file: None,
line: None,
module: None,
}
})
.collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Profile {
pub start_time: u64,
pub end_time: u64,
pub samples: HashMap<Stack, u64>,
pub total_samples: u64,
pub sample_period_ns: u64,
}
impl Profile {
pub fn new(start_time: u64, end_time: u64, sample_period_ns: u64) -> Self {
Self {
start_time,
end_time,
samples: HashMap::new(),
total_samples: 0,
sample_period_ns,
}
}
pub fn add_sample(&mut self, stack: Stack) {
*self.samples.entry(stack).or_insert(0) += 1;
self.total_samples += 1;
}
pub fn duration_ns(&self) -> u64 {
self.end_time.saturating_sub(self.start_time)
}
pub fn sampling_rate_hz(&self) -> f64 {
if self.sample_period_ns == 0 {
0.0
} else {
1_000_000_000.0 / self.sample_period_ns as f64
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockContentionStats {
pub count: u64,
pub total_wait_ns: u64,
pub max_wait_ns: u64,
pub min_wait_ns: u64,
}
impl Default for LockContentionStats {
fn default() -> Self {
Self {
count: 0,
total_wait_ns: 0,
max_wait_ns: 0,
min_wait_ns: u64::MAX,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockProfile {
pub start_time: u64,
pub end_time: u64,
pub contentions: HashMap<(u64, Stack), LockContentionStats>,
pub total_events: u64,
}
impl LockProfile {
pub fn new(start_time: u64) -> Self {
Self {
start_time,
end_time: 0,
contentions: HashMap::new(),
total_events: 0,
}
}
pub fn add_contention(&mut self, lock_addr: u64, stack: Stack, wait_ns: u64) {
let stats = self.contentions.entry((lock_addr, stack)).or_default();
stats.count += 1;
stats.total_wait_ns += wait_ns;
stats.max_wait_ns = stats.max_wait_ns.max(wait_ns);
stats.min_wait_ns = stats.min_wait_ns.min(wait_ns);
self.total_events += 1;
}
pub fn as_weighted_stacks(&self) -> HashMap<Stack, u64> {
let mut stacks = HashMap::new();
for ((_, stack), stats) in &self.contentions {
*stacks.entry(stack.clone()).or_insert(0) += stats.total_wait_ns;
}
stacks
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallStats {
pub syscall_id: u32,
pub name: String,
pub count: u64,
pub total_duration_ns: u64,
pub max_duration_ns: u64,
pub min_duration_ns: u64,
pub error_count: u64,
pub latency_histogram: Vec<u64>,
}
impl SyscallStats {
pub fn new(id: u32, name: String) -> Self {
Self {
syscall_id: id,
name,
count: 0,
total_duration_ns: 0,
max_duration_ns: 0,
min_duration_ns: u64::MAX,
error_count: 0,
latency_histogram: vec![0; 30],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallProfile {
pub start_time: u64,
pub end_time: u64,
pub syscalls: HashMap<u32, SyscallStats>,
pub total_events: u64,
}
impl SyscallProfile {
pub fn new(start_time: u64) -> Self {
Self {
start_time,
end_time: 0,
syscalls: HashMap::new(),
total_events: 0,
}
}
pub fn add_syscall(&mut self, id: u32, name: &str, duration_ns: u64, return_value: i64) {
let stats = self
.syscalls
.entry(id)
.or_insert_with(|| SyscallStats::new(id, name.to_string()));
stats.count += 1;
stats.total_duration_ns += duration_ns;
stats.max_duration_ns = stats.max_duration_ns.max(duration_ns);
stats.min_duration_ns = stats.min_duration_ns.min(duration_ns);
if return_value < 0 {
stats.error_count += 1;
}
let bucket = if duration_ns == 0 {
0
} else {
(63 - duration_ns.leading_zeros()).min(29) as usize
};
stats.latency_histogram[bucket] += 1;
self.total_events += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profile_add_sample() {
let mut profile = Profile::new(0, 1000, 10_000_000);
let stack = Stack::from_ips(&[0x400000, 0x400100]);
profile.add_sample(stack.clone());
profile.add_sample(stack.clone());
assert_eq!(profile.total_samples, 2);
assert_eq!(*profile.samples.get(&stack).unwrap(), 2);
}
#[test]
fn test_sampling_rate_calculation() {
let profile = Profile::new(0, 1000, 10_000_000); assert!((profile.sampling_rate_hz() - 100.0).abs() < 0.01);
}
#[test]
fn test_sampling_rate_zero_period() {
let profile = Profile::new(0, 1000, 0);
assert_eq!(profile.sampling_rate_hz(), 0.0);
}
#[test]
fn test_duration_ns() {
let profile = Profile::new(1000, 5000, 0);
assert_eq!(profile.duration_ns(), 4000);
}
#[test]
fn test_frame_new_unresolved() {
let frame = Frame::new_unresolved(0xdeadbeef);
assert_eq!(frame.ip, 0xdeadbeef);
assert!(!frame.is_symbolized());
}
#[test]
fn test_frame_is_symbolized() {
let mut frame = Frame::new_unresolved(0x1000);
assert!(!frame.is_symbolized());
frame.function = Some("main".to_string());
assert!(frame.is_symbolized());
}
#[test]
fn test_stack_from_ips() {
let stack = Stack::from_ips(&[0x1000, 0x2000, 0x3000]);
assert_eq!(stack.frames.len(), 3);
assert_eq!(stack.frames[0].ip, 0x1000);
assert_eq!(stack.frames[2].ip, 0x3000);
assert!(stack.frames.iter().all(|f| !f.is_symbolized()));
}
#[test]
fn test_profile_multiple_unique_stacks() {
let mut profile = Profile::new(0, 1000, 10_000_000);
profile.add_sample(Stack::from_ips(&[0x1000]));
profile.add_sample(Stack::from_ips(&[0x2000]));
profile.add_sample(Stack::from_ips(&[0x1000]));
assert_eq!(profile.total_samples, 3);
assert_eq!(profile.samples.len(), 2);
assert_eq!(
*profile.samples.get(&Stack::from_ips(&[0x1000])).unwrap(),
2
);
assert_eq!(
*profile.samples.get(&Stack::from_ips(&[0x2000])).unwrap(),
1
);
}
}