use crate::capability::Tier;
use crate::segment::SegmentKind;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SegmentMetrics {
pub kind: String,
pub original_tokens: usize,
pub compressed_tokens: usize,
pub dropped: bool,
}
impl SegmentMetrics {
#[must_use]
pub fn saved(&self) -> usize {
self.original_tokens.saturating_sub(self.compressed_tokens)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ContextCompilerMetrics {
pub tier: String,
pub total_budget: usize,
pub total_original_tokens: usize,
pub total_compressed_tokens: usize,
pub total_saved_tokens: usize,
pub savings_ratio_pct: f32,
pub per_segment: Vec<SegmentMetrics>,
pub summarizer_calls: u32,
pub summarizer_failures: u32,
pub elapsed_ms: u64,
}
impl ContextCompilerMetrics {
#[must_use]
pub fn new(tier: Tier, total_budget: usize) -> Self {
Self {
tier: tier.as_str().to_string(),
total_budget,
..Default::default()
}
}
pub fn record_segment(&mut self, kind: SegmentKind, original: usize, compressed: usize, dropped: bool) {
self.per_segment.push(SegmentMetrics {
kind: kind.as_str().to_string(),
original_tokens: original,
compressed_tokens: compressed,
dropped,
});
self.total_original_tokens = self.total_original_tokens.saturating_add(original);
self.total_compressed_tokens = self.total_compressed_tokens.saturating_add(compressed);
self.total_saved_tokens = self
.total_original_tokens
.saturating_sub(self.total_compressed_tokens);
self.savings_ratio_pct = if self.total_original_tokens == 0 {
0.0
} else {
(self.total_saved_tokens as f32 * 100.0) / self.total_original_tokens as f32
};
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn savings_ratio_recomputes_after_each_segment() {
let mut m = ContextCompilerMetrics::new(Tier::Heuristic, 10_000);
m.record_segment(SegmentKind::OlderTurn, 1000, 200, false);
m.record_segment(SegmentKind::ToolResult, 500, 100, false);
assert_eq!(m.total_original_tokens, 1500);
assert_eq!(m.total_compressed_tokens, 300);
assert_eq!(m.total_saved_tokens, 1200);
assert!((m.savings_ratio_pct - 80.0).abs() < 0.01);
}
#[test]
fn dropped_segment_counts_as_full_savings() {
let mut m = ContextCompilerMetrics::new(Tier::Heuristic, 10_000);
m.record_segment(SegmentKind::OlderTurn, 800, 0, true);
assert_eq!(m.total_saved_tokens, 800);
}
}