Skip to main content

rust_serv/metrics/
counter.rs

1//! Counter metric - monotonically increasing value
2
3use std::sync::atomic::{AtomicU64, Ordering};
4
5/// A counter is a cumulative metric that represents a single monotonically increasing counter
6/// whose value can only increase or be reset to zero.
7#[derive(Debug)]
8pub struct Counter {
9    value: AtomicU64,
10    name: String,
11    help: String,
12}
13
14impl Counter {
15    /// Create a new counter with name and help text
16    pub fn new(name: impl Into<String>, help: impl Into<String>) -> Self {
17        Self {
18            value: AtomicU64::new(0),
19            name: name.into(),
20            help: help.into(),
21        }
22    }
23
24    /// Increment the counter by 1
25    pub fn inc(&self) {
26        self.value.fetch_add(1, Ordering::Relaxed);
27    }
28
29    /// Increment the counter by a specific amount
30    pub fn add(&self, delta: u64) {
31        self.value.fetch_add(delta, Ordering::Relaxed);
32    }
33
34    /// Get the current value
35    pub fn get(&self) -> u64 {
36        self.value.load(Ordering::Relaxed)
37    }
38
39    /// Reset the counter to zero
40    pub fn reset(&self) {
41        self.value.store(0, Ordering::Relaxed);
42    }
43
44    /// Get the counter name
45    pub fn name(&self) -> &str {
46        &self.name
47    }
48
49    /// Get the help text
50    pub fn help(&self) -> &str {
51        &self.help
52    }
53}
54
55impl Clone for Counter {
56    fn clone(&self) -> Self {
57        Self {
58            value: AtomicU64::new(self.value.load(Ordering::Relaxed)),
59            name: self.name.clone(),
60            help: self.help.clone(),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_counter_creation() {
71        let counter = Counter::new("requests_total", "Total number of requests");
72        assert_eq!(counter.get(), 0);
73        assert_eq!(counter.name(), "requests_total");
74        assert_eq!(counter.help(), "Total number of requests");
75    }
76
77    #[test]
78    fn test_counter_inc() {
79        let counter = Counter::new("test", "test counter");
80        counter.inc();
81        assert_eq!(counter.get(), 1);
82        
83        counter.inc();
84        counter.inc();
85        assert_eq!(counter.get(), 3);
86    }
87
88    #[test]
89    fn test_counter_add() {
90        let counter = Counter::new("test", "test counter");
91        counter.add(10);
92        assert_eq!(counter.get(), 10);
93        
94        counter.add(5);
95        assert_eq!(counter.get(), 15);
96    }
97
98    #[test]
99    fn test_counter_reset() {
100        let counter = Counter::new("test", "test counter");
101        counter.inc();
102        counter.inc();
103        assert_eq!(counter.get(), 2);
104        
105        counter.reset();
106        assert_eq!(counter.get(), 0);
107    }
108
109    #[test]
110    fn test_counter_clone() {
111        let counter = Counter::new("test", "test counter");
112        counter.inc();
113        
114        let cloned = counter.clone();
115        assert_eq!(cloned.get(), 1);
116        assert_eq!(cloned.name(), "test");
117    }
118
119    #[test]
120    fn test_counter_concurrent_access() {
121        use std::sync::Arc;
122        use std::thread;
123
124        let counter = Arc::new(Counter::new("test", "test counter"));
125        let mut handles = vec![];
126
127        for _ in 0..10 {
128            let counter_clone = Arc::clone(&counter);
129            handles.push(thread::spawn(move || {
130                counter_clone.inc();
131            }));
132        }
133
134        for handle in handles {
135            handle.join().unwrap();
136        }
137
138        assert_eq!(counter.get(), 10);
139    }
140
141    #[test]
142    fn test_counter_large_values() {
143        let counter = Counter::new("test", "test counter");
144        counter.add(1_000_000);
145        assert_eq!(counter.get(), 1_000_000);
146    }
147}