oxur_repl/metrics/
usage.rs1use metrics::counter;
7use std::collections::VecDeque;
8
9const MAX_HISTORY: usize = 100;
11
12#[derive(Debug, Clone)]
35pub struct UsageMetrics {
36 session_id: String,
38
39 eval_count: u64,
41
42 help_count: u64,
44
45 stats_count: u64,
47
48 info_count: u64,
50
51 sessions_count: u64,
53
54 clear_count: u64,
56
57 banner_count: u64,
59
60 recent_commands: VecDeque<CommandType>,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum CommandType {
67 Eval,
68 Help,
69 Stats,
70 Info,
71 Sessions,
72 Clear,
73 Banner,
74}
75
76impl CommandType {
77 pub fn as_label(&self) -> &'static str {
79 match self {
80 CommandType::Eval => "eval",
81 CommandType::Help => "help",
82 CommandType::Stats => "stats",
83 CommandType::Info => "info",
84 CommandType::Sessions => "sessions",
85 CommandType::Clear => "clear",
86 CommandType::Banner => "banner",
87 }
88 }
89}
90
91impl UsageMetrics {
92 pub fn new(session_id: impl Into<String>) -> Self {
94 Self {
95 session_id: session_id.into(),
96 eval_count: 0,
97 help_count: 0,
98 stats_count: 0,
99 info_count: 0,
100 sessions_count: 0,
101 clear_count: 0,
102 banner_count: 0,
103 recent_commands: VecDeque::with_capacity(MAX_HISTORY),
104 }
105 }
106
107 fn record_command(&mut self, cmd_type: CommandType) {
109 match cmd_type {
111 CommandType::Eval => self.eval_count += 1,
112 CommandType::Help => self.help_count += 1,
113 CommandType::Stats => self.stats_count += 1,
114 CommandType::Info => self.info_count += 1,
115 CommandType::Sessions => self.sessions_count += 1,
116 CommandType::Clear => self.clear_count += 1,
117 CommandType::Banner => self.banner_count += 1,
118 }
119
120 if self.recent_commands.len() >= MAX_HISTORY {
122 self.recent_commands.pop_front();
123 }
124 self.recent_commands.push_back(cmd_type);
125
126 counter!("repl.commands.total", "type" => cmd_type.as_label()).increment(1);
128 }
129
130 pub fn record_eval(&mut self) {
132 self.record_command(CommandType::Eval);
133 }
134
135 pub fn record_help(&mut self) {
137 self.record_command(CommandType::Help);
138 }
139
140 pub fn record_stats(&mut self) {
142 self.record_command(CommandType::Stats);
143 }
144
145 pub fn record_info(&mut self) {
147 self.record_command(CommandType::Info);
148 }
149
150 pub fn record_sessions(&mut self) {
152 self.record_command(CommandType::Sessions);
153 }
154
155 pub fn record_clear(&mut self) {
157 self.record_command(CommandType::Clear);
158 }
159
160 pub fn record_banner(&mut self) {
162 self.record_command(CommandType::Banner);
163 }
164
165 pub fn total_commands(&self) -> u64 {
167 self.eval_count
168 + self.help_count
169 + self.stats_count
170 + self.info_count
171 + self.sessions_count
172 + self.clear_count
173 + self.banner_count
174 }
175
176 pub fn eval_percentage(&self) -> f64 {
178 let total = self.total_commands();
179 if total > 0 {
180 (self.eval_count as f64 / total as f64) * 100.0
181 } else {
182 0.0
183 }
184 }
185
186 pub fn session_id(&self) -> &str {
188 &self.session_id
189 }
190
191 pub fn snapshot(&self) -> UsageMetricsSnapshot {
193 UsageMetricsSnapshot {
194 session_id: self.session_id.clone(),
195 eval_count: self.eval_count,
196 help_count: self.help_count,
197 stats_count: self.stats_count,
198 info_count: self.info_count,
199 sessions_count: self.sessions_count,
200 clear_count: self.clear_count,
201 banner_count: self.banner_count,
202 total_commands: self.total_commands(),
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
209pub struct UsageMetricsSnapshot {
210 pub session_id: String,
212 pub eval_count: u64,
214 pub help_count: u64,
216 pub stats_count: u64,
218 pub info_count: u64,
220 pub sessions_count: u64,
222 pub clear_count: u64,
224 pub banner_count: u64,
226 pub total_commands: u64,
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_usage_metrics_creation() {
236 let metrics = UsageMetrics::new("test-session");
237 assert_eq!(metrics.session_id(), "test-session");
238 assert_eq!(metrics.total_commands(), 0);
239 }
240
241 #[test]
242 fn test_record_commands() {
243 let mut metrics = UsageMetrics::new("test");
244
245 metrics.record_eval();
246 metrics.record_eval();
247 metrics.record_help();
248 metrics.record_stats();
249
250 assert_eq!(metrics.eval_count, 2);
251 assert_eq!(metrics.help_count, 1);
252 assert_eq!(metrics.stats_count, 1);
253 assert_eq!(metrics.total_commands(), 4);
254 }
255
256 #[test]
257 fn test_eval_percentage() {
258 let mut metrics = UsageMetrics::new("test");
259
260 metrics.record_eval();
261 metrics.record_eval();
262 metrics.record_eval();
263 metrics.record_help();
264
265 assert_eq!(metrics.eval_percentage(), 75.0);
266 }
267
268 #[test]
269 fn test_snapshot() {
270 let mut metrics = UsageMetrics::new("test");
271
272 metrics.record_eval();
273 metrics.record_help();
274 metrics.record_stats();
275
276 let snapshot = metrics.snapshot();
277 assert_eq!(snapshot.eval_count, 1);
278 assert_eq!(snapshot.help_count, 1);
279 assert_eq!(snapshot.stats_count, 1);
280 assert_eq!(snapshot.total_commands, 3);
281 }
282
283 #[test]
284 fn test_command_history() {
285 let mut metrics = UsageMetrics::new("test");
286
287 for _ in 0..150 {
288 metrics.record_eval();
289 }
290
291 assert_eq!(metrics.recent_commands.len(), MAX_HISTORY);
293 }
294}