use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum GoldenTraceError {
NoBaseline,
IoError(String),
ParseError(String),
InvalidTrace(String),
VersionMismatch { expected: String, actual: String },
}
impl std::fmt::Display for GoldenTraceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoBaseline => write!(f, "No golden baseline exists"),
Self::IoError(msg) => write!(f, "IO error: {}", msg),
Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
Self::InvalidTrace(msg) => write!(f, "Invalid trace: {}", msg),
Self::VersionMismatch { expected, actual } => {
write!(f, "Version mismatch: expected {}, got {}", expected, actual)
}
}
}
}
impl std::error::Error for GoldenTraceError {}
pub type GoldenTraceResult<T> = Result<T, GoldenTraceError>;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyscallBreakdown {
pub read_count: u64,
pub write_count: u64,
pub mmap_count: u64,
pub futex_count: u64,
pub other_count: u64,
}
impl SyscallBreakdown {
pub fn new() -> Self {
Self::default()
}
pub fn total(&self) -> u64 {
self.read_count + self.write_count + self.mmap_count + self.futex_count + self.other_count
}
pub fn percentage_diff(&self, baseline: &SyscallBreakdown) -> SyscallBreakdownDelta {
SyscallBreakdownDelta {
read_delta: Self::calc_delta(self.read_count, baseline.read_count),
write_delta: Self::calc_delta(self.write_count, baseline.write_count),
mmap_delta: Self::calc_delta(self.mmap_count, baseline.mmap_count),
futex_delta: Self::calc_delta(self.futex_count, baseline.futex_count),
other_delta: Self::calc_delta(self.other_count, baseline.other_count),
total_delta: Self::calc_delta(self.total(), baseline.total()),
}
}
fn calc_delta(current: u64, baseline: u64) -> f64 {
if baseline == 0 {
if current == 0 {
0.0
} else {
100.0 }
} else {
((current as f64 - baseline as f64) / baseline as f64) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct SyscallBreakdownDelta {
pub read_delta: f64,
pub write_delta: f64,
pub mmap_delta: f64,
pub futex_delta: f64,
pub other_delta: f64,
pub total_delta: f64,
}
impl SyscallBreakdownDelta {
pub fn max_delta(&self) -> f64 {
self.read_delta
.abs()
.max(self.write_delta.abs())
.max(self.mmap_delta.abs())
.max(self.futex_delta.abs())
.max(self.other_delta.abs())
}
pub fn exceeds_threshold(&self, threshold_percent: f64) -> bool {
self.read_delta.abs() > threshold_percent
|| self.write_delta.abs() > threshold_percent
|| self.mmap_delta.abs() > threshold_percent
|| self.futex_delta.abs() > threshold_percent
|| self.other_delta.abs() > threshold_percent
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceMetrics {
pub total_time_us: f64,
pub p50_latency_us: f64,
pub p99_latency_us: f64,
pub throughput: f64,
pub peak_memory_bytes: u64,
pub syscalls: SyscallBreakdown,
#[serde(default)]
pub custom: HashMap<String, f64>,
}
impl Default for TraceMetrics {
fn default() -> Self {
Self {
total_time_us: 0.0,
p50_latency_us: 0.0,
p99_latency_us: 0.0,
throughput: 0.0,
peak_memory_bytes: 0,
syscalls: SyscallBreakdown::default(),
custom: HashMap::new(),
}
}
}
impl TraceMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn total_time_us(mut self, us: f64) -> Self {
self.total_time_us = us;
self
}
pub fn p50_latency_us(mut self, us: f64) -> Self {
self.p50_latency_us = us;
self
}
pub fn p99_latency_us(mut self, us: f64) -> Self {
self.p99_latency_us = us;
self
}
pub fn throughput(mut self, ops_per_sec: f64) -> Self {
self.throughput = ops_per_sec;
self
}
pub fn peak_memory_bytes(mut self, bytes: u64) -> Self {
self.peak_memory_bytes = bytes;
self
}
pub fn syscalls(mut self, syscalls: SyscallBreakdown) -> Self {
self.syscalls = syscalls;
self
}
pub fn with_custom(mut self, key: &str, value: f64) -> Self {
self.custom.insert(key.to_string(), value);
self
}
pub fn is_valid(&self) -> bool {
self.total_time_us >= 0.0
&& self.p50_latency_us >= 0.0
&& self.p99_latency_us >= 0.0
&& self.throughput >= 0.0
&& !self.total_time_us.is_nan()
&& !self.p50_latency_us.is_nan()
&& !self.p99_latency_us.is_nan()
&& !self.throughput.is_nan()
}
}