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 = (metrics.avg_latency_us * (metrics.requests - 1) + latency_us)
160 .checked_div(metrics.requests)
161 .unwrap_or(latency_us);
162 }
163 }
164
165 pub fn snapshot(&self) -> MetricsSnapshot {
167 let total = self.inner.total_requests.load(Ordering::Relaxed);
168 let allowed = self.inner.total_allowed.load(Ordering::Relaxed);
169 let rejected = self.inner.total_rejected.load(Ordering::Relaxed);
170 let total_latency = self.inner.total_latency_us.load(Ordering::Relaxed);
171 let max_latency = self.inner.max_latency_us.load(Ordering::Relaxed);
172 let min_latency = self.inner.min_latency_us.load(Ordering::Relaxed);
173
174 MetricsSnapshot {
175 total_requests: total,
176 total_allowed: allowed,
177 total_rejected: rejected,
178 allow_rate: if total > 0 {
179 allowed as f64 / total as f64
180 } else {
181 0.0
182 },
183 reject_rate: if total > 0 {
184 rejected as f64 / total as f64
185 } else {
186 0.0
187 },
188 avg_latency_us: total_latency.checked_div(total).unwrap_or(0),
189 max_latency_us: if total > 0 { max_latency } else { 0 },
190 min_latency_us: if total > 0 {
191 min_latency.min(max_latency)
192 } else {
193 0
194 },
195 uptime_secs: self.inner.start_time.elapsed().as_secs(),
196 per_key_count: self.inner.per_key.read().map(|m| m.len()).unwrap_or(0),
197 per_algorithm_count: self
198 .inner
199 .per_algorithm
200 .read()
201 .map(|m| m.len())
202 .unwrap_or(0),
203 }
204 }
205
206 pub fn per_key_metrics(&self) -> HashMap<String, KeyMetrics> {
208 self.inner
209 .per_key
210 .read()
211 .map(|m| m.clone())
212 .unwrap_or_default()
213 }
214
215 pub fn per_algorithm_metrics(&self) -> HashMap<String, AlgorithmMetrics> {
217 self.inner
218 .per_algorithm
219 .read()
220 .map(|m| m.clone())
221 .unwrap_or_default()
222 }
223
224 pub fn key_metrics(&self, key: &str) -> Option<KeyMetrics> {
226 self.inner
227 .per_key
228 .read()
229 .ok()
230 .and_then(|m| m.get(key).cloned())
231 }
232
233 pub fn reset(&self) {
235 self.inner.total_requests.store(0, Ordering::Relaxed);
236 self.inner.total_allowed.store(0, Ordering::Relaxed);
237 self.inner.total_rejected.store(0, Ordering::Relaxed);
238 self.inner.total_latency_us.store(0, Ordering::Relaxed);
239 self.inner.max_latency_us.store(0, Ordering::Relaxed);
240 self.inner.min_latency_us.store(u64::MAX, Ordering::Relaxed);
241 if let Ok(mut per_key) = self.inner.per_key.write() {
242 per_key.clear();
243 }
244 if let Ok(mut per_algo) = self.inner.per_algorithm.write() {
245 per_algo.clear();
246 }
247 }
248
249 pub fn to_prometheus(&self) -> String {
251 let snap = self.snapshot();
252 let mut output = String::new();
253 output.push_str("# HELP rate_limit_requests_total Total requests\n");
254 output.push_str("# TYPE rate_limit_requests_total counter\n");
255 output.push_str(&format!(
256 "rate_limit_requests_total {}\n",
257 snap.total_requests
258 ));
259 output.push_str("# HELP rate_limit_allowed_total Total allowed requests\n");
260 output.push_str("# TYPE rate_limit_allowed_total counter\n");
261 output.push_str(&format!(
262 "rate_limit_allowed_total {}\n",
263 snap.total_allowed
264 ));
265 output.push_str("# HELP rate_limit_rejected_total Total rejected requests\n");
266 output.push_str("# TYPE rate_limit_rejected_total counter\n");
267 output.push_str(&format!(
268 "rate_limit_rejected_total {}\n",
269 snap.total_rejected
270 ));
271 output.push_str("# HELP rate_limit_latency_us Latency in microseconds\n");
272 output.push_str("# TYPE rate_limit_latency_us gauge\n");
273 output.push_str(&format!(
274 "rate_limit_latency_us{{quantile=\"avg\"}} {}\n",
275 snap.avg_latency_us
276 ));
277 output.push_str(&format!(
278 "rate_limit_latency_us{{quantile=\"max\"}} {}\n",
279 snap.max_latency_us
280 ));
281 output.push_str(&format!(
282 "rate_limit_latency_us{{quantile=\"min\"}} {}\n",
283 snap.min_latency_us
284 ));
285 output.push_str("# HELP rate_limit_uptime_seconds Uptime in seconds\n");
286 output.push_str("# TYPE rate_limit_uptime_seconds gauge\n");
287 output.push_str(&format!("rate_limit_uptime_seconds {}\n", snap.uptime_secs));
288 output
289 }
290
291 pub fn to_json(&self) -> serde_json::Value {
293 let snap = self.snapshot();
294 serde_json::json!({
295 "total_requests": snap.total_requests,
296 "total_allowed": snap.total_allowed,
297 "total_rejected": snap.total_rejected,
298 "allow_rate": snap.allow_rate,
299 "reject_rate": snap.reject_rate,
300 "avg_latency_us": snap.avg_latency_us,
301 "max_latency_us": snap.max_latency_us,
302 "min_latency_us": snap.min_latency_us,
303 "uptime_secs": snap.uptime_secs,
304 "per_key_count": snap.per_key_count,
305 "per_algorithm_count": snap.per_algorithm_count,
306 })
307 }
308}
309
310impl Default for RateLimitMetrics {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl Clone for RateLimitMetrics {
317 fn clone(&self) -> Self {
318 Self {
319 inner: self.inner.clone(),
320 }
321 }
322}
323
324fn now_timestamp() -> i64 {
325 use std::time::{SystemTime, UNIX_EPOCH};
326 SystemTime::now()
327 .duration_since(UNIX_EPOCH)
328 .unwrap_or_default()
329 .as_millis() as i64
330}
331
332pub struct RateLimitMonitor {
336 metrics: RateLimitMetrics,
337 alert_threshold: f64,
338 last_snapshot: RwLock<Option<MetricsSnapshot>>,
339}
340
341impl RateLimitMonitor {
342 pub fn new(metrics: RateLimitMetrics, alert_threshold: f64) -> Self {
347 Self {
348 metrics,
349 alert_threshold,
350 last_snapshot: RwLock::new(None),
351 }
352 }
353
354 pub fn sample(&self) -> MetricsSnapshot {
356 let snap = self.metrics.snapshot();
357 if let Ok(mut last) = self.last_snapshot.write() {
358 *last = Some(snap.clone());
359 }
360 snap
361 }
362
363 pub fn check_alert(&self) -> Option<Alert> {
365 let snap = self.metrics.snapshot();
366 if snap.reject_rate > self.alert_threshold && snap.total_requests > 10 {
367 Some(Alert::HighRejectRate {
368 reject_rate: snap.reject_rate,
369 threshold: self.alert_threshold,
370 total_requests: snap.total_requests,
371 })
372 } else {
373 None
374 }
375 }
376
377 pub fn last_snapshot(&self) -> Option<MetricsSnapshot> {
379 self.last_snapshot.read().ok().and_then(|s| s.clone())
380 }
381
382 pub fn metrics(&self) -> &RateLimitMetrics {
384 &self.metrics
385 }
386}
387
388#[derive(Debug, Clone, serde::Serialize)]
390pub enum Alert {
391 HighRejectRate {
392 reject_rate: f64,
393 threshold: f64,
394 total_requests: u64,
395 },
396}
397
398impl std::fmt::Display for Alert {
399 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400 match self {
401 Alert::HighRejectRate {
402 reject_rate,
403 threshold,
404 total_requests,
405 } => write!(
406 f,
407 "High reject rate: {:.2}% (threshold: {:.2}%, total: {})",
408 reject_rate * 100.0,
409 threshold * 100.0,
410 total_requests
411 ),
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_metrics_new() {
422 let metrics = RateLimitMetrics::new();
423 let snap = metrics.snapshot();
424 assert_eq!(snap.total_requests, 0);
425 assert_eq!(snap.total_allowed, 0);
426 assert_eq!(snap.total_rejected, 0);
427 }
428
429 #[test]
430 fn test_metrics_record_allowed() {
431 let metrics = RateLimitMetrics::new();
432 metrics.record_allowed("k", "token_bucket", Duration::from_micros(100));
433 let snap = metrics.snapshot();
434 assert_eq!(snap.total_requests, 1);
435 assert_eq!(snap.total_allowed, 1);
436 assert_eq!(snap.total_rejected, 0);
437 }
438
439 #[test]
440 fn test_metrics_record_rejected() {
441 let metrics = RateLimitMetrics::new();
442 metrics.record_rejected("k", "token_bucket", Duration::from_micros(50));
443 let snap = metrics.snapshot();
444 assert_eq!(snap.total_requests, 1);
445 assert_eq!(snap.total_allowed, 0);
446 assert_eq!(snap.total_rejected, 1);
447 }
448
449 #[test]
450 fn test_metrics_allow_rate() {
451 let metrics = RateLimitMetrics::new();
452 metrics.record_allowed("k", "tb", Duration::from_micros(10));
453 metrics.record_allowed("k", "tb", Duration::from_micros(10));
454 metrics.record_rejected("k", "tb", Duration::from_micros(10));
455 let snap = metrics.snapshot();
456 assert!((snap.allow_rate - 2.0 / 3.0).abs() < 0.001);
457 assert!((snap.reject_rate - 1.0 / 3.0).abs() < 0.001);
458 }
459
460 #[test]
461 fn test_metrics_latency() {
462 let metrics = RateLimitMetrics::new();
463 metrics.record_allowed("k", "tb", Duration::from_micros(100));
464 metrics.record_allowed("k", "tb", Duration::from_micros(200));
465 metrics.record_allowed("k", "tb", Duration::from_micros(300));
466 let snap = metrics.snapshot();
467 assert_eq!(snap.avg_latency_us, 200);
468 assert_eq!(snap.max_latency_us, 300);
469 assert_eq!(snap.min_latency_us, 100);
470 }
471
472 #[test]
473 fn test_metrics_per_key() {
474 let metrics = RateLimitMetrics::new();
475 metrics.record_allowed("a", "tb", Duration::from_micros(10));
476 metrics.record_allowed("b", "tb", Duration::from_micros(10));
477 let per_key = metrics.per_key_metrics();
478 assert_eq!(per_key.len(), 2);
479 assert_eq!(per_key.get("a").unwrap().requests, 1);
480 assert_eq!(per_key.get("b").unwrap().requests, 1);
481 }
482
483 #[test]
484 fn test_metrics_per_algorithm() {
485 let metrics = RateLimitMetrics::new();
486 metrics.record_allowed("k", "token_bucket", Duration::from_micros(10));
487 metrics.record_allowed("k", "sliding_window", Duration::from_micros(10));
488 let per_algo = metrics.per_algorithm_metrics();
489 assert_eq!(per_algo.len(), 2);
490 assert_eq!(per_algo.get("token_bucket").unwrap().requests, 1);
491 }
492
493 #[test]
494 fn test_metrics_key_metrics() {
495 let metrics = RateLimitMetrics::new();
496 metrics.record_allowed("k", "tb", Duration::from_micros(10));
497 metrics.record_rejected("k", "tb", Duration::from_micros(10));
498 let km = metrics.key_metrics("k").unwrap();
499 assert_eq!(km.requests, 2);
500 assert_eq!(km.allowed, 1);
501 assert_eq!(km.rejected, 1);
502 }
503
504 #[test]
505 fn test_metrics_reset() {
506 let metrics = RateLimitMetrics::new();
507 metrics.record_allowed("k", "tb", Duration::from_micros(10));
508 metrics.reset();
509 let snap = metrics.snapshot();
510 assert_eq!(snap.total_requests, 0);
511 }
512
513 #[test]
514 fn test_metrics_to_prometheus() {
515 let metrics = RateLimitMetrics::new();
516 metrics.record_allowed("k", "tb", Duration::from_micros(10));
517 let prom = metrics.to_prometheus();
518 assert!(prom.contains("rate_limit_requests_total 1"));
519 assert!(prom.contains("rate_limit_allowed_total 1"));
520 }
521
522 #[test]
523 fn test_metrics_to_json() {
524 let metrics = RateLimitMetrics::new();
525 metrics.record_allowed("k", "tb", Duration::from_micros(10));
526 let json = metrics.to_json();
527 assert_eq!(json["total_requests"], 1);
528 assert_eq!(json["total_allowed"], 1);
529 }
530
531 #[test]
532 fn test_metrics_clone() {
533 let metrics = RateLimitMetrics::new();
534 metrics.record_allowed("k", "tb", Duration::from_micros(10));
535 let cloned = metrics.clone();
536 assert_eq!(cloned.snapshot().total_requests, 1);
537 }
538
539 #[test]
540 fn test_monitor_no_alert() {
541 let metrics = RateLimitMetrics::new();
542 let monitor = RateLimitMonitor::new(metrics, 0.5);
543 for _ in 0..10 {
544 monitor
545 .metrics()
546 .record_allowed("k", "tb", Duration::from_micros(10));
547 }
548 assert!(monitor.check_alert().is_none());
549 }
550
551 #[test]
552 fn test_monitor_high_reject_alert() {
553 let metrics = RateLimitMetrics::new();
554 let monitor = RateLimitMonitor::new(metrics, 0.5);
555 for _ in 0..10 {
556 monitor
557 .metrics()
558 .record_allowed("k", "tb", Duration::from_micros(10));
559 }
560 for _ in 0..20 {
561 monitor
562 .metrics()
563 .record_rejected("k", "tb", Duration::from_micros(10));
564 }
565 let alert = monitor.check_alert();
566 assert!(alert.is_some());
567 }
568
569 #[test]
570 fn test_monitor_sample() {
571 let metrics = RateLimitMetrics::new();
572 let monitor = RateLimitMonitor::new(metrics, 0.5);
573 monitor
574 .metrics()
575 .record_allowed("k", "tb", Duration::from_micros(10));
576 let snap = monitor.sample();
577 assert_eq!(snap.total_requests, 1);
578 assert!(monitor.last_snapshot().is_some());
579 }
580
581 #[test]
582 fn test_alert_display() {
583 let alert = Alert::HighRejectRate {
584 reject_rate: 0.8,
585 threshold: 0.5,
586 total_requests: 100,
587 };
588 let s = alert.to_string();
589 assert!(s.contains("80.00%"));
590 }
591
592 #[test]
593 fn test_metrics_snapshot_empty() {
594 let metrics = RateLimitMetrics::new();
595 let snap = metrics.snapshot();
596 assert_eq!(snap.allow_rate, 0.0);
597 assert_eq!(snap.reject_rate, 0.0);
598 assert_eq!(snap.avg_latency_us, 0);
599 }
600
601 #[test]
602 fn test_metrics_uptime() {
603 let metrics = RateLimitMetrics::new();
604 std::thread::sleep(Duration::from_millis(10));
605 let _snap = metrics.snapshot();
607 }
608
609 #[test]
610 fn test_metrics_per_key_count() {
611 let metrics = RateLimitMetrics::new();
612 metrics.record_allowed("a", "tb", Duration::from_micros(10));
613 metrics.record_allowed("b", "tb", Duration::from_micros(10));
614 metrics.record_allowed("c", "tb", Duration::from_micros(10));
615 let snap = metrics.snapshot();
616 assert_eq!(snap.per_key_count, 3);
617 }
618}