sliding_counter.h
1 // Copyright 2013, Beeri 15. All rights reserved.
2 // Author: Roman Gershman (romange@gmail.com)
3 //
4 #ifndef _UTIL_SLIDING_COUNTER_H
5 #define _UTIL_SLIDING_COUNTER_H
6 
7 #include <atomic>
8 #include <vector>
9 
10 #include "base/atomic_wrapper.h"
11 #include "base/integral_types.h"
12 #include "base/logging.h"
13 
14 namespace util {
15 
17 public:
18  static void SetCurrentTime_Test(uint32 time);
19 
20 protected:
22 
23  static uint32 CurrentTime();
24 
25  mutable base::atomic_wrapper<uint32> last_ts_;
26 
27  private:
28 };
29 
30 template<typename T, unsigned NUM, unsigned PRECISION> class SlidingSecondCounterT
31  : public SlidingSecondBase {
32  mutable base::atomic_wrapper<T> count_[NUM];
33 
34  // updates last_ts_ according to current_time_global_ and returns the latest bin.
35  // has const semantics even though it updates last_ts_ and count_ because
36  // it hides data updates behind it.
37  int32 MoveTsIfNeeded() const;
38 
39 public:
40  static constexpr unsigned SIZE = NUM;
41  static constexpr unsigned SPAN = PRECISION*NUM;
42 
44  static_assert(NUM > 1, "Invalid window size");
45  Reset();
46  }
47 
48  void Inc() { IncBy(1); }
49 
50  T IncBy(int32 delta) {
51  int32 bin = MoveTsIfNeeded();
52  T tmp = count_[bin].fetch_add(delta, std::memory_order_acq_rel);
53  return tmp;
54  }
55 
56  // Compares whether the value in the current bucket is greater or equal than delta and decrements
57  // by delta. This operation is atomic.
58  // Returns the value from the bin after the update...
59  T DecIfNotLess(int32 delta) {
60  int32 bin = MoveTsIfNeeded();
61  T val;
62  do {
63  val = count_[bin].load(std::memory_order_acquire);
64  if (val < delta) {
65  return val;
66  }
67  } while(!count_[bin].compare_exchange_weak(val, val - delta, std::memory_order_acq_rel));
68  return val - delta;
69  }
70 
71  // Sums last cnt counts, starting from (last_ts_ - offset) and going back.
72  // offset + cnt must be less or equal to NUM.
73  T SumLast(unsigned offset, unsigned count = unsigned(-1)) const;
74 
75  T Sum() const {
76  MoveTsIfNeeded();
77  T sum = 0;
78  for (unsigned i = 0; i < NUM; ++i)
79  sum += count_[i].load(std::memory_order_acquire);
80  return sum;
81  }
82 
83  void Reset() {
84  for (unsigned i = 0; i < NUM; ++i)
85  count_[i].store(0, std::memory_order_release);
86  }
87 
88  uint32 last_ts() const { return last_ts_; }
89  static unsigned span() { return SPAN; }
90  static unsigned bin_span() {return PRECISION; }
91 };
92 
93 template<unsigned NUM, unsigned PRECISION> using SlidingSecondCounter =
95 
96 class QPSCount {
97  // We store 1s resolution in 10 cells window.
98  // This way we can reliable read 9 already finished counts when we have another 1
99  // to be filled up.
100 public:
101  static constexpr unsigned kNum = 10;
102  static constexpr unsigned kPrecision = 1;
103 
104 private:
106 
107 public:
108  void Reset() { window_.Reset(); }
109 
110  void Inc() {
111  window_.Inc();
112  }
113 
114  uint32 Get() const;
115 };
116 
117 
118 
119 /*********************************************
120  Implementation section.
121 **********************************************/
122 
123 template<typename T, unsigned NUM, unsigned PRECISION>
125  uint32 current_time = CurrentTime() / PRECISION;
126  uint32 last_ts = last_ts_.load(std::memory_order_acquire);
127  if (last_ts >= current_time) {
128  // last_ts > current_time can happen if the thread was preempted before last_ts_.load,
129  // and another thread already updated last_ts_ before this load.
130  return last_ts % NUM;
131  }
132  if (last_ts + NUM <= current_time) {
133  // Reset everything.
134  for (uint32 i = 0; i < NUM; ++i) {
135  count_[i].store(0, std::memory_order_release);
136  }
137  } else {
138  // Reset delta upto current_time including.
139  for (uint32 i = last_ts + 1; i <= current_time; ++i) {
140  count_[i % NUM].store(0, std::memory_order_release);
141  }
142  }
143  if (last_ts_.compare_exchange_strong(last_ts, current_time, std::memory_order_acq_rel)) {
144  return current_time % NUM;
145  }
146  return last_ts % NUM;
147 }
148 
149 template<typename T, unsigned NUM, unsigned PRECISION>
150 T SlidingSecondCounterT<T, NUM, PRECISION>::SumLast(unsigned offset, unsigned count) const {
151  if (count > NUM - offset) {
152  count = NUM - offset;
153  }
154  T sum = 0;
155  int32 start = MoveTsIfNeeded() - offset - count + 1;
156  if (start < 0) start += NUM;
157  for (unsigned i = 0; i < count; ++i) {
158  sum += count_[ (start + i) % NUM ].load(std::memory_order_acquire);
159  }
160  return sum;
161 }
162 
163 } // namespace util
164 
165 #endif // _UTIL_SLIDING_COUNTER_H