use std::collections::HashMap;
use std::time::{Duration, Instant};
use super::types::{EscalationReason, EscalationThresholds, SyscallBreakdown, TraceResult};
#[derive(Debug)]
struct RateLimiter {
count: u32,
interval_start: Instant,
max_count: u32,
interval: Duration,
}
impl RateLimiter {
fn new(max_count: u32, interval: Duration) -> Self {
Self {
count: 0,
interval_start: Instant::now(),
max_count,
interval,
}
}
fn should_allow(&mut self) -> bool {
let now = Instant::now();
if now.duration_since(self.interval_start) >= self.interval {
self.count = 0;
self.interval_start = now;
}
if self.count < self.max_count {
self.count += 1;
true
} else {
false
}
}
fn current_count(&self) -> u32 {
self.count
}
}
#[derive(Debug)]
pub struct TracingEscalation {
thresholds: EscalationThresholds,
rate_limiter: RateLimiter,
otlp_endpoint: Option<String>,
trace_history: Vec<TraceResult>,
max_history: usize,
}
impl Default for TracingEscalation {
fn default() -> Self {
Self::new(EscalationThresholds::default())
}
}
impl TracingEscalation {
pub fn new(thresholds: EscalationThresholds) -> Self {
let rate_limiter = RateLimiter::new(thresholds.rate_limit, thresholds.rate_interval);
Self {
thresholds,
rate_limiter,
otlp_endpoint: None,
trace_history: Vec::new(),
max_history: 1000,
}
}
pub fn with_otlp_endpoint(mut self, endpoint: &str) -> Self {
self.otlp_endpoint = Some(endpoint.to_string());
self
}
pub fn should_trace(&self, cv_percent: f64, efficiency_percent: f64) -> bool {
cv_percent > self.thresholds.cv_threshold
|| efficiency_percent < self.thresholds.efficiency_threshold
}
pub fn should_trace_gpu_transfer(&self, transfer_overhead_percent: f64) -> bool {
transfer_overhead_percent > self.thresholds.gpu_transfer_threshold
}
pub fn escalation_reason(
&self,
cv_percent: f64,
efficiency_percent: f64,
) -> Option<EscalationReason> {
let cv_exceeded = cv_percent > self.thresholds.cv_threshold;
let efficiency_low = efficiency_percent < self.thresholds.efficiency_threshold;
match (cv_exceeded, efficiency_low) {
(true, true) => Some(EscalationReason::Both),
(true, false) => Some(EscalationReason::CvExceeded),
(false, true) => Some(EscalationReason::EfficiencyLow),
(false, false) => None,
}
}
pub fn try_trace(
&mut self,
brick_name: &str,
budget_us: u64,
actual_us: u64,
reason: EscalationReason,
breakdown: SyscallBreakdown,
) -> Option<TraceResult> {
if !self.rate_limiter.should_allow() {
return None;
}
let result = TraceResult {
brick_name: brick_name.to_string(),
budget_us,
actual_us,
reason,
syscall_breakdown: breakdown,
timestamp: Instant::now(),
};
if self.trace_history.len() >= self.max_history {
self.trace_history.remove(0);
}
self.trace_history.push(result.clone());
Some(result)
}
pub fn trace_count(&self) -> u32 {
self.rate_limiter.current_count()
}
pub fn history(&self) -> &[TraceResult] {
&self.trace_history
}
pub fn thresholds(&self) -> &EscalationThresholds {
&self.thresholds
}
pub fn set_thresholds(&mut self, thresholds: EscalationThresholds) {
self.thresholds = thresholds;
self.rate_limiter =
RateLimiter::new(self.thresholds.rate_limit, self.thresholds.rate_interval);
}
pub fn clear_history(&mut self) {
self.trace_history.clear();
}
}
#[derive(Debug, Clone)]
pub struct OtlpSpanAttributes {
pub attributes: HashMap<String, String>,
}
impl OtlpSpanAttributes {
pub fn from_trace_result(result: &TraceResult) -> Self {
Self {
attributes: result.as_otlp_attributes(),
}
}
pub fn with_attribute(mut self, key: &str, value: &str) -> Self {
self.attributes.insert(key.to_string(), value.to_string());
self
}
pub fn has_required_attributes(&self) -> bool {
let required = [
"brick.name",
"brick.budget_us",
"brick.actual_us",
"brick.efficiency",
"brick.over_budget",
"escalation.reason",
"syscall.overhead_percent",
"syscall.dominant",
];
required
.iter()
.all(|key| self.attributes.contains_key(*key))
}
}