metrics_lib/
rate_meter.rs

1//! # Ultra-Fast Rate Meter
2//!
3//! High-performance rate calculations with sliding window tracking.
4//!
5//! ## Features
6//!
7//! - **Sub-microsecond tick operations** - Blazingly fast rate tracking
8//! - **Sliding window calculations** - Accurate rate measurements
9//! - **Multiple time windows** - Second, minute, hour rates
10//! - **Lock-free** - Never blocks, never waits
11//! - **Zero allocations** - Pure atomic operations
12//! - **Rate limiting** - Built-in support for throttling
13
14use crate::{MetricsError, Result};
15use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
16use std::time::{Duration, Instant};
17
18/// Ultra-fast rate meter with sliding window calculations
19///
20/// Tracks events per second/minute/hour with minimal overhead.
21/// Cache-line aligned to prevent false sharing.
22#[repr(align(64))]
23pub struct RateMeter {
24    /// Total events seen (monotonic counter)
25    total_events: AtomicU64,
26    /// Current second's events
27    current_second_events: AtomicU32,
28    /// Current minute's events  
29    current_minute_events: AtomicU32,
30    /// Current hour's events
31    current_hour_events: AtomicU32,
32    /// Last update timestamp (seconds since epoch)
33    last_second: AtomicU64,
34    /// Last minute timestamp
35    last_minute: AtomicU64,
36    /// Last hour timestamp
37    last_hour: AtomicU64,
38    /// Window size for rate calculations (nanoseconds)
39    window_ns: u64,
40    /// Creation timestamp
41    created_at: Instant,
42}
43
44/// Rate statistics
45#[derive(Debug, Clone)]
46pub struct RateStats {
47    /// Total events recorded
48    pub total_events: u64,
49    /// Events per second (current window)
50    pub per_second: f64,
51    /// Events per minute (current window)
52    pub per_minute: f64,
53    /// Events per hour (current window)
54    pub per_hour: f64,
55    /// Average rate since creation
56    pub average_rate: f64,
57    /// Time since creation
58    pub age: Duration,
59    /// Current window fill percentage
60    pub window_fill: f64,
61}
62
63impl RateMeter {
64    /// Create new rate meter with 1-second window
65    #[inline]
66    pub fn new() -> Self {
67        Self::with_window(Duration::from_secs(1))
68    }
69
70    /// Create rate meter with custom window size
71    #[inline]
72    pub fn with_window(window: Duration) -> Self {
73        Self {
74            total_events: AtomicU64::new(0),
75            current_second_events: AtomicU32::new(0),
76            current_minute_events: AtomicU32::new(0),
77            current_hour_events: AtomicU32::new(0),
78            last_second: AtomicU64::new(0),
79            last_minute: AtomicU64::new(0),
80            last_hour: AtomicU64::new(0),
81            window_ns: window.as_nanos() as u64,
82            created_at: Instant::now(),
83        }
84    }
85
86    /// Record an event - THE FASTEST PATH
87    ///
88    /// This is optimized for maximum speed:
89    /// - Single atomic increment for total
90    /// - Lazy window updates only when needed
91    /// - Branch prediction friendly
92    #[inline(always)]
93    pub fn tick(&self) {
94        self.tick_n(1);
95    }
96
97    /// Try to record a single event with overflow checks
98    ///
99    /// Returns `Err(MetricsError::Overflow)` if incrementing the total or any
100    /// current window counter would overflow. On success returns `Ok(())`.
101    ///
102    /// Example
103    /// ```
104    /// use metrics_lib::{RateMeter, MetricsError};
105    /// let m = RateMeter::new();
106    /// m.try_tick().unwrap();
107    /// assert_eq!(m.total(), 1);
108    /// ```
109    #[inline(always)]
110    pub fn try_tick(&self) -> Result<()> {
111        self.try_tick_n(1)
112    }
113
114    /// Record N events at once
115    #[inline(always)]
116    pub fn tick_n(&self, n: u32) {
117        if n == 0 {
118            return;
119        }
120
121        // Always increment total (fastest path)
122        self.total_events.fetch_add(n as u64, Ordering::Relaxed);
123
124        // Update windows (lazy - only when needed)
125        let now = self.get_unix_timestamp();
126        self.update_windows(now, n);
127    }
128
129    /// Try to record N events at once with overflow checks
130    ///
131    /// - Returns `Ok(())` on success.
132    /// - Returns `Err(MetricsError::Overflow)` if incrementing any of the
133    ///   involved counters would overflow (`total_events`, `current_second_events`,
134    ///   `current_minute_events`, or `current_hour_events`).
135    ///
136    /// Example
137    /// ```
138    /// use metrics_lib::{RateMeter, MetricsError};
139    /// let m = RateMeter::new();
140    /// assert!(m.try_tick_n(5).is_ok());
141    /// assert_eq!(m.total(), 5);
142    /// ```
143    #[inline(always)]
144    pub fn try_tick_n(&self, n: u32) -> Result<()> {
145        if n == 0 {
146            return Ok(());
147        }
148
149        // Check total_events overflow
150        let total = self.total_events.load(Ordering::Relaxed);
151        if total.checked_add(n as u64).is_none() {
152            return Err(MetricsError::Overflow);
153        }
154
155        // Pre-check window counters roughly (race-safe best-effort). Since windows reset, we
156        // only guard against immediate and obvious overflow.
157        let sec = self.current_second_events.load(Ordering::Relaxed);
158        if sec.checked_add(n).is_none() {
159            return Err(MetricsError::Overflow);
160        }
161        let min = self.current_minute_events.load(Ordering::Relaxed);
162        if min.checked_add(n).is_none() {
163            return Err(MetricsError::Overflow);
164        }
165        let hour = self.current_hour_events.load(Ordering::Relaxed);
166        if hour.checked_add(n).is_none() {
167            return Err(MetricsError::Overflow);
168        }
169
170        // Apply updates
171        self.total_events.fetch_add(n as u64, Ordering::Relaxed);
172        let now = self.get_unix_timestamp();
173        self.update_windows(now, n);
174        Ok(())
175    }
176
177    /// Get current rate (events per second in current window)
178    ///
179    /// Note: This method is `#[must_use]`. The returned rate conveys the
180    /// current measurement; ignoring it may indicate a logic bug.
181    #[must_use]
182    #[inline]
183    pub fn rate(&self) -> f64 {
184        let now = self.get_unix_timestamp();
185        self.update_windows(now, 0);
186
187        let events = self.current_second_events.load(Ordering::Relaxed);
188        events as f64
189    }
190
191    /// Get rate per second
192    ///
193    /// Note: `#[must_use]` — see [`Self::rate`].
194    #[must_use]
195    #[inline]
196    pub fn rate_per_second(&self) -> f64 {
197        self.rate()
198    }
199
200    /// Get rate per minute
201    ///
202    /// Note: `#[must_use]`. The returned value reflects the current window
203    /// state; dropping it may hide incorrect flow.
204    #[must_use]
205    #[inline]
206    pub fn rate_per_minute(&self) -> f64 {
207        let now = self.get_unix_timestamp();
208        self.update_windows(now, 0);
209
210        let events = self.current_minute_events.load(Ordering::Relaxed);
211        events as f64
212    }
213
214    /// Get rate per hour
215    ///
216    /// Note: `#[must_use]`. The returned value reflects the current window
217    /// state; dropping it may hide incorrect flow.
218    #[must_use]
219    #[inline]
220    pub fn rate_per_hour(&self) -> f64 {
221        let now = self.get_unix_timestamp();
222        self.update_windows(now, 0);
223
224        let events = self.current_hour_events.load(Ordering::Relaxed);
225        events as f64
226    }
227
228    /// Get total events since creation
229    ///
230    /// Note: `#[must_use]`. Total is often used for invariants and sanity
231    /// checks; ignoring it may indicate a bug.
232    #[must_use]
233    #[inline(always)]
234    pub fn total(&self) -> u64 {
235        self.total_events.load(Ordering::Relaxed)
236    }
237
238    /// Check if rate exceeds limit
239    ///
240    /// Note: `#[must_use]`. The boolean result determines control flow; if you
241    /// don't branch on it, consider whether the call is needed.
242    #[must_use]
243    #[inline]
244    pub fn exceeds_rate(&self, limit: f64) -> bool {
245        self.rate() > limit
246    }
247
248    /// Check if we can allow N more events without exceeding limit
249    ///
250    /// Note: `#[must_use]`. The boolean result determines control flow; if you
251    /// don't branch on it, consider whether the call is needed.
252    #[must_use]
253    #[inline]
254    pub fn can_allow(&self, n: u32, limit: f64) -> bool {
255        let current_rate = self.rate();
256        (current_rate + n as f64) <= limit
257    }
258
259    /// Rate limiting - tick only if under limit
260    ///
261    /// Note: `#[must_use]`. The boolean result indicates whether a tick was
262    /// recorded; ignoring it may hide throttling decisions.
263    #[must_use]
264    #[inline]
265    pub fn tick_if_under_limit(&self, limit: f64) -> bool {
266        if self.can_allow(1, limit) {
267            self.tick();
268            true
269        } else {
270            false
271        }
272    }
273
274    /// Try to tick if under limit, with overflow checks
275    ///
276    /// Attempts to record a single event only if the current rate would not
277    /// exceed `limit`. Returns:
278    /// - `Ok(true)` if the event was recorded.
279    /// - `Ok(false)` if the event would exceed the limit (no change made).
280    /// - `Err(MetricsError::Overflow)` if counters would overflow while recording.
281    ///
282    /// Example
283    /// ```
284    /// use metrics_lib::RateMeter;
285    /// let m = RateMeter::new();
286    /// assert!(m.try_tick_if_under_limit(10.0).unwrap());
287    /// ```
288    #[inline]
289    pub fn try_tick_if_under_limit(&self, limit: f64) -> Result<bool> {
290        if self.can_allow(1, limit) {
291            self.try_tick()?;
292            Ok(true)
293        } else {
294            Ok(false)
295        }
296    }
297
298    /// Burst rate limiting - allow N events if under limit
299    ///
300    /// Note: `#[must_use]`. The boolean result indicates whether a burst was
301    /// recorded; ignoring it may hide throttling decisions.
302    #[must_use]
303    #[inline]
304    pub fn tick_burst_if_under_limit(&self, n: u32, limit: f64) -> bool {
305        if self.can_allow(n, limit) {
306            self.tick_n(n);
307            true
308        } else {
309            false
310        }
311    }
312
313    /// Reset all counters
314    #[inline]
315    pub fn reset(&self) {
316        let now = self.get_unix_timestamp();
317
318        self.total_events.store(0, Ordering::SeqCst);
319        self.current_second_events.store(0, Ordering::SeqCst);
320        self.current_minute_events.store(0, Ordering::SeqCst);
321        self.current_hour_events.store(0, Ordering::SeqCst);
322        self.last_second.store(now, Ordering::SeqCst);
323        self.last_minute.store(now / 60, Ordering::SeqCst);
324        self.last_hour.store(now / 3600, Ordering::SeqCst);
325    }
326
327    /// Get comprehensive statistics
328    ///
329    /// Note: `#[must_use]`. Statistics summarize current state; dropping the
330    /// result may indicate a logic bug.
331    #[must_use]
332    pub fn stats(&self) -> RateStats {
333        let now = self.get_unix_timestamp();
334        self.update_windows(now, 0);
335
336        let total_events = self.total();
337        let per_second = self.current_second_events.load(Ordering::Relaxed) as f64;
338        let per_minute = self.current_minute_events.load(Ordering::Relaxed) as f64;
339        let per_hour = self.current_hour_events.load(Ordering::Relaxed) as f64;
340
341        let age = self.created_at.elapsed();
342        let average_rate = if age.as_secs_f64() > 0.0 {
343            total_events as f64 / age.as_secs_f64()
344        } else {
345            0.0
346        };
347
348        // Calculate window fill (how much of the window has data)
349        let window_fill = if self.window_ns > 0 {
350            let window_seconds = self.window_ns as f64 / 1_000_000_000.0;
351            let elapsed_in_window = age.as_secs_f64().min(window_seconds);
352            (elapsed_in_window / window_seconds * 100.0).min(100.0)
353        } else {
354            100.0
355        };
356
357        RateStats {
358            total_events,
359            per_second,
360            per_minute,
361            per_hour,
362            average_rate,
363            age,
364            window_fill,
365        }
366    }
367
368    /// Get age since creation
369    ///
370    /// Note: `#[must_use]`. Age is frequently used in rate calculations; don't
371    /// call this for side effects.
372    #[must_use]
373    #[inline]
374    pub fn age(&self) -> Duration {
375        self.created_at.elapsed()
376    }
377
378    /// Check if rate meter is empty (no events recorded)
379    ///
380    /// Note: `#[must_use]`. The boolean result determines control flow.
381    #[must_use]
382    #[inline]
383    pub fn is_empty(&self) -> bool {
384        self.total() == 0
385    }
386
387    // Internal helper methods
388
389    #[inline(always)]
390    fn get_unix_timestamp(&self) -> u64 {
391        // Use the current system time in seconds since UNIX_EPOCH.
392        // The previous implementation added `created_at.elapsed()` to now, which
393        // double-counted time and could skew window transitions.
394        std::time::SystemTime::now()
395            .duration_since(std::time::UNIX_EPOCH)
396            .unwrap_or_default()
397            .as_secs()
398    }
399
400    #[inline]
401    fn update_windows(&self, now: u64, new_events: u32) {
402        // Update second window
403        let current_second = now;
404        let last_second = self.last_second.load(Ordering::Relaxed);
405
406        if current_second != last_second {
407            // New second - reset counter
408            if self
409                .last_second
410                .compare_exchange(
411                    last_second,
412                    current_second,
413                    Ordering::Relaxed,
414                    Ordering::Relaxed,
415                )
416                .is_ok()
417            {
418                self.current_second_events
419                    .store(new_events, Ordering::Relaxed);
420            } else {
421                // Another thread updated, add to current
422                self.current_second_events
423                    .fetch_add(new_events, Ordering::Relaxed);
424            }
425        } else if new_events > 0 {
426            // Same second - add events
427            self.current_second_events
428                .fetch_add(new_events, Ordering::Relaxed);
429        }
430
431        // Update minute window
432        let current_minute = now / 60;
433        let last_minute = self.last_minute.load(Ordering::Relaxed);
434
435        if current_minute != last_minute {
436            if self
437                .last_minute
438                .compare_exchange(
439                    last_minute,
440                    current_minute,
441                    Ordering::Relaxed,
442                    Ordering::Relaxed,
443                )
444                .is_ok()
445            {
446                self.current_minute_events
447                    .store(new_events, Ordering::Relaxed);
448            } else {
449                self.current_minute_events
450                    .fetch_add(new_events, Ordering::Relaxed);
451            }
452        } else if new_events > 0 {
453            self.current_minute_events
454                .fetch_add(new_events, Ordering::Relaxed);
455        }
456
457        // Update hour window
458        let current_hour = now / 3600;
459        let last_hour = self.last_hour.load(Ordering::Relaxed);
460
461        if current_hour != last_hour {
462            if self
463                .last_hour
464                .compare_exchange(
465                    last_hour,
466                    current_hour,
467                    Ordering::Relaxed,
468                    Ordering::Relaxed,
469                )
470                .is_ok()
471            {
472                self.current_hour_events
473                    .store(new_events, Ordering::Relaxed);
474            } else {
475                self.current_hour_events
476                    .fetch_add(new_events, Ordering::Relaxed);
477            }
478        } else if new_events > 0 {
479            self.current_hour_events
480                .fetch_add(new_events, Ordering::Relaxed);
481        }
482    }
483}
484
485impl Default for RateMeter {
486    #[inline]
487    fn default() -> Self {
488        Self::new()
489    }
490}
491
492impl std::fmt::Display for RateMeter {
493    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494        write!(f, "RateMeter({:.1}/s, {} total)", self.rate(), self.total())
495    }
496}
497
498impl std::fmt::Debug for RateMeter {
499    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500        let stats = self.stats();
501        f.debug_struct("RateMeter")
502            .field("total_events", &stats.total_events)
503            .field("per_second", &stats.per_second)
504            .field("per_minute", &stats.per_minute)
505            .field("average_rate", &stats.average_rate)
506            .field("age", &stats.age)
507            .finish()
508    }
509}
510
511// Thread safety
512unsafe impl Send for RateMeter {}
513unsafe impl Sync for RateMeter {}
514
515/// Specialized rate meters for common use cases
516pub mod specialized {
517    use super::*;
518
519    /// API rate limiter (requests per second)
520    #[repr(align(64))]
521    pub struct ApiRateLimiter {
522        meter: RateMeter,
523        limit: AtomicU32, // requests per second
524    }
525
526    impl ApiRateLimiter {
527        /// Create API rate limiter with requests per second limit
528        #[inline]
529        pub fn new(requests_per_second: u32) -> Self {
530            Self {
531                meter: RateMeter::new(),
532                limit: AtomicU32::new(requests_per_second),
533            }
534        }
535
536        /// Try to make a request - returns true if allowed
537        #[inline]
538        pub fn try_request(&self) -> bool {
539            let limit = self.limit.load(Ordering::Relaxed) as f64;
540            self.meter.tick_if_under_limit(limit)
541        }
542
543        /// Try to make N requests - returns true if all allowed
544        #[inline]
545        pub fn try_requests(&self, n: u32) -> bool {
546            let limit = self.limit.load(Ordering::Relaxed) as f64;
547            self.meter.tick_burst_if_under_limit(n, limit)
548        }
549
550        /// Update the rate limit
551        #[inline]
552        pub fn set_limit(&self, requests_per_second: u32) {
553            self.limit.store(requests_per_second, Ordering::Relaxed);
554        }
555
556        /// Get current limit
557        #[inline]
558        pub fn get_limit(&self) -> u32 {
559            self.limit.load(Ordering::Relaxed)
560        }
561
562        /// Get current rate
563        #[inline]
564        pub fn current_rate(&self) -> f64 {
565            self.meter.rate()
566        }
567
568        /// Get total requests
569        #[inline]
570        pub fn total_requests(&self) -> u64 {
571            self.meter.total()
572        }
573
574        /// Check if currently over limit
575        #[inline]
576        pub fn is_over_limit(&self) -> bool {
577            let limit = self.limit.load(Ordering::Relaxed) as f64;
578            self.meter.rate() > limit
579        }
580
581        /// Reset the rate limiter
582        #[inline]
583        pub fn reset(&self) {
584            self.meter.reset();
585        }
586    }
587
588    impl Default for ApiRateLimiter {
589        fn default() -> Self {
590            Self::new(1000)
591        } // 1000 req/s default
592    }
593
594    /// Throughput meter for measuring data rates
595    #[repr(align(64))]
596    pub struct ThroughputMeter {
597        meter: RateMeter,
598    }
599
600    impl ThroughputMeter {
601        /// Create new throughput meter
602        #[inline]
603        pub fn new() -> Self {
604            Self {
605                meter: RateMeter::new(),
606            }
607        }
608
609        /// Record bytes transferred
610        #[inline(always)]
611        pub fn record_bytes(&self, bytes: u64) {
612            self.meter.tick_n(bytes as u32);
613        }
614
615        /// Get bytes per second
616        #[inline]
617        pub fn bytes_per_second(&self) -> f64 {
618            self.meter.rate()
619        }
620
621        /// Get kilobytes per second
622        #[inline]
623        pub fn kb_per_second(&self) -> f64 {
624            self.meter.rate() / 1024.0
625        }
626
627        /// Get megabytes per second
628        #[inline]
629        pub fn mb_per_second(&self) -> f64 {
630            self.meter.rate() / (1024.0 * 1024.0)
631        }
632
633        /// Get gigabytes per second
634        #[inline]
635        pub fn gb_per_second(&self) -> f64 {
636            self.meter.rate() / (1024.0 * 1024.0 * 1024.0)
637        }
638
639        /// Get total bytes transferred
640        #[inline]
641        pub fn total_bytes(&self) -> u64 {
642            self.meter.total()
643        }
644
645        /// Reset the throughput meter
646        #[inline]
647        pub fn reset(&self) {
648            self.meter.reset();
649        }
650    }
651
652    impl Default for ThroughputMeter {
653        fn default() -> Self {
654            Self::new()
655        }
656    }
657}
658
659#[cfg(test)]
660mod tests {
661    use super::*;
662    use std::sync::Arc;
663    use std::thread;
664
665    #[test]
666    fn test_basic_operations() {
667        let meter = RateMeter::new();
668
669        assert!(meter.is_empty());
670        assert_eq!(meter.total(), 0);
671        assert_eq!(meter.rate(), 0.0);
672
673        meter.tick();
674        assert!(!meter.is_empty());
675        assert_eq!(meter.total(), 1);
676
677        meter.tick_n(5);
678        assert_eq!(meter.total(), 6);
679    }
680
681    #[test]
682    fn test_rate_calculations() {
683        let meter = RateMeter::new();
684
685        // Record events rapidly in same second
686        for _ in 0..100 {
687            meter.tick();
688        }
689
690        let rate = meter.rate();
691        assert_eq!(rate, 100.0);
692        assert_eq!(meter.rate_per_second(), 100.0);
693    }
694
695    #[test]
696    fn test_multiple_windows() {
697        let meter = RateMeter::new();
698
699        // Record events
700        for _ in 0..60 {
701            meter.tick();
702        }
703
704        let stats = meter.stats();
705        assert_eq!(stats.total_events, 60);
706        assert_eq!(stats.per_second, 60.0);
707        assert_eq!(stats.per_minute, 60.0);
708        assert_eq!(stats.per_hour, 60.0);
709    }
710
711    #[test]
712    fn test_rate_limiting() {
713        let meter = RateMeter::new();
714
715        // Allow requests under limit
716        assert!(meter.tick_if_under_limit(10.0));
717        assert!(meter.tick_if_under_limit(10.0));
718
719        // Add more to approach limit
720        meter.tick_n(8);
721
722        // Should now be at/over limit
723        assert!(!meter.tick_if_under_limit(10.0));
724        assert!(meter.exceeds_rate(9.0));
725        assert!(!meter.exceeds_rate(11.0));
726    }
727
728    #[test]
729    fn test_burst_rate_limiting() {
730        let meter = RateMeter::new();
731
732        // Try burst under limit
733        assert!(meter.tick_burst_if_under_limit(5, 10.0));
734        assert_eq!(meter.total(), 5);
735
736        // Try burst that would exceed limit
737        assert!(!meter.tick_burst_if_under_limit(10, 10.0));
738        assert_eq!(meter.total(), 5); // Should be unchanged
739
740        // Try smaller burst that fits
741        assert!(meter.tick_burst_if_under_limit(3, 10.0));
742        assert_eq!(meter.total(), 8);
743    }
744
745    #[test]
746    fn test_can_allow() {
747        let meter = RateMeter::new();
748
749        meter.tick_n(5);
750
751        assert!(meter.can_allow(3, 10.0)); // 5 + 3 = 8 <= 10
752        assert!(!meter.can_allow(6, 10.0)); // 5 + 6 = 11 > 10
753        assert!(meter.can_allow(5, 10.0)); // 5 + 5 = 10 <= 10
754    }
755
756    #[test]
757    fn test_reset() {
758        let meter = RateMeter::new();
759
760        meter.tick_n(100);
761        assert_eq!(meter.total(), 100);
762        assert!(meter.rate() > 0.0);
763
764        meter.reset();
765        assert_eq!(meter.total(), 0);
766        assert_eq!(meter.rate(), 0.0);
767        assert!(meter.is_empty());
768    }
769
770    #[test]
771    fn test_statistics() {
772        let meter = RateMeter::new();
773
774        meter.tick_n(50);
775
776        let stats = meter.stats();
777        assert_eq!(stats.total_events, 50);
778        assert_eq!(stats.per_second, 50.0);
779        assert!(stats.average_rate > 0.0);
780        assert!(stats.age > Duration::from_nanos(0));
781        assert!(stats.window_fill >= 0.0);
782    }
783
784    #[test]
785    fn test_api_rate_limiter() {
786        let limiter = specialized::ApiRateLimiter::new(10);
787
788        // Should allow requests under limit
789        for _ in 0..10 {
790            assert!(limiter.try_request());
791        }
792
793        // Should deny request over limit
794        assert!(!limiter.try_request());
795
796        // Check status - rate is at limit (10), not over it since request was denied
797        assert_eq!(limiter.current_rate(), 10.0);
798        assert_eq!(limiter.total_requests(), 10);
799        assert_eq!(limiter.get_limit(), 10);
800
801        // Update limit
802        limiter.set_limit(20);
803        assert_eq!(limiter.get_limit(), 20);
804        assert!(!limiter.is_over_limit()); // Now under new limit
805
806        // Test burst requests
807        limiter.reset();
808        assert!(limiter.try_requests(5));
809        assert_eq!(limiter.total_requests(), 5);
810
811        assert!(!limiter.try_requests(20)); // Would exceed limit
812        assert_eq!(limiter.total_requests(), 5); // Unchanged
813    }
814
815    #[test]
816    fn test_throughput_meter() {
817        let meter = specialized::ThroughputMeter::new();
818
819        meter.record_bytes(1024); // 1 KB
820        assert_eq!(meter.bytes_per_second(), 1024.0);
821        assert_eq!(meter.kb_per_second(), 1.0);
822        assert_eq!(meter.total_bytes(), 1024);
823
824        meter.record_bytes(1024 * 1024); // 1 MB more
825        assert_eq!(meter.total_bytes(), 1024 + 1024 * 1024);
826        assert!((meter.mb_per_second() - 1.001).abs() < 0.01);
827    }
828
829    #[test]
830    fn test_high_concurrency() {
831        let meter = Arc::new(RateMeter::new());
832        let num_threads = 50;
833        let ticks_per_thread = 1000;
834
835        let handles: Vec<_> = (0..num_threads)
836            .map(|_| {
837                let meter = Arc::clone(&meter);
838                thread::spawn(move || {
839                    for _ in 0..ticks_per_thread {
840                        meter.tick();
841                    }
842                })
843            })
844            .collect();
845
846        for handle in handles {
847            handle.join().unwrap();
848        }
849
850        assert_eq!(meter.total(), num_threads * ticks_per_thread);
851
852        let stats = meter.stats();
853        assert!(stats.average_rate > 0.0);
854        assert_eq!(stats.total_events, num_threads * ticks_per_thread);
855    }
856
857    #[test]
858    fn test_concurrent_rate_limiting() {
859        let limiter = Arc::new(specialized::ApiRateLimiter::new(100));
860        let num_threads = 20;
861
862        let handles: Vec<_> = (0..num_threads)
863            .map(|_| {
864                let limiter = Arc::clone(&limiter);
865                thread::spawn(move || {
866                    let mut successful = 0;
867                    for _ in 0..10 {
868                        if limiter.try_request() {
869                            successful += 1;
870                        }
871                    }
872                    successful
873                })
874            })
875            .collect();
876
877        let total_successful: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
878
879        // Should be limited to around 100 requests.
880        // Allow tolerance for concurrent overshoot across varied CI runners.
881        // Cap by total attempts (num_threads * 10) and relax to up to 2x the limit in extreme cases.
882        // Under coverage instrumentation, timing can skew more; keep a generous bound.
883        let total_attempts = num_threads * 10;
884        let strict_cap = 2 * 100; // 2x limit
885        let upper_bound = if cfg!(coverage) {
886            total_attempts.min(strict_cap.max(160))
887        } else {
888            total_attempts.min(strict_cap)
889        };
890        assert!(
891            total_successful <= upper_bound,
892            "total_successful={total_successful} > upper_bound={upper_bound}",
893        );
894        assert!(
895            total_successful >= 90,
896            "total_successful={total_successful} < lower_bound=90",
897        ); // Account for timing variations
898    }
899
900    #[test]
901    fn test_display_and_debug() {
902        let meter = RateMeter::new();
903        meter.tick_n(42);
904
905        let display_str = format!("{meter}");
906        assert!(display_str.contains("RateMeter"));
907        assert!(display_str.contains("42 total"));
908
909        let debug_str = format!("{meter:?}");
910        assert!(debug_str.contains("RateMeter"));
911        assert!(debug_str.contains("total_events"));
912    }
913
914    #[test]
915    fn test_custom_window() {
916        let meter = RateMeter::with_window(Duration::from_secs(5));
917
918        meter.tick_n(10);
919        assert_eq!(meter.total(), 10);
920        assert_eq!(meter.rate(), 10.0);
921
922        let stats = meter.stats();
923        assert!(stats.window_fill >= 0.0);
924    }
925
926    // New tests for try_ variants and overflow/error conditions
927    #[test]
928    fn test_try_tick_and_try_tick_n_ok() {
929        let meter = RateMeter::new();
930        assert!(meter.try_tick().is_ok());
931        assert!(meter.try_tick_n(5).is_ok());
932        assert_eq!(meter.total(), 6);
933    }
934
935    #[test]
936    fn test_try_tick_n_total_overflow() {
937        let meter = RateMeter::new();
938        // Directly set near overflow
939        meter.total_events.store(u64::MAX - 1, Ordering::Relaxed);
940        // Adding 2 should overflow
941        let err = meter.try_tick_n(2).unwrap_err();
942        assert_eq!(err, MetricsError::Overflow);
943    }
944
945    #[test]
946    fn test_try_tick_n_window_overflow() {
947        let meter = RateMeter::new();
948        // Force current windows to near overflow and ensure we are in the same second/minute/hour
949        let now = meter.get_unix_timestamp();
950        meter.last_second.store(now, Ordering::Relaxed);
951        meter.last_minute.store(now / 60, Ordering::Relaxed);
952        meter.last_hour.store(now / 3600, Ordering::Relaxed);
953
954        meter
955            .current_second_events
956            .store(u32::MAX - 1, Ordering::Relaxed);
957        meter
958            .current_minute_events
959            .store(u32::MAX - 1, Ordering::Relaxed);
960        meter
961            .current_hour_events
962            .store(u32::MAX - 1, Ordering::Relaxed);
963
964        // Any n >= 2 should trip the pre-check overflow guard
965        let err = meter.try_tick_n(2).unwrap_err();
966        assert_eq!(err, MetricsError::Overflow);
967    }
968
969    #[test]
970    fn test_try_tick_if_under_limit() {
971        let meter = RateMeter::new();
972        // Under limit should tick and return true
973        assert!(meter.try_tick_if_under_limit(10.0).unwrap());
974        // Drive rate up close to limit
975        assert!(meter.try_tick_n(8).is_ok());
976        // Now at 9; allow one more at limit 10
977        assert!(meter.try_tick_if_under_limit(10.0).unwrap());
978        // Next should be denied (would exceed)
979        assert!(!meter.try_tick_if_under_limit(10.0).unwrap());
980    }
981}
982
983#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
984#[allow(unused_imports)]
985mod benchmarks {
986    use super::*;
987    use std::time::Instant;
988
989    #[cfg_attr(not(feature = "bench-tests"), ignore)]
990    #[test]
991    fn bench_rate_meter_tick() {
992        let meter = RateMeter::new();
993        let iterations = 10_000_000;
994
995        let start = Instant::now();
996        for _ in 0..iterations {
997            meter.tick();
998        }
999        let elapsed = start.elapsed();
1000
1001        println!(
1002            "RateMeter tick: {:.2} ns/op",
1003            elapsed.as_nanos() as f64 / iterations as f64
1004        );
1005
1006        assert_eq!(meter.total(), iterations);
1007        // Should be under 400ns per tick (relaxed from 200ns)
1008        assert!(elapsed.as_nanos() / (iterations as u128) < 400);
1009    }
1010
1011    #[cfg_attr(not(feature = "bench-tests"), ignore)]
1012    #[test]
1013    fn bench_rate_meter_tick_n() {
1014        let meter = RateMeter::new();
1015        let iterations = 1_000_000;
1016
1017        let start = Instant::now();
1018        for i in 0..iterations {
1019            meter.tick_n((i % 10) + 1);
1020        }
1021        let elapsed = start.elapsed();
1022
1023        println!(
1024            "RateMeter tick_n: {:.2} ns/op",
1025            elapsed.as_nanos() as f64 / iterations as f64
1026        );
1027
1028        // Should be under 500ns per tick_n (relaxed from 300ns)
1029        assert!(elapsed.as_nanos() / (iterations as u128) < 500);
1030    }
1031
1032    #[cfg_attr(not(feature = "bench-tests"), ignore)]
1033    #[test]
1034    fn bench_rate_calculation() {
1035        let meter = RateMeter::new();
1036
1037        // Fill with data
1038        meter.tick_n(1000);
1039
1040        let iterations = 1_000_000;
1041        let start = Instant::now();
1042
1043        for _ in 0..iterations {
1044            let _ = meter.rate();
1045        }
1046
1047        let elapsed = start.elapsed();
1048        println!(
1049            "RateMeter rate: {:.2} ns/op",
1050            elapsed.as_nanos() as f64 / iterations as f64
1051        );
1052
1053        // Should be very fast (relaxed from 100ns to 300ns)
1054        assert!(elapsed.as_nanos() / iterations < 300);
1055    }
1056
1057    #[cfg_attr(not(feature = "bench-tests"), ignore)]
1058    #[test]
1059    fn bench_api_rate_limiter() {
1060        let limiter = specialized::ApiRateLimiter::new(1_000_000); // High limit
1061        let iterations = 1_000_000;
1062
1063        let start = Instant::now();
1064        for _ in 0..iterations {
1065            let _ = limiter.try_request();
1066        }
1067        let elapsed = start.elapsed();
1068
1069        println!(
1070            "ApiRateLimiter try_request: {:.2} ns/op",
1071            elapsed.as_nanos() as f64 / iterations as f64
1072        );
1073
1074        // Should be under 1000ns per request (relaxed from 300ns)
1075        assert!(elapsed.as_nanos() / iterations < 1000);
1076    }
1077}