ai_lib/
metrics.rs

1use async_trait::async_trait;
2
3/// Simple, injectable metrics trait used by adapters/clients.
4/// Keep the surface minimal: counters, gauges and a timer RAII helper.
5#[async_trait]
6pub trait Metrics: Send + Sync + 'static {
7    /// Increment a named counter by `value`.
8    async fn incr_counter(&self, name: &str, value: u64);
9
10    /// Record a gauge metric.
11    async fn record_gauge(&self, name: &str, value: f64);
12
13    /// Start a timer for a named operation. Returns a boxed Timer which should be stopped
14    /// when the operation completes. Implementations may return None if timers aren't supported.
15    async fn start_timer(&self, name: &str) -> Option<Box<dyn Timer + Send>>;
16
17    /// Record a histogram value for a named metric.
18    async fn record_histogram(&self, name: &str, value: f64);
19
20    /// Record a histogram value with tags/labels.
21    async fn record_histogram_with_tags(&self, name: &str, value: f64, tags: &[(&str, &str)]);
22
23    /// Increment a counter with tags/labels.
24    async fn incr_counter_with_tags(&self, name: &str, value: u64, tags: &[(&str, &str)]);
25
26    /// Record a gauge with tags/labels.
27    async fn record_gauge_with_tags(&self, name: &str, value: f64, tags: &[(&str, &str)]);
28
29    /// Record an error occurrence.
30    async fn record_error(&self, name: &str, error_type: &str);
31
32    /// Record a success/failure boolean metric.
33    async fn record_success(&self, name: &str, success: bool);
34}
35
36/// Timer interface returned by Metrics::start_timer.
37pub trait Timer: Send {
38    /// Stop the timer and record the duration.
39    fn stop(self: Box<Self>);
40}
41
42/// No-op metrics implementation suitable as a default.
43pub struct NoopMetrics;
44
45#[async_trait]
46impl Metrics for NoopMetrics {
47    async fn incr_counter(&self, _name: &str, _value: u64) {}
48    async fn record_gauge(&self, _name: &str, _value: f64) {}
49    async fn start_timer(&self, _name: &str) -> Option<Box<dyn Timer + Send>> {
50        None
51    }
52    async fn record_histogram(&self, _name: &str, _value: f64) {}
53    async fn record_histogram_with_tags(&self, _name: &str, _value: f64, _tags: &[(&str, &str)]) {}
54    async fn incr_counter_with_tags(&self, _name: &str, _value: u64, _tags: &[(&str, &str)]) {}
55    async fn record_gauge_with_tags(&self, _name: &str, _value: f64, _tags: &[(&str, &str)]) {}
56    async fn record_error(&self, _name: &str, _error_type: &str) {}
57    async fn record_success(&self, _name: &str, _success: bool) {}
58}
59
60/// A no-op timer (returned when StartTimer implementations want to return a concrete value).
61pub struct NoopTimer;
62impl Timer for NoopTimer {
63    fn stop(self: Box<Self>) {}
64}
65
66impl NoopMetrics {
67    pub fn new() -> Self {
68        NoopMetrics
69    }
70}
71
72impl Default for NoopMetrics {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78/// Convenience methods for common metric patterns
79#[allow(async_fn_in_trait)]
80pub trait MetricsExt: Metrics {
81    /// Record a request with timing and success/failure
82    async fn record_request(&self, name: &str, timer: Option<Box<dyn Timer + Send>>, success: bool) {
83        if let Some(t) = timer {
84            t.stop();
85        }
86        self.record_success(name, success).await;
87    }
88
89    /// Record a request with timing, success/failure, and tags
90    async fn record_request_with_tags(
91        &self,
92        name: &str,
93        timer: Option<Box<dyn Timer + Send>>,
94        success: bool,
95        tags: &[(&str, &str)],
96    ) {
97        if let Some(t) = timer {
98            t.stop();
99        }
100        self.record_success(name, success).await;
101        // Record additional metrics with tags
102        self.incr_counter_with_tags(&format!("{}.total", name), 1, tags).await;
103        if success {
104            self.incr_counter_with_tags(&format!("{}.success", name), 1, tags).await;
105        } else {
106            self.incr_counter_with_tags(&format!("{}.failure", name), 1, tags).await;
107        }
108    }
109
110    /// Record an error with context
111    async fn record_error_with_context(&self, name: &str, error_type: &str, context: &str) {
112        self.record_error(name, error_type).await;
113        self.incr_counter_with_tags(name, 1, &[("error_type", error_type), ("context", context)]).await;
114    }
115}
116
117impl<T: Metrics> MetricsExt for T {}