1use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, RwLock};
9use std::time::{Duration, Instant};
10
11pub struct RateLimitMetrics {
16 inner: Arc<MetricsInner>,
17}
18
19struct MetricsInner {
20 total_requests: AtomicU64,
22 total_allowed: AtomicU64,
24 total_rejected: AtomicU64,
26 total_latency_us: AtomicU64,
28 max_latency_us: AtomicU64,
30 min_latency_us: AtomicU64,
32 per_key: RwLock<HashMap<String, KeyMetrics>>,
34 per_algorithm: RwLock<HashMap<String, AlgorithmMetrics>>,
36 start_time: Instant,
38}
39
40#[derive(Debug, Clone, Default, serde::Serialize)]
42pub struct KeyMetrics {
43 pub requests: u64,
44 pub allowed: u64,
45 pub rejected: u64,
46 pub last_access_ms: i64,
47}
48
49#[derive(Debug, Clone, Default, serde::Serialize)]
51pub struct AlgorithmMetrics {
52 pub requests: u64,
53 pub allowed: u64,
54 pub rejected: u64,
55 pub avg_latency_us: u64,
56}
57
58#[derive(Debug, Clone, serde::Serialize)]
60pub struct MetricsSnapshot {
61 pub total_requests: u64,
62 pub total_allowed: u64,
63 pub total_rejected: u64,
64 pub allow_rate: f64,
65 pub reject_rate: f64,
66 pub avg_latency_us: u64,
67 pub max_latency_us: u64,
68 pub min_latency_us: u64,
69 pub uptime_secs: u64,
70 pub per_key_count: usize,
71 pub per_algorithm_count: usize,
72}
73
74impl RateLimitMetrics {
75 pub fn new() -> Self {
77 Self {
78 inner: Arc::new(MetricsInner {
79 total_requests: AtomicU64::new(0),
80 total_allowed: AtomicU64::new(0),
81 total_rejected: AtomicU64::new(0),
82 total_latency_us: AtomicU64::new(0),
83 max_latency_us: AtomicU64::new(0),
84 min_latency_us: AtomicU64::new(u64::MAX),
85 per_key: RwLock::new(HashMap::new()),
86 per_algorithm: RwLock::new(HashMap::new()),
87 start_time: Instant::now(),
88 }),
89 }
90 }
91
92 pub fn record_allowed(&self, key: &str, algorithm: &str, latency: Duration) {
94 self.record(key, algorithm, true, latency);
95 }
96
97 pub fn record_rejected(&self, key: &str, algorithm: &str, latency: Duration) {
99 self.record(key, algorithm, false, latency);
100 }
101
102 fn record(&self, key: &str, algorithm: &str, allowed: bool, latency: Duration) {
103 let latency_us = latency.as_micros() as u64;
104 self.inner.total_requests.fetch_add(1, Ordering::Relaxed);
105 if allowed {
106 self.inner.total_allowed.fetch_add(1, Ordering::Relaxed);
107 } else {
108 self.inner.total_rejected.fetch_add(1, Ordering::Relaxed);
109 }
110 self.inner
111 .total_latency_us
112 .fetch_add(latency_us, Ordering::Relaxed);
113
114 let mut max = self.inner.max_latency_us.load(Ordering::Relaxed);
115 while latency_us > max {
116 match self.inner.max_latency_us.compare_exchange(
117 max,
118 latency_us,
119 Ordering::Relaxed,
120 Ordering::Relaxed,
121 ) {
122 Ok(_) => break,
123 Err(new_max) => max = new_max,
124 }
125 }
126
127 let mut min = self.inner.min_latency_us.load(Ordering::Relaxed);
128 while latency_us < min {
129 match self.inner.min_latency_us.compare_exchange(
130 min,
131 latency_us,
132 Ordering::Relaxed,
133 Ordering::Relaxed,
134 ) {
135 Ok(_) => break,
136 Err(new_min) => min = new_min,
137 }
138 }
139
140 if let Ok(mut per_key) = self.inner.per_key.write() {
141 let metrics = per_key.entry(key.to_string()).or_default();
142 metrics.requests += 1;
143 if allowed {
144 metrics.allowed += 1;
145 } else {
146 metrics.rejected += 1;
147 }
148 metrics.last_access_ms = now_timestamp();
149 }
150
151 if let Ok(mut per_algo) = self.inner.per_algorithm.write() {
152 let metrics = per_algo.entry(algorithm.to_string()).or_default();
153 metrics.requests += 1;
154 if allowed {
155 metrics.allowed += 1;
156 } else {
157 metrics.rejected += 1;
158 }
159 metrics.avg_latency_us = if metrics.requests > 0 {
160 (metrics.avg_latency_us * (metrics.requests - 1) + latency_us) / metrics.requests
161 } else {
162 latency_us
163 };
164 }
165 }
166
167 pub fn snapshot(&self) -> MetricsSnapshot {
169 let total = self.inner.total_requests.load(Ordering::Relaxed);
170 let allowed = self.inner.total_allowed.load(Ordering::Relaxed);
171 let rejected = self.inner.total_rejected.load(Ordering::Relaxed);
172 let total_latency = self.inner.total_latency_us.load(Ordering::Relaxed);
173 let max_latency = self.inner.max_latency_us.load(Ordering::Relaxed);
174 let min_latency = self.inner.min_latency_us.load(Ordering::Relaxed);
175
176 MetricsSnapshot {
177 total_requests: total,
178 total_allowed: allowed,
179 total_rejected: rejected,
180 allow_rate: if total > 0 {
181 allowed as f64 / total as f64
182 } else {
183 0.0
184 },
185 reject_rate: if total > 0 {
186 rejected as f64 / total as f64
187 } else {
188 0.0
189 },
190 avg_latency_us: if total > 0 { total_latency / total } else { 0 },
191 max_latency_us: if total > 0 { max_latency } else { 0 },
192 min_latency_us: if total > 0 {
193 min_latency.min(max_latency)
194 } else {
195 0
196 },
197 uptime_secs: self.inner.start_time.elapsed().as_secs(),
198 per_key_count: self.inner.per_key.read().map(|m| m.len()).unwrap_or(0),
199 per_algorithm_count: self
200 .inner
201 .per_algorithm
202 .read()
203 .map(|m| m.len())
204 .unwrap_or(0),
205 }
206 }
207
208 pub fn per_key_metrics(&self) -> HashMap<String, KeyMetrics> {
210 self.inner
211 .per_key
212 .read()
213 .map(|m| m.clone())
214 .unwrap_or_default()
215 }
216
217 pub fn per_algorithm_metrics(&self) -> HashMap<String, AlgorithmMetrics> {
219 self.inner
220 .per_algorithm
221 .read()
222 .map(|m| m.clone())
223 .unwrap_or_default()
224 }
225
226 pub fn key_metrics(&self, key: &str) -> Option<KeyMetrics> {
228 self.inner
229 .per_key
230 .read()
231 .ok()
232 .and_then(|m| m.get(key).cloned())
233 }
234
235 pub fn reset(&self) {
237 self.inner.total_requests.store(0, Ordering::Relaxed);
238 self.inner.total_allowed.store(0, Ordering::Relaxed);
239 self.inner.total_rejected.store(0, Ordering::Relaxed);
240 self.inner.total_latency_us.store(0, Ordering::Relaxed);
241 self.inner.max_latency_us.store(0, Ordering::Relaxed);
242 self.inner.min_latency_us.store(u64::MAX, Ordering::Relaxed);
243 if let Ok(mut per_key) = self.inner.per_key.write() {
244 per_key.clear();
245 }
246 if let Ok(mut per_algo) = self.inner.per_algorithm.write() {
247 per_algo.clear();
248 }
249 }
250
251 pub fn to_prometheus(&self) -> String {
253 let snap = self.snapshot();
254 let mut output = String::new();
255 output.push_str("# HELP rate_limit_requests_total Total requests\n");
256 output.push_str("# TYPE rate_limit_requests_total counter\n");
257 output.push_str(&format!(
258 "rate_limit_requests_total {}\n",
259 snap.total_requests
260 ));
261 output.push_str("# HELP rate_limit_allowed_total Total allowed requests\n");
262 output.push_str("# TYPE rate_limit_allowed_total counter\n");
263 output.push_str(&format!(
264 "rate_limit_allowed_total {}\n",
265 snap.total_allowed
266 ));
267 output.push_str("# HELP rate_limit_rejected_total Total rejected requests\n");
268 output.push_str("# TYPE rate_limit_rejected_total counter\n");
269 output.push_str(&format!(
270 "rate_limit_rejected_total {}\n",
271 snap.total_rejected
272 ));
273 output.push_str("# HELP rate_limit_latency_us Latency in microseconds\n");
274 output.push_str("# TYPE rate_limit_latency_us gauge\n");
275 output.push_str(&format!(
276 "rate_limit_latency_us{{quantile=\"avg\"}} {}\n",
277 snap.avg_latency_us
278 ));
279 output.push_str(&format!(
280 "rate_limit_latency_us{{quantile=\"max\"}} {}\n",
281 snap.max_latency_us
282 ));
283 output.push_str(&format!(
284 "rate_limit_latency_us{{quantile=\"min\"}} {}\n",
285 snap.min_latency_us
286 ));
287 output.push_str("# HELP rate_limit_uptime_seconds Uptime in seconds\n");
288 output.push_str("# TYPE rate_limit_uptime_seconds gauge\n");
289 output.push_str(&format!("rate_limit_uptime_seconds {}\n", snap.uptime_secs));
290 output
291 }
292
293 pub fn to_json(&self) -> serde_json::Value {
295 let snap = self.snapshot();
296 serde_json::json!({
297 "total_requests": snap.total_requests,
298 "total_allowed": snap.total_allowed,
299 "total_rejected": snap.total_rejected,
300 "allow_rate": snap.allow_rate,
301 "reject_rate": snap.reject_rate,
302 "avg_latency_us": snap.avg_latency_us,
303 "max_latency_us": snap.max_latency_us,
304 "min_latency_us": snap.min_latency_us,
305 "uptime_secs": snap.uptime_secs,
306 "per_key_count": snap.per_key_count,
307 "per_algorithm_count": snap.per_algorithm_count,
308 })
309 }
310}
311
312impl Default for RateLimitMetrics {
313 fn default() -> Self {
314 Self::new()
315 }
316}
317
318impl Clone for RateLimitMetrics {
319 fn clone(&self) -> Self {
320 Self {
321 inner: self.inner.clone(),
322 }
323 }
324}
325
326fn now_timestamp() -> i64 {
327 use std::time::{SystemTime, UNIX_EPOCH};
328 SystemTime::now()
329 .duration_since(UNIX_EPOCH)
330 .unwrap_or_default()
331 .as_millis() as i64
332}
333
334pub struct RateLimitMonitor {
338 metrics: RateLimitMetrics,
339 alert_threshold: f64,
340 last_snapshot: RwLock<Option<MetricsSnapshot>>,
341}
342
343impl RateLimitMonitor {
344 pub fn new(metrics: RateLimitMetrics, alert_threshold: f64) -> Self {
349 Self {
350 metrics,
351 alert_threshold,
352 last_snapshot: RwLock::new(None),
353 }
354 }
355
356 pub fn sample(&self) -> MetricsSnapshot {
358 let snap = self.metrics.snapshot();
359 if let Ok(mut last) = self.last_snapshot.write() {
360 *last = Some(snap.clone());
361 }
362 snap
363 }
364
365 pub fn check_alert(&self) -> Option<Alert> {
367 let snap = self.metrics.snapshot();
368 if snap.reject_rate > self.alert_threshold && snap.total_requests > 10 {
369 Some(Alert::HighRejectRate {
370 reject_rate: snap.reject_rate,
371 threshold: self.alert_threshold,
372 total_requests: snap.total_requests,
373 })
374 } else {
375 None
376 }
377 }
378
379 pub fn last_snapshot(&self) -> Option<MetricsSnapshot> {
381 self.last_snapshot.read().ok().and_then(|s| s.clone())
382 }
383
384 pub fn metrics(&self) -> &RateLimitMetrics {
386 &self.metrics
387 }
388}
389
390#[derive(Debug, Clone, serde::Serialize)]
392pub enum Alert {
393 HighRejectRate {
394 reject_rate: f64,
395 threshold: f64,
396 total_requests: u64,
397 },
398}
399
400impl std::fmt::Display for Alert {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 match self {
403 Alert::HighRejectRate {
404 reject_rate,
405 threshold,
406 total_requests,
407 } => write!(
408 f,
409 "High reject rate: {:.2}% (threshold: {:.2}%, total: {})",
410 reject_rate * 100.0,
411 threshold * 100.0,
412 total_requests
413 ),
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_metrics_new() {
424 let metrics = RateLimitMetrics::new();
425 let snap = metrics.snapshot();
426 assert_eq!(snap.total_requests, 0);
427 assert_eq!(snap.total_allowed, 0);
428 assert_eq!(snap.total_rejected, 0);
429 }
430
431 #[test]
432 fn test_metrics_record_allowed() {
433 let metrics = RateLimitMetrics::new();
434 metrics.record_allowed("k", "token_bucket", Duration::from_micros(100));
435 let snap = metrics.snapshot();
436 assert_eq!(snap.total_requests, 1);
437 assert_eq!(snap.total_allowed, 1);
438 assert_eq!(snap.total_rejected, 0);
439 }
440
441 #[test]
442 fn test_metrics_record_rejected() {
443 let metrics = RateLimitMetrics::new();
444 metrics.record_rejected("k", "token_bucket", Duration::from_micros(50));
445 let snap = metrics.snapshot();
446 assert_eq!(snap.total_requests, 1);
447 assert_eq!(snap.total_allowed, 0);
448 assert_eq!(snap.total_rejected, 1);
449 }
450
451 #[test]
452 fn test_metrics_allow_rate() {
453 let metrics = RateLimitMetrics::new();
454 metrics.record_allowed("k", "tb", Duration::from_micros(10));
455 metrics.record_allowed("k", "tb", Duration::from_micros(10));
456 metrics.record_rejected("k", "tb", Duration::from_micros(10));
457 let snap = metrics.snapshot();
458 assert!((snap.allow_rate - 2.0 / 3.0).abs() < 0.001);
459 assert!((snap.reject_rate - 1.0 / 3.0).abs() < 0.001);
460 }
461
462 #[test]
463 fn test_metrics_latency() {
464 let metrics = RateLimitMetrics::new();
465 metrics.record_allowed("k", "tb", Duration::from_micros(100));
466 metrics.record_allowed("k", "tb", Duration::from_micros(200));
467 metrics.record_allowed("k", "tb", Duration::from_micros(300));
468 let snap = metrics.snapshot();
469 assert_eq!(snap.avg_latency_us, 200);
470 assert_eq!(snap.max_latency_us, 300);
471 assert_eq!(snap.min_latency_us, 100);
472 }
473
474 #[test]
475 fn test_metrics_per_key() {
476 let metrics = RateLimitMetrics::new();
477 metrics.record_allowed("a", "tb", Duration::from_micros(10));
478 metrics.record_allowed("b", "tb", Duration::from_micros(10));
479 let per_key = metrics.per_key_metrics();
480 assert_eq!(per_key.len(), 2);
481 assert_eq!(per_key.get("a").unwrap().requests, 1);
482 assert_eq!(per_key.get("b").unwrap().requests, 1);
483 }
484
485 #[test]
486 fn test_metrics_per_algorithm() {
487 let metrics = RateLimitMetrics::new();
488 metrics.record_allowed("k", "token_bucket", Duration::from_micros(10));
489 metrics.record_allowed("k", "sliding_window", Duration::from_micros(10));
490 let per_algo = metrics.per_algorithm_metrics();
491 assert_eq!(per_algo.len(), 2);
492 assert_eq!(per_algo.get("token_bucket").unwrap().requests, 1);
493 }
494
495 #[test]
496 fn test_metrics_key_metrics() {
497 let metrics = RateLimitMetrics::new();
498 metrics.record_allowed("k", "tb", Duration::from_micros(10));
499 metrics.record_rejected("k", "tb", Duration::from_micros(10));
500 let km = metrics.key_metrics("k").unwrap();
501 assert_eq!(km.requests, 2);
502 assert_eq!(km.allowed, 1);
503 assert_eq!(km.rejected, 1);
504 }
505
506 #[test]
507 fn test_metrics_reset() {
508 let metrics = RateLimitMetrics::new();
509 metrics.record_allowed("k", "tb", Duration::from_micros(10));
510 metrics.reset();
511 let snap = metrics.snapshot();
512 assert_eq!(snap.total_requests, 0);
513 }
514
515 #[test]
516 fn test_metrics_to_prometheus() {
517 let metrics = RateLimitMetrics::new();
518 metrics.record_allowed("k", "tb", Duration::from_micros(10));
519 let prom = metrics.to_prometheus();
520 assert!(prom.contains("rate_limit_requests_total 1"));
521 assert!(prom.contains("rate_limit_allowed_total 1"));
522 }
523
524 #[test]
525 fn test_metrics_to_json() {
526 let metrics = RateLimitMetrics::new();
527 metrics.record_allowed("k", "tb", Duration::from_micros(10));
528 let json = metrics.to_json();
529 assert_eq!(json["total_requests"], 1);
530 assert_eq!(json["total_allowed"], 1);
531 }
532
533 #[test]
534 fn test_metrics_clone() {
535 let metrics = RateLimitMetrics::new();
536 metrics.record_allowed("k", "tb", Duration::from_micros(10));
537 let cloned = metrics.clone();
538 assert_eq!(cloned.snapshot().total_requests, 1);
539 }
540
541 #[test]
542 fn test_monitor_no_alert() {
543 let metrics = RateLimitMetrics::new();
544 let monitor = RateLimitMonitor::new(metrics, 0.5);
545 for _ in 0..10 {
546 monitor
547 .metrics()
548 .record_allowed("k", "tb", Duration::from_micros(10));
549 }
550 assert!(monitor.check_alert().is_none());
551 }
552
553 #[test]
554 fn test_monitor_high_reject_alert() {
555 let metrics = RateLimitMetrics::new();
556 let monitor = RateLimitMonitor::new(metrics, 0.5);
557 for _ in 0..10 {
558 monitor
559 .metrics()
560 .record_allowed("k", "tb", Duration::from_micros(10));
561 }
562 for _ in 0..20 {
563 monitor
564 .metrics()
565 .record_rejected("k", "tb", Duration::from_micros(10));
566 }
567 let alert = monitor.check_alert();
568 assert!(alert.is_some());
569 }
570
571 #[test]
572 fn test_monitor_sample() {
573 let metrics = RateLimitMetrics::new();
574 let monitor = RateLimitMonitor::new(metrics, 0.5);
575 monitor
576 .metrics()
577 .record_allowed("k", "tb", Duration::from_micros(10));
578 let snap = monitor.sample();
579 assert_eq!(snap.total_requests, 1);
580 assert!(monitor.last_snapshot().is_some());
581 }
582
583 #[test]
584 fn test_alert_display() {
585 let alert = Alert::HighRejectRate {
586 reject_rate: 0.8,
587 threshold: 0.5,
588 total_requests: 100,
589 };
590 let s = alert.to_string();
591 assert!(s.contains("80.00%"));
592 }
593
594 #[test]
595 fn test_metrics_snapshot_empty() {
596 let metrics = RateLimitMetrics::new();
597 let snap = metrics.snapshot();
598 assert_eq!(snap.allow_rate, 0.0);
599 assert_eq!(snap.reject_rate, 0.0);
600 assert_eq!(snap.avg_latency_us, 0);
601 }
602
603 #[test]
604 fn test_metrics_uptime() {
605 let metrics = RateLimitMetrics::new();
606 std::thread::sleep(Duration::from_millis(10));
607 let snap = metrics.snapshot();
608 assert!(snap.uptime_secs == 0 || snap.uptime_secs >= 0);
609 }
610
611 #[test]
612 fn test_metrics_per_key_count() {
613 let metrics = RateLimitMetrics::new();
614 metrics.record_allowed("a", "tb", Duration::from_micros(10));
615 metrics.record_allowed("b", "tb", Duration::from_micros(10));
616 metrics.record_allowed("c", "tb", Duration::from_micros(10));
617 let snap = metrics.snapshot();
618 assert_eq!(snap.per_key_count, 3);
619 }
620}