1use crate::{MetricsError, Result};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17#[repr(align(64))]
22pub struct Counter {
23 value: AtomicU64,
25 created_at: Instant,
27}
28
29#[derive(Debug, Clone)]
31pub struct CounterStats {
32 pub value: u64,
34 pub age: Duration,
36 pub rate_per_second: f64,
38 pub total: u64,
40}
41
42impl Counter {
43 #[inline]
45 pub fn new() -> Self {
46 Self {
47 value: AtomicU64::new(0),
48 created_at: Instant::now(),
49 }
50 }
51
52 #[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 #[inline(always)]
68 pub fn inc(&self) {
69 self.value.fetch_add(1, Ordering::Relaxed);
70 }
71
72 #[inline(always)]
86 pub fn try_inc(&self) -> Result<()> {
87 let current = self.value.load(Ordering::Relaxed);
88 if current == u64::MAX {
89 return Err(MetricsError::Overflow);
90 }
91 self.value.fetch_add(1, Ordering::Relaxed);
92 Ok(())
93 }
94
95 #[inline(always)]
100 pub fn add(&self, amount: u64) {
101 self.value.fetch_add(amount, Ordering::Relaxed);
102 }
103
104 #[inline(always)]
117 pub fn try_add(&self, amount: u64) -> Result<()> {
118 if amount == 0 {
119 return Ok(());
120 }
121 let current = self.value.load(Ordering::Relaxed);
122 if current.checked_add(amount).is_none() {
123 return Err(MetricsError::Overflow);
124 }
125 self.value.fetch_add(amount, Ordering::Relaxed);
126 Ok(())
127 }
128
129 #[must_use]
131 #[inline(always)]
132 pub fn get(&self) -> u64 {
133 self.value.load(Ordering::Relaxed)
134 }
135
136 #[inline]
140 pub fn reset(&self) {
141 self.value.store(0, Ordering::SeqCst);
142 }
143
144 #[inline]
148 pub fn set(&self, value: u64) {
149 self.value.store(value, Ordering::SeqCst);
150 }
151
152 #[inline]
156 pub fn try_set(&self, value: u64) -> Result<()> {
157 self.set(value);
158 Ok(())
159 }
160
161 #[inline]
165 pub fn compare_and_swap(&self, expected: u64, new: u64) -> core::result::Result<u64, u64> {
166 match self
167 .value
168 .compare_exchange(expected, new, Ordering::SeqCst, Ordering::SeqCst)
169 {
170 Ok(prev) => Ok(prev),
171 Err(current) => Err(current),
172 }
173 }
174
175 #[must_use]
177 #[inline]
178 pub fn fetch_add(&self, amount: u64) -> u64 {
179 self.value.fetch_add(amount, Ordering::Relaxed)
180 }
181
182 #[inline]
195 pub fn try_fetch_add(&self, amount: u64) -> Result<u64> {
196 if amount == 0 {
197 return Ok(self.get());
198 }
199 let current = self.value.load(Ordering::Relaxed);
200 if current.checked_add(amount).is_none() {
201 return Err(MetricsError::Overflow);
202 }
203 Ok(self.value.fetch_add(amount, Ordering::Relaxed))
204 }
205
206 #[must_use]
208 #[inline]
209 pub fn add_and_get(&self, amount: u64) -> u64 {
210 self.value.fetch_add(amount, Ordering::Relaxed) + amount
211 }
212
213 #[must_use]
215 #[inline]
216 pub fn inc_and_get(&self) -> u64 {
217 self.value.fetch_add(1, Ordering::Relaxed) + 1
218 }
219
220 #[inline]
233 pub fn try_inc_and_get(&self) -> Result<u64> {
234 let current = self.value.load(Ordering::Relaxed);
235 let new_val = current.checked_add(1).ok_or(MetricsError::Overflow)?;
236 let prev = self.value.fetch_add(1, Ordering::Relaxed);
237 debug_assert_eq!(prev, current);
238 Ok(new_val)
239 }
240
241 #[must_use]
243 pub fn stats(&self) -> CounterStats {
244 let value = self.get();
245 let age = self.created_at.elapsed();
246 let age_seconds = age.as_secs_f64();
247
248 let rate_per_second = if age_seconds > 0.0 {
249 value as f64 / age_seconds
250 } else {
251 0.0
252 };
253
254 CounterStats {
255 value,
256 age,
257 rate_per_second,
258 total: value,
259 }
260 }
261
262 #[must_use]
264 #[inline]
265 pub fn age(&self) -> Duration {
266 self.created_at.elapsed()
267 }
268
269 #[must_use]
271 #[inline]
272 pub fn is_zero(&self) -> bool {
273 self.get() == 0
274 }
275
276 #[must_use]
278 #[inline]
279 pub fn rate_per_second(&self) -> f64 {
280 let age_seconds = self.age().as_secs_f64();
281 if age_seconds > 0.0 {
282 self.get() as f64 / age_seconds
283 } else {
284 0.0
285 }
286 }
287
288 #[inline]
290 pub fn saturating_add(&self, amount: u64) {
291 loop {
292 let current = self.get();
293 let new_value = current.saturating_add(amount);
294
295 if new_value == current {
297 break;
298 }
299
300 match self.compare_and_swap(current, new_value) {
302 Ok(_) => break,
303 Err(_) => continue, }
305 }
306 }
307}
308
309impl Default for Counter {
310 #[inline]
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl std::fmt::Display for Counter {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 write!(f, "Counter({})", self.get())
319 }
320}
321
322impl std::fmt::Debug for Counter {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 f.debug_struct("Counter")
325 .field("value", &self.get())
326 .field("age", &self.age())
327 .field("rate_per_second", &self.rate_per_second())
328 .finish()
329 }
330}
331
332unsafe impl Send for Counter {}
334unsafe impl Sync for Counter {}
335
336impl Counter {
338 #[inline]
342 pub fn batch_inc(&self, count: usize) {
343 if count > 0 {
344 self.add(count as u64);
345 }
346 }
347
348 #[inline]
350 pub fn inc_if(&self, condition: bool) {
351 if condition {
352 self.inc();
353 }
354 }
355
356 #[inline]
358 pub fn inc_max(&self, max_value: u64) -> bool {
359 loop {
360 let current = self.get();
361 if current >= max_value {
362 return false;
363 }
364
365 match self.compare_and_swap(current, current + 1) {
366 Ok(_) => return true,
367 Err(_) => continue, }
369 }
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use std::sync::Arc;
377 use std::thread;
378
379 #[test]
380 fn test_basic_operations() {
381 let counter = Counter::new();
382
383 assert_eq!(counter.get(), 0);
384 assert!(counter.is_zero());
385
386 counter.inc();
387 assert_eq!(counter.get(), 1);
388 assert!(!counter.is_zero());
389
390 counter.add(5);
391 assert_eq!(counter.get(), 6);
392
393 counter.reset();
394 assert_eq!(counter.get(), 0);
395
396 counter.set(42);
397 assert_eq!(counter.get(), 42);
398 }
399
400 #[test]
401 fn test_fetch_operations() {
402 let counter = Counter::new();
403
404 assert_eq!(counter.fetch_add(10), 0);
405 assert_eq!(counter.get(), 10);
406
407 assert_eq!(counter.inc_and_get(), 11);
408 assert_eq!(counter.add_and_get(5), 16);
409 }
410
411 #[test]
412 fn test_compare_and_swap() {
413 let counter = Counter::new();
414 counter.set(10);
415
416 assert_eq!(counter.compare_and_swap(10, 20), Ok(10));
418 assert_eq!(counter.get(), 20);
419
420 assert_eq!(counter.compare_and_swap(10, 30), Err(20));
422 assert_eq!(counter.get(), 20);
423 }
424
425 #[test]
426 fn test_saturating_add() {
427 let counter = Counter::new();
428 counter.set(u64::MAX - 5);
429
430 counter.saturating_add(10);
431 assert_eq!(counter.get(), u64::MAX);
432
433 counter.saturating_add(100);
435 assert_eq!(counter.get(), u64::MAX);
436 }
437
438 #[test]
439 fn test_conditional_operations() {
440 let counter = Counter::new();
441
442 counter.inc_if(true);
443 assert_eq!(counter.get(), 1);
444
445 counter.inc_if(false);
446 assert_eq!(counter.get(), 1);
447
448 assert!(counter.inc_max(5));
450 assert_eq!(counter.get(), 2);
451
452 counter.set(5);
453 assert!(!counter.inc_max(5));
454 assert_eq!(counter.get(), 5);
455 }
456
457 #[test]
458 fn test_statistics() {
459 let counter = Counter::new();
460 counter.add(100);
461
462 let stats = counter.stats();
463 assert_eq!(stats.value, 100);
464 assert_eq!(stats.total, 100);
465 assert!(stats.age > Duration::from_nanos(0));
466 assert!(stats.rate_per_second >= 0.0);
468 }
469
470 #[test]
471 fn test_high_concurrency() {
472 let counter = Arc::new(Counter::new());
473 let num_threads = 100;
474 let increments_per_thread = 1000;
475
476 let handles: Vec<_> = (0..num_threads)
477 .map(|_| {
478 let counter = Arc::clone(&counter);
479 thread::spawn(move || {
480 for _ in 0..increments_per_thread {
481 counter.inc();
482 }
483 })
484 })
485 .collect();
486
487 for handle in handles {
488 handle.join().unwrap();
489 }
490
491 assert_eq!(counter.get(), num_threads * increments_per_thread);
492
493 let stats = counter.stats();
494 assert!(stats.rate_per_second > 0.0);
495 }
496
497 #[test]
498 fn test_batch_operations() {
499 let counter = Counter::new();
500
501 counter.batch_inc(1000);
502 assert_eq!(counter.get(), 1000);
503
504 counter.batch_inc(0); assert_eq!(counter.get(), 1000);
506 }
507
508 #[test]
509 fn test_display_and_debug() {
510 let counter = Counter::new();
511 counter.set(42);
512
513 let display_str = format!("{counter}");
514 assert!(display_str.contains("42"));
515
516 let debug_str = format!("{counter:?}");
517 assert!(debug_str.contains("Counter"));
518 assert!(debug_str.contains("42"));
519 }
520}
521
522#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
523#[allow(unused_imports)]
524mod benchmarks {
525 use super::*;
526 use std::time::Instant;
527
528 #[cfg_attr(not(feature = "bench-tests"), ignore)]
529 #[test]
530 fn bench_counter_increment() {
531 let counter = Counter::new();
532 let iterations = 10_000_000;
533
534 let start = Instant::now();
535 for _ in 0..iterations {
536 counter.inc();
537 }
538 let elapsed = start.elapsed();
539
540 println!(
541 "Counter increment: {:.2} ns/op",
542 elapsed.as_nanos() as f64 / iterations as f64
543 );
544
545 assert!(elapsed.as_nanos() / iterations < 100);
547 assert_eq!(counter.get(), iterations as u64);
548 }
549
550 #[cfg_attr(not(feature = "bench-tests"), ignore)]
551 #[test]
552 fn bench_counter_add() {
553 let counter = Counter::new();
554 let iterations = 1_000_000;
555
556 let start = Instant::now();
557 for i in 0..iterations {
558 counter.add(i + 1);
559 }
560 let elapsed = start.elapsed();
561
562 println!(
563 "Counter add: {:.2} ns/op",
564 elapsed.as_nanos() as f64 / iterations as f64
565 );
566
567 assert!(elapsed.as_nanos() / (iterations as u128) < 200);
569 }
570
571 #[cfg_attr(not(feature = "bench-tests"), ignore)]
572 #[test]
573 fn bench_counter_get() {
574 let counter = Counter::new();
575 counter.set(42);
576 let iterations = 100_000_000;
577
578 let start = Instant::now();
579 let mut sum = 0;
580 for _ in 0..iterations {
581 sum += counter.get();
582 }
583 let elapsed = start.elapsed();
584
585 println!(
586 "Counter get: {:.2} ns/op",
587 elapsed.as_nanos() as f64 / iterations as f64
588 );
589
590 assert_eq!(sum, 42 * iterations);
592
593 assert!(elapsed.as_nanos() / (iterations as u128) < 50);
595 }
596}