]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/tcounters.hh
Introducing TCounters
[thirdparty/pdns.git] / pdns / tcounters.hh
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #pragma once
24
25 #include <sys/time.h>
26 #include <array>
27 #include <set>
28 #include <unistd.h>
29
30 #include "lock.hh"
31
32 namespace pdns
33 {
34 // We keep three sets of related counters:
35 //
36 // 1. The current counters (thread-local, updated individually by thread code very often)
37 // 2. The snapshot counters (thread local, updated by thread code in one single mutex protected copy)
38 // 3. The history counters (global) to keep track of the counters of deleted threads
39
40 // We have two main clasess: one that holds the thread local counters
41 // (both current and snapshot ones) and one that aggregates the
42 // values for all threads adn keeps the history counters.
43
44 // The thread local current counters are the ones updated by
45 // performance critical code. Every once in a while, all values in the
46 // current counters are copied to the snapshot thread local copies in
47 // a thread safe way.
48
49 // The snapshot counters are aggregated by the GlobalCounters
50 // class, as these can be accessed safely from multiple threads.
51
52 // Make sure to call the thread local tlocal.updatesAtomics() once
53 // in a while. This will fill the snapshot values for that thread if some
54 // time has passed since the last snap update.
55
56 // To fet aggregate value call globals.sum(counter1) or
57 // globals.avg(counter2), or any aggreggation function. If multiple
58 // counters need to be collected in a consistent way:
59 // auto data = globals.aggregatedSnap();
60 //
61 // Note that the aggregate values can mix somewhat older thread-local
62 // with newer thread-local info from another thread. So it is possible
63 // to see the following:
64 //
65 // If thread T1 increments "received" and then passes the packet to
66 // thread T2 that increments "processed", it may happen that the value
67 // of "processed" observed by sum() is higher than "received", as T1
68 // might not have called updateSnap() yet while T2 did. To avoid this
69 // inconsistentcy, be careful to update related counters in a single
70 // thread only.
71
72 // For an example of the use of these templates, see rec-tcounters.hh
73
74 template <typename Counters>
75 class TLocalCounters;
76
77 template <typename Counters>
78 class GlobalCounters
79 {
80 public:
81 // Register a thread local set of values
82 void subscribe(TLocalCounters<Counters>* ptr)
83 {
84 auto lock = d_guarded.lock();
85 lock->d_instances.emplace(ptr);
86 }
87
88 // Unregister, typically done when a thread exits
89 void unsubscribe(TLocalCounters<Counters>* ptr, const Counters& data)
90 {
91 auto lock = d_guarded.lock();
92 lock->d_instances.erase(ptr);
93 lock->d_history.merge(data);
94 }
95
96 // Two ways of computing aggregated values for a specific counter: simple additions of all thread data, or taking weighted averages into account
97 template <typename Enum>
98 auto sum(Enum index);
99 template <typename Enum>
100 auto avg(Enum index);
101 template <typename Enum>
102 auto max(Enum index);
103
104 // Aggregate all counter data for all threads
105 Counters aggregatedSnap();
106
107 // Reset history
108 void reset()
109 {
110 auto lock = d_guarded.lock();
111 lock->d_history = Counters();
112 }
113
114 private:
115 struct Guarded
116 {
117 // We have x instances, normally one per thread
118 std::set<TLocalCounters<Counters>*> d_instances;
119 // If an instance gets deleted because its thread is cleaned up, the values
120 // are accumulated in d_history
121 Counters d_history;
122 };
123 LockGuarded<Guarded> d_guarded;
124 };
125
126 template <typename Counters>
127 class TLocalCounters
128 {
129 public:
130 static const suseconds_t defaultSnapUpdatePeriodus = 100000;
131 TLocalCounters(GlobalCounters<Counters>& collector, timeval interval = timeval{0, defaultSnapUpdatePeriodus}) :
132 d_collector(collector), d_interval(interval)
133 {
134 collector.subscribe(this);
135 }
136
137 ~TLocalCounters()
138 {
139 d_collector.unsubscribe(this, d_current);
140 }
141
142 TLocalCounters(const TLocalCounters&) = delete;
143 TLocalCounters(TLocalCounters&&) = delete;
144 TLocalCounters& operator=(const TLocalCounters&) = delete;
145 TLocalCounters& operator=(TLocalCounters&&) = delete;
146
147 template <typename Enum>
148 auto& at(Enum index)
149 {
150 return d_current.at(index);
151 }
152
153 template <typename Enum>
154 auto snapAt(Enum index)
155 {
156 return d_snapshot.lock()->at(index);
157 }
158
159 [[nodiscard]] Counters getSnap()
160 {
161 return *(d_snapshot.lock());
162 }
163
164 bool updateSnap(const timeval& tv_now, bool force = false)
165 {
166 timeval tv_diff{};
167
168 if (!force) {
169 timersub(&tv_now, &d_last, &tv_diff);
170 }
171 if (force || timercmp(&tv_diff, &d_interval, >=)) {
172 // It's a copy
173 *(d_snapshot.lock()) = d_current;
174 d_last = tv_now;
175 return true;
176 }
177 return false;
178 }
179
180 bool updateSnap(bool force = false)
181 {
182 timeval tv_now{};
183
184 if (!force) {
185 gettimeofday(&tv_now, nullptr);
186 }
187 return updateSnap(tv_now, force);
188 }
189
190 private:
191 GlobalCounters<Counters>& d_collector;
192 Counters d_current;
193 LockGuarded<Counters> d_snapshot;
194 timeval d_last{0, 0};
195 const timeval d_interval;
196 };
197
198 // Sum for a specific index
199 // In the future we might the move the specifics of computing an aggregated value to the
200 // app specific Counters class
201 template <typename Counters>
202 template <typename Enum>
203 auto GlobalCounters<Counters>::sum(Enum index)
204 {
205 auto lock = d_guarded.lock();
206 auto sum = lock->d_history.at(index);
207 for (const auto& instance : lock->d_instances) {
208 sum += instance->snapAt(index);
209 }
210 return sum;
211 }
212
213 // Average for a specific index
214 // In the future we might the move the specifics of computing an aggregated value to the
215 // app specific Counters class
216 template <typename Counters>
217 template <typename Enum>
218 auto GlobalCounters<Counters>::avg(Enum index)
219 {
220 auto lock = d_guarded.lock();
221 auto wavg = lock->d_history.at(index);
222 auto sum = wavg.avg * wavg.weight;
223 auto count = wavg.weight;
224 for (const auto& instance : lock->d_instances) {
225 auto val = instance->snapAt(index);
226 count += val.weight;
227 sum += val.avg * val.weight;
228 }
229 return count > 0 ? sum / count : 0;
230 }
231
232 // Max for a specific index
233 // In the future we might the move the specifics of computing an aggregated value to the
234 // app specific Counters class
235 template <typename Counters>
236 template <typename Enum>
237 auto GlobalCounters<Counters>::max(Enum index)
238 {
239 auto lock = d_guarded.lock();
240 uint64_t max = 0; // ignore history
241 for (const auto& instance : lock->d_instances) {
242 max = std::max(instance->snapAt(index), max);
243 }
244 return max;
245 }
246
247 // Get a consistent snap of *all* aggregated values
248 template <typename Counters>
249 Counters GlobalCounters<Counters>::aggregatedSnap()
250 {
251 auto lock = d_guarded.lock();
252 Counters ret = lock->d_history;
253 for (const auto& instance : lock->d_instances) {
254 auto snap = instance->getSnap();
255 ret.merge(snap);
256 }
257 return ret;
258 }
259
260 }