use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::otlp_exporter::{OtlpConfig, OtlpExporter};
#[derive(Debug, Clone)]
pub struct SyscallEvent {
pub syscall: String,
pub duration: Duration,
pub result: i64,
}
#[derive(Debug, Clone, Copy)]
pub struct BrickEscalationThresholds {
pub cv_percent: f64,
pub efficiency_percent: f64,
pub max_traces_per_sec: u32,
}
impl Default for BrickEscalationThresholds {
fn default() -> Self {
Self { cv_percent: 15.0, efficiency_percent: 25.0, max_traces_per_sec: 100 }
}
}
impl BrickEscalationThresholds {
#[must_use]
pub fn with_cv(mut self, cv_percent: f64) -> Self {
self.cv_percent = cv_percent;
self
}
#[must_use]
pub fn with_efficiency(mut self, efficiency_percent: f64) -> Self {
self.efficiency_percent = efficiency_percent;
self
}
#[must_use]
pub fn with_rate_limit(mut self, max_per_sec: u32) -> Self {
self.max_traces_per_sec = max_per_sec;
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 compute_us: u64,
pub syscall_count: u64,
pub syscall_counts: HashMap<String, u64>,
}
impl SyscallBreakdown {
pub fn from_events(events: &[SyscallEvent], total_duration_us: u64) -> Self {
let mut breakdown = Self::default();
let mut syscall_time_us: u64 = 0;
for event in events {
let duration_us = event.duration.as_micros() as u64;
syscall_time_us += duration_us;
breakdown.syscall_count += 1;
let syscall_name = &event.syscall;
*breakdown.syscall_counts.entry(syscall_name.clone()).or_insert(0) += 1;
match syscall_name.as_str() {
"mmap" | "munmap" | "mprotect" | "brk" => breakdown.mmap_us += duration_us,
"futex" => breakdown.futex_us += duration_us,
"ioctl" => breakdown.ioctl_us += duration_us,
"read" | "pread64" | "readv" => breakdown.read_us += duration_us,
"write" | "pwrite64" | "writev" => breakdown.write_us += duration_us,
_ => breakdown.other_us += duration_us,
}
}
breakdown.compute_us = total_duration_us.saturating_sub(syscall_time_us);
breakdown
}
pub fn syscall_overhead_percent(&self) -> f64 {
let total = self.total_us();
if total == 0 {
0.0
} else {
((total - self.compute_us) as f64 / total as f64) * 100.0
}
}
pub fn total_us(&self) -> u64 {
self.mmap_us
+ self.futex_us
+ self.ioctl_us
+ self.read_us
+ self.write_us
+ self.other_us
+ self.compute_us
}
pub fn dominant_syscall(&self) -> &'static str {
let max = self
.mmap_us
.max(self.futex_us)
.max(self.ioctl_us)
.max(self.read_us)
.max(self.write_us)
.max(self.other_us);
if max == 0 {
"none"
} else if max == self.mmap_us {
"mmap"
} else if max == self.futex_us {
"futex"
} else if max == self.ioctl_us {
"ioctl"
} else if max == self.read_us {
"read"
} else if max == self.write_us {
"write"
} else {
"other"
}
}
}
#[derive(Debug, Clone)]
pub struct BrickMetadata {
pub name: String,
pub budget_us: u64,
pub actual_us: u64,
pub over_budget: bool,
pub efficiency: f64,
pub cv_percent: Option<f64>,
pub score: Option<u8>,
pub grade: Option<char>,
pub assertions_passed: u32,
pub assertions_failed: u32,
pub failed_assertion_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct TracedBrickResult<R> {
pub result: R,
pub duration_us: u64,
pub syscall_breakdown: SyscallBreakdown,
pub metadata: Option<BrickMetadata>,
pub span_id: Option<String>,
pub escalation_reason: Option<EscalationReason>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EscalationReason {
CvExceeded,
EfficiencyLow,
Both,
Manual,
}
impl std::fmt::Display for EscalationReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CvExceeded => write!(f, "cv_exceeded"),
Self::EfficiencyLow => write!(f, "efficiency_low"),
Self::Both => write!(f, "cv_and_efficiency"),
Self::Manual => write!(f, "manual"),
}
}
}
pub struct BrickTracer {
exporter: Option<Arc<OtlpExporter>>,
thresholds: BrickEscalationThresholds,
traces_this_second: AtomicU64,
current_second: AtomicU64,
enabled: bool,
}
impl BrickTracer {
pub fn new(endpoint: &str) -> anyhow::Result<Self> {
let config = OtlpConfig::new(endpoint.to_string(), "brick-tracer".to_string());
let exporter = OtlpExporter::new(config, None)?;
Ok(Self {
exporter: Some(Arc::new(exporter)),
thresholds: BrickEscalationThresholds::default(),
traces_this_second: AtomicU64::new(0),
current_second: AtomicU64::new(0),
enabled: true,
})
}
pub fn new_local() -> Self {
Self {
exporter: None,
thresholds: BrickEscalationThresholds::default(),
traces_this_second: AtomicU64::new(0),
current_second: AtomicU64::new(0),
enabled: true,
}
}
#[must_use]
pub fn with_thresholds(mut self, thresholds: BrickEscalationThresholds) -> Self {
self.thresholds = thresholds;
self
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn should_trace(&self, cv_percent: f64, efficiency_percent: f64) -> bool {
if !self.enabled {
return false;
}
if !self.check_rate_limit() {
return false;
}
cv_percent > self.thresholds.cv_percent
|| efficiency_percent < self.thresholds.efficiency_percent
}
pub fn escalation_reason(&self, cv_percent: f64, efficiency_percent: f64) -> EscalationReason {
let cv_exceeded = cv_percent > self.thresholds.cv_percent;
let eff_low = efficiency_percent < self.thresholds.efficiency_percent;
match (cv_exceeded, eff_low) {
(true, true) => EscalationReason::Both,
(true, false) => EscalationReason::CvExceeded,
(false, true) => EscalationReason::EfficiencyLow,
(false, false) => EscalationReason::Manual,
}
}
fn check_rate_limit(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let current = self.current_second.load(Ordering::Relaxed);
if now == current {
let count = self.traces_this_second.fetch_add(1, Ordering::Relaxed);
count < self.thresholds.max_traces_per_sec as u64
} else {
self.current_second.store(now, Ordering::Relaxed);
self.traces_this_second.store(1, Ordering::Relaxed);
true
}
}
pub fn trace<F, R>(&self, brick_name: &str, budget_us: u64, f: F) -> TracedBrickResult<R>
where
F: FnOnce() -> R,
{
contract_pre_error_handling!(brick_name);
self.trace_with_reason(brick_name, budget_us, EscalationReason::Manual, f)
}
pub fn trace_with_reason<F, R>(
&self,
brick_name: &str,
budget_us: u64,
reason: EscalationReason,
f: F,
) -> TracedBrickResult<R>
where
F: FnOnce() -> R,
{
contract_pre_error_handling!(brick_name);
let start = Instant::now();
let result = f();
let duration = start.elapsed();
let duration_us = duration.as_micros() as u64;
let breakdown = SyscallBreakdown { compute_us: duration_us, ..Default::default() };
let over_budget = duration_us > budget_us;
let efficiency =
if duration_us > 0 { (budget_us as f64 / duration_us as f64).min(1.0) } else { 1.0 };
let metadata = BrickMetadata {
name: brick_name.to_string(),
budget_us,
actual_us: duration_us,
over_budget,
efficiency,
cv_percent: None,
score: None,
grade: None,
assertions_passed: 0,
assertions_failed: 0,
failed_assertion_names: Vec::new(),
};
let span_id = if let Some(ref exporter) = self.exporter {
self.export_brick_span(exporter, &metadata, &breakdown, reason);
Some(format!("{:016x}", rand::random::<u64>()))
} else {
None
};
TracedBrickResult {
result,
duration_us,
syscall_breakdown: breakdown,
metadata: Some(metadata),
span_id,
escalation_reason: Some(reason),
}
}
fn export_brick_span(
&self,
exporter: &OtlpExporter,
metadata: &BrickMetadata,
breakdown: &SyscallBreakdown,
reason: EscalationReason,
) {
use crate::otlp_exporter::ComputeBlock;
let block = ComputeBlock {
operation: Box::leak(format!("brick: {}", metadata.name).into_boxed_str()),
duration_us: metadata.actual_us,
elements: 0, is_slow: metadata.over_budget,
};
exporter.record_compute_block(block);
tracing::info!(
brick.name = %metadata.name,
brick.budget_us = metadata.budget_us,
brick.actual_us = metadata.actual_us,
brick.over_budget = metadata.over_budget,
brick.efficiency = %format!("{:.2}", metadata.efficiency),
syscall.overhead_percent = %format!("{:.1}", breakdown.syscall_overhead_percent()),
syscall.dominant = breakdown.dominant_syscall(),
escalation.reason = %reason,
"ComputeBrick traced"
);
}
pub fn thresholds(&self) -> &BrickEscalationThresholds {
&self.thresholds
}
pub fn has_exporter(&self) -> bool {
self.exporter.is_some()
}
}
impl Default for BrickTracer {
fn default() -> Self {
Self::new_local()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f001_brick_tracer_creates_local() {
let tracer = BrickTracer::new_local();
assert!(tracer.enabled);
assert!(!tracer.has_exporter());
}
#[test]
fn f002_trace_captures_duration() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("TestBrick", 1000, || {
std::thread::sleep(Duration::from_micros(100));
42
});
assert_eq!(result.result, 42);
assert!(result.duration_us >= 100);
}
#[test]
fn f005_brick_name_present() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("MyBrick", 1000, || 42);
assert!(result.metadata.is_some());
assert_eq!(result.metadata.unwrap().name, "MyBrick");
}
#[test]
fn f006_brick_budget_present() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("MyBrick", 5000, || 42);
assert_eq!(result.metadata.unwrap().budget_us, 5000);
}
#[test]
fn f007_brick_actual_present() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("MyBrick", 1000, || {
std::thread::sleep(Duration::from_micros(10));
42
});
assert!(result.metadata.unwrap().actual_us > 0);
}
#[test]
fn f008_brick_efficiency_calculated() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("MyBrick", 10000, || {
std::thread::sleep(Duration::from_micros(100));
42
});
let meta = result.metadata.unwrap();
assert!(meta.efficiency > 0.0);
assert!(meta.efficiency <= 1.0);
}
#[test]
fn f021_should_trace_cv_exceeded() {
let tracer = BrickTracer::new_local();
assert!(tracer.should_trace(15.1, 80.0));
}
#[test]
fn f022_should_not_trace_cv_ok() {
let tracer = BrickTracer::new_local();
assert!(!tracer.should_trace(14.9, 80.0));
}
#[test]
fn f023_should_trace_efficiency_low() {
let tracer = BrickTracer::new_local();
assert!(tracer.should_trace(5.0, 24.9));
}
#[test]
fn f024_should_not_trace_efficiency_ok() {
let tracer = BrickTracer::new_local();
assert!(!tracer.should_trace(5.0, 25.1));
}
#[test]
fn f025_thresholds_configurable() {
let thresholds = BrickEscalationThresholds::default().with_cv(20.0).with_efficiency(30.0);
let tracer = BrickTracer::new_local().with_thresholds(thresholds);
assert!(!tracer.should_trace(15.0, 80.0));
assert!(!tracer.should_trace(5.0, 35.0));
assert!(tracer.should_trace(5.0, 25.0));
}
#[test]
fn f026_escalation_reason_present() {
let tracer = BrickTracer::new_local();
let result = tracer.trace_with_reason("MyBrick", 1000, EscalationReason::CvExceeded, || 42);
assert_eq!(result.escalation_reason, Some(EscalationReason::CvExceeded));
}
#[test]
fn f028_no_trace_when_ok() {
let tracer = BrickTracer::new_local();
assert!(!tracer.should_trace(5.0, 80.0));
}
#[test]
fn f029_trace_cv_high() {
let tracer = BrickTracer::new_local();
assert!(tracer.should_trace(20.0, 80.0));
}
#[test]
fn f030_trace_eff_low() {
let tracer = BrickTracer::new_local();
assert!(tracer.should_trace(5.0, 10.0));
}
#[test]
fn f031_trace_both_bad() {
let tracer = BrickTracer::new_local();
assert!(tracer.should_trace(30.0, 10.0));
}
#[test]
fn f034_should_trace_is_fast() {
let tracer = BrickTracer::new_local();
let start = Instant::now();
for _ in 0..10000 {
let _ = tracer.should_trace(5.0, 80.0);
}
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(10));
}
#[test]
fn test_syscall_breakdown_from_empty() {
let breakdown = SyscallBreakdown::from_events(&[], 1000);
assert_eq!(breakdown.compute_us, 1000);
assert_eq!(breakdown.syscall_count, 0);
}
#[test]
fn test_syscall_breakdown_overhead() {
let mut breakdown = SyscallBreakdown::default();
breakdown.mmap_us = 100;
breakdown.futex_us = 50;
breakdown.compute_us = 850;
assert_eq!(breakdown.total_us(), 1000);
assert!((breakdown.syscall_overhead_percent() - 15.0).abs() < 0.1);
}
#[test]
fn test_syscall_breakdown_dominant() {
let mut breakdown = SyscallBreakdown::default();
breakdown.futex_us = 500;
breakdown.mmap_us = 100;
breakdown.compute_us = 400;
assert_eq!(breakdown.dominant_syscall(), "futex");
}
#[test]
fn test_escalation_reason_display() {
assert_eq!(format!("{}", EscalationReason::CvExceeded), "cv_exceeded");
assert_eq!(format!("{}", EscalationReason::EfficiencyLow), "efficiency_low");
assert_eq!(format!("{}", EscalationReason::Both), "cv_and_efficiency");
assert_eq!(format!("{}", EscalationReason::Manual), "manual");
}
#[test]
fn test_escalation_reason_determination() {
let tracer = BrickTracer::new_local();
assert_eq!(tracer.escalation_reason(20.0, 80.0), EscalationReason::CvExceeded);
assert_eq!(tracer.escalation_reason(5.0, 10.0), EscalationReason::EfficiencyLow);
assert_eq!(tracer.escalation_reason(20.0, 10.0), EscalationReason::Both);
assert_eq!(tracer.escalation_reason(5.0, 80.0), EscalationReason::Manual);
}
#[test]
fn test_default_thresholds() {
let thresholds = BrickEscalationThresholds::default();
assert!((thresholds.cv_percent - 15.0).abs() < 0.001);
assert!((thresholds.efficiency_percent - 25.0).abs() < 0.001);
assert_eq!(thresholds.max_traces_per_sec, 100);
}
#[test]
fn test_tracer_disabled() {
let mut tracer = BrickTracer::new_local();
tracer.set_enabled(false);
assert!(!tracer.should_trace(50.0, 5.0)); }
#[test]
fn test_over_budget_detection() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("SlowBrick", 1, || {
std::thread::sleep(Duration::from_micros(100));
42
});
let meta = result.metadata.unwrap();
assert!(meta.over_budget);
assert!(meta.actual_us > meta.budget_us);
}
#[test]
fn test_under_budget() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("FastBrick", 1_000_000, || 42);
let meta = result.metadata.unwrap();
assert!(!meta.over_budget);
}
#[test]
fn test_syscall_event_creation() {
let event = SyscallEvent {
syscall: "mmap".to_string(),
duration: Duration::from_micros(100),
result: 0,
};
assert_eq!(event.syscall, "mmap");
assert_eq!(event.duration.as_micros(), 100);
assert_eq!(event.result, 0);
}
#[test]
fn test_syscall_breakdown_from_events_categorization() {
let events = vec![
SyscallEvent {
syscall: "mmap".to_string(),
duration: Duration::from_micros(100),
result: 0,
},
SyscallEvent {
syscall: "munmap".to_string(),
duration: Duration::from_micros(50),
result: 0,
},
SyscallEvent {
syscall: "mprotect".to_string(),
duration: Duration::from_micros(30),
result: 0,
},
SyscallEvent {
syscall: "brk".to_string(),
duration: Duration::from_micros(20),
result: 0,
},
SyscallEvent {
syscall: "futex".to_string(),
duration: Duration::from_micros(200),
result: 0,
},
SyscallEvent {
syscall: "ioctl".to_string(),
duration: Duration::from_micros(150),
result: 0,
},
SyscallEvent {
syscall: "read".to_string(),
duration: Duration::from_micros(80),
result: 100,
},
SyscallEvent {
syscall: "pread64".to_string(),
duration: Duration::from_micros(40),
result: 50,
},
SyscallEvent {
syscall: "readv".to_string(),
duration: Duration::from_micros(30),
result: 25,
},
SyscallEvent {
syscall: "write".to_string(),
duration: Duration::from_micros(60),
result: 100,
},
SyscallEvent {
syscall: "pwrite64".to_string(),
duration: Duration::from_micros(35),
result: 50,
},
SyscallEvent {
syscall: "writev".to_string(),
duration: Duration::from_micros(25),
result: 25,
},
SyscallEvent {
syscall: "close".to_string(),
duration: Duration::from_micros(10),
result: 0,
},
];
let breakdown = SyscallBreakdown::from_events(&events, 1500);
assert_eq!(breakdown.mmap_us, 200);
assert_eq!(breakdown.futex_us, 200);
assert_eq!(breakdown.ioctl_us, 150);
assert_eq!(breakdown.read_us, 150);
assert_eq!(breakdown.write_us, 120);
assert_eq!(breakdown.other_us, 10);
assert_eq!(breakdown.compute_us, 670);
assert_eq!(breakdown.syscall_count, 13);
}
#[test]
fn test_dominant_syscall_all_categories() {
let mut breakdown = SyscallBreakdown::default();
breakdown.ioctl_us = 500;
breakdown.mmap_us = 100;
assert_eq!(breakdown.dominant_syscall(), "ioctl");
let mut breakdown = SyscallBreakdown::default();
breakdown.read_us = 500;
breakdown.ioctl_us = 100;
assert_eq!(breakdown.dominant_syscall(), "read");
let mut breakdown = SyscallBreakdown::default();
breakdown.write_us = 500;
breakdown.read_us = 100;
assert_eq!(breakdown.dominant_syscall(), "write");
let mut breakdown = SyscallBreakdown::default();
breakdown.other_us = 500;
breakdown.write_us = 100;
assert_eq!(breakdown.dominant_syscall(), "other");
let mut breakdown = SyscallBreakdown::default();
breakdown.mmap_us = 500;
breakdown.other_us = 100;
assert_eq!(breakdown.dominant_syscall(), "mmap");
let breakdown = SyscallBreakdown::default();
assert_eq!(breakdown.dominant_syscall(), "none");
}
#[test]
fn test_syscall_overhead_zero_total() {
let breakdown = SyscallBreakdown::default();
assert_eq!(breakdown.syscall_overhead_percent(), 0.0);
}
#[test]
fn test_thresholds_with_rate_limit() {
let thresholds = BrickEscalationThresholds::default().with_rate_limit(50);
assert_eq!(thresholds.max_traces_per_sec, 50);
}
#[test]
fn test_thresholds_getter() {
let tracer = BrickTracer::new_local();
let thresholds = tracer.thresholds();
assert!((thresholds.cv_percent - 15.0).abs() < 0.001);
}
#[test]
fn test_brick_tracer_default() {
let tracer = BrickTracer::default();
assert!(tracer.enabled);
assert!(!tracer.has_exporter());
}
#[test]
fn test_traced_brick_result_all_fields() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("TestBrick", 1000, || "hello");
assert_eq!(result.result, "hello");
assert!(result.duration_us < 1000000); assert!(result.metadata.is_some());
assert!(result.span_id.is_none()); assert!(result.escalation_reason.is_some());
}
#[test]
fn test_brick_metadata_all_fields() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("TestBrick", 5000, || {
std::thread::sleep(Duration::from_micros(100));
42
});
let meta = result.metadata.unwrap();
assert_eq!(meta.name, "TestBrick");
assert_eq!(meta.budget_us, 5000);
assert!(meta.actual_us > 0);
assert!(meta.efficiency <= 1.0);
assert!(meta.cv_percent.is_none());
assert!(meta.score.is_none());
assert!(meta.grade.is_none());
assert_eq!(meta.assertions_passed, 0);
assert_eq!(meta.assertions_failed, 0);
assert!(meta.failed_assertion_names.is_empty());
}
#[test]
fn test_rate_limiting() {
let thresholds = BrickEscalationThresholds::default().with_rate_limit(5);
let tracer = BrickTracer::new_local().with_thresholds(thresholds);
for _ in 0..5 {
assert!(tracer.should_trace(20.0, 10.0));
}
let result = tracer.should_trace(20.0, 10.0);
let _ = result;
}
#[test]
fn test_syscall_breakdown_debug() {
let breakdown = SyscallBreakdown::default();
let debug = format!("{:?}", breakdown);
assert!(debug.contains("SyscallBreakdown"));
}
#[test]
fn test_brick_metadata_debug_clone() {
let meta = BrickMetadata {
name: "Test".to_string(),
budget_us: 1000,
actual_us: 500,
over_budget: false,
efficiency: 1.0,
cv_percent: Some(5.0),
score: Some(95),
grade: Some('A'),
assertions_passed: 2,
assertions_failed: 0,
failed_assertion_names: Vec::new(),
};
let debug = format!("{:?}", meta);
assert!(debug.contains("Test"));
let cloned = meta.clone();
assert_eq!(cloned.name, "Test");
assert_eq!(cloned.score, Some(95));
}
#[test]
fn test_traced_brick_result_clone() {
let tracer = BrickTracer::new_local();
let result = tracer.trace("TestBrick", 1000, || 42);
let cloned = result.clone();
assert_eq!(cloned.result, 42);
}
#[test]
fn test_escalation_reason_eq() {
assert!(EscalationReason::CvExceeded == EscalationReason::CvExceeded);
assert!(EscalationReason::CvExceeded != EscalationReason::EfficiencyLow);
}
#[test]
fn test_syscall_event_clone_debug() {
let event = SyscallEvent {
syscall: "read".to_string(),
duration: Duration::from_micros(100),
result: 42,
};
let cloned = event.clone();
assert_eq!(cloned.syscall, "read");
let debug = format!("{:?}", event);
assert!(debug.contains("read"));
}
#[test]
fn test_syscall_breakdown_clone() {
let mut breakdown = SyscallBreakdown::default();
breakdown.mmap_us = 100;
let cloned = breakdown.clone();
assert_eq!(cloned.mmap_us, 100);
}
#[test]
fn test_thresholds_debug_clone() {
let thresholds = BrickEscalationThresholds::default();
let debug = format!("{:?}", thresholds);
assert!(debug.contains("BrickEscalationThresholds"));
let cloned = thresholds;
assert!((cloned.cv_percent - 15.0).abs() < 0.001);
}
#[test]
fn test_escalation_reason_copy() {
let reason = EscalationReason::Both;
let copied = reason;
assert_eq!(copied, EscalationReason::Both);
}
}