]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/tcounters.hh
Merge pull request #14020 from omoerbeek/rec-compiling-rust-dcos
[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 classes: one that holds the thread local counters
41 // (both current and snapshot ones) and one that aggregates the
42 // values for all threads and 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 fetch aggregate values, 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 // inconsistency, 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 constexpr 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 // coverity[auto_causes_copy]
155 auto snapAt(Enum index)
156 {
157 return d_snapshot.lock()->at(index);
158 }
159
160 [[nodiscard]] Counters getSnap()
161 {
162 return *(d_snapshot.lock());
163 }
164
165 bool updateSnap(const timeval& tv_now, bool force = false)
166 {
167 timeval tv_diff{};
168
169 if (!force) {
170 timersub(&tv_now, &d_last, &tv_diff);
171 }
172 if (force || timercmp(&tv_diff, &d_interval, >=)) {
173 // It's a copy
174 *(d_snapshot.lock()) = d_current;
175 d_last = tv_now;
176 return true;
177 }
178 return false;
179 }
180
181 bool updateSnap(bool force = false)
182 {
183 timeval tv_now{};
184
185 if (!force) {
186 gettimeofday(&tv_now, nullptr);
187 }
188 return updateSnap(tv_now, force);
189 }
190
191 private:
192 GlobalCounters<Counters>& d_collector;
193 Counters d_current;
194 LockGuarded<Counters> d_snapshot;
195 timeval d_last{0, 0};
196 const timeval d_interval;
197 };
198
199 // Sum for a specific index
200 // In the future we might want to move the specifics of computing an aggregated value to the
201 // app specific Counters class
202 template <typename Counters>
203 template <typename Enum>
204 auto GlobalCounters<Counters>::sum(Enum index)
205 {
206 auto lock = d_guarded.lock();
207 auto sum = lock->d_history.at(index);
208 for (const auto& instance : lock->d_instances) {
209 sum += instance->snapAt(index);
210 }
211 return sum;
212 }
213
214 // Average for a specific index
215 // In the future we might want to move the specifics of computing an aggregated value to the
216 // app specific Counters class
217 template <typename Counters>
218 template <typename Enum>
219 auto GlobalCounters<Counters>::avg(Enum index)
220 {
221 auto lock = d_guarded.lock();
222 auto wavg = lock->d_history.at(index);
223 auto sum = wavg.avg * wavg.weight;
224 auto count = wavg.weight;
225 for (const auto& instance : lock->d_instances) {
226 auto val = instance->snapAt(index);
227 count += val.weight;
228 sum += val.avg * val.weight;
229 }
230 return count > 0 ? sum / count : 0;
231 }
232
233 // Max for a specific index
234 // In the future we might want to move the specifics of computing an aggregated value to the
235 // app specific Counters class
236 template <typename Counters>
237 template <typename Enum>
238 auto GlobalCounters<Counters>::max(Enum index)
239 {
240 auto lock = d_guarded.lock();
241 uint64_t max = 0; // ignore history
242 for (const auto& instance : lock->d_instances) {
243 max = std::max(instance->snapAt(index), max);
244 }
245 return max;
246 }
247
248 // Get a consistent snap of *all* aggregated values
249 template <typename Counters>
250 Counters GlobalCounters<Counters>::aggregatedSnap()
251 {
252 auto lock = d_guarded.lock();
253 Counters ret = lock->d_history;
254 for (const auto& instance : lock->d_instances) {
255 auto snap = instance->getSnap();
256 ret.merge(snap);
257 }
258 return ret;
259 }
260
261 }