Skip to main content

metrics_lib/
counter.rs

1//! # Ultra-Fast Atomic Counter
2//!
3//! The fastest counter implementation possible - sub-3ns increments.
4//!
5//! ## Features
6//!
7//! - **Sub-3ns increments** - Single atomic instruction
8//! - **Zero allocations** - Pure stack operations
9//! - **Lock-free** - Never blocks, never waits
10//! - **Cache optimized** - Aligned to prevent false sharing
11//! - **Overflow safe** - Handles u64::MAX gracefully
12
13use crate::{MetricsError, Result};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17/// Ultra-fast atomic counter
18///
19/// Optimized for maximum throughput with minimal memory overhead.
20/// Cache-line aligned to prevent false sharing.
21#[repr(align(64))]
22pub struct Counter {
23    /// Main counter value
24    value: AtomicU64,
25    /// Creation timestamp for rate calculations
26    created_at: Instant,
27}
28
29/// Counter statistics
30#[derive(Debug, Clone)]
31pub struct CounterStats {
32    /// Current counter value
33    pub value: u64,
34    /// Time since counter creation
35    pub age: Duration,
36    /// Average increments per second since creation
37    pub rate_per_second: f64,
38    /// Total increments (same as value for basic counter)
39    pub total: u64,
40}
41
42impl Counter {
43    /// Create new counter starting at zero
44    #[inline]
45    pub fn new() -> Self {
46        Self {
47            value: AtomicU64::new(0),
48            created_at: Instant::now(),
49        }
50    }
51
52    /// Create counter with initial value
53    #[inline]
54    pub fn with_value(initial: u64) -> Self {
55        Self {
56            value: AtomicU64::new(initial),
57            created_at: Instant::now(),
58        }
59    }
60
61    /// Increment by 1 - THE FASTEST PATH
62    ///
63    /// This is optimized to be as fast as physically possible:
64    /// - Single atomic fetch_add instruction
65    /// - Relaxed memory ordering for maximum speed
66    /// - Inlined for zero function call overhead
67    #[inline(always)]
68    pub fn inc(&self) {
69        self.value.fetch_add(1, Ordering::Relaxed);
70    }
71
72    /// Try to increment by 1 with overflow check
73    ///
74    /// Returns `Ok(())` on success, or `Err(MetricsError::Overflow)` if the
75    /// increment would overflow `u64::MAX`.
76    ///
77    /// Example
78    /// ```
79    /// use metrics_lib::{Counter, MetricsError};
80    /// let c = Counter::with_value(u64::MAX - 1);
81    /// c.try_inc().unwrap();
82    /// assert_eq!(c.get(), u64::MAX);
83    /// assert!(matches!(c.try_inc(), Err(MetricsError::Overflow)));
84    /// ```
85    #[inline(always)]
86    pub fn try_inc(&self) -> Result<()> {
87        // CAS loop: the overflow check and the increment are together atomic,
88        // so this is safe under concurrent access (no TOCTOU race).
89        loop {
90            let current = self.value.load(Ordering::Relaxed);
91            if current == u64::MAX {
92                return Err(MetricsError::Overflow);
93            }
94            match self.value.compare_exchange_weak(
95                current,
96                current + 1,
97                Ordering::Relaxed,
98                Ordering::Relaxed,
99            ) {
100                Ok(_) => return Ok(()),
101                Err(_) => continue,
102            }
103        }
104    }
105
106    /// Add arbitrary amount - also blazingly fast
107    ///
108    /// Zero branch optimization - if amount is 0, still does the atomic
109    /// operation to maintain consistent performance characteristics
110    #[inline(always)]
111    pub fn add(&self, amount: u64) {
112        self.value.fetch_add(amount, Ordering::Relaxed);
113    }
114
115    /// Try to add an arbitrary amount with overflow check
116    ///
117    /// Returns `Ok(())` on success, or `Err(MetricsError::Overflow)` if the
118    /// addition would overflow `u64::MAX`.
119    ///
120    /// Example
121    /// ```
122    /// use metrics_lib::{Counter, MetricsError};
123    /// let c = Counter::with_value(u64::MAX - 5);
124    /// assert!(c.try_add(4).is_ok());
125    /// assert!(matches!(c.try_add(2), Err(MetricsError::Overflow)));
126    /// ```
127    #[inline(always)]
128    pub fn try_add(&self, amount: u64) -> Result<()> {
129        if amount == 0 {
130            return Ok(());
131        }
132        // CAS loop: overflow check and addition are together atomic.
133        loop {
134            let current = self.value.load(Ordering::Relaxed);
135            let new_val = current.checked_add(amount).ok_or(MetricsError::Overflow)?;
136            match self.value.compare_exchange_weak(
137                current,
138                new_val,
139                Ordering::Relaxed,
140                Ordering::Relaxed,
141            ) {
142                Ok(_) => return Ok(()),
143                Err(_) => continue,
144            }
145        }
146    }
147
148    /// Get current value - single atomic load
149    #[must_use]
150    #[inline(always)]
151    pub fn get(&self) -> u64 {
152        self.value.load(Ordering::Relaxed)
153    }
154
155    /// Reset to zero - use sparingly
156    ///
157    /// Note: This uses SeqCst ordering to ensure all threads see the reset
158    #[inline]
159    pub fn reset(&self) {
160        self.value.store(0, Ordering::SeqCst);
161    }
162
163    /// Set to specific value - use sparingly
164    ///
165    /// Note: This uses SeqCst ordering for consistency
166    #[inline]
167    pub fn set(&self, value: u64) {
168        self.value.store(value, Ordering::SeqCst);
169    }
170
171    /// Try to set to a specific value (always succeeds for `u64`)
172    ///
173    /// This method never fails and always returns `Ok(())`.
174    #[inline]
175    pub fn try_set(&self, value: u64) -> Result<()> {
176        self.set(value);
177        Ok(())
178    }
179
180    /// Atomic compare-and-swap
181    ///
182    /// Returns Ok(previous_value) if successful, Err(current_value) if failed
183    #[inline]
184    pub fn compare_and_swap(&self, expected: u64, new: u64) -> core::result::Result<u64, u64> {
185        match self
186            .value
187            .compare_exchange(expected, new, Ordering::SeqCst, Ordering::SeqCst)
188        {
189            Ok(prev) => Ok(prev),
190            Err(current) => Err(current),
191        }
192    }
193
194    /// Add amount and return previous value
195    #[must_use]
196    #[inline]
197    pub fn fetch_add(&self, amount: u64) -> u64 {
198        self.value.fetch_add(amount, Ordering::Relaxed)
199    }
200
201    /// Checked fetch_add that returns the previous value or error on overflow
202    ///
203    /// Returns the previous value on success, or `Err(MetricsError::Overflow)`
204    /// if adding `amount` would overflow `u64::MAX`.
205    ///
206    /// Example
207    /// ```
208    /// use metrics_lib::{Counter, MetricsError};
209    /// let c = Counter::with_value(u64::MAX - 1);
210    /// assert_eq!(c.try_fetch_add(1).unwrap(), u64::MAX - 1);
211    /// assert!(matches!(c.try_fetch_add(1), Err(MetricsError::Overflow)));
212    /// ```
213    #[inline]
214    pub fn try_fetch_add(&self, amount: u64) -> Result<u64> {
215        if amount == 0 {
216            return Ok(self.get());
217        }
218        // CAS loop: overflow check and fetch_add are together atomic.
219        loop {
220            let current = self.value.load(Ordering::Relaxed);
221            let new_val = current.checked_add(amount).ok_or(MetricsError::Overflow)?;
222            match self.value.compare_exchange_weak(
223                current,
224                new_val,
225                Ordering::Relaxed,
226                Ordering::Relaxed,
227            ) {
228                Ok(prev) => return Ok(prev),
229                Err(_) => continue,
230            }
231        }
232    }
233
234    /// Add amount and return new value
235    #[must_use]
236    #[inline]
237    pub fn add_and_get(&self, amount: u64) -> u64 {
238        self.value.fetch_add(amount, Ordering::Relaxed) + amount
239    }
240
241    /// Increment and return new value
242    #[must_use]
243    #[inline]
244    pub fn inc_and_get(&self) -> u64 {
245        self.value.fetch_add(1, Ordering::Relaxed) + 1
246    }
247
248    /// Checked increment that returns new value or error on overflow
249    ///
250    /// Returns the new value, or `Err(MetricsError::Overflow)` if the
251    /// increment would overflow `u64::MAX`.
252    ///
253    /// Example
254    /// ```
255    /// use metrics_lib::{Counter, MetricsError};
256    /// let c = Counter::with_value(u64::MAX - 1);
257    /// assert_eq!(c.try_inc_and_get().unwrap(), u64::MAX);
258    /// assert!(matches!(c.try_inc_and_get(), Err(MetricsError::Overflow)));
259    /// ```
260    #[inline]
261    pub fn try_inc_and_get(&self) -> Result<u64> {
262        // CAS loop: the returned value is the exact new value after the atomic
263        // increment — correct under concurrent access, unlike a load+store pattern.
264        loop {
265            let current = self.value.load(Ordering::Relaxed);
266            let new_val = current.checked_add(1).ok_or(MetricsError::Overflow)?;
267            match self.value.compare_exchange_weak(
268                current,
269                new_val,
270                Ordering::Relaxed,
271                Ordering::Relaxed,
272            ) {
273                Ok(_) => return Ok(new_val),
274                Err(_) => continue,
275            }
276        }
277    }
278
279    /// Get comprehensive statistics
280    #[must_use]
281    pub fn stats(&self) -> CounterStats {
282        let value = self.get();
283        let age = self.created_at.elapsed();
284        let age_seconds = age.as_secs_f64();
285
286        let rate_per_second = if age_seconds > 0.0 {
287            value as f64 / age_seconds
288        } else {
289            0.0
290        };
291
292        CounterStats {
293            value,
294            age,
295            rate_per_second,
296            total: value,
297        }
298    }
299
300    /// Get age since creation
301    #[must_use]
302    #[inline]
303    pub fn age(&self) -> Duration {
304        self.created_at.elapsed()
305    }
306
307    /// Check if counter is zero
308    #[must_use]
309    #[inline]
310    pub fn is_zero(&self) -> bool {
311        self.get() == 0
312    }
313
314    /// Get rate per second since creation
315    #[must_use]
316    #[inline]
317    pub fn rate_per_second(&self) -> f64 {
318        let age_seconds = self.age().as_secs_f64();
319        if age_seconds > 0.0 {
320            self.get() as f64 / age_seconds
321        } else {
322            0.0
323        }
324    }
325
326    /// Saturating add - won't overflow past u64::MAX
327    #[inline]
328    pub fn saturating_add(&self, amount: u64) {
329        loop {
330            let current = self.get();
331            let new_value = current.saturating_add(amount);
332
333            // If no change needed (already at max), break
334            if new_value == current {
335                break;
336            }
337
338            // Try to update
339            match self.compare_and_swap(current, new_value) {
340                Ok(_) => break,
341                Err(_) => continue, // Retry with new current value
342            }
343        }
344    }
345}
346
347impl Default for Counter {
348    #[inline]
349    fn default() -> Self {
350        Self::new()
351    }
352}
353
354impl std::fmt::Display for Counter {
355    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356        write!(f, "Counter({})", self.get())
357    }
358}
359
360impl std::fmt::Debug for Counter {
361    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362        f.debug_struct("Counter")
363            .field("value", &self.get())
364            .field("age", &self.age())
365            .field("rate_per_second", &self.rate_per_second())
366            .finish()
367    }
368}
369
370// Counter is composed solely of `AtomicU64` (Send + Sync) and `Instant`
371// (Send + Sync). The compiler derives Send + Sync automatically; no
372// explicit unsafe impl is needed.
373
374/// Batch counter operations for even better performance
375impl Counter {
376    /// Batch increment - for very high throughput scenarios
377    ///
378    /// When you have many increments to do, batch them for better performance
379    #[inline]
380    pub fn batch_inc(&self, count: usize) {
381        if count > 0 {
382            self.add(count as u64);
383        }
384    }
385
386    /// Conditional increment - only increment if condition is true
387    #[inline]
388    pub fn inc_if(&self, condition: bool) {
389        if condition {
390            self.inc();
391        }
392    }
393
394    /// Increment with maximum value
395    #[inline]
396    pub fn inc_max(&self, max_value: u64) -> bool {
397        loop {
398            let current = self.get();
399            if current >= max_value {
400                return false;
401            }
402
403            match self.compare_and_swap(current, current + 1) {
404                Ok(_) => return true,
405                Err(_) => continue, // Retry
406            }
407        }
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414    use std::sync::Arc;
415    use std::thread;
416
417    #[test]
418    fn test_basic_operations() {
419        let counter = Counter::new();
420
421        assert_eq!(counter.get(), 0);
422        assert!(counter.is_zero());
423
424        counter.inc();
425        assert_eq!(counter.get(), 1);
426        assert!(!counter.is_zero());
427
428        counter.add(5);
429        assert_eq!(counter.get(), 6);
430
431        counter.reset();
432        assert_eq!(counter.get(), 0);
433
434        counter.set(42);
435        assert_eq!(counter.get(), 42);
436    }
437
438    #[test]
439    fn test_fetch_operations() {
440        let counter = Counter::new();
441
442        assert_eq!(counter.fetch_add(10), 0);
443        assert_eq!(counter.get(), 10);
444
445        assert_eq!(counter.inc_and_get(), 11);
446        assert_eq!(counter.add_and_get(5), 16);
447    }
448
449    #[test]
450    fn test_compare_and_swap() {
451        let counter = Counter::new();
452        counter.set(10);
453
454        // Successful swap
455        assert_eq!(counter.compare_and_swap(10, 20), Ok(10));
456        assert_eq!(counter.get(), 20);
457
458        // Failed swap
459        assert_eq!(counter.compare_and_swap(10, 30), Err(20));
460        assert_eq!(counter.get(), 20);
461    }
462
463    #[test]
464    fn test_saturating_add() {
465        let counter = Counter::new();
466        counter.set(u64::MAX - 5);
467
468        counter.saturating_add(10);
469        assert_eq!(counter.get(), u64::MAX);
470
471        // Should not overflow
472        counter.saturating_add(100);
473        assert_eq!(counter.get(), u64::MAX);
474    }
475
476    #[test]
477    fn test_conditional_operations() {
478        let counter = Counter::new();
479
480        counter.inc_if(true);
481        assert_eq!(counter.get(), 1);
482
483        counter.inc_if(false);
484        assert_eq!(counter.get(), 1);
485
486        // Test inc_max
487        assert!(counter.inc_max(5));
488        assert_eq!(counter.get(), 2);
489
490        counter.set(5);
491        assert!(!counter.inc_max(5));
492        assert_eq!(counter.get(), 5);
493    }
494
495    #[test]
496    fn test_statistics() {
497        let counter = Counter::new();
498        counter.add(100);
499
500        let stats = counter.stats();
501        assert_eq!(stats.value, 100);
502        assert_eq!(stats.total, 100);
503        assert!(stats.age > Duration::from_nanos(0));
504        // Rate might be 0 if test runs too fast
505        assert!(stats.rate_per_second >= 0.0);
506    }
507
508    #[test]
509    fn test_high_concurrency() {
510        let counter = Arc::new(Counter::new());
511        let num_threads = 100;
512        let increments_per_thread = 1000;
513
514        let handles: Vec<_> = (0..num_threads)
515            .map(|_| {
516                let counter = Arc::clone(&counter);
517                thread::spawn(move || {
518                    for _ in 0..increments_per_thread {
519                        counter.inc();
520                    }
521                })
522            })
523            .collect();
524
525        for handle in handles {
526            handle.join().unwrap();
527        }
528
529        assert_eq!(counter.get(), num_threads * increments_per_thread);
530
531        let stats = counter.stats();
532        assert!(stats.rate_per_second > 0.0);
533    }
534
535    #[test]
536    fn test_batch_operations() {
537        let counter = Counter::new();
538
539        counter.batch_inc(1000);
540        assert_eq!(counter.get(), 1000);
541
542        counter.batch_inc(0); // Should be no-op
543        assert_eq!(counter.get(), 1000);
544    }
545
546    #[test]
547    fn test_display_and_debug() {
548        let counter = Counter::new();
549        counter.set(42);
550
551        let display_str = format!("{counter}");
552        assert!(display_str.contains("42"));
553
554        let debug_str = format!("{counter:?}");
555        assert!(debug_str.contains("Counter"));
556        assert!(debug_str.contains("42"));
557    }
558
559    #[test]
560    fn test_checked_operations_and_overflow_paths() {
561        let counter = Counter::new();
562
563        counter.try_set(3).unwrap();
564        assert_eq!(counter.get(), 3);
565
566        counter.try_inc().unwrap();
567        assert_eq!(counter.get(), 4);
568
569        counter.try_add(0).unwrap();
570        assert_eq!(counter.get(), 4);
571
572        assert_eq!(counter.try_fetch_add(2).unwrap(), 4);
573        assert_eq!(counter.get(), 6);
574
575        assert_eq!(counter.try_fetch_add(0).unwrap(), 6);
576        assert_eq!(counter.try_inc_and_get().unwrap(), 7);
577
578        let overflow = Counter::with_value(u64::MAX);
579        assert!(matches!(overflow.try_inc(), Err(MetricsError::Overflow)));
580        assert!(matches!(overflow.try_add(1), Err(MetricsError::Overflow)));
581        assert!(matches!(
582            overflow.try_fetch_add(1),
583            Err(MetricsError::Overflow)
584        ));
585        assert!(matches!(
586            overflow.try_inc_and_get(),
587            Err(MetricsError::Overflow)
588        ));
589    }
590}
591
592#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
593#[allow(unused_imports)]
594mod benchmarks {
595    use super::*;
596    use std::time::Instant;
597
598    #[cfg_attr(not(feature = "bench-tests"), ignore)]
599    #[test]
600    fn bench_counter_increment() {
601        let counter = Counter::new();
602        let iterations = 10_000_000;
603
604        let start = Instant::now();
605        for _ in 0..iterations {
606            counter.inc();
607        }
608        let elapsed = start.elapsed();
609
610        println!(
611            "Counter increment: {:.2} ns/op",
612            elapsed.as_nanos() as f64 / iterations as f64
613        );
614
615        // Should be under 100ns per increment (relaxed from 50ns)
616        assert!(elapsed.as_nanos() / iterations < 100);
617        assert_eq!(counter.get(), iterations as u64);
618    }
619
620    #[cfg_attr(not(feature = "bench-tests"), ignore)]
621    #[test]
622    fn bench_counter_add() {
623        let counter = Counter::new();
624        let iterations = 1_000_000;
625
626        let start = Instant::now();
627        for i in 0..iterations {
628            counter.add(i + 1);
629        }
630        let elapsed = start.elapsed();
631
632        println!(
633            "Counter add: {:.2} ns/op",
634            elapsed.as_nanos() as f64 / iterations as f64
635        );
636
637        // Should be similar to increment performance (relaxed from 100ns to 200ns)
638        assert!(elapsed.as_nanos() / (iterations as u128) < 200);
639    }
640
641    #[cfg_attr(not(feature = "bench-tests"), ignore)]
642    #[test]
643    fn bench_counter_get() {
644        let counter = Counter::new();
645        counter.set(42);
646        let iterations = 100_000_000;
647
648        let start = Instant::now();
649        let mut sum = 0;
650        for _ in 0..iterations {
651            sum += counter.get();
652        }
653        let elapsed = start.elapsed();
654
655        println!(
656            "Counter get: {:.2} ns/op",
657            elapsed.as_nanos() as f64 / iterations as f64
658        );
659
660        // Prevent optimization
661        assert_eq!(sum, 42 * iterations);
662
663        // Should be under 50ns per get (relaxed from 20ns)
664        assert!(elapsed.as_nanos() / (iterations as u128) < 50);
665    }
666}