1use chrono::{DateTime, Utc};
12use parking_lot::Mutex;
13use serde::{Deserialize, Serialize};
14use std::fmt::Write;
15use std::sync::Arc;
16use std::time::Instant;
17
18pub mod discovery_metrics;
19pub mod execution_metrics;
20pub mod filtering_metrics;
21pub mod sdk_metrics;
22pub mod security_metrics;
23pub mod skill_metrics;
24
25pub use discovery_metrics::DiscoveryMetrics;
26pub use execution_metrics::ExecutionMetrics;
27pub use filtering_metrics::FilteringMetrics;
28pub use sdk_metrics::SdkMetrics;
29pub use security_metrics::SecurityMetrics;
30pub use skill_metrics::SkillMetrics;
31
32#[derive(Clone)]
34pub struct MetricsCollector {
35 discovery: Arc<Mutex<DiscoveryMetrics>>,
36 execution: Arc<Mutex<ExecutionMetrics>>,
37 sdk: Arc<Mutex<SdkMetrics>>,
38 filtering: Arc<Mutex<FilteringMetrics>>,
39 skills: Arc<Mutex<SkillMetrics>>,
40 security: Arc<Mutex<SecurityMetrics>>,
41 start_time: Instant,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct MetricsSummary {
46 pub timestamp: DateTime<Utc>,
47 pub session_duration_ms: u64,
48 pub discovery: DiscoveryMetrics,
49 pub execution: ExecutionMetrics,
50 pub sdk: SdkMetrics,
51 pub filtering: FilteringMetrics,
52 pub skills: SkillMetrics,
53 pub security: SecurityMetrics,
54}
55
56impl MetricsCollector {
57 pub fn new() -> Self {
59 Self {
60 discovery: Arc::new(Mutex::new(DiscoveryMetrics::new())),
61 execution: Arc::new(Mutex::new(ExecutionMetrics::new())),
62 sdk: Arc::new(Mutex::new(SdkMetrics::new())),
63 filtering: Arc::new(Mutex::new(FilteringMetrics::new())),
64 skills: Arc::new(Mutex::new(SkillMetrics::new())),
65 security: Arc::new(Mutex::new(SecurityMetrics::new())),
66 start_time: Instant::now(),
67 }
68 }
69
70 pub fn record_discovery_query(&self, keyword: String, result_count: u64, response_time_ms: u64) {
74 let mut metrics = self.discovery.lock();
75 metrics.record_query(keyword, result_count, response_time_ms);
76 }
77
78 pub fn record_discovery_failure(&self, keyword: String) {
80 let mut metrics = self.discovery.lock();
81 metrics.record_failure(keyword);
82 }
83
84 pub fn record_discovery_cache_hit(&self) {
86 let mut metrics = self.discovery.lock();
87 metrics.record_cache_hit();
88 }
89
90 pub fn record_execution_start(&self, language: String) {
94 let mut metrics = self.execution.lock();
95 metrics.record_start(language);
96 }
97
98 pub fn record_execution_complete(&self, language: String, duration_ms: u64, memory_mb: u64) {
100 let mut metrics = self.execution.lock();
101 metrics.record_complete(language, duration_ms, memory_mb, true);
102 }
103
104 pub fn record_execution_failure(&self, language: String, duration_ms: u64) {
106 let mut metrics = self.execution.lock();
107 metrics.record_failure(language, duration_ms);
108 }
109
110 pub fn record_execution_timeout(&self, language: String, duration_ms: u64) {
112 let mut metrics = self.execution.lock();
113 metrics.record_timeout(language, duration_ms);
114 }
115
116 pub fn record_retry_attempt(&self) {
118 let mut metrics = self.execution.lock();
119 metrics.record_retry_attempt();
120 }
121
122 pub fn record_retry_success(&self) {
124 let mut metrics = self.execution.lock();
125 metrics.record_retry_success();
126 }
127
128 pub fn record_retry_exhausted(&self) {
130 let mut metrics = self.execution.lock();
131 metrics.record_retry_exhausted();
132 }
133
134 pub fn record_circuit_open(&self) {
136 let mut metrics = self.execution.lock();
137 metrics.record_circuit_open();
138 }
139
140 pub fn record_half_open(&self) {
142 let mut metrics = self.execution.lock();
143 metrics.record_half_open();
144 }
145
146 pub fn record_breaker_denial(&self) {
148 let mut metrics = self.execution.lock();
149 metrics.record_breaker_denial();
150 }
151
152 pub fn record_circuit_breaker_metrics(
159 metrics: &Option<Arc<MetricsCollector>>,
160 half_open: bool,
161 denial: bool,
162 circuit_open: bool,
163 ) {
164 if let Some(metrics) = metrics {
165 if half_open {
166 metrics.record_half_open();
167 }
168 if denial {
169 metrics.record_breaker_denial();
170 }
171 if circuit_open {
172 metrics.record_circuit_open();
173 }
174 }
175 }
176
177 pub fn record_result_size(&self, size_bytes: usize) {
179 let mut metrics = self.execution.lock();
180 metrics.record_result_size(size_bytes);
181 }
182
183 pub fn record_sdk_generation(&self, generation_time_ms: u64, tools_count: u64) {
187 let mut metrics = self.sdk.lock();
188 metrics.record_generation(generation_time_ms, tools_count);
189 }
190
191 pub fn record_sdk_cache_hit(&self) {
193 let mut metrics = self.sdk.lock();
194 metrics.record_cache_hit();
195 }
196
197 pub fn record_sdk_tool_definition_tokens(&self, tokens: u64) {
200 let mut metrics = self.sdk.lock();
201 metrics.record_tool_definition_tokens(tokens);
202 }
203
204 pub fn record_filtering_operation(
208 &self,
209 operation_type: String,
210 input_size: u64,
211 output_size: u64,
212 duration_ms: u64,
213 ) {
214 let mut metrics = self.filtering.lock();
215 metrics.record_operation(operation_type, input_size, output_size, duration_ms);
216 }
217
218 pub fn record_skill_execution(&self, skill_name: String, duration_ms: u64, success: bool) {
222 let mut metrics = self.skills.lock();
223 metrics.record_execution(skill_name, duration_ms, success);
224 }
225
226 pub fn record_skill_created(&self, skill_name: String, language: String) {
228 let mut metrics = self.skills.lock();
229 metrics.record_created(skill_name, language);
230 }
231
232 pub fn record_skill_deleted(&self, skill_name: String) {
234 let mut metrics = self.skills.lock();
235 metrics.record_deleted(skill_name);
236 }
237
238 pub fn record_pii_detection(&self, pattern_type: String) {
242 let mut metrics = self.security.lock();
243 metrics.record_detection(pattern_type);
244 }
245
246 pub fn record_pii_tokenization(&self, token_count: usize) {
248 let mut metrics = self.security.lock();
249 metrics.record_tokenization(token_count);
250 }
251
252 pub fn record_audit_event(&self, event_type: String, severity: String) {
254 let mut metrics = self.security.lock();
255 metrics.record_audit_event(event_type, severity);
256 }
257
258 pub fn get_discovery_metrics(&self) -> DiscoveryMetrics {
262 self.discovery.lock().clone()
263 }
264
265 pub fn get_execution_metrics(&self) -> ExecutionMetrics {
267 self.execution.lock().clone()
268 }
269
270 pub fn get_sdk_metrics(&self) -> SdkMetrics {
272 self.sdk.lock().clone()
273 }
274
275 pub fn get_filtering_metrics(&self) -> FilteringMetrics {
277 self.filtering.lock().clone()
278 }
279
280 pub fn get_skill_metrics(&self) -> SkillMetrics {
282 self.skills.lock().clone()
283 }
284
285 pub fn get_security_metrics(&self) -> SecurityMetrics {
287 self.security.lock().clone()
288 }
289
290 pub fn get_summary(&self) -> MetricsSummary {
292 MetricsSummary {
293 timestamp: Utc::now(),
294 session_duration_ms: self.start_time.elapsed().as_millis().min(u64::MAX as u128) as u64,
295 discovery: self.get_discovery_metrics(),
296 execution: self.get_execution_metrics(),
297 sdk: self.get_sdk_metrics(),
298 filtering: self.get_filtering_metrics(),
299 skills: self.get_skill_metrics(),
300 security: self.get_security_metrics(),
301 }
302 }
303
304 pub fn export_json(&self) -> anyhow::Result<serde_json::Value> {
308 let summary = self.get_summary();
309 Ok(serde_json::to_value(summary)?)
310 }
311
312 pub fn export_prometheus(&self) -> String {
314 let discovery = self.get_discovery_metrics();
315 let execution = self.get_execution_metrics();
316 let filtering = self.get_filtering_metrics();
317 let skills = self.get_skill_metrics();
318 let security = self.get_security_metrics();
319
320 let mut output = String::new();
321
322 let _ = write!(
324 output,
325 "# HELP vtcode_discovery_queries_total Total tool discovery queries\n\
326 # TYPE vtcode_discovery_queries_total counter\n\
327 vtcode_discovery_queries_total {}\n\n",
328 discovery.total_queries
329 );
330
331 let _ = write!(
332 output,
333 "# HELP vtcode_discovery_hit_rate Hit rate of discovery queries\n\
334 # TYPE vtcode_discovery_hit_rate gauge\n\
335 vtcode_discovery_hit_rate {}\n\n",
336 discovery.hit_rate()
337 );
338
339 let _ = write!(
341 output,
342 "# HELP vtcode_execution_total Total code executions\n\
343 # TYPE vtcode_execution_total counter\n\
344 vtcode_execution_total {}\n\n",
345 execution.total_executions
346 );
347
348 let _ = write!(
349 output,
350 "# HELP vtcode_execution_duration_ms Code execution average duration\n\
351 # TYPE vtcode_execution_duration_ms gauge\n\
352 vtcode_execution_duration_ms {}\n\n",
353 execution.avg_duration_ms()
354 );
355
356 let _ = write!(
357 output,
358 "# HELP vtcode_retry_attempts_total Total retry attempts\n\
359 # TYPE vtcode_retry_attempts_total counter\n\
360 vtcode_retry_attempts_total {}\n\n",
361 execution.retry_attempts
362 );
363
364 let _ = write!(
365 output,
366 "# HELP vtcode_retry_successes_total Total retries that later succeeded\n\
367 # TYPE vtcode_retry_successes_total counter\n\
368 vtcode_retry_successes_total {}\n\n",
369 execution.retry_successes
370 );
371
372 let _ = write!(
373 output,
374 "# HELP vtcode_retry_exhausted_total Total operations that exhausted retries\n\
375 # TYPE vtcode_retry_exhausted_total counter\n\
376 vtcode_retry_exhausted_total {}\n\n",
377 execution.retry_exhausted
378 );
379
380 let _ = write!(
381 output,
382 "# HELP vtcode_circuit_open_total Total circuit breaker open transitions\n\
383 # TYPE vtcode_circuit_open_total counter\n\
384 vtcode_circuit_open_total {}\n\n",
385 execution.circuit_open_events
386 );
387
388 let _ = write!(
389 output,
390 "# HELP vtcode_circuit_half_open_total Total circuit breaker half-open transitions\n\
391 # TYPE vtcode_circuit_half_open_total counter\n\
392 vtcode_circuit_half_open_total {}\n\n",
393 execution.half_open_events
394 );
395
396 let _ = write!(
397 output,
398 "# HELP vtcode_circuit_breaker_denials_total Total circuit breaker denials\n\
399 # TYPE vtcode_circuit_breaker_denials_total counter\n\
400 vtcode_circuit_breaker_denials_total {}\n\n",
401 execution.breaker_denials
402 );
403
404 let _ = write!(
406 output,
407 "# HELP vtcode_filtering_operations_total Total filtering operations\n\
408 # TYPE vtcode_filtering_operations_total counter\n\
409 vtcode_filtering_operations_total {}\n\n",
410 filtering.total_operations
411 );
412
413 let _ = write!(
414 output,
415 "# HELP vtcode_context_tokens_saved Estimated tokens saved by filtering\n\
416 # TYPE vtcode_context_tokens_saved counter\n\
417 vtcode_context_tokens_saved {}\n\n",
418 filtering.estimated_tokens_saved()
419 );
420
421 let _ = write!(
423 output,
424 "# HELP vtcode_skills_total Total saved skills\n\
425 # TYPE vtcode_skills_total gauge\n\
426 vtcode_skills_total {}\n\n",
427 skills.total_skills
428 );
429
430 let _ = write!(
431 output,
432 "# HELP vtcode_skill_reuse_ratio Ratio of skill reuse\n\
433 # TYPE vtcode_skill_reuse_ratio gauge\n\
434 vtcode_skill_reuse_ratio {}\n\n",
435 skills.reuse_ratio()
436 );
437
438 let _ = write!(
440 output,
441 "# HELP vtcode_pii_detections_total Total PII patterns detected\n\
442 # TYPE vtcode_pii_detections_total counter\n\
443 vtcode_pii_detections_total {}\n\n",
444 security.pii_detections
445 );
446
447 let _ = write!(
448 output,
449 "# HELP vtcode_tokens_created_total Total PII tokens created\n\
450 # TYPE vtcode_tokens_created_total counter\n\
451 vtcode_tokens_created_total {}\n\n",
452 security.tokens_created
453 );
454
455 output
456 }
457}
458
459impl Default for MetricsCollector {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_metrics_collector_creation() {
471 let collector = MetricsCollector::new();
472 let summary = collector.get_summary();
473 assert_eq!(summary.discovery.total_queries, 0);
474 assert_eq!(summary.execution.total_executions, 0);
475 }
476
477 #[test]
478 fn test_discovery_metrics_recording() {
479 let collector = MetricsCollector::new();
480 collector.record_discovery_query("file".to_owned(), 5, 50);
481
482 let metrics = collector.get_discovery_metrics();
483 assert_eq!(metrics.total_queries, 1);
484 assert!(metrics.avg_response_time_ms() > 0);
485 }
486
487 #[test]
488 fn test_execution_metrics_recording() {
489 let collector = MetricsCollector::new();
490 collector.record_execution_start("python3".to_owned());
491 collector.record_execution_complete("python3".to_owned(), 1000, 50);
492
493 let metrics = collector.get_execution_metrics();
494 assert_eq!(metrics.total_executions, 1);
495 assert_eq!(metrics.successful_executions, 1);
496 assert_eq!(metrics.avg_duration_ms(), 1000);
497 }
498
499 #[test]
500 fn test_metrics_summary_export() {
501 let collector = MetricsCollector::new();
502 collector.record_discovery_query("test".to_owned(), 3, 30);
503 collector.record_pii_detection("email".to_owned());
504
505 let summary = collector.get_summary();
506 assert_eq!(summary.discovery.total_queries, 1);
507 assert_eq!(summary.security.pii_detections, 1);
508 }
509
510 #[test]
511 fn test_reliability_metrics_recording() {
512 let collector = MetricsCollector::new();
513 collector.record_retry_attempt();
514 collector.record_retry_success();
515 collector.record_circuit_open();
516 collector.record_half_open();
517 collector.record_breaker_denial();
518
519 let metrics = collector.get_execution_metrics();
520 assert_eq!(metrics.retry_attempts, 1);
521 assert_eq!(metrics.retry_successes, 1);
522 assert_eq!(metrics.circuit_open_events, 1);
523 assert_eq!(metrics.half_open_events, 1);
524 assert_eq!(metrics.breaker_denials, 1);
525 }
526
527 #[test]
528 fn test_prometheus_export() {
529 let collector = MetricsCollector::new();
530 collector.record_execution_complete("python3".to_owned(), 500, 40);
531
532 let prometheus = collector.export_prometheus();
533 assert!(prometheus.contains("vtcode_execution_total"));
534 assert!(prometheus.contains("vtcode_execution_duration_ms"));
535 assert!(prometheus.contains("vtcode_retry_attempts_total"));
536 }
537
538 #[test]
539 fn test_json_export() {
540 let collector = MetricsCollector::new();
541 collector.record_discovery_query("test".to_owned(), 2, 25);
542
543 let json = collector.export_json().unwrap();
544 assert!(json.get("timestamp").is_some());
545 assert!(json.get("discovery").is_some());
546 }
547}