Skip to main content

sz_orm_limit/
metrics.rs

1//! 限流统计与监控(Rate Limit Metrics)
2//!
3//! 提供限流器的运行时统计、监控和可观测性支持。
4//! 可集成到 Prometheus、OpenTelemetry 等监控系统。
5
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, RwLock};
9use std::time::{Duration, Instant};
10
11/// 限流指标收集器
12///
13/// 收集限流器的运行时指标,包括允许/拒绝计数、延迟分布等。
14/// 线程安全,可在多线程环境下使用。
15pub struct RateLimitMetrics {
16    inner: Arc<MetricsInner>,
17}
18
19struct MetricsInner {
20    /// 总请求数
21    total_requests: AtomicU64,
22    /// 允许请求数
23    total_allowed: AtomicU64,
24    /// 拒绝请求数
25    total_rejected: AtomicU64,
26    /// 总延迟(微秒)
27    total_latency_us: AtomicU64,
28    /// 最大延迟(微秒)
29    max_latency_us: AtomicU64,
30    /// 最小延迟(微秒)
31    min_latency_us: AtomicU64,
32    /// 按 key 分组的统计
33    per_key: RwLock<HashMap<String, KeyMetrics>>,
34    /// 按算法分组的统计
35    per_algorithm: RwLock<HashMap<String, AlgorithmMetrics>>,
36    /// 启动时间
37    start_time: Instant,
38}
39
40/// 单个 key 的指标
41#[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/// 单个算法的指标
50#[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/// 指标快照
59#[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    /// 创建指标收集器
76    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    /// 记录一次允许的请求
93    pub fn record_allowed(&self, key: &str, algorithm: &str, latency: Duration) {
94        self.record(key, algorithm, true, latency);
95    }
96
97    /// 记录一次拒绝的请求
98    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    /// 获取指标快照
166    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    /// 获取所有 key 的指标
207    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    /// 获取所有算法的指标
216    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    /// 获取特定 key 的指标
225    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    /// 重置所有指标
234    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    /// 导出为 Prometheus 格式文本
250    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    /// 导出为 JSON
292    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
332/// 限流监控器
333///
334/// 定期采样指标并触发告警。适用于长期运行的限流器。
335pub struct RateLimitMonitor {
336    metrics: RateLimitMetrics,
337    alert_threshold: f64,
338    last_snapshot: RwLock<Option<MetricsSnapshot>>,
339}
340
341impl RateLimitMonitor {
342    /// 创建监控器
343    ///
344    /// - `metrics`:指标收集器
345    /// - `alert_threshold`:拒绝率告警阈值(0.0~1.0)
346    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    /// 采样并存储快照
355    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    /// 检查是否触发告警
364    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    /// 获取上次快照
378    pub fn last_snapshot(&self) -> Option<MetricsSnapshot> {
379        self.last_snapshot.read().ok().and_then(|s| s.clone())
380    }
381
382    /// 获取指标收集器
383    pub fn metrics(&self) -> &RateLimitMetrics {
384        &self.metrics
385    }
386}
387
388/// 告警
389#[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        // uptime_secs is a u64 field — always >= 0 trivially; no meaningful assertion needed
606        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}