use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EscalationReason {
CvExceeded,
EfficiencyLow,
Both,
MemoryCliff,
GpuTransferOverhead,
Manual,
}
impl EscalationReason {
pub fn description(&self) -> &'static str {
match self {
EscalationReason::CvExceeded => "CV exceeded threshold (unstable performance)",
EscalationReason::EfficiencyLow => "Efficiency below threshold",
EscalationReason::Both => "Both CV exceeded and efficiency low",
EscalationReason::MemoryCliff => "Memory cliff detected (sudden drop)",
EscalationReason::GpuTransferOverhead => "GPU transfer overhead exceeded threshold",
EscalationReason::Manual => "Manual escalation requested",
}
}
pub fn otlp_value(&self) -> &'static str {
match self {
EscalationReason::CvExceeded => "cv_exceeded",
EscalationReason::EfficiencyLow => "efficiency_low",
EscalationReason::Both => "both",
EscalationReason::MemoryCliff => "memory_cliff",
EscalationReason::GpuTransferOverhead => "gpu_transfer_overhead",
EscalationReason::Manual => "manual",
}
}
}
#[derive(Debug, Clone)]
pub struct EscalationThresholds {
pub cv_threshold: f64,
pub efficiency_threshold: f64,
pub gpu_transfer_threshold: f64,
pub memory_cliff_threshold: f64,
pub rate_limit: u32,
pub rate_interval: Duration,
}
impl Default for EscalationThresholds {
fn default() -> Self {
Self {
cv_threshold: 15.0,
efficiency_threshold: 25.0,
gpu_transfer_threshold: 50.0,
memory_cliff_threshold: 30.0,
rate_limit: 100,
rate_interval: Duration::from_secs(60),
}
}
}
impl EscalationThresholds {
pub fn new() -> Self {
Self::default()
}
pub fn with_cv(mut self, threshold: f64) -> Self {
self.cv_threshold = threshold;
self
}
pub fn with_efficiency(mut self, threshold: f64) -> Self {
self.efficiency_threshold = threshold;
self
}
pub fn with_gpu_transfer(mut self, threshold: f64) -> Self {
self.gpu_transfer_threshold = threshold;
self
}
pub fn with_rate_limit(mut self, limit: u32) -> Self {
self.rate_limit = limit;
self
}
pub fn with_rate_interval(mut self, interval: Duration) -> Self {
self.rate_interval = interval;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct SyscallBreakdown {
pub mmap_us: u64,
pub futex_us: u64,
pub ioctl_us: u64,
pub read_us: u64,
pub write_us: u64,
pub other_us: u64,
pub total_us: u64,
}
impl SyscallBreakdown {
pub fn new() -> Self {
Self::default()
}
pub fn compute_us(&self) -> u64 {
let syscall_total = self.mmap_us
+ self.futex_us
+ self.ioctl_us
+ self.read_us
+ self.write_us
+ self.other_us;
self.total_us.saturating_sub(syscall_total)
}
pub fn syscall_overhead_percent(&self) -> f64 {
if self.total_us == 0 {
return 0.0;
}
let syscall_total = self.mmap_us
+ self.futex_us
+ self.ioctl_us
+ self.read_us
+ self.write_us
+ self.other_us;
(syscall_total as f64 / self.total_us as f64) * 100.0
}
pub fn dominant_syscall(&self) -> &'static str {
let categories = [
(self.mmap_us, "mmap"),
(self.futex_us, "futex"),
(self.ioctl_us, "ioctl"),
(self.read_us, "read"),
(self.write_us, "write"),
(self.other_us, "other"),
];
if categories.iter().all(|(time, _)| *time == 0) {
return "none";
}
categories
.iter()
.max_by_key(|(time, _)| time)
.map(|(_, name)| *name)
.unwrap_or("none")
}
pub fn add_syscall(&mut self, syscall: &str, duration_us: u64) {
match syscall {
"mmap" | "munmap" | "mprotect" | "brk" => {
self.mmap_us = self.mmap_us.saturating_add(duration_us)
}
"futex" => self.futex_us = self.futex_us.saturating_add(duration_us),
"ioctl" => self.ioctl_us = self.ioctl_us.saturating_add(duration_us),
"read" | "pread64" | "readv" => self.read_us = self.read_us.saturating_add(duration_us),
"write" | "pwrite64" | "writev" => {
self.write_us = self.write_us.saturating_add(duration_us)
}
_ => self.other_us = self.other_us.saturating_add(duration_us),
}
}
pub fn as_otlp_attributes(&self) -> HashMap<String, u64> {
let mut attrs = HashMap::new();
attrs.insert("syscall.mmap_us".to_string(), self.mmap_us);
attrs.insert("syscall.futex_us".to_string(), self.futex_us);
attrs.insert("syscall.ioctl_us".to_string(), self.ioctl_us);
attrs.insert("syscall.read_us".to_string(), self.read_us);
attrs.insert("syscall.write_us".to_string(), self.write_us);
attrs.insert("syscall.other_us".to_string(), self.other_us);
attrs.insert("syscall.compute_us".to_string(), self.compute_us());
attrs.insert("syscall.total_us".to_string(), self.total_us);
attrs
}
}
#[derive(Debug, Clone)]
pub struct TraceResult {
pub brick_name: String,
pub budget_us: u64,
pub actual_us: u64,
pub reason: EscalationReason,
pub syscall_breakdown: SyscallBreakdown,
pub timestamp: Instant,
}
impl TraceResult {
pub fn over_budget(&self) -> bool {
self.actual_us > self.budget_us
}
pub fn efficiency(&self) -> f64 {
if self.actual_us == 0 {
return 100.0;
}
(self.budget_us as f64 / self.actual_us as f64) * 100.0
}
pub fn as_otlp_attributes(&self) -> HashMap<String, String> {
let mut attrs = HashMap::new();
attrs.insert("brick.name".to_string(), self.brick_name.clone());
attrs.insert("brick.budget_us".to_string(), self.budget_us.to_string());
attrs.insert("brick.actual_us".to_string(), self.actual_us.to_string());
attrs.insert(
"brick.efficiency".to_string(),
format!("{:.1}", self.efficiency()),
);
attrs.insert(
"brick.over_budget".to_string(),
self.over_budget().to_string(),
);
attrs.insert(
"escalation.reason".to_string(),
self.reason.otlp_value().to_string(),
);
attrs.insert(
"syscall.overhead_percent".to_string(),
format!("{:.1}", self.syscall_breakdown.syscall_overhead_percent()),
);
attrs.insert(
"syscall.dominant".to_string(),
self.syscall_breakdown.dominant_syscall().to_string(),
);
attrs
}
}