Flow 2.0.0
Flow project: Full implementation reference.
stat_fwd.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include <boost/type_traits/type_identity.hpp>
22#include <atomic>
23#include <iosfwd>
24#include <string>
25#include <vector>
26
27/**
28 * Sub-module of flow::util providing lightweight facilities for working with simple `struct`s of
29 * stat counters/gauges/etc. Histogram_counter is available as a stand-alone utility as well.
30 * That aside, the rest of this doc header discusses the aforementioned simple-`struct`s-of-stats facilities:
31 * a/k/a `Stat_set` wrangling.
32 *
33 * ### Premise of `Stat_set` wrangling ###
34 * A `Stat_set` stands for a `struct` not actually named that, used by you to track stats for some unified
35 * purpose. An application would have potentially many different `Stat_set`s.
36 *
37 * At a core level, working with such `struct`s requires little to no
38 * scaffolding: define a `struct` with some per-stat members, initialized right there inline; then just
39 * `++` an individual counter member to increment it (example); read it when interested; that's about it.
40 *
41 * If (and this is certainly not always the case) multiple threads might make updates to one such `struct`, it
42 * is common to declare stat data members as `std::atomic<T_numeric> m_...;`. Then updating and reading these
43 * is reasonably straightforward (though often over-assumed to be completely straightforward).
44 *
45 * However on-demand operations covering every member -- pretty-printing
46 * the `struct`'s contents being a simple example -- become tedious to write and error-prone to maintain.
47 * A more complex example use-case is aggregating the stats from N threads' thread-local `struct`s of the same type:
48 * listing each member is tedious enough; and then there's the fact that adding N accumulators is one thing
49 * (just a sum), while adding N gauges is quite different (perhaps a mean). Then there are `Histogram_counter`s
50 * which in many ways are equivalent to arrays of `atomic` counters, but there's also bucket-structure metadata
51 * contained therein, and resetting the counters is syntactically different from assigning zero; so there's more
52 * potential tedium there.
53 *
54 * This sub-module, without changing the typical stat-holding `struct` at all, makes that stuff simple(r).
55 *
56 * The key feature is the ability to write, once, a *declare-stats function* for
57 * a given stats `struct` (referred to as `Stat_set` by convention in docs and such) -- listing its members
58 * via the FLOW_UTIL_STAT_DECLARE() macro -- and then pass that `struct` to various utility functions (such as
59 * stats_to_ostream<Stat_set>() which provides output of a `Stat_set` to an `ostream`; better yet use
60 * `<< print(stats)` for syntactic sugar) that iterate the members generically.
61 *
62 * This eliminates the boiler-plate of manually maintaining `operator<<`, aggregation, resetting, ..., for stats
63 * structures. Only one bit of duplication is required: Declare the option in C++ (as normal); *and* add a line
64 * `FLOW_UTIL_STAT_DECLARE*()`ing it as well. Failure to do the latter will merely mean that data member
65 * will be ignored when one calls the ops supported such as stats_to_ostream() or stats_reset(); other than
66 * that it's still perfectly usable.
67 *
68 * Even ignoring the preceding 2 paragraphs, some small utilities are available to simplify per-stat update
69 * operations themselves:
70 * - For `atomic<T_numeric>` stats:
71 * load(), store(), fetch_add(), fetch_sub(), exchange() are, essentially, respectively:
72 * read value, assign value, `+=` (and `++`), `-=` (and `--`), `atomic::exchange()`. load() returns the
73 * value; the mutators except store() return the *pre-change* value (matching
74 * `atomic::fetch_add()`/`fetch_sub()`/`exchange()` semantics; note the non-`atomic` overloads of
75 * fetch_add()/fetch_sub() return `void` instead -- see their doc headers).
76 * - Note: Often simply reading, assigning, and incrementing `atomic`s can be done the usual-looking way
77 * (e.g., `atomic<int>` can be `++`ed). However this uses strict memory-ordering and may be slower than
78 * one prefers. The supplied element-level primitives (`load()`, `store()`, `fetch_add()`, et al.)
79 * are, from your PoV, syntactic sugar to standardize
80 * making updates and loads of `atomic` stat members consistently with best practices in the context of
81 * individual stats.
82 * - For high-water-mark gauges (`atomic<T_numeric>` or otherwise): update_hi_wmark() updates a target
83 * stat with a new value, if and only if the latter exceeds the former.
84 *
85 * ### Design ###
86 * This is a vastly simplified analog of flow::cfg::Option_set which uses a similar macro-based
87 * declaration pattern (FLOW_CFG_OPTION_SET_DECLARE_OPTION()) to do many things with config `struct`s.
88 * As in that case, there is a number of fairly diverse operations, all of which require iterating members
89 * of a user-supplied `Stat_set` (not natively possible due to lack of reflection in C++).
90 * In our case the mechanism is *far* simpler, however:
91 * - You make `Stat_set` updates and reads without our help (modulo `atomic` and high-water-mark
92 * helpers (`load()`, `store()`, `fetch_add()`, et al.) which are *highly* recommended but basically
93 * straightforward syntactic sugar).
94 * - All the fanciness occurs strictly on a transactional basis, when and only when you need to do something
95 * fancy. A `stats_*()` function is called, and it'll do what it must inside. You're still only dealing
96 * with your simple `Stat_set`.
97 * - Want to print your `Stat_set`? Use `os << stat::print(stats)` (this will invoke stats_to_ostream()).
98 * - Want to reset your `Stat_set`, without messing up your gauges (which should *not* be reset to 0/etc.)
99 * and `Histogram_counter`s (which should be `.clear()`ed, or equivalent, while keeping bucket structure)?
100 * Use `stats_reset(&stats, ...)`.
101 * - Want to aggregate N `Stat_set`s into a result-`Stat_set`, which might have counters (including
102 * bunches-of-counters such as `Histogram_counter` or a custom type of your own) and gauges?
103 * Use `stats_aggregate(&target_stats, input_stats_vec.begin(), input_stats_vec.end())`.
104 * - Want to aggregate N *sharded* (perhaps thread-locally-sharded) `Stat_set`s into a result-`Stat_set`?
105 * Use `stats_aggregate_shards(&target_stats, input_stats_vec.begin(), input_stats_vec.end(), ...)`
106 * - ....
107 *
108 * @note For those familiar with flow::cfg: In that context there is complex state that persists beyond any
109 * particular op (like "output the values"). So there is `Option_set<Value_set>` each of which manages
110 * a `Value_set` (the latter being the managed `struct` like our `Stat_set`), with full-on aux state such
111 * as hash table(s) representing current config setting values et al. In *our* context, there is no such
112 * persistent data structure. Any op beyond just updating stats and reading them (<-- directly via
113 * `Stat_set` itself, lightning-quick) shall mount any required state on-demand, likely on the stack, and let it
114 * disappear once done with the op. We do not care (within reason) about the perf cost of each such op,
115 * as the assumption is that it is rarely invoked. Hence you will need to avoid invoking these ops willy-nilly
116 * many times per second/etc.
117 *
118 * ### Pattern (ignoring `atomic`s) ###
119 * Define a `struct` or equivalent. In it declare (in addition to whatever else you might keep there that's not tracked
120 * by util::stat, though informally we recommend to keep it spare of such for simplicity) each public stat member.
121 * By convention we recommend each member that can be default-initialized to be default-initialized (and then
122 * avoid initializing them in any ctor). (These recommendations are for readability/consistency.)
123 * Those that aren't default-initialized must be initialized (via ctor), meaning we formally do not support
124 * handling `Stat_set` members with garbage values.
125 *
126 * @note Tip: We recommend segregating stat-related items -- most prominently/at a minimum `Stat_set`s and their
127 * `declare_stats()` free functions -- into sub-namespaces named `stat`. We won't mention this again, and it
128 * is not mandatory; but it is good organizationally and may over time help avoid ADL name-collision
129 * annoyances.
130 *
131 * ~~~
132 * namespace my::cool::module::stat
133 * {
134 *
135 * struct My_stats
136 * {
137 * uint64_t m_msg_count = 0;
138 * uint64_t m_byte_count = 0;
139 * size_t m_cur_q_size = 0;
140 * Histogram_counter m_histo_pkt_sizes{...};
141 * float m_weird_weight_lb;
142 * float m_weird_weight_lb_hi_wmark; // Highest m_weird_weight_lb observed so far. Recommended naming conv shown.
143 *
144 * // If unavoidable, can do this too. Tip: If you can design a no-args ctor, your life *will* be easier.
145 * My_stats(float weird_weight_lb);
146 * };
147 * My_stats::My_stats(float weird_weight_lb): m_weird_weight_lb(weird_weight_lb) {}
148 *
149 * void declare_stats(...) { ... } // See below.
150 *
151 * } // namespace my::cool::module::stat
152 * ~~~
153 *
154 * Then write a declare-stats function using FLOW_UTIL_STAT_DECLARE() (ADL-visible; same namespace as the `struct`).
155 * It must have this exact signature including all the names.
156 *
157 * ~~~
158 * template<typename Visitor>
159 * void declare_stats(const Stat_name& name_prefix, const My_stats* src_stats, My_stats* target_stats,
160 * Visitor&& visitor)
161 * {
162 * FLOW_UTIL_STAT_DECLARE(m_msg_count, ACCUMULATOR);
163 * FLOW_UTIL_STAT_DECLARE(m_byte_count, ACCUMULATOR);
164 * FLOW_UTIL_STAT_DECLARE(m_cur_q_size, GAUGE);
165 * // Example of a non-numeric type; acts as ACCUMULATOR: it has += which does the right thing internally.
166 * FLOW_UTIL_STAT_DECLARE(m_histo_pkt_sizes, ACCUMULATOR);
167 * FLOW_UTIL_STAT_DECLARE(m_weird_weight_lb, GAUGE);
168 * // Declares a special stat of type Stat_type::S_HI_WMARK that gauges preceding S_GAUGE m_weird_weight_lb.
169 * FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_weird_weight_lb_hi_wmark, m_weird_weight_lb);
170 * }
171 * ~~~
172 *
173 * Now stats_to_ostream() (more conveniently via `os << print()`), stats_reset(), stats_assign(),
174 * stats_aggregate(), stats_aggregate_one(), stats_aggregate_shards() (and other utilities that might exist)
175 * will find the above via ADL on `My_stats` and iterate the declared members as needed.
176 *
177 * @note How does it work internally? For context/curiosity: It is done by calling ADL-located
178 * `declare_stats()`, passing it an appropriate `visitor()` that will -- in the stats_to_ostream()
179 * case -- print the given option. The last piece of the puzzle is that the FLOW_UTIL_STAT_DECLARE()
180 * macro cooperates by calling `visitor()`, passing it relevant things about the individual stat member
181 * including its macro-stringified name (`#m_byte_count` => `"byte_count"`).
182 *
183 * ### Types (including `atomic`s) ###
184 * The above informally (without specifying requirements for each managed-stat-member type) shows how to do it.
185 * Plus it does not discuss `atomic<>` such members, when concurrent stat-updates (and, rarely -- it is assumed --
186 * reads) may occur. Let's get into that. Take a given `T m_v` in `struct Stat_set`.
187 *
188 * `T = atomic<T_numeric>` is supported. (As of this writing `T_numeric = float` and other floating-point types
189 * are, due to a C++17 technicality -- the lack of `atomic<...>::fetch_add()` -- not supported. If there is demand
190 * for this, it would be easy to add it. In C++20, when/if we move to that, it'll just work as-is.)
191 * However, in that case, when making updates and reads:
192 * - It is *recommended* that reads, stores, and additions/subtractions/increments/decrements are done via
193 * load(), store(), fetch_add(), fetch_sub() respectively.
194 * - It is *required* (formally against the risk of undefined behavior, we do declare!) to make high-water-mark
195 * updates via update_hi_wmark(). (Anyway why not? Do you really want to write that atomic-logic yourself
196 * every time/your own utility? Use ours.)
197 *
198 * `atomic<T>`, where `T` is not numeric, is formally unsupported. We haven't defined "numeric" but it includes
199 * the native numeric types; and also anything whose representation is identical to that of a native numeric type,
200 * including `chrono::duration` (with its `"::rep"` being a native numeric type) and specifically #Fine_duration
201 * (alias for, probably, `chrono::duration<int64_t, nano>`). `atomic<Fine_duration>` accumulators are cool
202 * and superior to storing `.count()`s and doing unit-conversion manually!
203 *
204 * Now let's assume `T` is not `atomic<>`. In that case:
205 * - Firstly, any `T_numeric` described above (plus floating-point ones) is supported; period.
206 * So to repeat, that's: native numeric types and types whose representation is identical to that of a native
207 * numeric type (including `chrono::duration` with `"::rep = T_numeric"`; and especially `Fine_duration`).
208 * - Other `T`s can also work. Whether that is so is determined by which `stats_*<Stat_set>()` functions are
209 * instantiated in your application. If none then there are no further requirements; store whatever you want.
210 * Else:
211 * - `T` must be copyable.
212 * - For stats_to_ostream<Stat_set>() (or via `os << print<Stat_set>(...)`): `T` must be `ostream<<`able.
213 * - For stats_aggregate_one<Stat_set>():
214 * `ACCUMULATOR`- or `GAUGE`-typed `T` must have `+=`.
215 * `HI_WMARK`-typed `T` must have `<`.
216 * - For stats_aggregate_shards<Stat_set>(): Same.
217 * - For stats_aggregate<Stat_set>(): Same requirements as `stats_aggregate_one()`; plus:
218 * `GAUGE`-typed `T` must have `/=`.
219 * - For stats_since_reset_state<Stat_set>():
220 * `ACCUMULATOR`-typed `T` must have `-=`.
221 * `HI_WMARK`-typed `T` must have `<`.
222 * - For stats_reset<Stat_set>():
223 * `ACCUMULATOR`-typed `T`: No additional requirements. However, you may optionally define the
224 * following, in the same namespace as `Stat_set`, *if* you prefer/require that a reset of an individual
225 * `T* target_val` does not merely perform `store(target_val, load(fresh_val))`, where `fresh_val` is
226 * supplied within the `Stat_set fresh_stats` to stats_reset(). The optional ADL-findable function is:
227 * - `void reset(T* target_val, const T& fresh_val)`. It may ignore `fresh_val` or use it.
228 *
229 * @note `T` = Histogram_counter satisfies all of the above. Therefore it can be used with any op mentioned.
230 * You may use it as a model, as source code is available nearby.
231 * In particular, there is a `stat::reset(Histogram_counter* target_val, const Histogram& fresh_val)` which
232 * performs `target_val->clear()` instead of the aforementioned store-load from `fresh_val`. Thus, it is
233 * not necessary to supply a `fresh_val` with the exact correct bucket structure.
234 *
235 * ### Assigning ###
236 * Naturally, `Stat_set a = b` (and similar copying) can be useful. It is often auto-generated, or can be by using
237 * `= default` (on copy ctor + assignment of your `Stat_set`). However, if you have at least 1 `atomic<>`, then
238 * this cannot be done without providing some kind of `Copyable_atomic<>` adapter and using that for such members.
239 *
240 * We recommend using stats_assign() instead. Your `Stat_set` no longer need be copyable by default. Even if
241 * you lack `atomic<>`s doing so may prevent some headaches later.
242 *
243 * ### Resetting (vanilla) ###
244 * This is another common operation. Three things to note on this.
245 * - It is important to have a crisp conceptual understanding of what *reset* means in this context (stat-keeping).
246 * Stat-keeping here means to record information about what has happened over a particular period of time in the
247 * past. Without resetting, that period of time begins when `Stat_set` is initialized (usually: simply
248 * constructed). A reset means: *starting a new measurement period*.
249 * - Most stats are `ACCUMULATOR`s. A reset means, usually, assignment of such a stat to zero (or zeroes,
250 * for conceptual compounds-of-counters including Histogram_counter).
251 * - For `GAUGE`s and derived `HI_WMARK`s that is not the case. E.g., if you're measuring current temperature,
252 * then starting a new measurement period does not change the current temperature... that only happens if the
253 * weather changes.
254 * - You may use stats_reset() to reset a `Stat_set`. Assuming you've classified your individual stats
255 * properly (via `FLOW_UTIL_STAT_DECLARE*()`), it will simply work.
256 * - You may be tempted to not use stats_reset() and instead do something like: `stats = {}` or
257 * `stats = Stat_set{some, ctor, args}` -- depending on what it takes to originally init your `Stat_set`.
258 * We recommend against this:
259 * - This will only work for `ACCUMULATOR`s. It is easy to forget that `GAUGE`s et al look quite similar
260 * inside the `struct {}` body and not handle them specially, thus you've got a bug. (Etc.)
261 * - It may be perfectly accurate now (e.g., you have *have* `ACCUMULATOR`s anyway), but the moment that changes,
262 * you have to change-over -- and it is very easy to forget at that point. A maintainer might just not even
263 * know about these subtleties.
264 *
265 * That said, consult stats_reset() doc header for further details about how to use it.
266 *
267 * ### Composition (`struct Stat_set` contains `struct Set2`) ###
268 * It is pretty common to, firstly, group items within a `Stat_set` into nested `struct`s, if only just for the
269 * sake of organization. For example:
270 *
271 * ~~~
272 * struct Stat_set
273 * {
274 * struct Rcv
275 * {
276 * size_t m_n_bytes_transmitted; // Bytes received.
277 * unsigned int m_would_blocks; // # of times EAGAIN encountered.
278 * } m_rcv;
279 * struct Snd
280 * {
281 * size_t m_n_bytes_transmitted; // Bytes sent.
282 * size_t m_buf_sz; // Send buffer adaptive size (gauge).
283 * } m_snd;
284 * };
285 * ~~~
286 *
287 * Here the situation is not-different from just a basic un-nested `Stat_set`. If that's all that's happening
288 * simply declare each stat like any other:
289 *
290 * ~~~
291 * template<typename Visitor>
292 * void declare_stats(const Stat_name& name_prefix, const Stat_set* src_stats, Stat_set* target_stats,
293 * Visitor&& visitor)
294 * {
295 * // `os << print(stats);` would output "rcv.n_bytes_transmitted=..., ..., snd.buf_sz=..." -- note the prefixes.
296 * FLOW_UTIL_STAT_DECLARE(m_rcv.m_n_bytes_transmitted, ACCUMULATOR);
297 * FLOW_UTIL_STAT_DECLARE(m_rcv.m_would_blocks, ACCUMULATOR);
298 * FLOW_UTIL_STAT_DECLARE(m_snd.m_n_bytes_transmitted, ACCUMULATOR);
299 * FLOW_UTIL_STAT_DECLARE(m_snd.m_buf_sz, GAUGE);
300 * }
301 * ~~~
302 *
303 * However, it is also possible to use *composition*, wherein perhaps a `struct Set2` is
304 * - sometimes used by itself (maybe some object A maintains a `Set2` that it updates and at times `stats_reset()`s
305 * if so instructed); but also
306 * - sometimes used as part of a containing `struct Stat_set` (maybe some object B maintains an object A, plus
307 * its own object-B-only stats that it updates, and when asked for the full object-B stats it creates
308 * a `Stat_set`, copying its own B-items + A's `Set2` into that `Stat_set` and returns the latter by value).
309 * - The full `Stat_set` might then be printed via `os << print(...Stat_set...)` via
310 * stats_to_ostream<Stat_set>().
311 *
312 * We say "sometimes" and "maybe," as it's just one possibility. What's relevant generally is that `Set` is
313 * at times `stats_*()`ed by itself and at other times a `Stat_set` *directly containing* it is
314 * `stats_*()`ed as a whole.
315 *
316 * In this case use the following pattern. Note that it may be tempting to take shortcuts to a line here or there,
317 * so as to skip maybe `declare_stats()`ing `Set2` on its own: In our experience this will not work out for long,
318 * and eventually you'll probably be forced to do the following after all. The good news is it involves *no*
319 * copy-pasting of stat-lists; if done as shown, each stat `m_...` shall be declared exactly once
320 * (well, twice: inside its immediate `struct` and in that `struct`'s `declare_stats()`).
321 *
322 * ~~~
323 * struct Set2
324 * {
325 * type1_t m_stat1;
326 * type2_t m_stat2;
327 * };
328 *
329 * struct Stat_set
330 * {
331 * Set2 m_set2; // Nested stat-set.
332 *
333 * // ...other items...
334 * };
335 *
336 * template<typename Visitor>
337 * void declare_stats(const Stat_name& name_prefix, const Set2* src_stats, Set2* target_stats,
338 * Visitor&& visitor)
339 * {
340 * FLOW_UTIL_STAT_DECLARE(m_stat1, ACCUMULATOR);
341 * FLOW_UTIL_STAT_DECLARE(m_stat2, GAUGE);
342 * }
343 *
344 * template<typename Visitor>
345 * void declare_stats(const Stat_name& name_prefix, const Stat_set* src_stats, Stat_set* target_stats,
346 * Visitor&& visitor)
347 * {
348 * // The crux: Forward to the Set2 declare_stats. Add-on a prefix -- if desired -- so that
349 * // at a minimum the print() output would look like:
350 * // set2.stat1=[...] set2.stat2=[...] ...<Stat_set's own stuff>...
351 * // It is recursively possible for some other Stat_superset to contain Stat_set and, for output,
352 * // furnish us with our own "prefix.", which would be in `name_prefix`, so here we'd tack-on ours
353 * // to that, resulting in Stat_superset pretty-print to look like maybe
354 * // stat_set.set2.stat1=[...] stat_set.set2.stat2=[...] stat_set.<...> stat_set.<...>
355 * declare_stats(Stat_name{name_prefix, "set2."},
356 * src_stats ? &src_stats->m_set2 : nullptr, // Attn: must do the null-check.
357 * target_stats ? &target_stats->m_set2 : nullptr,
358 * visitor); // Attn: No need to forward(visitor).
359 *
360 * // ...Declare more here....
361 * }
362 * ~~~
363 *
364 * A few points for clarification:
365 * - It is not mandatory to add to the prefix when forwarding to an "inner" `declare_stats()`.
366 * It is an aesthetic/clarity decision, as of this writing affecting pretty-print only; if no further
367 * qualification is desired then just forward `name_prefix` without tacking on anything else.
368 * See the Stat_name doc header for the forms (including one sub-prefix named once and reused for several
369 * inner `declare_stats()`s). A Stat_name is cheap: the strings are built only if a `stats_*()` op needs the names.
370 * - util::stat machinery will always pass the prefix "" to the top-level thing being `stats_*()`ed.
371 * However that should be none of any `declare_stats()`'s concern: if not composing then ignore
372 * `name_prefix`; if composing then (at least) forward it to inner `declare_stats()`(s).
373 * - The order of the `FLOW_UTIL_STAT_DECLARE*()` invocations *is* meaningful, deliberately: the
374 * `stats_*()` ops by convention respect it -- most visibly stats_to_ostream() (pretty-print) and
375 * stats_field_names(), which emit in exactly that order. I.e., the default presentation order is
376 * under your (`declare_stats()`-author's) control. Any future `stats_*()`-like extensions should --
377 * all else being equal -- do the same. (Nothing stops a *consumer* from then post-processing however
378 * they like: sort by name, whatever.) Gentle recommendation: keep the member-declaration order and
379 * the `declare_stats()` order as similar as possible, so the `struct` reads like its printout.
380 *
381 * One last tip: If you are composing with an inner `Set2` as shown, then probably it is best to *also*
382 * formally compose with the remaining options as opposed to `FLOW_UTIL_STAT_DECLARE*()`ing the remaining
383 * stat-members inline. Otherwise you'll likely run into trouble if trying to, say, stats_reset() one's own direct
384 * stuff. This may seem like a burden, but at worst it just adds a little boiler-plate in the form of
385 * additional `struct`s and corresponding `declare_stats()`s. What's important is you should never have to
386 * copy/paste `FLOW_UTIL_STAT_DECLARE*()`s themselves -- which is where maintenance errors would crop up.
387 * Composing with everything -- or with nothing! -- is how one avoids such unpleasant things.
388 *
389 * Alternative stat-collection methods
390 * -----------------------------------
391 * So far we've described the basic *vanilla* stat-collection system. There are some other systems supported.
392 * To talk about these let's slightly formalize what's involved regardless of specifics.
393 *
394 * "Stat collection" as a whole basically consists of the following aspects.
395 * -# *Stat-updates*: As events occur, we save things into `Stat_set`s. In vanilla stat-collection as described
396 * so far this just means the init operation (construction usually); conceptual increments for `ACCUMULATOR`s;
397 * and either increments, decrements, or stores for `GAUGE`s. `HI_WMARK`s are updates along with their `GAUGE`s.
398 * -# *Stat-consumption*: This means obtaining a reference to or a copy of the currently-accurate `Stat_set`,
399 * whenever the user wishes to do something with this result: print, query in detail, whatever.
400 * For vanilla stat-collection it is simply making a copy (`stats_assign()`) of the current `Stat_set`,
401 * or using it in-place. Straightforward: even if we add basic concurrency (`atomic`s), it's just a matter
402 * of being aware that individual stat-members can change at any time.
403 * -# *Stat-resets*: To restate, a stat-reset conceptually means making it so that subsequent stat-consumptions
404 * represent measurements taken from the current point in time forward. In short: `ACCUMULATOR`s reset to
405 * their init-values (basically zeroed), `GAUGE`s undisturbed, and `HI_WMARK`s assigned to the current
406 * values of their respective `GAUGE`s. With vanilla stat-consumption one simply calls stats_reset() which
407 * does just that. Adding basic concurrency does not change this.
408 *
409 * Now we can list the other specifically-supported available techniques. Some are described in-depth in below
410 * sections, but we'll list them all here.
411 *
412 * ### Stat-collection system: Vanilla ###
413 * Described already; recapped just above. One can also think of this as/call this *continuous sampling*.
414 *
415 * ### Stat-collection system: Vanilla with basic concurrency ###
416 * We'll get into it more below, but basically it's simple. ~All stat-members are `atomic<T_numeric>` or
417 * Histogram_counter (which is conceptually and in fact a set of `atomic<T_numeric>`s). Multiple threads make
418 * updates via fetch_add(), fetch_sub(), store(), and custom-type equivalents (Histogram_counter::record_value()
419 * et al being the key example of a custom action -- in reality reducing to `fetch_add()`). Stat-consumption
420 * is the same -- with the added knowledge individual fields can change concurrently. (Make a copy via
421 * stats_assign() as convenient.) Resetting is also the same: stats_reset().
422 *
423 * Under heavy load with many multi-threaded updates, thread contention can affect performance. That brings us
424 * to:
425 *
426 * ### Stat-collection system: TL-sharding under concurrency ###
427 * This is the most complex stat-collection system. It is described in-depth below. It achieves better performance
428 * by keeping thread-local `Stat_set` *shards*, one per stat-updating thread (created on-demand for such a thread
429 * the first time it observes a stat-relevant event). Stat-consumption *reconstructs* a result `Stat_set` by
430 * summing each field's shard-values, the total for that stat-member being recorded as the current value.
431 * If done properly, `ACC`s and `GAUGE`s are equally accurate as with vanilla stat-collection. `HI_WMARK`s
432 * are not, as it is impossible to maintain a continuously sampled high-water-mark -- a stat-consumption-like
433 * operation would be necessary after any thread updates a shard, defeating the point of the thing. We suggest
434 * a mitigation technique to still maintain useful HWM results.
435 *
436 * Stat-resets are conceptually a bit subtle, but in practice it is simply this: stats_aggregate_shards() for
437 * stat-consumption, stats_reset_shard_aggregate() for stat-resets; the signatures and how they're to be invoked
438 * are quite similar for both.
439 *
440 * Additionally you'll need flow::util::Thread_local_state_registry or equivalent to be able to loop
441 * through the shards at stat-consumption/reset points. Lastly you'll need a thoughtful strategy for dealing
442 * with exiting threads-with-shards. Again we get into all of that below.
443 *
444 * ### Stat-collection system: Query another stats-source ###
445 * This is described in full in the doc header for stats_since_reset_state() and its buddy stats_mark_reset_state()
446 * which together make up our support for this stat-collection system.
447 *
448 * In short, it just means getting stats from another stat-system or *data source*. For example: the memory
449 * manager jemalloc has a rich stat-keeping API. If building something -- our sister project Flow-IPC, say --
450 * that uses jemalloc in a central way, it would make sense to pluck out certain values from this jemalloc API
451 * and package them in a `util::stat`-friendly way. Then they too can be `print()`ed, perhaps `stats_aggregate()`d,
452 * and so on.
453 *
454 * At its core this does not require any special API: define your `Stat_set` in a compatible way, then store
455 * values in it after querying them from the data source; store() + assignment and custom ops like
456 * Histogram_counter::overwrite_count_for_bucket() are straightforward. The addition of stat-resetting complicates
457 * the situation slightly. That's where stats_mark_reset_state() and stats_since_reset_state() complete the
458 * picture.
459 *
460 * Aggregation; TL-sharding; `atomic`-design recommendations
461 * ---------------------------------------------------------
462 * ### Cost of `atomic<T_numeric>` ###
463 * This section isn't meant to be a complete tutorial, by any means, but it should be pretty good when it comes to
464 * stat-keeping. It is also presented without context -- that comes in the next section -- so as to pin down some
465 * core assumptions. Lastly: we assume x86-64 (the most salient assumption -- but other modern architectures like
466 * ARM64 should be in-the-ballpark, with `memory_order_relaxed`; YMMV); and gcc STL `std::atomic` circa
467 * 2026 + modern Linux.
468 *
469 * For stats, we make the full assumption that all ops shall use the most performant memory-ordering:
470 * `memory_order_relaxed`. (All our syntactic-sugary helpers, and code internal to this facility, use this:
471 * load(), store(), fetch_add(), fetch_sub(), and update_hi_wmark().) The temporary loss of
472 * cross-thread coherence from using this ordering is generally considered completely reasonable in the context
473 * of consuming stats (whether for logging or reporting/monitoring or ...).
474 *
475 * @note Here we fully assume that it is unacceptable to use a naked variable, if it can be concurrently
476 * modified. So whenever we speak of a non-`atomic`, assume it can't be changed simultaneously.
477 * It is officially undefined-behavior in standard C++, so let's just not.
478 * (We aren't saying this will destroy the world or result in memory corruption. We're only saying we're
479 * not going to try to even analyze what-all this entails.)
480 *
481 * What are the ops that matter? There are three; everything else basically reduces to these.
482 * - Load (`a = load()` for `atomic`s, a simple memory-access of a variable otherwise: `a`): Obtain the number,
483 * which may be changing concurrently (if `atomic`), and copy it into a variable/return it/whatever.
484 * - A high-water-mark update, for the purposes of this discussion, reduces -- most of the time -- to a
485 * load: load A; => is B > A? => no (usually) => we're done. So we don't discuss the HWM-update as its own
486 * op, for simplicity, from here on.
487 * - Store (`store(&a, b)` for `atomic`s, else simple assignment `a = b`): Take a number and store it into
488 * a variable (which may be changing concurrently if `atomic`; so then either the store happens first, or the other
489 * change happens first).
490 * - Typically, as potentially-concurrent updates go: stores compete exclusively with stores.
491 * - Add (similarly subtract) (`fetch_add(&a, b)` or `a += b`): Kinda like storing, but it's addition;
492 * in the `atomic` case either the destructive-addition -- as 1 op taken together -- happens first, or the other
493 * change happens first.
494 * - Typically, as potentially-concurrent updates go: add/sub ops compete exclusively with add/sub ops.
495 *
496 * Now then: Given the stated assumptions about the environment:
497 * - First assume there is *no contention*: Nothing is touching the same possibly-`atomic` value in another thread.
498 * Then:
499 * - Load and store take the same # of cycles, whether acting on an `atomic` or not.
500 * - Add/sub is not quite identical. fetch_add() is ~5-25 cycles more than naked `a += b`. However, in
501 * and of itself, this difference -- which amounts (in 2026) to low-single-digit nanoseconds -- is
502 * typically considered negligible.
503 * - Now assume there *is contention*: Other thread(s) are touching the thing in another thread right then.
504 * - Under heavy contention, cache-line ping-pong between cores can add hundreds to thousands of cycles to the
505 * cost of (among other things) an add/sub. How likely is heavy contention to occur? That depends and is
506 * really up to you to analyze. *Usually* though for stat-keeping to hit heavy contention, it would have to
507 * be during a truly frequent operation -- for example every `malloc()` in a memory-manager library +
508 * an application that uses the heap quite a lot from many threads.
509 *
510 * Bottom line: relaxed-atomic ops on stats (word-sized in our context) cost essentially nothing for load/store and
511 * low-single-digit ns for add/subtract (more generally, read-modify-write) *when uncontended*.
512 *
513 * Keep these things in mind for the following discussion, particularly starting from the section following the
514 * next one. Not everything applies, depending on the design you choose for a particular `Stat_set`/stat!
515 * E.g., if you choose the thread-local approach, then there will be no contention; and although it is still
516 * (as you'll see) prudent to use `atomic`s, the only perf-pessimistic factoid above that's actually relevant
517 * becomes the low-single-digit-ns cost of an add/sub operation.
518 *
519 * ### Aggregation (no concurrency) ###
520 * Okay: temporarily forget concurrency. Assume everything happens in series: updates, consumption of stats.
521 * (To be clear -- that is quite common. Either you do stuff in one thread, or you already have mutexes
522 * synchronizing things + adding stat-updates is a drop in the bucket in those critical sections.)
523 *
524 * stats_aggregate() can be useful in this scenario. Perhaps you keep N `Stat_set`s independently for whatever
525 * reason, and perhaps they're useful in their own right, but also perhaps at times you want to see a total view
526 * of them all. stats_aggregate() will sum `ACCUMULATOR`s, take the mean of `GAUGE`s, and
527 * take the max of `HI_WMARK`s. That's convenient and beats doing it by hand by a mile.
528 *
529 * Along the same lines stats_sum() is available. It may be appropriate for post-processing instead of or
530 * as a complement to stats_aggregate(). One can also develop custom operations inspired by these.
531 *
532 * ### Concurrency: Intro / Centralized stat-keeping ###
533 * Aggregation can also be helpful, or even at times essentially mandatory, when there *is* concurrency.
534 * So let's discuss concurrency w/r/t stat-keeping, in and of itself; then in the next section show *if* and
535 * how *sharded aggregation* (stats_aggregate_shards()) helps.
536 *
537 * Suppose you have N threads. Some mechanism acts, and therefore records stats about it, concurrently in those N
538 * threads. One viable approach is to maintain a single `Stat_set` full of `atomic<T_numeric>`s. Each update,
539 * using fetch_add(), fetch_sub(), store(), update_hi_wmark(), updates an `atomic`.
540 * Then, when one is interested in consuming (to print, or publish for monitoring, or...), in some thread you
541 * load() each stat of interest. (By and large everything in this paragraph reduces to the aforementioned 3
542 * basics ops: load, store, add/sub.)
543 * - Pros: it's quite easy, both the updating and the consuming! "Just" don't forget to use the
544 * `load()`/`store()`/`fetch_add()`/... element-level ops,
545 * or equivalent, as naked ops like `+=` will use the unnecessarily-slower memory-ordering mode.
546 * - Cons: under *heavy contention* (see above about how likely that is to occur), all the concurrent ops can
547 * be much slower than one imagines a read/assign/`+=` should be. Again, the big enemy is the cache-line
548 * ping-pong among cores. Again though... that's under *heavy contention*.
549 *
550 * To recap: `atomic<>` fields and `Histogram_counter`s; store(), fetch_add(), fetch_sub(), update_hi_wmark()
551 * from multiple threads to update; load() to read individual `atomic`s. Heavy contention can reduce perf.
552 * This is *centralized stat-keeping*.
553 *
554 * So should you do it? Depends. It really is easy, and in many situations heavy contention will not happen.
555 * That aside, everything is a matter of trade-offs; so the decision is not necessarily an absolute one but depends
556 * on the alternative(s) too. We now present the alternative, where another form of aggregation
557 * (stats_aggregate_shards()) is essential: The TL-sharding design.
558 *
559 * ### Aggregation (with concurrency): TL-sharding ###
560 * This technique, *TL-sharding* (thread-local sharding), is the alternative to centralized stat-keeping, when
561 * many threads bring possibility of perf-killing contention. In summary: It eliminates contention entirely by
562 * making stat-updates thread-local. The price paid is complexity and a reduced fidelity for `HI_WMARK` fields.
563 * (We suggest mitigations for both of these below.)
564 *
565 * It works as follows. You still will make your `Stat_set` full of `atomic<>s` (low, brief contention is possible
566 * at stat-consumption and stats-reset time, as we'll soon show). Now consider potentially-stat-updating threads:
567 * T1, T2, .... Each thread shall keep a thread-local (TL) `Stat_set`; each such `Stat_set` is called
568 * a *shard*. Each thread, while regularly executing, shall only update its shard.
569 * - `ACCUMULATOR`: This is the most straightforward. In each thread, the given stat-member `atomic`s is
570 * `fetch_add()`-ed-to, never subtracted or otherwise touched over the course of stat-updating.
571 * (Other `ACCUMULATOR` types, including Histogram_counter, are supported. Details omitted here, but it's
572 * simple.)
573 * - `GAUGE`: (We are concerned with numeric `GAUGE`s in this context.) Roughly speaking there are two basic
574 * possibilities.
575 * - (Simple) Imagine a `GAUGE` of "juggled balls in the air": `B`. In thread T1, if I throw up a ball, it is `++B`
576 * in that thread (`fetch_add(&B, 1)`, but you get the point). If I catch a ball, it is `--B`. Let's
577 * stipulate, though, that if I throw up a ball in thread Tx, I can only catch it also in Tx. So, I might
578 * do `++B, ++B, --B, ++B, --B, --B,`; but since one cannot catch an unthrown ball, there's no
579 * `++B, --B, --B` (for example). So: This is simple; `fetch_add()`, `fetch_sub()`, all good. An
580 * `unsigned` stat-member `B` in the `Stat_set` works great.
581 * - (Less simple) Now suppose in thread T1 I can throw up a ball but can catch it in another thread like T2;
582 * or in T1 again is also possible. This is not really so different in the end. The only change:
583 * a given thread's shard *can legitimately dip into negative values*. Therefore the `Stat_set` member `B`
584 * in such cases must be a `signed` type.
585 * - Generally we recommend using `int64_t`. It can go negative; but the range on either side of zero
586 * is sufficiently enormous for most applications.
587 * - `HI_WMARK`: Put a pin in this one for a moment. For now just a recipe: *Do not update* `HI_WMARK`s
588 * in shard-`Stat_set`s. `HI_WMARK`s are determined during stat-consumption (discussed below).
589 *
590 * As noted, stat-updating is simple enough; not really any different than in centralized stat-keeping. Just
591 * remember:
592 * - Some `GAUGE` members may need to be of a signed type, namely if a given shard's value may go negative.
593 * Usually use `int64_t`.
594 * - Do not update `HI_WMARK` members at all.
595 *
596 * What about stat-consumption? It works as follows conceptually:
597 * - `ACCUMULATOR`: At stat-consumption: Simply sum across all shards.
598 * - `GAUGE`: Ditto. (Even if a shard can show a negative value, upon summing the true non-negative `GAUGE`-value
599 * shall be reconstructed.)
600 * - `HI_WMARK`: Unfortunately it's impossible to compute the true high-water-mark: Since the gauged-value
601 * is composed of different shards contributing differently at various points in time, without our summing
602 * across the shards at all of those times, we cannot know anything about those times between stat-consumption
603 * points. Therefore we can only determine the max-value *from across the stat-consumption points* themselves.
604 * This is not as good (we'll discuss mitigations below), but the algorithm is simple: At each stat-consumption
605 * check whether the gauged-value is greater-than the max recorded last time. If so, overwrite that record
606 * with the new max. This is the high-water-mark at this point.
607 *
608 * The above describes (1) a recipe for updating shards-`Stat_set`s and (2) conceptually how a resulting
609 * "reconstructed" `Stat_set` is computed out of the shards at stat-consumption points. Now we shall concretely
610 * describe the recipe for (2).
611 * - Firstly -- entirely due to the (assumed in general case) presence of `HI_WMARK`s in the `Stat_set` under
612 * discussion -- it is required that you keep a `Stat_set` across stat-consumption requests; let's call it `S`.
613 * `S` shall start off as a fresh (essentially zeroed) object. At stat-consumption, which we'll describe
614 * shortly, it will be filled-out with the current reconstructed-from-shards `ACCUMULATOR`s and `GAUGE`s --
615 * as well as the HWM values. A copy of this `S` shall be the stat-consumed result. Simply put: don't discard
616 * `S` but rather keep it for next time.
617 * - Secondly: In order to be able, at stat-consumption time, to loop-through extant threads' shards, you
618 * will require a *registry of extant threads/shards*. Hence a mere `thread_local` or `thread_specific_ptr` or
619 * flow::util::Thread_local_ptr is insufficient. flow::util::Thread_local_state_registry is, essentially,
620 * an extension of `Thread_local_ptr`/`thread_specific_ptr` that adds the ability to lock central lock, cycle
621 * through all currently extant TL objects (in this case: `Stat_set`s) to do something (in this case: aggregate
622 * stats), and unlock. Therefore: Use `Thread_local_state_registry` (or equivalent) to maintain your
623 * `Stat_set`s.
624 * - If you already have a thread-locality-based setup (before considering stat-keeping) for your algorithm and
625 * data, then either (a) you are using `Thread_local_state_registry` already, or (b) you can upgrade to it
626 * (from `thread_local` or `thread_specific_ptr` or `Thread_local_ptr`).
627 * - At stat-consumption: You are in some thread -- either one of T1, T2, ..., or some other thread entirely.
628 * Call stats_aggregate_shards(). In and of itself it is straightforward:
629 * Give it `&S` -- both as an in-arg (storing HWM values so far) and out-arg (it'll be filled with the
630 * reconstructed-from-shards current stats); and give it an iterator-range through the extant shard `Stat_set`s;
631 * plus a fresh (init-valued) `Stat_set` for its last arg -- consulted only if the shard-range is empty
632 * (e.g., stats consumed before any stat-updating thread has yet existed; HWMs survive such stretches).
633 * - To obtain this range, use Thread_local_state_registry::while_locked() (or equivalent) to grab a stable
634 * list of extant `Stat_set`s. This list is supplied to the `F()` in `.while_locked(F)`.
635 * - See stats_aggregate_shards() doc header for tips about range mechanics to set this up.
636 * In short, Boost or C++2x range functionality makes it simple and efficient; or one can just dump the
637 * shard-copies into a temporary `deque` first.
638 * - Here it is usually best to make a copy of `S` (via stats_assign()). This copy is your stat-consumption
639 * result. (`S` itself can be modified by another stat-consumption or stat-reset. Make sure a
640 * stat-consumption or stat-reset targeting `S` never executes concurrently with another such op.)
641 *
642 * Resetting of stats that are collected via TL-sharding is not achieved by stats_reset(). Instead use
643 * stats_reset_shard_aggregate(). The input parameters are essentially the same as for stats_aggregate_shards():
644 * `&S` and the range of TL-shard `Stat_set`s. This will (1) zero out the per-shard `ACCUMULATOR`s and (2)
645 * compute (as during stat-consumption) the `GAUGE`s by summing the per-shard values and save the result
646 * into each relevant `HI_WMARK` (if any).
647 *
648 * @warning Pay special attention to your design w/r/t what happens when a thread (with an active shard `Stat_set`)
649 * exits. (If it is possible to design your algorithm so that this never occurs, it may significantly
650 * simplify things.) What -- if anything -- to do depends on your mix of `ACC`s and `GAUGE`s. *Usually*
651 * a `GAUGE` shard-value in a newly-dead thread is to be treated the same as being zero. If so: nothing
652 * special to do yet. If not -- meaning perhaps the dead thread's `GAUGE`'s shard-value's proper
653 * contribution to the total is its last value at thread exit -- then treat it as an `ACC` in this
654 * context. This brings us to `ACC`s. Logically, an `ACC` counts some event's occurrence, so once
655 * its value exceeds zero, that's forever. Therefore, you must set up a mechanism such that, at thread
656 * exit time, that thread's `Stat_set` shard is saved in some central store of "finalized" shards.
657 * For stats_aggregate_shards() (consumption) and stats_reset_shard_aggregate() (reset) all such shard
658 * `Stat_set`s must be part of the range given to those functions. Note also that
659 * flow::util::Thread_local_state_registry provides a straightforward mechanism for executing code
660 * at thread exit.
661 *
662 * So that's how it works. Complexity aside, it has really just one functional drawback: The HWM (`HI_WMARK`)
663 * fidelity. As explained above, it can never be perfect, but the HWM quality is basically proportional to
664 * how frequently and regularly one consumes stats. Hence, if HWM quality is a concern (which, obviously, is
665 * contingent on your `Stat_set` featuring `HI_WMARK`s in the first place), one can simply stat-consume at regular
666 * intervals (e.g., every 5 seconds). `S` can be quietly updated; it is not necessary to log it or use it or copy
667 * it or anything. Then a "real" stat-consumption, whenever desired, can count on pretty well-sampled HWMs.
668 *
669 * At the risk of verbosity and redundancy, a recap of pros and cons of TL-sharding:
670 * - Con: Introducing a `Thread_local_state_registry`, or equivalent thing that tracks extant TL-objects,
671 * undoubtedly adds complexity. The utility helps a ton, but that is still the case. Adding it *on account of
672 * stat-keeping specifically* is definitely not a light-weight decision.
673 * - In many cases, though, there is already a `Thread_local_state_registry` (or similar) tracking other
674 * TL-objects. In that case adding a `Stat_set` into the TL-state structure is a no-brainer; it is ~free.
675 * - If you have `thread_specific_ptr` or `Thread_local_ptr` (better version of `t_s_p` in Flow), then
676 * upgrading to `Thread_local_state_registry` is not hard, albeit no longer ~free. From a naked `thread_local`
677 * it is a hair more work still.
678 * - Don't forget the above warning regarding thread exit.
679 * - Con: `HI_WMARK`s lose resolution. A `HI_WMARK` no longer tracks the entire time range since init or reset;
680 * instead it samples only at aggregation-times.
681 * - This is a built-in limitation of the TL-sharding approach.
682 * - A pretty good mitigation -- regular quiet stat-aggregating -- is explained above.
683 * - Con: There are some coherence caveats. These are discussed below. In short these are well within reason.
684 * - Pros: Risk of slow stat-updates, on account of contention, is eliminated. Hence, that part of the trade-off
685 * can just be forgotten => reduced hand-wringing. The thought of mere stat-keeping slowing down the stuff that's
686 * being stat-tracked (likely to assess perf at least in part!) is very unpleasant.
687 *
688 * ### Stat-coherence caveats under concurrency and aggregation ###
689 * We omit here a detailed survey of surface-level strangeness that can at times be observed under concurrency
690 * and arguably increased strangeness if using TL-sharding as well. It would be voluminous and arguably out of
691 * place; more importantly it's not difficult to figure out specifics, once the source of issues is clear:
692 *
693 * When there is just one given observed quantity, which can concurrently change, things are simple: We observe
694 * `m_x == A`, and `m_x` is an `atomic` that is routinely modified by other thread(s), then the `A` result is
695 * correct at that instant but not necessarily in the next instant.
696 *
697 * The *coherence* caveats begin when 2+ quantities are related in some way, meaning one is computed from another
698 * (at least partially). Examples:
699 * - `GAUGE m_x`; `HI_WMARK m_x_hi_wmark` based off `m_x`.
700 * - Due to concurrency one might see `m_x > m_x_hi_wmark`: a surface-level contradiction/malfunction.
701 * - You've designed your `Stat_set` so that, e.g., value A plus value B should equal value C.
702 * - Due to concurrency one might see A plus B being almost equal to C but not quite.
703 *
704 * Under TL-sharded aggregation the same basic dynamics are at play, but there is also looping through N
705 * shard `Stat_set`s, each of which is concurrently modifiable, *and* it takes (some) time to loop through them.
706 * In short, there are more interrelationships at play.
707 *
708 * The bottom line: These possibilities are not worth analyzing deeply. One must simply remember a basic
709 * principle *when reading* result `Stat_set` values at stat-consumption time:
710 * - Treat each stat-member in `Stat_set` as being separately computed from all others.
711 * - Due to concurrency, expected relationships between 2+ stat-members will generally hold but might
712 * be *slightly* off in a particular `Stat_set` snapshot.
713 *
714 * @note Tip: Regarding the situation wherein stat C is defined, by you, as equal to A+B, where A and B is each
715 * also a stat: We recommend against this. Without concurrency it is redundant albeit potentially does provide
716 * a bit of sanity-encouragement. Still, though, it's more state -- without conveying more information. *With*
717 * concurrency, it can make reading consumed `Stat_set`s or aggregations
718 * thereof still more confusing. Instead: all else being equal it is better to define only A and C, or A and B,
719 * as actual declared-stats. (The remaining quantity -- if significant -- can be explained in a comment;
720 * or a helper accessor can compute it.)
721 */
722namespace flow::util::stat
723{
724
725// Types.
726
727// Find doc header near the body of this class.
728class Histogram_counter;
729
730/// Describes an individual member of a `Stat_set`, as decided by the user (you) via FLOW_UTIL_STAT_DECLARE().
731enum class Stat_type
732{
733 /**
734 * Value (typically numeric, or conceptually a collection of numerics) that counts/tallies (typically
735 * via `+= N`, with `N > 0`, or equivalent) some event occurring over a past period of
736 * time, always in one (typically increasing) direction, starting at some *init* value (typically `{}` which is
737 * often zero). For example a count of packets received; or a tally of total bytes received in packets; or
738 * a total `float` weight processed by a plant.
739 * - Cf. `S_GAUGE` which covers values that over time can go in any direction; e.g., the length of a queue.
740 * This requires some care, as superficially they often look similar and also `+=`ed (but the key is -- also
741 * `-=`ed).
742 *
743 * Recommend, for numeric integer values, to use an unsigned type.
744 *
745 * `atomic<T_numeric>` is supported. Again, recommend using an unsigned `T_numeric`.
746 *
747 * Histogram_counter is supported, as it satisfies various requirements below.
748 *
749 * ### Reset ###
750 * A *reset* of an `S_ACCUMULATOR` `stats.m_v` is formally defined as:
751 * -# Input: `const Stat_set& fresh_stats`, where every stat including `fresh_stats.m_v` is at its init state.
752 * -# Make it so that `stats.m_v` is as-if one performed:
753 * `store(&stats.m_v, load(fresh_stats.m_v));`.
754 *
755 * The default reset() does exactly that. However, this behavior may be overridden -- for all `ACCUMULATOR`s of
756 * type `T` -- by defining an ADL-findable reset() with a signature compatible with stat::reset(). An example
757 * of this is `T` = Histogram_counter.
758 *
759 * Informally, a reset of an `ACCUMULATOR` means, basically, zeroing/reinitializing it; the formal definition
760 * is so-written above due the mechanism we find most convenient for this when operating on `Stat_set` `struct`s.
761 * Namely it is convenient for you to declare a `struct` data member and right-there comment it and
762 * shows its init value, e.g: `unsigned int m_cool_counter = 0;`.
763 *
764 * ### Aggregation ###
765 * An aggregation into an `S_ACCUMULATOR` `stats.m_v` from `src_stats.m_v` is formally defined as:
766 * `fetch_add(&stats.m_v, load(src_stats.m_v))`. Therefore, for non-`atomic`s, a reasonable
767 * `+=` operation must be defined for its type, if aggregation is ever compiled for the containing `Stat_set`.
768 *
769 * @see stats_aggregate_one(). Or repeated ~N times: stats_aggregate(), stats_aggregate_shards().
770 *
771 * ### Delta ###
772 * The delta between `S_ACCUMULATOR` `stats.m_v` from `reset_stats.m_v` is formally defined as:
773 * `fetch_sub(&stats.m_v, load(reset_stats.m_v))`. Therefore, for non-`atomic`s, a reasonable
774 * `-=` operation must be defined for its type, if aggregation is ever compiled for the containing `Stat_set`.
775 *
776 * @see stats_since_reset_state() which relies on the delta operation for `ACCUMULATOR`s.
777 */
779
780 /**
781 * Value (often numeric) that represents the state of something at some point in time. When numeric it
782 * can go up and down, for example; but really it can be almost anything (an `enum`! a temperature!) and thus
783 * isn't even a "stat" in the classic sense but more like simply a variable.
784 * - Cf. `S_ACCUMULATOR`. For example, suppose you've got a queue Q. A `GAUGE` might be the *current* length
785 * of Q. An `ACCUMULATOR` might be a count of how many times a value was pushed onto Q.
786 *
787 * While a `GAUGE`'s init value is sometimes meaningless until a measurement is taken, it must still be
788 * set (not left uninitialized) when creating the containing `Stat_set`. For example, one might set a temperature
789 * gauge to absolute zero.
790 *
791 * `atomic<T_numeric>` is supported.
792 *
793 * ### Reset ###
794 * A *reset* of an `S_GAUGE` `stats.m_v` is formally defined as a no-op. Informally, the reset of a
795 * `Stat_set` means starting a new time period of stat-keeping; but doing so has no effect on the current state
796 * of whatever a `GAUGE` tracks. So it is to be left alone.
797 *
798 * ### Aggregation ###
799 * An aggregation into an `S_GAUGE` `stats.m_v` from `src_stats.m_v` is formally defined the same as for
800 * `S_ACCUMULATOR`.
801 *
802 * @see stats_aggregate_one(), stats_aggregate_shards().
803 *
804 * However: Aggregation of `.m_v` from `N Stat_set`s is defined as (1) aggregating them as-if it is `S_ACCUMULATOR`
805 * (i.e., summing via `+=` or `atomic` variant) and (2) performing a scaling by `N` as follows: `result /= N`.
806 * Therefore, for non-`atomic`s, a reasonable `/=` operation must be defined for its type, if `N`-aggregation is
807 * ever compiled for the containing `Stat_set`. Thus aggregating `N GAUGE`s is achieved via taking the mean
808 * thereof, via successive `+=`s and a `/=` (or their `atomic` variations).
809 *
810 * @see stats_aggregate(). The preceding paragraph does not apply to stats_aggregate_shards() however.
811 */
812 S_GAUGE,
813
814 /**
815 * Value of the same type as a specific other value `stats.m_v` in the same `Stat_set` with `Stat_type` `S_GAUGE`
816 * that represents the highest value that `stats.m_v` has reached over a past period of time.
817 * For example, suppose you've got a queue Q. A `GAUGE stat.m_q_size` might be the *current* length of Q.
818 * Then a `HI_WMARK stat.m_q_size_hi_wmark` might be gauging `stat.m_q_size`; each time the latter is updated,
819 * the former is updated to the same value if and only if the old value `<` the new value.
820 *
821 * When the gauged-stat is initialized (or reset) to `X`, its high-water mark stat must be set to the same value
822 * `X`.
823 *
824 * @warning When declaring a `HI_WMARK stat.m_v` as gauging `GAUGE stats.m_y`, the `FLOW_UTIL_STAT_DECLARE*()`
825 * invocations must appear in order `m_y`, `m_v`.
826 *
827 * ### Reset ###
828 * A *reset* of an `S_HI_WMARK` `stats.m_v` is formally defined as:
829 * `store(&stats.m_v, load(stats.m_y));`. (Recall that, as a `GAUGE`, `stats.m_y` would not have been
830 * touched by the reset.)
831 *
832 * ### Aggregation ###
833 * An aggregation into an `S_GAUGE` `stats.m_v` from `src_stats.m_v` is formally defined the same as:
834 * `update_hi_wmark(&stats.m_v, load(src_stats.m_v))`. Therefore, for non-`atomic`s, a reasonable
835 * `<` operation must be defined for its type, if aggregation is ever compiled for the containing `Stat_set`.
836 *
837 * @see stats_aggregate_one(). Or repeated ~N times: stats_aggregate().
838 *
839 * However: by nature of sharding, stats_aggregate_shards() works differently. See its doc header for
840 * details. Nevertheless, still, for non-`atomic`s a reasonable `<` operation must be defined for its type,
841 * if one ever invokes stats_aggregate_shards<Stat_set>() for the containing `Stat_set`.
842 *
843 * @see stats_since_reset_state() is yet another high-water-mark technique (that again requires `<` in
844 * similar ways).
845 */
847}; // enum class Stat_type
848
849// Find doc header near the body of these compound types.
850
851template<typename Stat_set>
852struct Stat_set_printable;
853class Stat_name;
854template<typename Stat_set_t, size_t N>
855class Stat_set_list;
856template<typename Tag_t, typename Stat_set_t, size_t N = 1>
857class Global_stats;
858
859// Free functions.
860
861/**
862 * Default `load(stats.m_val)` that simply return `stats.m_val`.
863 * Used by the generic `stat::stats_*()` algorithms when a more specific overload, such as/particularly for
864 * `atomic<>`s, is not found via ADL.
865 *
866 * @note In order to performantly (minimizing copying) support large `T` when used in `stat::stats_*()`
867 * constructions like `store(&x, load(y))`, we must take and return `T` by ref, not by value.
868 * For small `T`, any decent optimizer will inline this template instantiation, reducing it to
869 * copy-equivalent perf after all.
870 *
871 * @tparam T
872 * Value type.
873 * @param val
874 * Value; usually a member of a `Stat_set`.
875 * @return `val`.
876 */
877template<typename T>
878const T& load(const T& val);
879
880/**
881 * `load(stats.m_val)` that loads a copy of the value stored inside an `atomic<T> stats.m_val`.
882 * Proper way for you to read an `atomic` stat; and used by the generic `stat::stats_*()` algorithms when
883 * needing to load a stat, and its type happens to be `atomic<T>`.
884 *
885 * May be useful for general `atomic` loading with `relaxed` memory-order, outside any particular `Stat_set`.
886 *
887 * @param val
888 * Ref to value; usually a member of a `Stat_set`.
889 * @return Copy of `val`.
890 */
891template<typename T>
892T load(const std::atomic<T>& val);
893
894/**
895 * Default `store(&target_stats.m_val, val)` that simply assigns `*target_val = val`.
896 * Used by the generic `stat::stats_*()` algorithms when a more specific overload, such as/particularly for
897 * `atomic<>`s, is not found via ADL.
898 *
899 * @note `T` is deduced from `target_val` only; `val` will be implicitly converted to `T`. This means
900 * you can write `store(&stats.m_val, 1)` instead of `store(&stats.m_val, size_t(1))`
901 * (or whatever the actual type is). Same applies to the other similar element-level primitives.
902 * It is also possible to make it, like, `template<typename T, typename U> void store(T*, U)`
903 * and inside `{}` do a `static_cast<T>`. However in that case one wouldn't get range-narrowing
904 * warnings and the like (which is less safe).
905 *
906 * @note In order to performantly (minimizing copying) support large `T` when used in `stat::stats_*()`
907 * constructions like `store(&x, load(y))`, we must take `T` by ref, not by value.
908 * For small `T`, any decent optimizer will inline this template instantiation, reducing it to
909 * copy-equivalent perf after all.
910 *
911 * @tparam T
912 * Value type. Must be copy-assignable.
913 * @param target_val
914 * Ptr to mutable value to replace; usually a member of a `Stat_set`.
915 * @param val
916 * Value to store.
917 */
918template<typename T>
919void store(T* target_val, const boost::type_identity_t<T>& val);
920
921/**
922 * `store(&target_stats.m_val, val)` that stores a copy of `val` into an `atomic<T> target_stats.m_val`.
923 * Proper way for you to store to an `atomic` stat; and used by the generic `stat::stats_*()` algorithms when
924 * needing to assign-to a stat, and its type happens to be `atomic<T>`.
925 *
926 * May be useful for general `atomic` storing with `relaxed` memory-order, outside any particular `Stat_set`.
927 *
928 * @param target_val
929 * Ptr to mutable value to replace; usually a member of a `Stat_set`.
930 * @param val
931 * Value to store.
932 */
933template<typename T>
934void store(std::atomic<T>* target_val, boost::type_identity_t<T> val);
935
936/**
937 * Default `fetch_add(&target_stats.m_val, addend_val)` that simply applies `*target_val += addend_val`.
938 * Used by the generic `stat::stats_*()` algorithms when a more specific overload,
939 * such as/particularly for `atomic<>`s, is not found via ADL.
940 *
941 * @note In order to performantly (minimizing copying) support large `T` when used in `stat::stats_*()`
942 * constructions like `fetch_add(&x, load(y))`, we must take `T` by ref, not by value.
943 * For small `T`, any decent optimizer will inline this template instantiation, reducing it to
944 * copy-equivalent perf after all.
945 *
946 * @note Corollary: We cannot return `T`, the pre-mod value of `*target_val`, the way the `atomic`-taking
947 * overload (or indeed `std::atomic::fetch_add()`) does. Doing so would require pre-copying `T` -- at least
948 * in the abstract machine (optimizers may elide this in practice, when the caller does not use the
949 * return value; but we would rather not rely on that).
950 *
951 * @internal
952 * @note Corollary of corollary: `stat_*()` algorithms shall not rely on `fetch_add()` returning anything.
953 * Same for `fetch_sub()`.
954 * @endinternal
955 *
956 * @tparam T
957 * Value type. Must have `+=`.
958 * @param target_val
959 * Ptr to mutable value to increment; usually a member of a `Stat_set`.
960 * @param addend_val
961 * Value to add-to `*target_val`.
962 */
963template<typename T>
964void fetch_add(T* target_val, const boost::type_identity_t<T>& addend_val);
965
966/**
967 * `fetch_add(&target_stats.m_val, addend_val)` that increments by `addend_val` value inside
968 * `atomic<T> target_stats.m_val` and returns the pre-change value. Proper way to destructively-increase
969 * an `atomic` stat (`ACCUMULATOR`s, `GAUGE`s); and used by the generic `stat::stats_*()` algorithms when needing
970 * to aggregate a stat, and its type happens to be `atomic<T>`.
971 *
972 * May be useful for general `atomic` add with `relaxed` memory-order, outside any particular `Stat_set`.
973 *
974 * @see To destructively-decrease an `atomic` stat (or just numeric value) consider fetch_sub().
975 *
976 * @note Subtlety: If `T` is signed, then it is possible to *decrease* `*target_val` by passing a negative
977 * `addend_val`. However, both for maintainability (what if `T` becomes unsigned down the line?) and
978 * code expressiveness, we recommend in that case to use fetch_sub() instead. Alternatively just
979 * use the regular `atomic` API (`target_val->fetch_{add|sub}(..., memory_order_relaxed)`).
980 * @note If `T` is unsigned, then you *cannot* use fetch_add() for that purpose. You must use
981 * fetch_sub() or the regular `atomic` API `fetch_sub()` per previous note.
982 *
983 * @param target_val
984 * Ptr to mutable value to replace; usually a member of a `Stat_set`.
985 * @param addend_val
986 * Value to add-to `*target_val`. If `< 0` consider calling `fetch_sub(target_val, -addend_val)` instead.
987 * @return Pre-change `*target_val` (matches `atomic::fetch_add()` semantics).
988 */
989template<typename T>
990T fetch_add(std::atomic<T>* target_val, boost::type_identity_t<T> addend_val);
991
992/**
993 * Default `fetch_sub(&target_stats.m_val, _val)` that simply applies `*target_val -= subtrahend_val`.
994 * Used by the generic `stat::stats_*()` algorithms when a more specific overload,
995 * such as/particularly for `atomic<>`s, is not found via ADL.
996 *
997 * Notes for fetch_add() equivalent, regarding `const T&` (versus `T`-by-value) and returning no `T`, apply
998 * here equally. See that doc header please.
999 *
1000 * @tparam T
1001 * Value type. Must have `-=`.
1002 * @param target_val
1003 * Ptr to mutable value to decrement; usually a member of a `Stat_set`.
1004 * @param subtrahend_val
1005 * Value to subtract-from `*target_val`.
1006 */
1007template<typename T>
1008void fetch_sub(T* target_val, const boost::type_identity_t<T>& subtrahend_val);
1009
1010/**
1011 * `fetch_sub(&target_stats.m_val, subtrahend_val)` that decrements by `subtrahend_val` value inside
1012 * `atomic<T> target_stats.m_val` and returns the pre-change value. Proper way to destructively-decrease
1013 * an `atomic` stat (`GAUGE`s); and used by the generic `stat::stats_*()` algorithms (when needing
1014 * to, at least, find the delta against an earlier `ACCUMULATOR` value -- stats_since_reset_state()), and its type
1015 * happens to be `atomic<T>`.
1016 *
1017 * May be useful for general `atomic` subtract with `relaxed` memory-order, outside any particular `Stat_set`.
1018 *
1019 * @param target_val
1020 * Ptr to mutable value to replace; usually a member of a `Stat_set`.
1021 * @param subtrahend_val
1022 * Value to subtract-from `*target_val`.
1023 * @return Pre-change `*target_val` (matches `atomic::fetch_sub()` semantics).
1024 */
1025template<typename T>
1026T fetch_sub(std::atomic<T>* target_val, boost::type_identity_t<T> subtrahend_val);
1027
1028/**
1029 * Equivalent to `target_val->exchange(val, memory_order_relaxed)`; sets `*target_val` to `val` and returns
1030 * the pre-change value. Useful at least for atomically reading-and-modifying a gauge that can change arbitrarily
1031 * as opposed to via fetch_add() and fetch_sub().
1032 *
1033 * May be useful for general `atomic` exchange with `relaxed` memory-order, outside any particular `Stat_set`.
1034 *
1035 * @note Atomic-only. There is intentionally no non-`atomic` overload: for plain numeric stats the ~equivalent
1036 * `swap()` or a couple assignments are fine. The atomic-only nature mirrors fetch_sub().
1037 *
1038 * @param target_val
1039 * Ptr to mutable value to replace; usually a member of a `Stat_set`.
1040 * @param val
1041 * Value to store into `*target_val`.
1042 * @return Pre-change `*target_val` (matches `atomic::exchange()` semantics).
1043 */
1044template<typename T>
1045T exchange(std::atomic<T>* target_val, boost::type_identity_t<T> val);
1046
1047/**
1048 * Default `update_hi_wmark(&target_stats.m_val, val)` that updates `*target_val` to `val` if and only if the
1049 * latter exceeds the former. Used by the generic `stat::stats_*()` algorithms when a more specific overload,
1050 * such as/particularly for `atomic<>`s, is not found via ADL.
1051 *
1052 * @note In order to performantly (minimizing copying) support large `T` when used in `stat::stats_*()`
1053 * constructions like `update_hi_wmark(&x, load(y))`, we must take `T` by ref, not by value.
1054 * For small `T`, any decent optimizer will inline this template instantiation, reducing it to
1055 * copy-equivalent perf after all.
1056 *
1057 * @tparam T
1058 * Value type. Must have `<`.
1059 * @param target_val
1060 * Ptr to mutable value to potentially replace; usually a member of a `Stat_set`.
1061 * @param val
1062 * Value to potentially store in `*target_val`.
1063 */
1064template<typename T>
1065void update_hi_wmark(T* target_val, const boost::type_identity_t<T>& val);
1066
1067/**
1068 * `update_hi_wmark(&target_stats.m_val, val)` that stores copy of `val` value inside
1069 * `atomic<T> target_stats.m_val` if and only if the latter is less-than the former.
1070 * Proper way to update a Stat_type::S_HI_WMARK `atomic` stat (though usually skipped if implementing sharding);
1071 * and used by the generic `stat::stats_*()` algorithms when needing to aggregate a stat, and its type happens
1072 * to be `atomic<T>`.
1073 *
1074 * May be useful for general `atomic` update-if-new-max with `relaxed` memory-order, outside any particular `Stat_set`.
1075 * Writing it manually is somewhat annoying, involving a loop and weak-CAS and the like.
1076 *
1077 * @param target_val
1078 * Ptr to mutable value to potentially replace; usually a member of a `Stat_set`.
1079 * @param val
1080 * Value to potentially store in `*target_val`.
1081 */
1082template<typename T>
1083void update_hi_wmark(std::atomic<T>* target_val, boost::type_identity_t<T> val);
1084
1085/**
1086 * Executes `store(*target_val, load(fresh_val))`. Used by the generic stat::stats_reset() algorithm when a more
1087 * specific resetter, such as for Histogram_counter, is not found via ADL.
1088 *
1089 * There is not usually any particular reason to call this directly.
1090 *
1091 * @tparam T
1092 * Value type. Must be copy-assignable.
1093 * @param target_val
1094 * See above.
1095 * @param fresh_val
1096 * See above.
1097 */
1098template<typename T>
1099void reset(T* target_val, const T& fresh_val);
1100
1101/**
1102 * Executes `target_val->clear()`, where `*target_val` is a Histogram_counter.
1103 * This overrides the default `reset()` behavior. Note that `fresh_val` is ignored in this impl.
1104 *
1105 * There is not usually any particular reason to call this directly.
1106 *
1107 * @param target_val
1108 * See above.
1109 * @param fresh_val
1110 * See above.
1111 */
1112void reset(Histogram_counter* target_val, const Histogram_counter& fresh_val);
1113
1114/**
1115 * Outputs a human-readable representation of all declared stats in `stats` to the given `ostream`.
1116 * Each stat is printed with name and value; no line-breaks inserted.
1117 *
1118 * @see util::stat namespace doc header for background about the pattern and detailed requirements
1119 * on `Stat_set`.
1120 *
1121 * @tparam Stat_set
1122 * See above.
1123 * @param os
1124 * Stream to which to serialize.
1125 * @param stats
1126 * Value to serialize.
1127 */
1128template<typename Stat_set>
1129void stats_to_ostream(std::ostream& os, const Stat_set& stats);
1130
1131/**
1132 * Returns the names of all declared stats of the given `Stat_set` type, in declaration order: the same
1133 * names, in the same order, as in stats_to_ostream() output. No live `Stat_set` is required: this is
1134 * reflection off the type's `declare_stats()`; a throwaway `Stat_set` is constructed internally from
1135 * `ctor_args` (typically none -- default-construction) purely to have something to walk. Its values are
1136 * irrelevant.
1137 *
1138 * Precipitating use-case: test code enforcing that every stat-field is accounted for (asserted, or deliberately
1139 * skipped) in a coverage manifest -- so that adding a field fails a test until it is classified; or a
1140 * monitoring/aggregation layer enumerating the available stats generically.
1141 *
1142 * @see util::stat namespace doc header for background about the pattern and detailed requirements
1143 * on `Stat_set`.
1144 *
1145 * @tparam Stat_set
1146 * See above. Unlike for the `stats_*()` APIs proper, some public constructor is required -- but
1147 * only by this function, and any one will do (see `ctor_args`).
1148 * @tparam Ctor_args
1149 * See `ctor_args`.
1150 * @param ctor_args
1151 * Args for constructing the internal throwaway `Stat_set` (values irrelevant); typically none.
1152 * @return See above.
1153 */
1154template<typename Stat_set, typename... Ctor_args>
1155std::vector<std::string> stats_field_names(Ctor_args&&... ctor_args);
1156
1157/**
1158 * With the help of a freshly-initialized `Stat_set`, resets the target `Stat_set` so as to immediately
1159 * begin a fresh measurement period. Only those members that were properly `FLOW_UTIL_STAT_DECLARE*()`d
1160 * shall be affected.
1161 *
1162 * Note that `fresh_stats` is not simply used as a source for all individual values in `*target_stats`;
1163 * nor does `Stat_set` need to be copyable as a whole. In particular as of this writing:
1164 * - Each Stat_type::S_ACCUMULATOR is indeed loaded from `fresh_stats` and stored into `*target_stats`.
1165 * Rationale: fresh measurement period => start-over from init stat.
1166 * - If it is a Histogram_counter, the right thing will happen: the equivalent of Histogram_counter::clear()
1167 * which does not affect bucket structure.
1168 * - Each Stat_type::S_GAUGE is untouched.
1169 * Rationale: fresh measurement period does not make the last measurement taken invalid.
1170 * - Each Stat_type::S_HI_WMARK is loaded from its gauged-member already in `*target_stats` and stored into
1171 * the `HI_WMARK`-typed stat member in question.
1172 * Rationale: fresh measurement period => the gauged measurement, recorded earlier in this same
1173 * stats_reset() call, is the only and therefore highest value seen so far.
1174 *
1175 * ### Thread safety versus concurrent stats_reset() call on same target ###
1176 * Pragmatically speaking: each field in in `*target_stats` is updated, in `FLOW_UTIL_STAT_DECLARE*()` order,
1177 * in one such pass; some of the fields are copied from `fresh_stats`. You could draw conclusions from this
1178 * description. Or you can try following this ~formal description:
1179 * - If the concurrent stats_reset() has a differently-valued `fresh_stats`, then behavior is formally
1180 * undefined. (Really in conceivable non-abusive use cases `fresh_stats` should probably always have the
1181 * same value, for a given `Stat_set` type, informally speaking. It's the fresh-stats template by definition.
1182 * If you disregard this for some reason then: In practice, depending on field types, one might end up with a
1183 * mix of (1) correctly untouched/updated values (`GAUGE`s/`HI_WMARK`s respectively), (2) some values from
1184 * "our" `fresh_stats` (`ACCUMULATOR`s), (3) some value from "their" `fresh_stats` (ditto).)
1185 * - If the concurrent stats_reset() has the same-valued `fresh_stats`, then it is safe/OK as of this
1186 * writing.
1187 *
1188 * @tparam Stat_set
1189 * See util::stat namespace doc header for background about the pattern and detailed requirements
1190 * on `Stat_set`.
1191 * @param target_stats
1192 * The `Stat_set` to modify (reset).
1193 * @param fresh_stats
1194 * A `Stat_set` whose each declared-stat is equal to what that declared-stat in `*target_stats` equalled
1195 * at initial construction thereof. (This is often `Stat_set{}` but not always; for some `Stat_set`s in
1196 * some contexts it is necessary to initialize it via non-default ctor and/or some other steps.)
1197 */
1198template<typename Stat_set>
1199void stats_reset(Stat_set* target_stats, const Stat_set& fresh_stats);
1200
1201/**
1202 * Given a source `Stat_set`, makes the target `Stat_set` equal to the former. Only those members that were
1203 * properly `FLOW_UTIL_STAT_DECLARE*()`d shall be affected.
1204 *
1205 * Note that `src_stats` need not be copyable as a whole.
1206 *
1207 * ### Rationale ###
1208 * This can be useful in a few ways. In general making a copy of a `Stat_set` is useful, for example
1209 * as step 1 of stats_aggregate(): one does not want to (e.g.) destructively aggregate into `v[0]`, where
1210 * `v` is (e.g.) a `vector<Stat_set>`. Why not use an auto-generated copy-ctor/copy-assignment? Answer:
1211 *
1212 * The precipitating use case was a `Stat_set` containing 1+ `atomic<T>`s. `atomic<T>` is not copy-constructible
1213 * or copy-assignable; and each such member has to be explicitly atomic-loaded from the source and then
1214 * atomic-stored onto the target. An alternative approach would have been to write a `Copyable_atomic<T>`
1215 * wrapper around `atomic<T>` and require you, if needing stat-set-copying or anything (like stats_aggregate())
1216 * that needs it in turn, to declare `Copyable_atomic<T>`s instead of vanilla `atomic<T>`s.
1217 *
1218 * @tparam Stat_set
1219 * See util::stat namespace doc header for background about the pattern and detailed requirements
1220 * on `Stat_set`.
1221 * @param target_stats
1222 * The `Stat_set` to modify (replace).
1223 * @param src_stats
1224 * Source of values to store-to `*target_stats`.
1225 */
1226template<typename Stat_set>
1227void stats_assign(Stat_set* target_stats, const Stat_set& src_stats);
1228
1229/**
1230 * Aggregates the values from `src_stats` into `*target_stats`, in-place. Only those members that were
1231 * properly `FLOW_UTIL_STAT_DECLARE*()`d shall be affected. The aggregation rule for each declared stat
1232 * member is determined by its Stat_type:
1233 * - Stat_type::S_ACCUMULATOR, Stat_type::S_GAUGE: `fetch_add()`-style add (essentially `a += b`).
1234 * - If it is a Histogram_counter: that reduces to bucket-by-bucket `+=`.
1235 * - Stat_type::S_HI_WMARK: take the higher of the two values, via `update_hi_wmark()`.
1236 *
1237 * (The above is informal; see each Stat_type's doc header for the formal rules.)
1238 *
1239 * @warning Doing (essentially) `*target_val += src_val` for each `ACCUMULATOR` makes perfect sense, but
1240 * for `GAUGE` it's a subtle question. For example: if a `GAUGE` is a temperature, does it
1241 * really make sense to add 30 Celsius to 40 Celsius to get an "aggregated" 70 Celsius? That really
1242 * depends on what "aggregate one stat-set onto another" really *means* practically speaking.
1243 * To be direct in answering: Primarily it is a stepping stone for the generalized N-`Stat_set`
1244 * stats_aggregate(). That one would perform stats_aggregate_one() N times -- and then for the
1245 * `GAUGE`s `/=` each one by N, obtaining a mean... which actually *does* make sense. However pre-applying
1246 * something like that here would not make much sense. So we're going with the pragmatic answer that
1247 * works in that context; and also it might (arguably) be useful for some future other operations
1248 * on-top-of stats_aggregate_one().
1249 *
1250 * @see stats_aggregate() for aggregating N `Stat_set`s into one.
1251 * @see stats_aggregate_shards() for aggregating N `Stat_set`s into one, when each `Stat_set` represents a
1252 * shard. See util::stat doc header for discussion of the sharding (including thread-local sharding)
1253 * approach.
1254 *
1255 * @tparam Stat_set
1256 * See util::stat namespace doc header for background about the pattern and detailed requirements
1257 * on `Stat_set`.
1258 * @param target_stats
1259 * The `Stat_set` to modify (aggregate-into).
1260 * @param src_stats
1261 * Source of values to aggregate-into `*target_stats`.
1262 */
1263template<typename Stat_set>
1264void stats_aggregate_one(Stat_set* target_stats, const Stat_set& src_stats);
1265
1266/**
1267 * Aggregates N source `Stat_set`s -- drawn from the iterator range `[src_stats_begin, src_stats_end)` --
1268 * into `*target_stats`. Only those members that were properly `FLOW_UTIL_STAT_DECLARE*()`d shall be affected.
1269 * Equivalent to:
1270 * -# `stats_assign(target_stats, *src_stats_begin);`
1271 * -# `stats_aggregate_one(target_stats, *it);` for each `it` in `[src_stats_begin + 1, src_stats_end)`.
1272 * -# Each Stat_type::S_GAUGE-typed member of `*target_stats` is then divided by `N` (yielding the mean).
1273 *
1274 * Informally summarized per Stat_type (see each Stat_type's doc header for the formal rules):
1275 * - Stat_type::S_ACCUMULATOR (including Histogram_counter): sum via successive `+=`.
1276 * - Stat_type::S_HI_WMARK: max via successive `<`.
1277 * - Stat_type::S_GAUGE: arithmetic mean (sum-then-`/= N`; note that for integer types that is
1278 * integer division, hence truncation toward zero).
1279 *
1280 * `[src_stats_begin, src_stats_end)` must be a non-empty range; else undefined behavior (assertion might trip).
1281 *
1282 * @see stats_aggregate_shards() for aggregating N `Stat_set`s into one, when each `Stat_set` represents a
1283 * shard. See util::stat doc header for discussion of the sharding (including thread-local sharding)
1284 * approach.
1285 *
1286 * @tparam Stat_set
1287 * See util::stat namespace doc header for background about the pattern and detailed requirements
1288 * on `Stat_set`.
1289 * @tparam It
1290 * An iterator type dereferencing to `Stat_set` (or `const Stat_set`; it is read-only-accessed
1291 * regardless), typically a container's `const_iterator` or equivalent.
1292 * @param target_stats
1293 * The `Stat_set` to modify (aggregate-into).
1294 * @param src_stats_begin
1295 * Iterator to the first source `Stat_set`; must not equal `src_stats_end`.
1296 * @param src_stats_end
1297 * One-past-end iterator.
1298 */
1299template<typename Stat_set, typename It>
1300void stats_aggregate(Stat_set* target_stats, const It& src_stats_begin, const It& src_stats_end);
1301
1302/**
1303 * Equivalent to stats_aggregate() as-if every stat, regardless of its actual stat::Stat_type, is an
1304 * ACCUMULATOR; that is simply computes the sum via `+=` or equivalent.
1305 *
1306 * ### Rationale / use case ###
1307 * stats_aggregate() and stats_sum() are identical for Stat_type::S_ACCUMULATOR; so the different behavior
1308 * is for Stat_type::S_GAUGE and Stat_type::S_HI_WMARK. Taking these one at a time:
1309 *
1310 * A `GAUGE` can be something like a temperature; in which case an "aggregated" `GAUGE` would be a mean
1311 * (the sum divided by N = range-size). Instead, though, it could be something where a sum does make sense.
1312 * For example, say you've got N memory-arenas, and a stat-member `Stat_type::m_alloc_sz`, the currently
1313 * allocated memory: things that have been allocated and not yet deallocated. If you'd like aggregation to
1314 * mean the average per arena, then stats_aggregate() is appropriate. If you'd like it to mean the total
1315 * allocated in the system at the moment, a simply sum is instead appropraite; hence stats_sum().
1316 *
1317 * A `HI_WMARK` is a trickier proposition. In any case, however, by definition it tracks another `GAUGE`.
1318 * So if a mean makes sense for the `GAUGE` (stats_aggregate()), and each `HI_WMARK` truly tracks the
1319 * highest value so far per `Stat_set`, then the correct aggregated `HI_WMARK` would be simply the max
1320 * (according to `<`) of the N `HI_WMARK`s (stats_aggregate()). If the `GAUGE` is something you want
1321 * summed, like the allocated-size example above, then it's difficult to say what `HI_WMARK` should be.
1322 * Choosing the max is probably wrong; you'd get the high-looking `GAUGE` and a low-looking per-arena
1323 * corresponding `HI_WMARK`. Summing the `HI_WMARK`s is not exactly right, either; as we lack the
1324 * information about the highest-sum value over the measurement period; summing the `HI_WMARK`s gives
1325 * the answer as-if all the max `GAUGE` values occurred always at the same time. Nevertheless this is
1326 * stats_sum(), so we sum them.
1327 *
1328 * @note Your `Stat_set` might lack any `HI_WMARK`s. Then that dilemma does not matter.
1329 *
1330 * ### What if some of my `GAUGE`s are `stats_aggregate()`able while others are `stats_sum()`able? ###
1331 * There are a few things you can do; it really depends on what exactly is best for you. Worst-case,
1332 * you can always stats_sum() and then manually `/=` the mean-appropriate `GAUGE`s.
1333 * Though, then you've got the `HI_WMARK` dillema still (if applicable).
1334 *
1335 * It is also perfectly possible to write your own `stats_*()`-like function which would act in the way
1336 * you'd prefer. While the available `Stat_type`s are as they are (cannot be simply extended), you
1337 * can make compile-time or run-time choices like the following.
1338 * - Use the type `T* = decltype(target_val)` (in the `Visitor`) combined with `if constexpr()`.
1339 * - Possibly even draw inferences from the stat *name* (`const Stat_name& name` in the `Visitor`; see
1340 * Stat_name), perhaps based on some naming convention of your own.
1341 *
1342 * @tparam Stat_set
1343 * See util::stat namespace doc header for background about the pattern and detailed requirements
1344 * on `Stat_set`. However, in this case, the requirements for the non-`ACCUMULATOR` stat-members
1345 * of `Stat_set` shall be as-if they are also `ACCUMULATOR`s. In short: each must have `+=`;
1346 * no `<` or `/=` is required.
1347 * @tparam It
1348 * An iterator type dereferencing to `Stat_set` (or `const Stat_set`; it is read-only-accessed
1349 * regardless), typically a container's `const_iterator` or equivalent.
1350 * @param target_stats
1351 * The `Stat_set` to modify (sum-into).
1352 * @param src_stats_begin
1353 * Iterator to the first source `Stat_set`; must not equal `src_stats_end`.
1354 * @param src_stats_end
1355 * One-past-end iterator.
1356 */
1357template<typename Stat_set, typename It>
1358void stats_sum(Stat_set* target_stats, const It& src_stats_begin, const It& src_stats_end);
1359
1360/**
1361 * Sharded-stats consumption:
1362 * Aggregates N source `Stat_set`s, interpreted as shards of the aggregated whole -- drawn from the iterator
1363 * range `[src_stats_begin, src_stats_end)` -- into `*target_stats`. For purposes of Stat_type::S_HI_WMARK
1364 * computation, `target_stats->m_x` for each `HI_WMARK`-typed `m_x`, shall be taken as an input-value (and also
1365 * as an out-arg); see below for explanation. In particular, as explained below, `HI_WMARK` values carry a
1366 * special meaning.
1367 *
1368 * `[src_stats_begin, src_stats_end)` may be empty, meaning at the moment no shards are contributing to the
1369 * reconstructed `Stat_set`, hence the result `*target_stats` should be essentially zero-filled (w/r/t
1370 * `ACCUMULATOR`s and `GAUGE`s). In this case you must supply non-null `fresh_stats_from_0_shards`; the pointee shall
1371 * have the same semantics as `fresh_stats` to stats_reset(). (In other words it is used as the source of
1372 * zero-values for all `ACCUMULATOR`s and `GAUGE`s.)
1373 *
1374 * @note Grounding example: N threads contribute stats such as some gauge
1375 * `uint Stat_set::m_gauge` whose value in `*target_stats` is the sum of `m_gauge` across the N `Stat_set`s.
1376 * What if there are no threads yet -- N is 0? The proper value of `target_stats->m_gauge` at this time
1377 * is `0`. (The case where there are N>0 threads, and then they all exit: That may or may not be the same
1378 * situation. Explaining that case is out of scope here, but we implore you to read the TL-sharding
1379 * section of the util::stat namespace doc header.)
1380 *
1381 * @see To execute a reset of `*target_stats`: Use stats_reset_shard_aggregate(). You'll note its signature
1382 * is quite similar to the present function's.
1383 *
1384 * @note Tip: `boost::adaptors::transformed` (or older `make_transform_iterator`) can help walk things that aren't
1385 * directly storing `Stat_set`s (e.g., util::Thread_local_state_registry::State_per_thread_map to
1386 * util::Thread_local_state_registry::while_locked()).
1387 * In C++2x there is a built-in equivalent. Adaptors like `filtered`, `map_keys`, `map_values` tend to
1388 * be also very helpful.
1389 *
1390 * @note The in-range is walked *exactly once*. Combined with `transformed` (or equivalent) that fact may be
1391 * helpful if one needs to pre-massage each input `Stat_set` just ahead of aggregation.
1392 *
1393 * @note Tip: It may be required to walk 2+ ranges. In C++2x or with Boost.range this can be done easily by
1394 * concatenating ranges (`boost::range::join()`). Before C++2x / without Boost.range: honestly: don't
1395 * bother. Most `range`-less solution involve a temp container of shard copies, which is fine -- but it
1396 * would not work for stats_reset_shard_aggregate() (which modifies the shards).
1397 *
1398 * ### Semantics ###
1399 * Cf. stats_aggregate(): that function is a true aggregator, in the sense that `*target_stats` is fully
1400 * an out-arg; its current stat-relevant contents are overwritten and not themselves taken into account; and
1401 * in the conceptual sense that each input-`Stat_set` in the range is meaningful in and of itself, so
1402 * `*target_stats`, upon return, represents an aggregation of the inputs into a recap/summary/combo.
1403 * Namely: `ACCUMULATOR`s (including `Histogram_counter`s) are simply summed; and (attn!) `GAUGE`s are *averaged*,
1404 * while each `HI_WMARK` is simply the highest of input `HI_WMARK`s for that respective stat.
1405 *
1406 * In our case, by contrast: Each input is a shard that is in itself not necessarily meaningful; while the
1407 * output combines the shards to come out with a meaningful total state. Informally speaking, the aggregated
1408 * result is the goal and the only generally meaningful `Stat_set` for human consumption.
1409 *
1410 * In particular, this is essential for thread-local (TL) sharding as described in util::stat doc header,
1411 * section "Aggregation (with concurrency)."
1412 *
1413 * Specifically, then:
1414 * - `ACCUMULATOR`: For such a stat, the output is still simply the sum. E.g.: if in thread T we counted
1415 * N widgets, and in thread U M widgets, in total at the moment there are N plus M widgets.
1416 * - If it is a Histogram_counter: Same, repeated for each bucket.
1417 * - `GAUGE`: (Attn!) The output is the sum (not the mean). E.g., suppose we're gauging an overall ref-count,
1418 * but it can go up or down in multiple threads, each with a `Stat_set` in the input range. If in thread
1419 * T we've seen 5 `++`es and 7 `--`es, in some order; and in thread U it was 13 and 4 of those respectively,
1420 * then the input `Stat_set`s will report -2 and 9. In and of itself these say little about the actual
1421 * cross-thread ref-count. However stats_aggregate_shards() shall sum these, resulting in: 7.
1422 * (stats_aggregate() would instead come out with 7/2, or 3; clearly nonsensical.)
1423 * - `HI_WMARK`: (Attn!) This one is particularly special in how it is handled. The actual action is:
1424 * (1) take the gauged-stat `GAUGE`'s aggregated value (preceding bullet); (2) update `target_stat->m_x`
1425 * via: `update_hi_wmark(&target_stat->m_x, gauged_value)`. E.g., if the ref-count recorded in
1426 * `*target_stats` was 5, then it would now be updated to 7; if it were 8 then left alone.
1427 * - Rationale: It is not possible to obtain the actual high-water mark in the sense of being the highest
1428 * value for the (e.g.) total ref-count since the last reset/init. So we do the best we can:
1429 * report the highest (e.g.) total ref-count as sampled across all such stats_aggregate_shards() calls
1430 * including this one. If an even-higher (would-be -- but not in fact -- aggregated) value occurred at
1431 * some point between such aggregations, we will not capture it.
1432 *
1433 * ### Thread safety versus concurrent stats_aggregate_shards() call on same target ###
1434 * Pragmatically speaking: we mention this, specifically, because we expect it to be a common pattern (when
1435 * using sharding) for a user API to have, e.g., an internally stored -- but publicly accessible, by
1436 * `const` ref or by value -- member `m_current_stats` containing the last shard-aggregation result. This
1437 * accessor would first, typically, perform a shard-aggregation using
1438 * `stats_aggregate_shards(&m_current_stats, ...)`. So can such a user guarantee that this accessor is
1439 * thread-safe against concurrent calls to itself? That would be a salient question.
1440 *
1441 * @note Why would one would maintain `m_current_stats` instead of simply generating a fresh
1442 * stat-set `struct` and returning it by value? There's exactly one reason: Stat_type::S_HI_WMARK
1443 * members (if there are 1+ such stats in the `struct`). With sharding, these only work at
1444 * sampling times (see above); and information about previous sampling times is provided to
1445 * stats_aggregate_shards() by providing the last-sampled `*target_stats` in `*target_stats`
1446 * (or a fresh `struct` the first time).
1447 *
1448 * So that's why we ask this question specifically. What's the answer? It is: Calling
1449 * stats_aggregate_shards() on the same `*target_stats` concurrently => undefined behavior.
1450 * Pragmatically speaking: the sharding is a multi-step procedure, and we don't want to think about
1451 * how 2+ such procedures running concurrently would interact. (Cf. stats_reset() or stats_assign(), where
1452 * it is reasonably easy.)
1453 *
1454 * ### What is the arg `fresh_stats_from_0_shards`? ###
1455 * (This assumes `src_stats_begin == src_stats_end`; otherwise it is unneeded/ignored.)
1456 *
1457 * Usually it carries the basic meaning as `fresh_stats` for stats_reset(). There are no shards in this case
1458 * (or for stats_reset_shard_aggregate() the premise is we're resetting stuff, same implication in this context).
1459 * Therefore we need to get the zero-value from somewhere (we cannot assume it is `0` or `{}`, as we support
1460 * custom types and lack this requirement for individual fields). Usually this is no problem: `Stat_set{}` or
1461 * some equivalent will give a clean `struct` full of zeroes/equivalents.
1462 *
1463 * The exact requirement here, though, is subtly different: the `Stat_set` value to use
1464 * when *there are no shards to sum*. For `ACCUMULATOR`s, same. For `HI_WMARK`s, irrelevant (they are ignored
1465 * in the `fresh_stats_from_0_shards` context). For `GAUGE`s, though, in some advanced situations... read on.
1466 *
1467 * The subtlety, if there is one applicable, is that for certain advanced tricks, one *may* desire that
1468 * when a new *shard* is created -- in TL-sharding, the first time a thread desires to update any TL-sharded
1469 * stat member -- its value is *not* equal to `fresh_stats_from_0_shards`. (We want to be abundantly clear
1470 * here: *This is a special trick; normally this will not come to pass.*) Here we explain by example, which
1471 * as of this writing is the only one the authors have come across; it is also in some sense the simplest
1472 * and most concept-illustrative use-case. `Stat_set` might contain a field -- a `GAUGE` --
1473 * `uint m_n_shards = 1` with default ctor `Stat_set{}` indeed initializing it to `1`. No stat-update code
1474 * ever needs to modify it; any shard will contain `1` forever. Upon aggregation (stats_aggregate_shards()), the
1475 * ones get summed: so one can readily see how many shards composed that stat-consumption result, a nice pithy
1476 * diagnostic. Kinda cool! Now: consider the case where there are no shards: `src_stats_begin == src_stats_end`;
1477 * the function will grab the `GAUGE m_n_shards = 1` from `fresh_stats_from_0_shards`; but that is wrong.
1478 * Thus the subtlety: `Stat_set{}` (typically used for shard init -- though you're free to use any other technique,
1479 * this one is just easiest when possible) should have `= 1`; but for aggregation (this function) and the
1480 * quite-similar reset (stats_reset_shard_aggregate()) it should have `= 0`. This does not break any other formal
1481 * rules or invariants, and it'll get the desired result. To achieve this, either use a 2nd ctor or just assign
1482 * `= 0` when preparing non-null `*fresh_stats_from_0_shards` (and similarly for stats_reset_shard_aggregate()).
1483 *
1484 * @note In that use-case, it is reasonable to also have a `HI_WMARK m_n_shards_hi_wmark = 0`. This does not
1485 * require any further steps for correctness. The initial HWM value is `0`, which is accurate -- no shards
1486 * have yet been aggregated; and from that point on it'll survive as zero until the first aggregation, if any,
1487 * with 1+ shards involved.
1488 *
1489 * @tparam Stat_set
1490 * See util::stat namespace doc header for background about the pattern and detailed requirements
1491 * on `Stat_set`.
1492 * @tparam It
1493 * An iterator type dereferencing to `const Stat_set&` with `++` available.
1494 * @param target_stats
1495 * The `Stat_set` to modify (aggregate-into). `HI_WMARK`s' current values are taken into account
1496 * as inputs. All other current values are disregarded.
1497 * @param src_stats_begin
1498 * Iterator to the first source `const Stat_set`.
1499 * @param src_stats_end
1500 * One-past-end iterator. May equal `src_stats_begin`; in which case `fresh_stats_from_0_shards` comes
1501 * into play.
1502 * @param fresh_stats_from_0_shards
1503 * If `src_stats_begin != src_stats_end`: ignored; suggest passing-in null.
1504 * Else: see above.
1505 */
1506template<typename Stat_set, typename It>
1507void stats_aggregate_shards(Stat_set* target_stats, const It& src_stats_begin, const It& src_stats_end,
1508 const Stat_set* fresh_stats_from_0_shards);
1509
1510/**
1511 * Reset-stats companion to stats_aggregate_shards(); together the two functions provide support for the
1512 * (TL-)sharded stat-collection pattern.
1513 * - Use stats_aggregate_shards() for stat-consumption. A copy of out-`*target_stats` = the current `Stat_set`.
1514 * Keep `*target_stats` for next time.
1515 * - Use stats_reset_shard_aggregate() for stat-resets. Keep `*target_stats` for next time.
1516 *
1517 * The signature is very similar to that of stats_aggregate_shards(), but there is a key difference that is
1518 * not immediately obvious from the signature alone:
1519 * - stats_reset_shard_aggregate() can *modify the iterator pointee* shard `Stat_set`s (as well as
1520 * modify `*target_stats`). Namely it zeroes the `ACCUMULATOR`s therein.
1521 * - I.e.: `*src_stats_begin` is `Stat_set&`.
1522 * - stats_aggregate_shards() does not modify the iterator pointees.
1523 * - I.e.: `*src_stats_begin` is `const Stat_set&`.
1524 *
1525 * That said, you can treat it as a black box. Just start with a `*target_stats` equal to `fresh_stats`, and
1526 * keep the `*target_stats` for all subsequent stats_aggregate_shards() and stats_reset_shard_aggregate() calls.
1527 *
1528 * @note It would be a mistake to return `*target_stats` (or a copy) for consumption after this op: its
1529 * `ACCUMULATOR`s are, on purpose, not made coherent (see above), so it is not "the current stats" --
1530 * it merely carries what the *next* consumption needs. Reiterating: To emit actual current stats, that (or at
1531 * least that part of) stat-consumption must follow stats_aggregate_shards() (*the* stat-consumption API for
1532 * TL-sharded stats).
1533 *
1534 * ### What is the arg `fresh_stats_from_0_shards`? ###
1535 * See stats_aggregate_shards(). Other than this arg always being required, the semantics are the same.
1536 * There is a possible subtlety involved either way, though, so we encourage you to read the eponymous section of
1537 * the stats_aggregate_shards() doc header.
1538 *
1539 * @tparam Stat_set
1540 * See stats_aggregate_shards().
1541 * @tparam It
1542 * An iterator type dereferencing to `Stat_set&` with `++` available.
1543 * @param target_stats
1544 * The `Stat_set` to modify (`HI_WMARK`s and `GAUGE`s only). `HI_WMARK`s' current values are taken into account
1545 * as inputs. All other current values are disregarded.
1546 * @param src_stats_begin
1547 * Iterator to the first source `Stat_set`. Range elements are potentially modified (`ACCUMULATOR`s only).
1548 * @param src_stats_end
1549 * One-past-end iterator. May equal `src_stats_begin`.
1550 * @param fresh_stats_from_0_shards
1551 * See above.
1552 */
1553template<typename Stat_set, typename It>
1554void stats_reset_shard_aggregate(Stat_set* target_stats, const It& src_stats_begin, const It& src_stats_end,
1555 const Stat_set& fresh_stats_from_0_shards);
1556
1557/**
1558 * Intended for situations where the source of stat readings -- including `ACCUMULATOR`s and not
1559 * just `GAUGE`s -- is not by continuous sampling but rather by querying a data source (such as a library with its
1560 * own stats outputs), this obtains a view of `*target_stats` (also placed back into `*target_stats` destructively)
1561 * relative to a past reference point, `*reset_state`. This requires methodical explanation.
1562 *
1563 * ### The premise: *raw stats* from a data source ###
1564 * In this mode of working with a `Stat_set`, the basic assumptions are different from the mainstream described
1565 * in util::stat doc header. To wit:
1566 * - A given Stat_set::S_ACCUMULATOR stat is not manually updated via fetch_add() or equivalents
1567 * each time the underlying measured-count increases. Instead, *at stat consumption time* (when one would
1568 * `load()`-or-equivalent ~all stat-members) a *raw reading* of the actual value of the `ACCUMULATOR` at that
1569 * time is obtained from some data-source (e.g.: a memory manager's stats-facility, in this case tracking
1570 * something accumulator-like, such as the # of `malloc()`s to have been called to date). One then
1571 * directly saves it into the stat-member.
1572 * - If it is a Histogram_counter: same (use Histogram_counter::overwrite_count_for_bucket()).
1573 * - A given Stat_set::S_GAUGE is handled similarly (but that's normal anyway); a `GAUGE` can be set to anything
1574 * anytime unless sharded.
1575 * - A Stat_set::S_HI_WMARK is never manually recorded: stats_since_reset_state() will compute it automatically.
1576 * By the nature of this mode, however, the `HI_WMARK` is therefore the max of readings taken from the
1577 * data-source at stat consumption times. (In this way it is similar to how
1578 * stats_aggregate_shards() + sharding works.)
1579 *
1580 * If that were all, there would be no need for any special function like stats_since_reset_state(). Indeed,
1581 * if one is happy with using `Stat_set` simply to represent the readings from a stats data-source -- then that's
1582 * it. E.g., one can use print() for printing them nicely. However, it is often useful to also be able to
1583 * perform some equivalent of stats_reset(): Particularly for `ACCUMULATOR`s (including histograms) one might
1584 * want to know how many widgets have accumulated since a particular point in time one had earlier *reset-marked*.
1585 * Along the same lines, `HI_WMARK`s would track the time period since that marked point in time. (`GAUGE`s by
1586 * definition are independent of their preceding states.) For this, one uses:
1587 * - stats_since_reset_state() (this function) to compute *relative-to-marked-state* accumulated values (by
1588 * subtraction) and high-water marks;
1589 * - stats_mark_reset_state() to mark the latest/base reset-state;
1590 * - and a simple induction-based algorithm we now describe.
1591 *
1592 * We reiterate that that if the reset/stats-since-mark functionality is not needed, then the following is
1593 * irrelevant.
1594 *
1595 * ### The algorithm ###
1596 * The algorithm requires a single persistent `Stat_set`; we call it `*reset_state`. You'll need to first
1597 * set it to a *base state*. Often by nature of the observed data-source values, it is sufficient to zero
1598 * (or in the case of exotic `HI_WMARK`s, set those to their lowest-possible values). Sometimes that is not the
1599 * case for certain `ACCUMULATOR`s; for example, suppose the data-source value V represents some
1600 * event's count since 1970). In that case obtain the base state as follows:
1601 * - Read (raw readings) from data-source into `*reset_state`. (Omit `HI_WMARK` members.)
1602 * - `stats_mark_reset_state(reset_state)`. (Sets all `HI_WMARK`s, if any, from their `GAUGE`s.)
1603 *
1604 * Now, there are just two possible triggers: stat-consumption and reset-marking.
1605 *
1606 * To consume stats:
1607 * - Suppose `*target_stats` is where you want the current relative stats.
1608 * - Read (raw readings) from data-source into `*target_stats`. (Omit `HI_WMARK` members.)
1609 * - `stats_since_reset_state(target_stats, reset_state)`.
1610 * - `*reset_state` might be modified due to `HI_WMARK` mechanics. You don't care: you care about `*target_stats`.
1611 * - `*target_stats` contains the current relative stats which were your object.
1612 * - Reminder: `HI_WMARK` = max of the `GAUGE`d stat's values starting with the last
1613 * reset-marked (or base) state and compared against any stats_since_reset_state() calls since.
1614 * We lack access to the data-source's values at any time points in-between.
1615 *
1616 * To reset-mark:
1617 * - Read (raw readings) from data-source into `*reset_state`. (Omit `HI_WMARK` members.)
1618 * - `stats_mark_reset_state(reset_state)`.
1619 *
1620 * @note Recall that to save (raw readings) directly into a Histogram_counter stat, use
1621 * a series of Histogram_counter::overwrite_count_for_bucket().
1622 *
1623 * ### Thread safety ###
1624 * Formally behavior is undefined if `*target_stats` or `*reset_state` is/are modified concurrently to this
1625 * function. (fetch_sub(), store(), and load() are still used for/within all stat-members, for
1626 * conceptual consistency and a bit of perf; but we make no guarantee as to reasonable behavior under
1627 * concurrent modification.)
1628 *
1629 * @tparam Stat_set
1630 * See util::stat namespace doc header for background about the pattern and detailed requirements
1631 * on `Stat_set`.
1632 * @param target_stats
1633 * In-out arg: As input: Contains raw (straight from data-source) readings for all non-`HI_WMARK` stats
1634 * (the latter are disregarded).
1635 * Post-condition: Contains stats relative to last reset-marked (or base) state.
1636 * @param reset_state
1637 * The last reset-marked (or base) state. May be modified (`HI_WMARK`s only).
1638 */
1639template<typename Stat_set>
1640void stats_since_reset_state(Stat_set* target_stats, Stat_set* reset_state);
1641
1642/**
1643 * Sets each `HI_WMARK`-declared stat-member in `*target_stats` to its gauged-stat's current value in
1644 * `*target_stats`; required for stats_since_reset_state() algorithm; may be useful otherwise as well.
1645 *
1646 * @see stats_since_reset_state() which forms the basis of an algorithm for using `Stat_set`s to store
1647 * values from outside stats data-sources, with available reset states via the present function.
1648 *
1649 * @tparam Stat_set
1650 * See util::stat namespace doc header for background about the pattern and detailed requirements
1651 * on `Stat_set`.
1652 * @param target_stats
1653 * In-out arg: `GAUGE`s are used as input; `HI_WMARK`s are overwritten; all others are ignored.
1654 */
1655template<typename Stat_set>
1656void stats_mark_reset_state(Stat_set* target_stats);
1657
1658/**
1659 * Returns a lightweight proxy object that, when streamed via `operator<<`, pretty-prints the given
1660 * `Stat_set` using stats_to_ostream(). This enables idiomatic chained-`<<` usage:
1661 *
1662 * ~~~
1663 * os << "Stats: [" << stat::print(my_stats) << "]; done.\n";
1664 * ~~~
1665 *
1666 * @tparam Stat_set
1667 * See stats_to_ostream() for requirements.
1668 * @param stats
1669 * The stats values to print. The returned proxy holds a `const` reference; `stats` must remain
1670 * alive through the `<<` expression.
1671 * @return Proxy suitable for `ostream <<`.
1672 */
1673template<typename Stat_set>
1674Stat_set_printable<Stat_set> print(const Stat_set& stats);
1675
1676/**
1677 * Prints string representation of the given `printable.m_stats` to the given `ostream` via stats_to_ostream().
1678 *
1679 * @tparam Stat_set
1680 * See stats_to_ostream() for requirements.
1681 * @param os
1682 * Stream to which to serialize.
1683 * @param printable
1684 * Proxy obtained via print(); contains object to serialize.
1685 * @return `os`.
1686 */
1687template<typename Stat_set>
1688std::ostream& operator<<(std::ostream& os, const Stat_set_printable<Stat_set>& printable);
1689
1690/**
1691 * Prints string representation of the given `Histogram_counter` to the given `ostream`.
1692 *
1693 * @relatesalso Histogram_counter
1694 *
1695 * @param os
1696 * Stream to which to write.
1697 * @param val
1698 * Object to serialize.
1699 * @return `os`.
1700 */
1701std::ostream& operator<<(std::ostream& os, const Histogram_counter& val);
1702
1703} // namespace flow::util::stat
Sub-module of flow::util providing lightweight facilities for working with simple structs of stat cou...
Definition: histo.cpp:26
void stats_mark_reset_state(Stat_set *target_stats)
Sets each HI_WMARK-declared stat-member in *target_stats to its gauged-stat's current value in *targe...
Definition: stat_set.hpp:853
Stat_type
Describes an individual member of a Stat_set, as decided by the user (you) via FLOW_UTIL_STAT_DECLARE...
Definition: stat_fwd.hpp:732
@ S_HI_WMARK
Value of the same type as a specific other value stats.m_v in the same Stat_set with Stat_type S_GAUG...
@ S_GAUGE
Value (often numeric) that represents the state of something at some point in time.
@ S_ACCUMULATOR
Value (typically numeric, or conceptually a collection of numerics) that counts/tallies (typically vi...
void fetch_sub(T *target_val, const boost::type_identity_t< T > &subtrahend_val)
Default fetch_sub(&target_stats.m_val, _val) that simply applies *target_val -= subtrahend_val.
Definition: stat_set.hpp:369
void stats_aggregate_shards(Stat_set *target_stats, const It &src_stats_begin, const It &src_stats_end, const Stat_set *fresh_stats_from_0_shards)
Sharded-stats consumption: Aggregates N source Stat_sets, interpreted as shards of the aggregated who...
Definition: stat_set.hpp:577
void update_hi_wmark(T *target_val, const boost::type_identity_t< T > &val)
Default update_hi_wmark(&target_stats.m_val, val) that updates *target_val to val if and only if the ...
Definition: stat_set.hpp:388
void stats_sum(Stat_set *target_stats, const It &src_stats_begin, const It &src_stats_end)
Equivalent to stats_aggregate() as-if every stat, regardless of its actual stat::Stat_type,...
Definition: stat_set.hpp:559
void stats_aggregate_one(Stat_set *target_stats, const Stat_set &src_stats)
Aggregates the values from src_stats into *target_stats, in-place.
Definition: stat_set.hpp:508
const T & load(const T &val)
Default load(stats.m_val) that simply return stats.m_val.
Definition: stat_set.hpp:332
void stats_assign(Stat_set *target_stats, const Stat_set &src_stats)
Given a source Stat_set, makes the target Stat_set equal to the former.
Definition: stat_set.hpp:496
void reset(Histogram_counter *target_val, const Histogram_counter &)
Executes target_val->clear(), where *target_val is a Histogram_counter.
Definition: histo.cpp:385
void stats_reset_shard_aggregate(Stat_set *target_stats, const It &src_stats_begin, const It &src_stats_end, const Stat_set &fresh_stats_from_0_shards)
Reset-stats companion to stats_aggregate_shards(); together the two functions provide support for the...
Definition: stat_set.hpp:681
void fetch_add(T *target_val, const boost::type_identity_t< T > &addend_val)
Default fetch_add(&target_stats.m_val, addend_val) that simply applies *target_val += addend_val.
Definition: stat_set.hpp:356
void stats_since_reset_state(Stat_set *target_stats, Stat_set *reset_state)
Intended for situations where the source of stat readings – including ACCUMULATORs and not just GAUGE...
Definition: stat_set.hpp:795
void stats_aggregate(Stat_set *target_stats, const It &src_stats_begin, const It &src_stats_end)
Aggregates N source Stat_sets – drawn from the iterator range [src_stats_begin, src_stats_end) – into...
Definition: stat_set.hpp:530
std::vector< std::string > stats_field_names(Ctor_args &&... ctor_args)
Returns the names of all declared stats of the given Stat_set type, in declaration order: the same na...
Definition: stat_set.hpp:442
T exchange(std::atomic< T > *target_val, boost::type_identity_t< T > val)
Equivalent to target_val->exchange(val, memory_order_relaxed); sets *target_val to val and returns th...
Definition: stat_set.hpp:382
std::ostream & operator<<(std::ostream &os, const Histogram_counter &val)
Prints string representation of the given Histogram_counter to the given ostream.
Definition: histo.cpp:390
void stats_to_ostream(std::ostream &os, const Stat_set &stats)
Outputs a human-readable representation of all declared stats in stats to the given ostream.
Definition: stat_set.hpp:414
void stats_reset(Stat_set *target_stats, const Stat_set &fresh_stats)
With the help of a freshly-initialized Stat_set, resets the target Stat_set so as to immediately begi...
Definition: stat_set.hpp:467
Stat_set_printable< Stat_set > print(const Stat_set &stats)
Returns a lightweight proxy object that, when streamed via operator<<, pretty-prints the given Stat_s...
Definition: stat_set.hpp:436
void store(T *target_val, const boost::type_identity_t< T > &val)
Default store(&target_stats.m_val, val) that simply assigns *target_val = val.
Definition: stat_set.hpp:344