Skip to main content

rustant_core/
metrics.rs

1//! Agent metrics — counters and histograms for observability.
2//!
3//! When the `metrics` feature is enabled, this module provides OpenTelemetry
4//! integration.  When disabled (the default), all operations are no-ops so
5//! there is zero runtime overhead.
6
7use std::time::Instant;
8
9/// Agent-level metrics for task execution, tool calls, and token usage.
10#[derive(Debug, Default)]
11pub struct AgentMetrics {
12    pub tasks_started: u64,
13    pub tasks_completed: u64,
14    pub tasks_failed: u64,
15    pub tool_calls: u64,
16    pub total_input_tokens: u64,
17    pub total_output_tokens: u64,
18    pub errors: u64,
19    start_time: Option<Instant>,
20}
21
22impl AgentMetrics {
23    pub fn new() -> Self {
24        Self {
25            start_time: Some(Instant::now()),
26            ..Default::default()
27        }
28    }
29
30    /// Record a task start.
31    pub fn record_task_start(&mut self) {
32        self.tasks_started += 1;
33    }
34
35    /// Record a task completion.
36    pub fn record_task_complete(&mut self) {
37        self.tasks_completed += 1;
38    }
39
40    /// Record a task failure.
41    pub fn record_task_failed(&mut self) {
42        self.tasks_failed += 1;
43    }
44
45    /// Record a tool invocation.
46    pub fn record_tool_call(&mut self) {
47        self.tool_calls += 1;
48    }
49
50    /// Record token usage from an LLM call.
51    pub fn record_tokens(&mut self, input: u64, output: u64) {
52        self.total_input_tokens += input;
53        self.total_output_tokens += output;
54    }
55
56    /// Record an error.
57    pub fn record_error(&mut self) {
58        self.errors += 1;
59    }
60
61    /// Get uptime in seconds.
62    pub fn uptime_secs(&self) -> u64 {
63        self.start_time.map(|s| s.elapsed().as_secs()).unwrap_or(0)
64    }
65
66    /// Get a summary snapshot of all metrics.
67    pub fn snapshot(&self) -> MetricsSnapshot {
68        MetricsSnapshot {
69            tasks_started: self.tasks_started,
70            tasks_completed: self.tasks_completed,
71            tasks_failed: self.tasks_failed,
72            tool_calls: self.tool_calls,
73            total_input_tokens: self.total_input_tokens,
74            total_output_tokens: self.total_output_tokens,
75            errors: self.errors,
76            uptime_secs: self.uptime_secs(),
77        }
78    }
79}
80
81/// Immutable snapshot of metrics at a point in time.
82#[derive(Debug, Clone, serde::Serialize)]
83pub struct MetricsSnapshot {
84    pub tasks_started: u64,
85    pub tasks_completed: u64,
86    pub tasks_failed: u64,
87    pub tool_calls: u64,
88    pub total_input_tokens: u64,
89    pub total_output_tokens: u64,
90    pub errors: u64,
91    pub uptime_secs: u64,
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_metrics_default() {
100        let m = AgentMetrics::new();
101        assert_eq!(m.tasks_started, 0);
102        assert_eq!(m.tasks_completed, 0);
103        assert_eq!(m.tool_calls, 0);
104    }
105
106    #[test]
107    fn test_record_task_lifecycle() {
108        let mut m = AgentMetrics::new();
109        m.record_task_start();
110        m.record_task_start();
111        m.record_task_complete();
112        m.record_task_failed();
113
114        assert_eq!(m.tasks_started, 2);
115        assert_eq!(m.tasks_completed, 1);
116        assert_eq!(m.tasks_failed, 1);
117    }
118
119    #[test]
120    fn test_record_tokens() {
121        let mut m = AgentMetrics::new();
122        m.record_tokens(100, 50);
123        m.record_tokens(200, 100);
124        assert_eq!(m.total_input_tokens, 300);
125        assert_eq!(m.total_output_tokens, 150);
126    }
127
128    #[test]
129    fn test_snapshot() {
130        let mut m = AgentMetrics::new();
131        m.record_task_start();
132        m.record_tool_call();
133        m.record_error();
134
135        let snap = m.snapshot();
136        assert_eq!(snap.tasks_started, 1);
137        assert_eq!(snap.tool_calls, 1);
138        assert_eq!(snap.errors, 1);
139    }
140
141    #[test]
142    fn test_uptime() {
143        let m = AgentMetrics::new();
144        // Just verify it doesn't panic and returns something >= 0
145        assert!(m.uptime_secs() < 5);
146    }
147
148    #[test]
149    fn test_noop_when_default() {
150        let m = AgentMetrics::default();
151        assert_eq!(m.uptime_secs(), 0); // No start_time in default
152    }
153}