Skip to main content

vtcode_core/metrics/
sdk_metrics.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct SdkMetrics {
5    pub total_generations: u64,
6    pub total_generation_time_ms: u64,
7    pub cache_hits: u64,
8    pub tools_total_generated: u64,
9    pub max_tools_per_generation: u64,
10    /// Estimated total tokens consumed by tool definitions across all turns.
11    /// Tool definitions are a fixed overhead per turn; tracking this helps
12    /// identify when the toolset is growing beyond efficient bounds.
13    #[serde(default)]
14    pub total_tool_definition_tokens: u64,
15}
16
17impl SdkMetrics {
18    pub fn new() -> Self {
19        Self {
20            total_generations: 0,
21            total_generation_time_ms: 0,
22            cache_hits: 0,
23            tools_total_generated: 0,
24            max_tools_per_generation: 0,
25            total_tool_definition_tokens: 0,
26        }
27    }
28
29    pub fn record_generation(&mut self, generation_time_ms: u64, tools_count: u64) {
30        self.total_generations += 1;
31        self.total_generation_time_ms += generation_time_ms;
32        self.tools_total_generated += tools_count;
33        if tools_count > self.max_tools_per_generation {
34            self.max_tools_per_generation = tools_count;
35        }
36    }
37
38    pub fn record_cache_hit(&mut self) {
39        self.cache_hits += 1;
40    }
41
42    /// Record estimated tokens consumed by tool definitions in a single turn.
43    pub fn record_tool_definition_tokens(&mut self, tokens: u64) {
44        self.total_tool_definition_tokens = self.total_tool_definition_tokens.saturating_add(tokens);
45    }
46
47    pub fn avg_generation_time_ms(&self) -> u64 {
48        self.total_generation_time_ms.checked_div(self.total_generations).unwrap_or(0)
49    }
50
51    pub fn avg_tools_per_generation(&self) -> u64 {
52        self.tools_total_generated.checked_div(self.total_generations).unwrap_or(0)
53    }
54
55    pub fn cache_hit_rate(&self) -> f64 {
56        if self.total_generations > 0 {
57            self.cache_hits as f64 / self.total_generations as f64
58        } else {
59            0.0
60        }
61    }
62
63    /// Average tool definition tokens per generation.
64    pub fn avg_tool_definition_tokens_per_generation(&self) -> u64 {
65        self.total_tool_definition_tokens
66            .checked_div(self.total_generations)
67            .unwrap_or(0)
68    }
69}
70
71impl Default for SdkMetrics {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_record_generation() {
83        let mut metrics = SdkMetrics::new();
84        metrics.record_generation(50, 10);
85
86        assert_eq!(metrics.total_generations, 1);
87        assert_eq!(metrics.total_generation_time_ms, 50);
88        assert_eq!(metrics.tools_total_generated, 10);
89    }
90
91    #[test]
92    fn test_avg_generation_time() {
93        let mut metrics = SdkMetrics::new();
94        metrics.record_generation(50, 10);
95        metrics.record_generation(60, 12);
96        metrics.record_generation(40, 8);
97
98        assert_eq!(metrics.avg_generation_time_ms(), 50);
99    }
100
101    #[test]
102    fn test_max_tools() {
103        let mut metrics = SdkMetrics::new();
104        metrics.record_generation(50, 10);
105        metrics.record_generation(60, 25);
106        metrics.record_generation(40, 8);
107
108        assert_eq!(metrics.max_tools_per_generation, 25);
109    }
110
111    #[test]
112    fn test_cache_hit_rate() {
113        let mut metrics = SdkMetrics::new();
114        metrics.record_generation(50, 10);
115        metrics.record_generation(60, 12);
116        metrics.record_cache_hit();
117        metrics.record_cache_hit();
118
119        assert_eq!(metrics.total_generations, 2);
120        assert_eq!(metrics.cache_hits, 2);
121        assert!(metrics.cache_hit_rate() > 0.0);
122    }
123}