1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
7use std::sync::Arc;
8
9#[derive(Debug, Default)]
11pub struct RuntimeMetrics {
12 pub active_sessions: AtomicUsize,
14 pub total_sessions: AtomicU64,
16 pub total_steps: AtomicU64,
18 pub total_tool_calls: AtomicU64,
20 pub failed_tool_calls: AtomicU64,
22 pub backpressure_shed_count: AtomicU64,
24 pub memory_recall_count: AtomicU64,
26}
27
28impl RuntimeMetrics {
29 pub fn new() -> Arc<Self> {
31 Arc::new(Self::default())
32 }
33
34 pub fn active_sessions(&self) -> usize {
36 self.active_sessions.load(Ordering::Relaxed)
37 }
38
39 pub fn total_sessions(&self) -> u64 {
41 self.total_sessions.load(Ordering::Relaxed)
42 }
43
44 pub fn total_steps(&self) -> u64 {
46 self.total_steps.load(Ordering::Relaxed)
47 }
48
49 pub fn total_tool_calls(&self) -> u64 {
51 self.total_tool_calls.load(Ordering::Relaxed)
52 }
53
54 pub fn failed_tool_calls(&self) -> u64 {
56 self.failed_tool_calls.load(Ordering::Relaxed)
57 }
58
59 pub fn backpressure_shed_count(&self) -> u64 {
61 self.backpressure_shed_count.load(Ordering::Relaxed)
62 }
63
64 pub fn memory_recall_count(&self) -> u64 {
66 self.memory_recall_count.load(Ordering::Relaxed)
67 }
68
69 pub fn reset(&self) {
73 self.active_sessions.store(0, Ordering::Relaxed);
74 self.total_sessions.store(0, Ordering::Relaxed);
75 self.total_steps.store(0, Ordering::Relaxed);
76 self.total_tool_calls.store(0, Ordering::Relaxed);
77 self.failed_tool_calls.store(0, Ordering::Relaxed);
78 self.backpressure_shed_count.store(0, Ordering::Relaxed);
79 self.memory_recall_count.store(0, Ordering::Relaxed);
80 }
81
82 pub fn to_snapshot(&self) -> (usize, u64, u64, u64, u64, u64, u64) {
88 (
89 self.active_sessions.load(Ordering::Relaxed),
90 self.total_sessions.load(Ordering::Relaxed),
91 self.total_steps.load(Ordering::Relaxed),
92 self.total_tool_calls.load(Ordering::Relaxed),
93 self.failed_tool_calls.load(Ordering::Relaxed),
94 self.backpressure_shed_count.load(Ordering::Relaxed),
95 self.memory_recall_count.load(Ordering::Relaxed),
96 )
97 }
98}
99
100#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_metrics_new_returns_arc_with_zero_counters() {
108 let m = RuntimeMetrics::new();
109 assert_eq!(m.active_sessions(), 0);
110 assert_eq!(m.total_sessions(), 0);
111 assert_eq!(m.total_steps(), 0);
112 assert_eq!(m.total_tool_calls(), 0);
113 assert_eq!(m.failed_tool_calls(), 0);
114 assert_eq!(m.backpressure_shed_count(), 0);
115 assert_eq!(m.memory_recall_count(), 0);
116 }
117
118 #[test]
119 fn test_active_sessions_increments_and_decrements() {
120 let m = RuntimeMetrics::new();
121 m.active_sessions.fetch_add(1, Ordering::Relaxed);
122 assert_eq!(m.active_sessions(), 1);
123 m.active_sessions.fetch_sub(1, Ordering::Relaxed);
124 assert_eq!(m.active_sessions(), 0);
125 }
126
127 #[test]
128 fn test_total_sessions_increments() {
129 let m = RuntimeMetrics::new();
130 m.total_sessions.fetch_add(1, Ordering::Relaxed);
131 m.total_sessions.fetch_add(1, Ordering::Relaxed);
132 assert_eq!(m.total_sessions(), 2);
133 }
134
135 #[test]
136 fn test_total_steps_increments() {
137 let m = RuntimeMetrics::new();
138 m.total_steps.fetch_add(5, Ordering::Relaxed);
139 assert_eq!(m.total_steps(), 5);
140 }
141
142 #[test]
143 fn test_total_tool_calls_increments() {
144 let m = RuntimeMetrics::new();
145 m.total_tool_calls.fetch_add(3, Ordering::Relaxed);
146 assert_eq!(m.total_tool_calls(), 3);
147 }
148
149 #[test]
150 fn test_failed_tool_calls_increments() {
151 let m = RuntimeMetrics::new();
152 m.failed_tool_calls.fetch_add(2, Ordering::Relaxed);
153 assert_eq!(m.failed_tool_calls(), 2);
154 }
155
156 #[test]
157 fn test_backpressure_shed_count_increments() {
158 let m = RuntimeMetrics::new();
159 m.backpressure_shed_count.fetch_add(7, Ordering::Relaxed);
160 assert_eq!(m.backpressure_shed_count(), 7);
161 }
162
163 #[test]
164 fn test_memory_recall_count_increments() {
165 let m = RuntimeMetrics::new();
166 m.memory_recall_count.fetch_add(4, Ordering::Relaxed);
167 assert_eq!(m.memory_recall_count(), 4);
168 }
169
170 #[test]
171 fn test_reset_zeroes_all_counters() {
172 let m = RuntimeMetrics::new();
173 m.active_sessions.store(3, Ordering::Relaxed);
174 m.total_sessions.store(10, Ordering::Relaxed);
175 m.total_steps.store(50, Ordering::Relaxed);
176 m.total_tool_calls.store(20, Ordering::Relaxed);
177 m.failed_tool_calls.store(2, Ordering::Relaxed);
178 m.backpressure_shed_count.store(1, Ordering::Relaxed);
179 m.memory_recall_count.store(8, Ordering::Relaxed);
180
181 m.reset();
182
183 assert_eq!(m.active_sessions(), 0);
184 assert_eq!(m.total_sessions(), 0);
185 assert_eq!(m.total_steps(), 0);
186 assert_eq!(m.total_tool_calls(), 0);
187 assert_eq!(m.failed_tool_calls(), 0);
188 assert_eq!(m.backpressure_shed_count(), 0);
189 assert_eq!(m.memory_recall_count(), 0);
190 }
191
192 #[test]
193 fn test_to_snapshot_captures_correct_values() {
194 let m = RuntimeMetrics::new();
195 m.active_sessions.store(1, Ordering::Relaxed);
196 m.total_sessions.store(2, Ordering::Relaxed);
197 m.total_steps.store(3, Ordering::Relaxed);
198 m.total_tool_calls.store(4, Ordering::Relaxed);
199 m.failed_tool_calls.store(5, Ordering::Relaxed);
200 m.backpressure_shed_count.store(6, Ordering::Relaxed);
201 m.memory_recall_count.store(7, Ordering::Relaxed);
202
203 let snap = m.to_snapshot();
204 assert_eq!(snap, (1, 2, 3, 4, 5, 6, 7));
205 }
206
207 #[test]
208 fn test_metrics_is_send_sync() {
209 fn assert_send_sync<T: Send + Sync>() {}
210 assert_send_sync::<RuntimeMetrics>();
211 }
212
213 #[test]
214 fn test_multiple_increments_are_cumulative() {
215 let m = RuntimeMetrics::new();
216 for _ in 0..100 {
217 m.total_sessions.fetch_add(1, Ordering::Relaxed);
218 }
219 assert_eq!(m.total_sessions(), 100);
220 }
221
222 #[test]
223 fn test_arc_clone_shares_state() {
224 let m = RuntimeMetrics::new();
225 let m2 = Arc::clone(&m);
226 m.total_sessions.fetch_add(1, Ordering::Relaxed);
227 assert_eq!(m2.total_sessions(), 1);
228 }
229}