Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
arena_lend_stats.hpp
Go to the documentation of this file.
1/* Flow-IPC: SHM-jemalloc
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26#pragma once
27
29#include "ipc/shm/shm_stats.hpp"
30#include <flow/util/stat/histo.hpp>
31#include <flow/util/stat/stat_set.hpp>
32#include <cstdint>
33#include <string>
34#include <type_traits>
35
37{
38
39// Types.
40
41/**
42 * Similar mission to shm::stat::Owner_obj_stats (which is relevant regardless of SHM-provider type);
43 * adds items relevant to arena-lending SHM-providers only.
44 */
46{
47 // Data.
48
49 /**
50 * Of the first-class object deletions counted in shm::stat::Owner_obj_stats::m_destroy_count, count
51 * of the ones that occurred synchronously/immediately upon the `shared_ptr` disposer executing.
52 *
53 * To increment this, the required sequence of events is as follows: `arena.construct<T>()`-returned
54 * `shared_ptr` group's ref-count reached zero; disposer executed; this happened to be the last
55 * process holding a reference to object (no borrowers also holding at that time); and this happened
56 * to occur in the same thread as the original `arena.construct()`ing thread.
57 *
58 * @note Let S = shm::stat::Owner_obj_stats::m_destroy_count, A = #m_sync_destroy_count, and
59 * B = #m_drain_destroy_count. Then (modulo concurrency artifacts):
60 * C = (S - A - B) = the following (derived) stat:
61 * Of the first-class object deletions counted as S, count of the ones such that (1) the
62 * first-class object `arena.construct()`ed in user thread U1 had its last cross-process reference
63 * dropped in thread U2; and then (2) the object was destroyed asynchronously during
64 * a subsequent opportunistic (a/k/a piggy-backed) *zombie-reaper scan* in U1.
65 */
66 std::atomic<uint64_t> m_sync_destroy_count = 0;
67
68 /**
69 * Of the first-class object deletions counted in shm::stat::Owner_obj_stats::m_destroy_count, count
70 * of the ones that occurred in a specialized drain-thread that sprang up via SHM-jemalloc when
71 * an `arena.construct()`ing user thread exited with live objects extant. (Note that such a thread-exit
72 * is not an error: it is perfectly legal for a thread to `arena.construct()` and then later join its parent,
73 * a/k/a exit, while the earlier-`construct()`ed object is still live, presumably in-use by other
74 * threads/processes.)
75 *
76 * To increment this, the required sequence of events is as follows: (see previous paragraph); then:
77 * a user thread, in any process, dropped the last `shared_ptr` (whether `.construct()`ed or
78 * `.borrow_object()`ed) group referencing the first-class object; the detached drain-thread (controlled by
79 * SHM-jemalloc) woke up, noticed the now-zombie object; and therefore destroyed it.
80 */
81 std::atomic<uint64_t> m_drain_destroy_count = 0;
82}; // struct Owner_obj_stats
83
84/**
85 * Cousin of arena_lend::stat::Owner_obj_stats, this tracks the *zombie-reaper scan* algorithm whose task
86 * is to find all first-class objects `arena.construct()`ed by this thread (or by the now-joined thread whose
87 * exit caused this specialized drain-thread to be spawned) that are *zombie objects*; and to destroy them.
88 * A zombie object is one such that no user thread, cross-process, holds a reference to it any longer.
89 *
90 * ### Context: How it works ###
91 * Despite the scary name, a zombie object is a completely normal phenomenon -- it just means
92 * the last guy to let go of its ref to the object happened to not be the original `.construct()`ing
93 * thread-and-process. Only that original thread may destroy such an object, and since we do not
94 * control user threads, we can only do so opportunistically (a/k/a *piggy-backed*). If that (user) thread happens to
95 * exit (join its parent) while an object is still live, then a *specialized drain-thread* is launched at that point
96 * by SHM-jemalloc and will periodically execute the zombie-reaping algorithm in the same way as
97 * the piggy-backed scan.
98 *
99 * There are therefore two of Zombie_obj_reaper_stats per arena: one counting the piggy-backed scans' stats;
100 * the other counting the dragin-threads' scans' stats.
101 *
102 * @internal
103 * The piggy-backed code path is detail::Thread_lcl_obj_db_admin::this_thread_piggy_scan().
104 * (Additionally, up to once per `.construct()`ing thread, detail::Thread_lcl_obj_db_admin::~Thread_lcl_obj_db_admin()
105 * dtor launches a scan. It is counted together with the piggy-backed scans.)
106 *
107 * The drain-thread code path is detail::Thread_lcl_obj_db_admin::degraded_admin_thread_body().
108 */
110{
111 // Types.
112
113 /**
114 * Groups stats that count events any of which can occur 0+ -- not a maximum of one time -- per scan.
115 *
116 * @internal
117 * The `ATOMIC_OR_NOT` parameterization helps in a certain impl detail; we use some local batching to
118 * speed-up stats-updates, at which point the temporary local Events instance is faster as just storing
119 * regular values, as no other thread will ever see them.
120 * @endinternal
121 *
122 * @tparam ATOMIC_OR_NOT
123 * If `true`, each counter is `atomic<T>`; else just `T`, where `T` is whatever appropriate integer type.
124 */
125 template<bool ATOMIC_OR_NOT = true>
126 struct Events
127 {
128 // Constants
129
130 /// The template parameter's value.
131 static constexpr bool S_ATOMIC_OR_NOT = ATOMIC_OR_NOT;
132
133 // Types.
134
135 /// Used for all ACCUMULATORs in a `*this`.
136 using Counter = std::conditional_t<S_ATOMIC_OR_NOT, std::atomic<uint64_t>, uint64_t>;
137
138 // Data.
139
140 /**
141 * The number of single-object hints scanned (each of which may or may not have resulted in 1 zombie-object
142 * being found and therefore reaped).
143 *
144 * @see Zombie_obj_reaper_stats::m_hints_sufficient_scans doc header for related background.
145 *
146 * Modulo concurrency artifacts, one can derive the quantity
147 * `m_hints_checked - m_hints_stale - m_hints_stale_and_slots_reused`.
148 * This quantity is the following: Of the single-object hints counted in #m_hints_checked, count of how many
149 * were indeed accurate hints (object is zombie and therefore reaped).
150 */
152
153 /**
154 * Of the single-object hints counted in #m_hints_checked, count of how many resulted in the finding that
155 * the indicated object was already destroyed (not a zombie; reaped earlier by this algorithm). This phenomenon
156 * is not an error but merely a consequence of concurrent events.
157 */
159
160 /**
161 * Of the single-object hints counted in #m_hints_checked, count of how many resulted in the finding that
162 * the indicated object was in fact not a zombie but live (1+ references are held cross-process).
163 * Like #m_hints_stale this phenomenon is not an error but a consequence of concurrent events though less likely.
164 *
165 * It would be interesting if this were often seen to be much greater than zero.
166 *
167 * @internal
168 *
169 * Thread_lcl_obj_db_admin::unused_obj_scan() explains how this could happen.
170 */
172
173 /**
174 * In the (typically-unnecessary, we hope) FIFO-zombie-search phases of the zombie-reaping scans,
175 * the number of objects in the obj-DB that were checked for zombie status (regardless of the answer).
176 *
177 * Modulo concurrency artifacts, one can derive the quantity
178 * `m_fifo_objs_checked - m_fifo_objs_live`. This quantity is the following: Of the checked-objects counted in
179 * #m_fifo_objs_checked, count of those that were indeed zombies and therefore were destroyed.
180 */
182
183 /**
184 * Of the checked-objects counted in #m_fifo_objs_checked, count of those that were live -- not zombies -- and
185 * were thus left alone. To be clear: it is normal for this to happen for a given object.
186 */
188 }; // struct Events
189
190 // Data.
191
192 /**
193 * Number of times the zombie-reaper scan algorithm executed.
194 *
195 * @note Does not include dead-thread "scans": This is where the scan immediately no-ops due to the following:
196 * The user thread has exited earlier; and any live objects originally constructed in that thread in the
197 * `*this`-tracked arena, that may have been around at thread exit time, have by now all been freed;
198 * therefore the thread's aux pool no longer exists: nothing to scan.
199 * (For aux-pool background: see Obj_db_aux_pool_stats doc header.)
200 *
201 * Modulo concurrency artifacts, one can derive the quantity
202 * `m_scans - m_hints_sufficient_scans - m_fifo_scans - m_fifo_exhausted_scans`.
203 * This quantity is the following: Of the scans counted in #m_scans, count of how many immediately
204 * exited due to the zombie-object-count estimate N equalling zero. This is the fast-path;
205 * we hope that many -- most -- scans result in this eventuality over time.
206 *
207 * @note The previous paragraph does not mention `m_exhaustive_scans`. There is however a relationship
208 * between #m_fifo_exhausted_scans and #m_exhaustive_scans. See other doc headers nearby.
209 */
210 std::atomic<uint64_t> m_scans = 0;
211
212 /**
213 * Of the scans counted in #m_scans, count of how many were *exhaustive* scans; that is performed up to once
214 * per constructing-thread, when that (user) thread exits. Each such scan also counts toward
215 * #m_fifo_exhausted_scans.
216 */
217 std::atomic<uint64_t> m_exhaustive_scans = 0;
218
219 /**
220 * Of the scans counted in #m_scans, count of how many did *not* immediately exit due to the
221 * zombie-object-count estimate N equalling zero; but that successfully reaped at least N zombies
222 * based only on the initial *hint phase* of the scan.
223 *
224 * The *hint phase* checks a special non-user-data SHM pool (*aux pool*) for a limited number of
225 * hints, each of which points at a known zombie. This is fast, as it requires no searching
226 * through the obj-DB. However, there is a limited number of possible hints (as of this writing 16; but
227 * this could change and is cited here only for ballpark-exposition purposes), so depending on
228 * timing of piggy-backing and other factors this phase may not be sufficient to "get" all N zombies.
229 */
230 std::atomic<uint64_t> m_hints_sufficient_scans = 0;
231
232 /**
233 * Of the scans counted in #m_scans, count of how many did *not* immediately exit due to the
234 * zombie-object-count estimate N equalling zero; and for which the initial hint-phase of the algorithm
235 * did not successfully reap at least N zombies.
236 *
237 * @see #m_hints_sufficient_scans doc header for related background.
238 *
239 * @note For a given scan, it is not possible that #m_fifo_scans *and* #m_fifo_exhausted_scans were
240 * both incremented. #m_fifo_scans counts only the scans where (1) the scan entered this phase
241 * and (2) during this phase reaped enough zombies as to reach N together with the hint-phase.
242 * #m_fifo_exhausted_scans counts only those where even that somehow was not enough.
243 *
244 * The phase after the hint-phase is called here the FIFO-scan phase; so-named because while
245 * it is in a sense a brute-force search through the obj-DB -- importantly, one that stops once N
246 * zombies have been reaped -- it is performed in FIFO order; that is based on chronological order
247 * of `.construct()`ion. It is arguably likely (but certainly not guaranteed) that objects become
248 * zombies roughly in the same order that they came into existence as fresh live objects.
249 */
250 std::atomic<uint64_t> m_fifo_scans = 0;
251
252 /**
253 * Of the scans counted in #m_scans, count of how many did *not* immediately exit due to the
254 * zombie-object-count estimate N equalling zero; and then ultimately failed to find at least N
255 * zombies to reap.
256 *
257 * Every exhaustive scan (#m_exhaustive_scans) counts here, since it has no real estimate by definition.
258 * A non-exhaustive scan *occasionally* is counted here too: N is a cheap, loosely-synchronized snapshot, so
259 * it can count a zombie that this scan cannot yet see as one. That is harmless; a later scan reaps it.
260 *
261 * @see #m_fifo_scans doc header for related background.
262 */
263 std::atomic<uint64_t> m_fifo_exhausted_scans = 0;
264
266 "N_UNUSED_IDX_HINTS should be divisible by 4, if only for this stat histogram's buckets "
267 "to be nice and even.");
268 /**
269 * Histogram that records, at the start of each run through the zombie-reaping scan algorithm (see #m_scans),
270 * the zombie-object-count estimate N -- excluding N=0 (no work to do; immediate exit) and excluding
271 * *exhaustive* scans (see #m_exhaustive_scans): those do not read the estimate at all.
272 *
273 * As noted in various nearby stat-member doc headers, each run through the zombie-reaper scan shall
274 * strive to find (and therefore destroy) at least N zombie-objects in the obj-DB.
275 *
276 * If the histogram shows that the most frequent eventualities are those where N is under the
277 * value of the internal constant `"detail::Lend_tracker_pool::S_N_UNUSED_IDX_HINTS"` -- the max # of
278 * zombie-object hints that can be communicated via SHM-aux-pool at a time -- then the algorithm
279 * is performant. Otherwise it might be best to tune that constant in the *up* direction.
280 * (Conversely, if the histogram clusters around the left side, leaving lots of room before
281 * the aforementioned constant, then even more perf may be squeezed out by *lowering* it.)
282 *
283 * @note We choose the ~middle of the bucket range to match the aforementioned constant
284 * `"detail::Lend_tracker_pool::S_N_UNUSED_IDX_HINTS"`.
285 *
286 *
287 * Suppose `"Lend_tracker_pool::S_N_UNUSED_IDX_HINTS"` is 16; then we get this bucket structure:
288 * [1..3 | 4..7 | 8..11 | 12..15 | 16..19 | 20..23 | 24..27 | 28..31 | 32..35.. ].
289 * Recall that, as usual, the final bucket gets all the overflow events; meaning cases where the
290 * estimate N showed anything over 32 -- not necessarily only up to 35.
291 */
292 flow::util::stat::Histogram_counter
296 1};
297
298 /// See Events doc header.
300}; // struct Zombie_obj_reaper_stats
301
302/**
303 * A bundle of several individually-documented stat-sets -- object construct/dispose/live and
304 * zombie-reaper stats -- grouped into this one `Stat_set` because they are all collected together (for
305 * performance, via thread-local sharding) and therefore consumed together but separately from non-sharded
306 * items. The grouping itself is based on these impl constraints; to the stat-consuming user that is not
307 * too interesting. What is interesting is what each member tracks; see that member's own type's doc header.
308 *
309 * @see Pool_stats: A peer grouping of per-arena stats; it must be separate due to not being TL-sharded when
310 * internally collected; therefore the method of return is different from a `*this`.
311 * @see Memory_manager_stats: A peer grouping of per-arena stats; it is separate due to coming from an
312 * external data source (memory manager: e.g., jemalloc) as opposed to from internal algorithms/data.
313 * (They could still be bundled together. Nevertheless the subject matter/data source are, we feel,
314 * so sharply different that Memory_manager_stats simply should remain separate from all others.)
315 *
316 * ### Semantics ###
317 * Generally, they're just `struct`s as usual. In a publicly returned `*this` (returned by, e.g.,
318 * jemalloc::Ipc_arena::sharded_stats()), the value in a given leaf stat-member is what it is: the measurement
319 * of that thing, since the last reset (or init). However:
320 *
321 * As a TL-sharded `Stat_set` (see `flow::util::stat` doc header for full discussion), there are some caveats
322 * or interesting notes to keep in mind about it. To recap these:
323 * - Stat-members named `..._hi_wmark` (by convention having `Stat_type::S_HI_WMARK`: high-water-mark) do not
324 * (cannot, due to limitations of sharding) represent, simply, the highest value (since reset if any) that
325 * the gauged-stat has ever achieved. Instead each `_hi_wmark` represents the highest value (since reset if
326 * any) that the gauged-stat has achieved *sampled at stat-consumption (or last reset, or init) times*.
327 * Hence the more regular stat-consumption is, the more accurate/interesting `_hi_wmark`s are.
328 * - A diagnostic member named `m_n_shards` -- in this case shm::stat::Owner_obj_stats::m_n_shards -- is
329 * interesting in that it shows how many shard-`*this`es were used to generate (generally via summing, at
330 * last stat-consume time) the resulting `*this` being inspected. That's how many threads overall participated
331 * in reporting data, each reporting at least one stat-member's worth.
332 *
333 * @internal
334 *
335 * Stat sub-`struct`s grouped in this one `struct`, so that Thread_lcl_obj_db_admin and
336 * Thread_lcl_obj_db_client TL-registries can, at stat consumption time, aggregate per-thread shards corresponding to
337 * each of those sub-`struct`s. Since a `*this` is a single (composed) `Stat_set` (in the `flow::util::stat`
338 * sense), it is possible to aggregate N threads' shards into an overall result `Stat_set` with a single
339 * `flow::util::stat::stats_aggregate_shards()` call. After that, the stat-consumption API can copy-out or
340 * otherwise rearrange/present individual sub-`struct`s to the user, re-organizing them as it sees fit. Or
341 * it can just present them all in this same grouping, which is what we chose to do ultimately; hence why this
342 * grouping `struct` is public.
343 *
344 * Impl nitty-gritty notes
345 * -----------------------
346 * If none of the below makes any sense, have a look at the stat-consumption API's impl as well as a few places
347 * where these stat-members are updated. In any case, also, a familiarity with `flow::util::stat` namespace doc
348 * header is assumed.
349 *
350 * @see detail::stat::sharded_stats() (stat-consume), detail::stat::sharded_stats_reset() (stat-reset).
351 * @see detail::Thread_lcl_obj_db_admin, detail::Thread_lcl_obj_db_client, and
352 * detail::stat::Finalized_shards: These three hold shard `*this`es; the former two regularly update
353 * theirs. Then the free function `sharded_stats()` aggregates them into a target `*this`;
354 * `sharded_stats_reset()` resets that target `*this`.
355 *
356 * The following is arguably really about Thread_lcl_obj_db_admin + Thread_lcl_obj_db_admin impl details,
357 * and could be best discussed in one of those doc headers, but we're all friends (but not `friend`s) here,
358 * and if you're interested in stats-related impl design, then this is a pretty good place to talk about it.
359 * In any case Sharded_stats impl (the way it chooses to group sub-`struct`s basically) and
360 * Thread_lcl_obj_db_admin + Thread_lcl_obj_db_client impls are all designed to work together.
361 *
362 * ### Who updates the stat-member values between stat-consumptions? ###
363 * For each given stat-member, it may be updated only by Thread_lcl_obj_db_admin, or only by
364 * Thread_lcl_obj_db_client, or by both (across potentially many threads). If it's not *both* but only one --
365 * let's say `_admin` for exposition purposes -- then no per-thread `_client`
366 * will ever touch that stat-member, so its value shall remain zero in all per-`_client` shard `*this`es.
367 * During aggregation any given `_client` simply won't contribute (to the aggregated total for this stat-member)
368 * and is harmless.
369 *
370 * There is an alternative approach: Have 3 `struct`s, conceptually the non-overlapping union of which
371 * equals the 1 `struct` you're reading now; one with the `_admin`-only stats, one with the `_client`-only stats,
372 * and one with those that both touch. One could claim this is cleaner; it does save some processor cycles
373 * and RAM -- but the saved cycles are at stat consumption time (rare), and the RAM difference is small.
374 * The increase in boiler-plate is considerable however; there are 3 types instead of 1; each has to be
375 * `stats_aggregate_shards()`ed separately. So, we're doing it the other way (previous paragraph).
376 *
377 * There is another slightly different category of stat-member in here too; as of this writing comprising
378 * only `m_live_obj.m_live_object_zombies`. (So the preceding 2 paragraphs describe all but that stat-member.)
379 * For this category:
380 * - The stat-members are still sharded/aggregated as described.
381 * - They are, however, not updated during the regular operation of `Thread_lcl_obj_db_*` -- that is, between
382 * stat-consumption points. Instead, at stat-consumption -- before the actual `stats_aggregate_shards()`
383 * call but under central lock that allows an undisturbed aggregation-walk over the extant per-thread
384 * `Thread_lcl_obj_db_*` objects -- we grab the required values (as of this writing, all are fittingly
385 * GAUGEs) and stuff them into the per-thread `*this`. Then they get aggregated with everything else.
386 *
387 * @see stats_update_pre_consumption() impl which explains why we do this.
388 *
389 * ### Stat consumption / Stat resetting ###
390 * We've already discussed stat-consumption above. Just to be explicit about it though: User wants stats so:
391 * We follow the suggested pattern for TL-sharded stat-consumption from `flow::util::stat` doc header.
392 * (For simplicity of exposition let's ignore Thread_lcl_obj_db_client and pretend only Thread_lcl_obj_db_admin
393 * is involved. Call it `Admin` + some other made-up short identifiers for brevity.) So there is a
394 * `static Thread_local_state_registry<Admin> s_reg`. At stat-consumption we do `s_reg.while_locked(F)`,
395 * where `F()` is where we do the aggregation. To wit in `F()` we use `state_per_thread` which
396 * is essentially a set-container of per-thread `Admin`s. While the state *within* each `Admin` can
397 * absolutely concurrently change (in our case the state of interest is specifically the `*this` stat-set
398 * inside `Admin`), no new `Admin`s can be created (new thread joins the fray) or removed (thread exits) --
399 * within `F(){}` body. Now we can use some iterator magic to feed an iterator-range of `*this`es to
400 * `flow::util::stat::stats_aggregate_shards()`, corresponding to the set-container of `Admin`s.
401 *
402 * Resetting is similar (and again it follows the `flow::util::stat` doc header's suggestions).
403 * User wants the ongoing stat-collection to be reset (in short: leave GAUGEs alone, reset ACCUMULATORs to zero,
404 * and reset HI_WMARKs to match the GAUGEs' current values). So we do the above, except that in `F()` upon
405 * acquiring the set-container of extant `Admin`s, we don't feed their respective `*this`es into a single
406 * `stats_aggregate_shards()` but rather into `stats_reset_shard_aggregate()`.
407 *
408 * This also has the nice property of synchronizing (a/k/a making serial) stat-consumptions and stat-resets.
409 * The `X.while_locked()` is both cases is on the same `X`, thus internally the same central mutex is being
410 * locked.
411 *
412 * Adding Thread_lcl_obj_db_client into this does not essentially change the picture. The code itself will
413 * clarify those details.
414 */
416{
417 // Data.
418
419 /**
420 * Sub-`struct` with individual stats; see type's doc header.
421 *
422 * Per suggested convention in `Owner_obj_stats` doc header, we place this one first.
423 */
425 /// Sub-`struct` with individual stats; see type's doc header.
427 /// Sub-`struct` with individual stats; see type's doc header.
429 /// Sub-`struct` with individual stats; see type's doc header.
431 /// (During mainstream piggy-backed zombie reaping) Sub-`struct` with individual stats; see type's doc header.
433 /// (During drain-phase thread, after user thread exited) Sub-`struct` with individual stats; see type's doc header.
435}; // struct Sharded_stats
436
437/**
438 * Stats about the RAM use of the arena-lending SHM-provider's auxiliary (non-user-data) SHM-pools which are
439 * used internally to store and performantly communicate (cross-process) the current use-counts per
440 * first-class (`arena.construct<T>()`ed) object.
441 *
442 * ### Background et al ###
443 * Internally, an arena-lending SHM-provider (including SHM-jemalloc) maintains a *first-class object*
444 * (a/k/a `arena.construct()`ed object) database in memory. A given such object's *use-count* is defined as 1
445 * upon `.construct()` (and thus insertion into this obj-DB). Each `shm_session.lend_object()` -- typically
446 * so as to share it to another process which obtains its first-class object handle via
447 * `shm_session.borrow_object()` -- increments use-count by 1. Conversely, when the `.construct()`ed `shared_ptr`
448 * group reaches ref-count 0, use-count decrements by 1; and similarly for the `.borrow_object()`ed `shared_ptr`
449 * group. Once the use-count reaches zero, it is garbage-collected (GCed).
450 *
451 * The use-count is a *cross-process* quantity: process A constructs/lends/drops
452 * handle (`= 1`, `++`, `--` respectively), but process B borrows/drops (`--` for the latter), usually.
453 * Process A is the one with the original *arena*, so it must be aware of then the use-count drops to zero,
454 * including if process B was the one to have executed the last `--`. To avoid sending actual IPC messages for
455 * this situation, we maintain an internal-use only SHM-pool (per arena *per `arena.construct()`ing thread*),
456 * called *aux pool*; it is created the first time per `arena` that a given *thread* does `arena.construct<T>()`.
457 * Other thread(s), in this (owner) and borrowing process(es), each opens a SHM-handle to this same pool as
458 * needed, when a use-count must be affected (namely on `shm_session.lend_object()`, dispose in owner process;
459 * dispose in borrower process). When the aux-pool is no longer necessary (arena shutdown; or user-thread exit +
460 * no live objects from it left), it is destroyed.
461 *
462 * The point for stats is this: These aux pools take non-trivial RAM. This `Stat_set` tracks various facts
463 * about that topic and similar (e.g., not just about RAM per se but also counts of things that are causing the
464 * RAM use, or vaddr space use for that matter).
465 */
467{
468 // Data.
469
470 /**
471 * Number of aux SHM-pools currently in existence; incremented when a given owner-process thread first
472 * executes `arena.construct()`; decremented when the pool's name is removed from the file-system.
473 *
474 * Caveat(s) and fact(s):
475 * - The actual RAM is returned for general OS use no sooner than (1) this counter decrements *and* (2) all
476 * cross-process handles to the pool are closed. When properly functioning and without pathological
477 * user app behavior, (2) should occur soon after (1).
478 */
479 std::atomic<uint64_t> m_aux_pool_count = 0;
480
481 /// Max of #m_aux_pool_count observed so far.
482 std::atomic<uint64_t> m_aux_pool_count_hi_wmark = 0;
483
484 /**
485 * Of the quanta counted in #m_use_ct_quanta, count of the ones in active use.
486 *
487 * Caveat(s) and fact(s):
488 * - Each pool currently contributing to #m_aux_pool_count contributes at least 1 to #m_use_ct_quanta.
489 * - Its contribution may go up over time but never down...
490 * - ...except when #m_aux_pool_count is decremented by 1: then the contribution on that 1 pool's
491 * behalf so far is decremented from #m_use_ct_active_quanta.
492 * - Even just 1 live-object *ever* (up to pool deletion per above) being tracked in a given quantum means
493 * that quantum counts as 1 full unit in #m_use_ct_active_quanta. This is the case even if no active
494 * live-object (use-count) in that quantum still exists at this time (meaning all were freed). (The
495 * quantum still is available for use; the RAM is resident through deletion; so it all counts.)
496 */
497 std::atomic<uint64_t> m_use_ct_active_quanta = 0;
498
499 /// Max of #m_use_ct_active_quanta observed so far.
500 std::atomic<uint64_t> m_use_ct_active_quanta_hi_wmark = 0;
501
502 /**
503 * Count of quanta available -- not necessarily (or typically) all active -- across the currently live
504 * aux SHM-pools as counted in #m_aux_pool_count.
505 *
506 * ### Background ###
507 * The max available space in an aux-pool is divided into N *quanta*. (As of this writing N=32, but this could
508 * change; citing the number here for exposition purposes.) To limit unnecessary RAM use, the algorithm
509 * will issue use-count slots as needed from only 1 quantum. Once it is completely full (there are enough
510 * live first-class objects from the tracked arena constructed in the tracked thread), quantum 2 is added
511 * to the set of active quanta; use-count slots shall be issued from both active quanta. Once both of those
512 * run out at the same time, quantum 3 is added; etc. If all N quanta are exhausted at the same time, it
513 * is a fatal error -- but the count and capacity of each quantum are such that this is quite unlikely.
514 *
515 * This stat-member is the sum of these Ns across all #m_aux_pool_count extant aux SHM-pools. Hence it goes
516 * up by N when `m_aux_pool_count` goes up by 1 and both values are subtracted together similarly.
517 *
518 * @note As of this writing N is a compile-time constant; hence one could derive this value by grabbing N
519 * somewhere from the Flow-IPC code and multiplying by #m_aux_pool_count. This could change, however,
520 * so we found it prudent to make this a dynamic stat, for completeness and forward-compatibility of sorts.
521 * Due to internal impl details, the perf impact of this is negligible.
522 *
523 * ### Caveat(s) and fact(s) ###
524 * - This stat-member does *not* correspond to actual RAM taken away from general OS use (#m_resident_sz).
525 * It does correspond to the vaddr space *mapped* (#m_mapped_sz). One can think of it as the worst case
526 * scenario, if all owner-process threads to have ever constructed objects in this arena will construct as
527 * many objects as humanly possible.
528 * - Even just 1 live-object *ever* (up to pool deletion per above) being tracked in a given quantum means
529 * that quantum counts as N full units in #m_use_ct_quanta.
530 */
531 std::atomic<uint64_t> m_use_ct_quanta = 0;
532
533 /// Max of #m_use_ct_quanta observed so far.
534 std::atomic<uint64_t> m_use_ct_quanta_hi_wmark = 0;
535
536 /**
537 * Of the mapped space tracked (in bytes) in #m_mapped_sz, the approximate/ceiling size of *resident* RAM
538 * (RAM taken from general OS use): small header plus space used by #m_use_ct_active_quanta quanta. In short
539 * it's a (conservative) estimate of RAM used by the SHM aux-pools.
540 *
541 * Caveat(s) and fact(s):
542 * - Each active quantum added to #m_use_ct_quanta adds a constant (per-pool at least) amount of RAM
543 * to #m_resident_sz; except that the very 1st quantum (per-pool) adds a bit more due to the required
544 * header/metadata.
545 * - This value is conservative, in that it acts as-if each active quantum is immediately full of used
546 * use-count slots (even after just 1 actual use-count was allocated therein). The accounting required
547 * to obtain a tighter ceiling would be too onerous in terms of complexity and performance.
548 * - In particular, even if several pages-full of use-count slots were all freed, we do not return that
549 * vaddr space into general OS use -- it remains resident.
550 * - This value is approximate, +/- the OS page size (typically 4Ki bytes). We make a reasonable attempt
551 * to keep certain bounds and offsets consistent with page boundaries, but it is not fully rigorous.
552 */
553 std::atomic<size_t> m_resident_sz = 0;
554
555 /// Max of #m_resident_sz observed so far.
556 std::atomic<size_t> m_resident_sz_hi_wmark = 0;
557
558 /**
559 * Total vaddr space, in bytes, reserved for SHM aux-pools. This is to #m_resident_sz what #m_use_ct_quanta is
560 * to #m_use_ct_active_quanta. Therefore it is important to remember this is not RAM taken from general OS
561 * use but merely mapped. #m_resident_sz is the actual RAM taken. One can think of it as the worst case
562 * scenario, if all owner-process threads to have ever constructed objects in this arena will construct as
563 * many objects as humanly possible.
564 *
565 * Thus, when #m_aux_pool_count goes up by 1, #m_mapped_sz is increased by K; and both values are subtracted
566 * at the same time also, at pool deletion.
567 *
568 * @note As of this writing K is a compile-time constant in practice, though certain internal subtleties would
569 * make it challenging to find the exact K without some run-time voodoo. Even ignoring that, however,
570 * the constance of K could change. So we found it prudent to make this a dynamic stat.
571 * (Due to internal impl details, the perf impact of this is negligible.) For consistency, at least,
572 * we also implemented #m_use_ct_quanta as a dynamic stat rather than relying on knowing K and multiplying
573 * by `m_aux_pool_count`.
574 *
575 * @internal
576 * The "internal subtleties" come from, at least, the fact that detail::Use_count_registry (our custom
577 * boost.ipc memory-algorithm impl) -- by bipc's design -- receives `extra_hdr_sz`, which contributes to
578 * K, as a run-time argument from `bipc::basic_managed_shared_memory` (as used in detail::Lend_tracker_pool).
579 * It's some bipc internal header-stuff, that might even actually be zero-sized in our case; we can deduce it
580 * empirically and hard-code it... but it'd be an unnecessary kludge.
581 */
582 std::atomic<size_t> m_mapped_sz = 0;
583
584 /// Max of #m_mapped_sz observed so far.
585 std::atomic<size_t> m_mapped_sz_hi_wmark = 0;
586}; // struct Obj_db_aux_pool_stats
587
588/**
589 * Similar mission to Obj_db_aux_pool_stats but covers information that, by nature, is not per-arena but rather
590 * global (but still segregated by SHM-provider, such as SHM-jemalloc versus other-arena-lending-SHM-provider X).
591 *
592 * @see Obj_db_aux_pool_stats doc header for background on aux-pool mechanism.
593 *
594 * ### (Further) background et al ###
595 * Generally they can also be contrasted like this:
596 * - Obj_db_aux_pool_stats: Admin/owner perspective; counts aux-pools themselves as well as the RAM use thereof.
597 * Each aux-pool whose name is in file-system <=> one *admin* SHM-pool handle.
598 * - Obj_db_aux_pool_global_stats: Client (in both owner process and borrower process(es)) perspective; counts
599 * *client* SHM-pool handles, their openings/closings (the former triggered lazily by each user thread
600 * needing to access an aux-pool, excluding the admin/constructing thread).
601 * - The latter is called a *client* thread. Cf. admin thread (<=> actual named in-SHM aux-pool).
602 * The first of the following user-invoked events triggers opening a per-arena, per-client-thread
603 * aux-SHM-pool handle:
604 * - *owner-side* `shared_ptr<T>` group (from `arena.construct<T>()`) reaches ref-count=0 in non-admin thread
605 * => disposer runs in that thread => cross-process use-count happens to reach zero. (Can instead happen
606 * in admin-thread disposer; or in *borrower-side* disposer -- next bullet.)
607 * - *borrower-side* `shared_ptr<T>` group (from `Shm_session::borrow_object()`) reaches ref-count=0.
608 * (Outside of test scenarios, this usually occurs in different process from arena-owner's.)
609 * - The following triggers the closing of such an aux-SHM-pool handle:
610 * -# In admin/owner-land (tracked by Obj_db_aux_pool_stats) the SHM-pool name is deleted from file-system
611 * (user constructing thread exited; all live objects immediately or subsequently dropped by user app).
612 * -# In-SHM this is marked by a *dead* flag become `true` permanently.
613 * -# In each given client thread, at certain opportunistic/piggy-backed points, the flag is checked
614 * and observed to be `true`. Therefore, as all live objects are gone and none may appear further
615 * (admin thread has exited): close the client aux-SHM-pool handle.
616 * -# Result: aux SHM-pool RAM returned to OS for general use (once all such cross-process pool-handles
617 * have closed; that is the above steps have executed in every relevant thread cross-process).
618 */
620{
621 // Data.
622
623 /**
624 * (Gauge) In this process, the number of all per-owner-thread, per-arena thread-local aux SHM-pool handles currently
625 * open. This total, across all processes (1 owner process + 1 per each borrower process), plus the original
626 * admin handles (Obj_db_aux_pool_stats::m_aux_pool_count across all arenas) = total descriptor use by the
627 * aux pool mechanism.
628 *
629 * Modulo concurrency artifacts this shall equal #m_client_tl_aux_pool_hndl_open_count minus
630 * #m_client_tl_aux_pool_hndl_reap_count (can use as sanity-check).
631 */
632 std::atomic<uint64_t> m_client_tl_aux_pool_live_hndls = 0;
633
634 /// Max of #m_client_tl_aux_pool_live_hndls observed so far.
636
637 /**
638 * In this process: number of times a client aux-SHM-pool handle (as described for
639 * #m_client_tl_aux_pool_live_hndls) has been opened.
640 */
641 std::atomic<uint64_t> m_client_tl_aux_pool_hndl_open_count = 0;
642
643 /**
644 * In this process: number of times an attempt to open a client aux-SHM-pool handle (as described for
645 * #m_client_tl_aux_pool_live_hndls) failed -- most likely because the aux-pool no longer exists in the
646 * file-system. Depending on the operation attempting it, that condition is either benign or grave.
647 * Benign: one is releasing (dropping one's last handle to) a *borrowed* object, but the lending arena --
648 * possibly its whole process -- is already gone; then the object is moot by contract, and the release
649 * quietly succeeds. Grave, in any other situation (e.g., resource exhaustion; or an external actor
650 * deleting shared-memory objects): the operation in question shall throw. Hence: in an application that
651 * is otherwise humming along, this stat counts benign-release events; while a non-zero value accompanied
652 * by throwing/aborting operations indicates the grave sort.
653 *
654 * The grave type *will* mean a thrown exception, and in some cases a
655 * guaranteed abort; it would be surprising if your application continued
656 * operating just fine, even if you managed to catch this truly-exceptional exception. So, in and of itself, seeing
657 * a non-zero value here should not be a source of worry. If everything else is fine, then it's the
658 * benign thing. As for the benign thing:
659 *
660 * This is an interesting value, if non-zero, indicating a quite-tight (chronologically) shutdown sequence:
661 * a process decided to end a session; immediately ended a lent (through that session) SHM-arena; and the
662 * opposing side detected the session-end and immediately did the compliant thing: nullify any outstanding
663 * `borrowed_object()`ed SHM-handles (and probably then end the session object). The nullify step there is
664 * what would trigger a `++` here. To be clear we reiterate: that does not mean you are doing anything wrong.
665 *
666 * @internal
667 *
668 * Incremented in Thread_lcl_obj_db_client::new_pool_data(), in the stats-keeping surrounding the
669 * client-mode Lend_tracker_pool ctor throwing. The benign case is the not-found-tolerant catch clause in
670 * the borrower-side Thread_lcl_obj_db_client::disposing_obj() overload; every other path lets the
671 * exception fly ("grave" situation mentioned above).
672 */
674
675 /**
676 * In this process: number of times a client aux-SHM-pool handle (as described for
677 * #m_client_tl_aux_pool_live_hndls) has been closed.
678 */
679 std::atomic<uint64_t> m_client_tl_aux_pool_hndl_reap_count = 0;
680
681 /**
682 * In this process: number of times a client aux-SHM-pool handle (as described for
683 * #m_client_tl_aux_pool_live_hndls) was used to check whether the aux-pool is in *dead* flag state.
684 * (If the check showed an indeed-dead pool, #m_client_tl_aux_pool_hndl_reap_count goes up by 1 also.
685 * Otherwise it does not.) Can use this to get a sense of absolute frequency of the reap-checks
686 * as well as to compute the rate at which it results in a reaping (closing of client pool-handle).
687 */
689}; // struct Obj_db_aux_pool_global_stats
690
691/**
692 * Similar mission to shm::stat::Owner_obj_stats (which is relevant regardless of SHM-provider type) and
693 * Owner_obj_stats but applied specifically to the mechanism of arena-lending SHM-providers wherein each SHM-arena
694 * is divided into multiple, potentially many, SHM-pools.
695 *
696 * ### Background ###
697 * From the perspective of the arena (hence `Owner_` prefix) in which first-class objects are constructed
698 * (and GCed) and/therefore raw buffers are [de]allocated: a memory manager (such as jemalloc) carves allocations
699 * out of contiguous *vaddr areas* (a/k/a *extents*), which it obtains by having memory *memory-mapped*. Mapped
700 * space is not RAM in use: a page becomes *resident* (taken from general OS use) only once accessed. In an
701 * arena-lend SHM-provider (such as SHM-jemalloc) each such mapping is of a named *SHM-pool* (a/k/a segment,
702 * SHM-object; in Flow-IPC we use "[SHM-]pool"). (A SHM-pool can be accessed by other process(es); however that is
703 * not of direct interest from the owner's PoV, and therefore in the context of this discussion.)
704 *
705 * So, here we concern ourselves with SHM-pools, which compose the arena. Events of interest are, e.g.,
706 * memory-manager requested an extent (for us, pool), so we opened a SHM-pool and mapped M total bytes in that pool;
707 * perhaps the reverse (pool no longer needed, close it). These are straightforward, really, but a few
708 * things to keep in mind:
709 * - Pools are generally created when a user, directly or indirectly (usually indirectly in Flow-IPC),
710 * executes something like `alloc(N)`, and then *the memory manager, lazily requests pool creation* upon
711 * having run out of already-mapped pool space.
712 * - A pool can never change size, and it either exists (is mapped) or does not (is not mapped, name removed
713 * from file-system), in the arena-lending SHM-provider's owner context (i.e., arena context).
714 * - A given memory manager may want to work on finer units than entire pools, or possibly the reverse:
715 * it could split a pool into 2+ extents; or possibly merge 2+ pools into an extent -- if they are
716 * adjacent in vaddr-space. In short, we do not concern ourselves with that here. It is not super interesting
717 * to us from the SHM-provider perspective; and in any case see preceding bullet.
718 *
719 * @internal
720 * There are some counts that would be available to count, in SHM-jemalloc extent-hooks in particular at least --
721 * unclear if they would have equivalents for other/all arena-lend SHM-providers -- which we did not include.
722 * They are at least (again, for SHM-jemalloc) as follows.
723 * - Split-extent and merge-extents requests: Potentially interesting albeit inside-baseball. Not included
724 * chiefly because for all we currently know, they could be frequent enough to where the non-sharded
725 * counting otherwise used for Owner_pool_stats become sufficiently costly to have to introduce sharding or
726 * at least have a compile-time gate for whether to track these. Information is likely also available in
727 * native-jemalloc stats, which are available without us, *and* we facilitate grabbing them more conveniently
728 * elsewhere as well.
729 * - Commits/decommits: Too hairy/inside-baseball; not really an area of SHM-provider interest; not sure if perf
730 * risk. Probably available in native-jemalloc stats (ditto prev bullet). Might also be more of a thing or
731 * less of a thing depending on OS which speaks to its inside-baseballhood.
732 * - Lazy-purge, forced-purge: Probably not a perf risk. Again it seems too hairy/inside-baseball; and again
733 * probably available in native-jemalloc stats. As in prev bullet might be OS-dependent.
734 */
736{
737 // Data.
738
739 /**
740 * Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested the creation of a new
741 * extent of a certain size, and we therefore obliged it by creating a named SHM-pool of that actual size.
742 *
743 * ### SHM-jemalloc-specific ###
744 * *As of this writing*, due to the policy described in #m_pool_optional_destroy_request_count
745 * doc header, #m_pool_destroy_count and #m_pool_destroy_sz are in practice likely the be zero, until arena
746 * shuts down... at which point there is no jemalloc::Ipc_arena object wherein to query current stats.
747 *
748 * Therefore:
749 * - #m_pool_create_count is effectively the **current pool count** for the arena;
750 * - #m_pool_create_sz is effectively the **current mapped (not necessarily resident!) total size** for
751 * the arena;
752 * - but do glance at `m_pool_destroy_*` to sanity-check they are zero. If not then (1) look into why and (2)
753 * subtract those to get the current-pool-count and current-mapped-size values.
754 *
755 * @internal
756 * @todo Once the aforementioned policy changes, we should do a few things in Owner_pool_stats:
757 * (1) add GAUGEs `atomic<size_t> m_n_pools` and `m_mapped_sz`; (2) implement their tracking by live-updating them via
758 * `fetch_add()` and `fetch_sub()`, whenever the existing `m_pool_create/destroy_*` values are updated;
759 * (3) add `_hi_wmark` counterparts to both; and (4) update them in the usual way; these will (as is typical
760 * elsewhere in Flow-IPC) become roughly the reason to even keep separate GAUGEs which are otherwise derivable
761 * from existing create/destroy ACCUMULATORs. Until all that, the existing ACCUMULATORs are sufficient;
762 * and the `_destroy_` stat-members are nice canaries to detect unexpected (non-shutdown) pool-removals.
763 */
764 std::atomic<uint64_t> m_pool_create_count = 0;
765
766 /// Across the pool-creations counted in #m_pool_create_count, the sum of the requested/actual sizes of those pools.
767 std::atomic<uint64_t> m_pool_create_sz = 0;
768
769 /**
770 * Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested *something*, and as a result
771 * we removed 1 SHM-pool, undoing a pool-creation op counted in #m_pool_create_count.
772 *
773 * ### SHM-jemalloc-specific ###
774 * *As of this writing*, due to the policy described in #m_pool_optional_destroy_request_count
775 * doc header, #m_pool_destroy_count and #m_pool_destroy_sz are in practice likely the be zero, until arena
776 * shuts down... at which point there is no jemalloc::Ipc_arena object wherein to query current stats.
777 * Hence this is likely to always be zero.
778 *
779 * ### Context ###
780 * From the PoV of what the SHM-provider has *done*, this is symmetrical to #m_pool_create_count. That one
781 * counts creations; this one counts destructions thereof. Simple.
782 *
783 * From the PoV of the memory manager, in terms of what it *requested* the SHM-provider do, it is more complex.
784 * The following information is based on SHM-jemalloc (as of this writing the only arena-lending SHM-provider
785 * in Flow-IPC); it is conceivable other arena-lending SHM-providers might change the picture somewhat, leading
786 * to changes to this/related members and/or creation of provider-specific `struct`(s).
787 * - Creation: As noted: mem-mgr requests extent; we create SHM-pool. Request is always honored; that is
788 * counted in #m_pool_create_count.
789 * - Destruction: A couple of possible triggers:
790 * - Arena shutdown (user-triggered; e.g., application shutting down) in progress; mem-mgr requests the
791 * destruction of each pool one-by-one. This is always honored and is (at least part of) #m_pool_destroy_count.
792 * - Fine... conceivably a pool might be removed not as one pool-sized chunk but rather in pieces, until
793 * the final removed-piece gets the un-removed byte count for the pool down to zero. We state this
794 * here only for the sake of the pedants; the point is -- in however many steps -- arena shutdown =>
795 * pool removed, for each live pool.
796 * - Mem-mgr is observing a large number of allocs have been dealloc-ed, so some pools are no longer
797 * needed -- perhaps they can be returned as free RAM to OS. This may or may not be honored; it is counted
798 * in #m_pool_destroy_count only if, yes, was honored.
799 *
800 * This stat-member tracks only the result of such requests, not the requests. However see doc header
801 * for #m_pool_optional_destroy_request_count for more on that topic.
802 */
803 std::atomic<uint64_t> m_pool_destroy_count = 0;
804
805 /// Across the pool-destructions counted in #m_pool_destroy_count, the sum of the actual sizes of those pools.
806 std::atomic<uint64_t> m_pool_destroy_sz = 0;
807
808 /**
809 * Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested the *optional* removal
810 * (unmap + delete name from file-system) of 1 SHM-pool; "optional" meaning the SHM-provider could accept
811 * (thus counting in #m_pool_destroy_count) or reject the request.
812 *
813 * @see #m_pool_destroy_count doc header for background.
814 * @note This does *not* count removal-requests during arena shutdown; these removal-requests are not mandatory;
815 * they are always honored.
816 *
817 * SHM-jemalloc: As of this writing we *always* deny these requests; and SHM-jemalloc is the only arena-lend-type
818 * SHM-provider. Therefore we do not yet have a `m_pool_optional_destroy_accept_count` counterpart; it would
819 * always be zero.
820 *
821 * @note The reason for this policy is out of our scope here; but it may well not be indefinitely the case that
822 * we follow it. We might be more intelligent about it in a future SHM-jemalloc version.
823 * @note Incidentally: If an optional pool-destroy request from mem-mgr is rejected by SHM-provider, mem-mgr
824 * shall likely reuse the SHM-pool for future allocations if suitable. The fact it remains mapped indefinitly
825 * does not even mean it will *necessarily* stay resident indefinitely; mem-mgrs can and do use special
826 * OS calls to *purge* unused pages (return physical RAM back to the OS for general availability).
827 *
828 * @internal
829 * We considered adding an `_sz` counterpart to #m_pool_optional_destroy_request_count; but to me (ygoldfel) is
830 * is not immediately clear what that should be. As of this writing the extent-hook handler
831 * Ipc_arena::optional_remove_shm_pool() simply returns `false`; and I am not immediately sure if in practice
832 * the `size` arg would end up equalling the removed pool's size had it returned `true` and actually removed
833 * some pool. It is probably an answerable question, and this might even be an official to-do, but I figured
834 * let's have it return `true` sometimes first before worrying about such a thing (which is currently a
835 * hypothetical in terms of actually acting on it).
836 */
837 std::atomic<uint64_t> m_pool_optional_destroy_request_count = 0;
838}; // struct Owner_pool_stats
839
840/**
841 * Bundles together per-arena Owner_pool_stats and Obj_db_aux_pool_stats, so they can be accessed via one accessor
842 * (e.g., jemalloc::Ipc_arena::pool_stats()) and handled (e.g., `ostream << print()`, `stat_set_aggregate()`)
843 * as one unit. The subject matter loosely connecting these sub-`struct`s is that they deal with SHM-pools
844 * (though of quite different kinds).
845 *
846 * @see Sharded_stats: A peer grouping of per-arena stats; it must be separate due to being TL-sharded when
847 * internally collected; therefore the method of return is different.
848 * @see Memory_manager_stats: A peer grouping of per-arena stats; it must separate due to coming from an
849 * external data source (memory manager: e.g., jemalloc) and therefore also featuring an incompatible method
850 * of return versus a `*this`.
851 *
852 * ### Nitpick ###
853 * Why not follow various precedent, given that Owner_pool_stats and Obj_db_aux_pool_stats are only stored
854 * in this one `struct`, and do the following?
855 * - Remove `_stats` from both type names and make them nested in Pool_stats.
856 * - Have one declare_stats() covering all the stat-members directly instead of 3: 2 per sub-`struct`, 1
857 * for Pool_stats which forwards to the other 2.
858 *
859 * There's no massive reason. It was just slightly more convenient; a certain `#include` cycle goes away,
860 * as certain headers don't need to include more than a `_fwd.hpp`. The user experience is ~identical; in fact
861 * arguably a little more flexible this way, as with it the individual fields (and their types) #m_owner_pool and
862 * #m_obj_db_aux_pool can be handled by themselves if desired: `ostream << print()`, `stat_set_aggregate()`, etc.
863 *
864 * Doing it the other way would have been more compact in this file basically. It would also be defensible.
865 */
867{
868 // Data.
869
870 /// Sub-`struct` with individual stats; see type's doc header.
872 /// Sub-`struct` with individual stats; see type's doc header.
874};
875
876/**
877 * Similar mission to Owner_pool_stats but covers information that, by nature, is not per-arena but rather
878 * global (but still segregated by SHM-provider, such as SHM-jemalloc versus other-arena-lending-SHM-provider X);
879 * concerned with raw-offset-pointer lookups.
880 */
882{
883 // Data.
884
885 /**
886 * (Gauge) Number of thread-local vaddr-to-SHM-pool caches currently in existence; equals the number of threads
887 * that have ever needed to perform at least 1 such lookup, minus the number of those threads that have
888 * exited.
889 *
890 * ### Context/background ###
891 * As such this stat-member illuminates the application's cross-thread pattern of use of these lookups.
892 * In particular, many STL-compliant containers (in SHM) would perform these lookups in various situations; but
893 * it really depends on the purpose and implementation of a given container. Rewinding a bit though:
894 *
895 * The lookup in question is as follows: You've got some data structure or datum in-SHM, created in a SHM-arena
896 * (possibly but not necessarily via `arena.construct<T>(...)`, where `T` contains or is something relevant), and
897 * in working with it you've got an owner-process-local vaddr -- which may or may not in fact be in the SHM-arena (if
898 * not then in the stack/heap... but don't worry about that now, off-topic) -- and you need to find out (1) whether
899 * it is in fact in the SHM-arena (<=> in exactly 1 of the pools composing said arena) and (2) if so, which
900 * one. (The latter "you" is usually not user code per se but something inside an STL-compliant container's code,
901 * with its allocator tparam set to our shm::stl::Stateless_allocator, trying to do something like
902 * `pointer_traits::pointer_to()`. That is it is converting a raw pointer `T*` to a fancy-pointer; in Flow-IPC's
903 * arena-lending-SHM-provider case `Shm_pool_offset_ptr<T>`.)
904 *
905 * This is a non-trivial computation, and it requires lookups (possibly multiple lookups in a binary-search
906 * algorithm) in a *global map from vaddr to SHM-pool base-vaddr*. Moreover, this map/table might change at
907 * any time, as concurrent user SHM-allocations sometimes trigger the memory manager (e.g., jemalloc in
908 * SHM-jemalloc) to ask for a new mem-mapped vaddr-area -- i.e., in our case a new SHM pool. To massively
909 * speed-up these lookups, we store thread-local copies of the table: *pulled* from the canonical central
910 * copy at first per-thread such lookup (a/k/a lazy init) and then zealously *pushed* whenever that canonical
911 * copy is modified (pool inserted/removed, in *any* arena).
912 *
913 * Thus:
914 * - This stat-member goes up by 1 at *first such reverse-lookup* in a given user thread.
915 * - This stat-member goes down by 1 when 1 such thread exits.
916 */
917 std::atomic<unsigned int> m_pool_rev_lookup_tl_cache_count = 0;
918
919 /// Max of #m_pool_rev_lookup_tl_cache_count observed so far.
920 std::atomic<unsigned int> m_pool_rev_lookup_tl_cache_count_hi_wmark = 0;
921}; // struct Owner_pool_lookup_global_stats
922
923/**
924 * IDs an arena, as a GAUGE in contexts like Shm_pool_info and per-arena Borrower_pool_stats, uniquely
925 * machine-wide up to boot. If N/A: special value zero is used for all fields.
926 */
928{
929 // Data.
930
931 /**
932 * Iff per-arena context (else zero): Unique (machine-wide, between boots) ID of containing SHM-arena: first half,
933 * currently the owner PID.
934 */
935 uint64_t m_id1 = 0;
936
937 /**
938 * Iff per-arena context (else zero): Unique (machine-wide, between boots) ID of containing SHM-arena: second half,
939 * currently: ordinal within process.
940 */
941 uint64_t m_id2 = 0;
942}; // struct Uniq_arena_id
943
944/**
945 * Loosely similar subject matter to Owner_pool_stats but regarding the *borrower-side* view of SHM-pools,
946 * as opposed to an owner/arena-side view thereof.
947 *
948 * ### Background et al ###
949 * The most basic fact here is that, essentially, an owner-process has an arena which in a sense *produces*
950 * SHM-pools (as required by the memory manager, such as jemalloc, during allocations), and it also *deletes*
951 * them. Cf. the borrower side which *consumes* them via a procedure called pool-lending/borrowing.
952 * (This occurs in the background without user participation.) The setup is:
953 * - owner process A with arena X;
954 * - borrower process B;
955 * - a session SAB (`Shm_session` object SA in A, peer object SB in B) = communication context between them;
956 * - user explicitly *lends* arena: `SA.lend_arena(X)`. Now:
957 * - whenever arena X creates pool P1, it auto-lends P1 to B via SAB by-virtue of the fact that X has been lent
958 * through SAB. (There can of course be more pools over time.)
959 *
960 * None of this is limited to, like, one thing. A can have session SAC to process C; it can even have a
961 * separate session SAB2 -- but only via a separate `Shm_session` SA2 (and SB2 in B) -- again to B. (If X
962 * is lent through all of these sessions -- SAB, SAC, SAB2 in this example -- then a pool like P1 is auto-lent
963 * through all of them: twice to B, once to C.)
964 *
965 * @note What is "borrowing a pool"? It is: open a SHM-pool handle according to the pool's name (same one with
966 * which owner-process-arena create-opened it) -- as of this writing for read-only access, enforced by
967 * the kernel -- and memory-map it to a process-local vaddr.
968 *
969 * Due to this setup, the accounting is a bit tricky. Consider, now, a given process; B for example. It maintains,
970 * internally, a **global** *borrower repository* (*BR* for short), whose basic purpose is to maintain
971 * *every* pool currently being borrowed. We've shown above, though, that a given arena X can be (from a BR's
972 * PoV) be borrowed 2x+; and therefore a given pool P1 in X can be borrowed 2x+ also. BR shall nevertheless
973 * only maintain *one* SHM-pool handle (and *one* record of containing arena), while maintaining a *use-count*
974 * for each.
975 * - When an arena, and in-arena pool's, use-count goes from 0 to 1: record arena; *open pool*.
976 * - When an arena, and in-arena pool's, use-count goes from not-0 to not-0 (borrowed again/unborrowed, but use-count
977 * remains 1+): just inc or dec the use-counts; neither open nor close pool; just let it be.
978 * - When an arena, and in-arena pool's, use-count goes from 1 to 0 (unborrowed, as from session closing):
979 * *close pool* (un-memory-map; close pool-handle; delete record of arena).
980 *
981 * Therefore, from BR's (global) PoV, you've got: arena/pool registering (a/k/a borrowing), where use-count goes up;
982 * the opposite (goes down); opening (use-count reaches 1); and closing (use-count reaches 0). Plus, since each
983 * pool has a permanent (can never change) size, each actual-pool-handle + sums thereof <=> mapped-size + sums
984 * thereof. All these things, including use-counts, are counted in Borrower_pool_stats; and the global BR thus owns
985 * a global Borrower_pool_stats.
986 *
987 * The complexity continues: the above talks of the BR's PoV; but there is also:
988 * - BR's PoV, but broken down by *ever-borrowed arena*. (An arena, in this context, is identified by a
989 * machine-wide-unique, between boots, ID: owner/PID + in-process-ordinal-ID.) (Subject to culling of old
990 * per-arena stats; see session::shm::arena_lend::jemalloc::Shm_session::borrower_pool_stats_process_wide().)
991 * - A given arena X can be borrowed via session SAB, then unborrowed when SAB closes; then borrowed again
992 * via fully-subsequent session SAB2. Accumulators like pool-open-count for pool P1 will carry-over
993 * throughout and shall persist during any gap when X isn't borrowed by anything. Nothing is degenerate;
994 * though #m_n_borrowed_arenas cannot exceed 1 (an arena is either currently being borrowed, or it isn't).
995 * - A given `Shm_session`'s point-of-view. To wit: a given arena can only be borrowed via SB once
996 * (borrowing same via SB2 is not the same thing; the processes are the same in our example, but the
997 * session -- while similar in configuration -- is a different session). So all of the same things apply;
998 * but the use-count of a given arena, and pool therein, can only ever be 1. The values of some stat-members,
999 * when applied to a `Shm_session`, will thus carry certain degenerate values which are correct but of low
1000 * interest. They are still recorded, however, so we can reuse the same `struct` -- the amount of boiler-plate
1001 * otherwise would be obnoxious.
1002 * - Remember though: from this PoV, last-deregistering a pool does *not* necessarily mean closing its handle
1003 * and unmapping it. Same with first-registering versus opening+mapping. (It only does mean that from the
1004 * global BR's overall PoV, or as broken-down by arena.)
1005 *
1006 * Well... don't worry about it too much right now. Things will make more sense once you look at the actual
1007 * stat-consumption API... or, indeed, each of the 3:
1008 * - BR's global `Borrower_pool_stats` object. (No degenerate-valued members.)
1009 * - BR's global by-arena map: arena-ID => `Borrower_pool_stats`. (No degenerate-valued members.)
1010 * - A `Shm_session`'s non-global `Borrower_pool_stats` object. (Some degenerate-valued arena/pool-oriented members.)
1011 */
1013{
1014 // Data.
1015
1016 /// Iff per-arena context (else zeroes): Unique (machine-wide, between boots) ID of containing SHM-arena.
1018
1019 /// How many times an arena was borrowed (opposing user did `S.lend_arena(X)`), including when already-being-borrowed.
1020 std::atomic<uint64_t> m_arena_register_count = 0;
1021
1022 /**
1023 * Of the borrowings counted by #m_arena_register_count, how many have been undone (as of this writing: via
1024 * closing of the pertinent `Shm_session S`; there is no support for explicit un-lending while retaining
1025 * session, but in theory this could be added).
1026 */
1027 std::atomic<uint64_t> m_arena_deregister_count = 0;
1028
1029 /**
1030 * Of the borrowings counted by #m_arena_register_count, exclude those wherein arena was borrowed while already
1031 * borrowed.
1032 *
1033 * @note Non-degenerate from PoV of the global `*this` and the per-arena breakdown: 2+ concurrent
1034 * `Shm_session`s can borrow the same arena (e.g., session rotation with overlap), and only the
1035 * first borrowing counts here. Per-session, however, this (modulo concurrency artifacts) shall
1036 * equal #m_arena_register_count (a given arena is borrowed at most once via a given session).
1037 */
1038 std::atomic<uint64_t> m_arena_first_register_count = 0;
1039
1040 /**
1041 * Of the unborrowings counted by #m_arena_deregister_count, exclude those wherein arena was unborrowed while
1042 * it still remains borrowed on behalf of another session.
1043 *
1044 * @note Non-degenerate from PoV of the global `*this` and the per-arena breakdown (see
1045 * #m_arena_first_register_count). Per-session, however, this (modulo concurrency artifacts) shall
1046 * equal #m_arena_deregister_count.
1047 */
1048 std::atomic<uint64_t> m_arena_last_deregister_count = 0;
1049
1050 /// (Gauge) Effectively #m_arena_first_register_count minus #m_arena_last_deregister_count.
1051 std::atomic<uint64_t> m_n_borrowed_arenas = 0;
1052
1053 /// Max of #m_n_borrowed_arenas observed so far.
1054 std::atomic<uint64_t> m_n_borrowed_arenas_hi_wmark = 0;
1055
1056 /// Analogous to #m_arena_register_count but counts individual in-arena SHM-pools.
1057 std::atomic<uint64_t> m_pool_register_count = 0;
1058
1059 /// Analogous to #m_arena_deregister_count but counts individual in-arena SHM-pools.
1060 std::atomic<uint64_t> m_pool_deregister_count = 0;
1061
1062 /**
1063 * Analogous to #m_arena_first_register_count but counts individual in-arena SHM-pools.
1064 *
1065 * From the global or per-arena point-of-view (not per-session): this counts actual open+map ops.
1066 */
1067 std::atomic<uint64_t> m_pool_open_count = 0;
1068
1069 /**
1070 * Analogous to #m_arena_last_deregister_count but counts individual in-arena SHM-pools.
1071 *
1072 * From the global or per-arena point-of-view (not per-session): this counts actual unmap+close ops.
1073 */
1074 std::atomic<uint64_t> m_pool_close_count = 0;
1075
1076 /// (Gauge) Analogous to #m_n_borrowed_arenas but counts individual in-arena SHM-pools.
1077 std::atomic<uint64_t> m_n_open_pools = 0;
1078
1079 /// Max of #m_n_open_pools observed so far.
1080 std::atomic<uint64_t> m_n_open_pools_hi_wmark = 0;
1081
1082 /**
1083 * (Gauge) The combined vaddr-space, in bytes, across the SHM-pools counted in #m_n_open_pools (modulo
1084 * concurrency artifacts). Reminders:
1085 * - These are in SHM; mapping this in borrower-process does not use any more RAM than is actually used by
1086 * the pool owner-side.
1087 * - A pool's (mapped) size does not necessarily (or even typically) equal how much RAM is taken away
1088 * from general OS use; mapped space is only so-taken (a/k/a resident) if pages have been
1089 * read/written. (Furthermore, by the memory manager's prerogative, previously resident pages can be
1090 * made un-resident when not in use. How aggressively this is done is up to the memory manager; e.g.
1091 * jemalloc. All of this occurs within the owner/arena process, not borrower-side.)
1092 */
1093 std::atomic<uint64_t> m_mapped_sz = 0;
1094
1095 /// Max of #m_mapped_sz observed so far.
1096 std::atomic<uint64_t> m_mapped_sz_hi_wmark = 0;
1097}; // struct Borrower_pool_stats
1098
1099/// Analogous subject matter to Owner_pool_lookup_global_stats but borrower-side.
1101{
1102 // Data.
1103
1104 /**
1105 * (Gauge) Number of thread-local vaddr-to-SHM-pool caches currently in existence; equals the number of threads
1106 * that have ever needed to perform at least 1 such lookup, minus the number of those threads that have
1107 * exited.
1108 *
1109 * ### Context/background ###
1110 * Identical to Owner_pool_lookup_global_stats, conceptually, but applied to interpreting vaddrs belonging to borrowed
1111 * data structures.
1112 */
1113 std::atomic<unsigned int> m_pool_rev_lookup_tl_cache_count = 0;
1114
1115 /// Max of #m_pool_rev_lookup_tl_cache_count observed so far.
1116 std::atomic<unsigned int> m_pool_rev_lookup_tl_cache_count_hi_wmark = 0;
1117
1118 /**
1119 * (Gauge) Number of thread-local SHM-pool-to-vaddr caches currently in existence; equals the number of threads
1120 * that have ever needed to perform at least 1 such lookup, minus the number of those threads that have
1121 * exited.
1122 *
1123 * ### Context/background ###
1124 * While conceptually not-dissimilar to the reverse-lookup (#m_pool_rev_lookup_tl_cache_count), the
1125 * forward lookup is, in fact, significantly more frequent in practice. Consider a given in-SHM STL-compliant
1126 * container -- as usual, using our shm::stl::Stateless_allocator as the allocator tparam -- being borrowed
1127 * from a (typically) cross-process arena. Any dynamically-sized container (among the simplest being
1128 * `vector`) will store at least one pointer to a dynamically-sized buffer; but since said buffer is
1129 * in SHM, and so is the pointer, the pointer cannot be stored in raw vaddr from -- as the vaddr in owner
1130 * process A =/= vaddr in borrower process B =/= (ditto in other potential borrower process(es)). Therefore
1131 * a `Shm_pool_offset_ptr` is stored. In SHM-jemalloc et al this basically encodes a SHM-pool ID and offset,
1132 * within the same `sizeof(void*)` bytes required for a raw ptr. The fwd lookup tracked here comes down to,
1133 * most saliently, this:
1134 * - look in the global borrowed-pool repository (called *BR* in Borrwer_pool_stats doc header); it
1135 * stores a map from pool ID to the pool's base-vaddr (in this borrower process).
1136 *
1137 * Any dereferencing so as to read something in the STL-container will mean performing this fwd-lookup.
1138 *
1139 * For performance, as in the reverse-direction, we internally distribute the canonical map onto each
1140 * thread-local (TL) copy, as needed per-thread. When the canonical map changes (pool first-borrowed or
1141 * last-unborrowed), this is proactively distributed per-TL-copy. In this stat-member, similarly
1142 * to #m_pool_rev_lookup_tl_cache_count, we count how many of these copies (extant threads) there currently are.
1143 *
1144 * @note Why is there no equivalent stat-member in Owner_pool_lookup_global_stats -- why does that one feature only
1145 * the reverse-lookup-TL-copy tracking? Answer: never mind; it is too inside-baseball to explain here.
1146 * Bottom line is, subtle algorithmic differences between owner-side and borrower-side are such that
1147 * owner-side can use a pull-only TL-distribution scheme -- where no tracking of extant threads is
1148 * required -- and therefore, unless we add such tracking just for stats, one cannot count them.
1149 * We don't want to waste the cycles (just for stats): so we don't. Hence that info is not available.
1150 */
1151 std::atomic<unsigned int> m_pool_fwd_lookup_tl_cache_count = 0;
1152
1153 /// Max of #m_pool_fwd_lookup_tl_cache_count observed so far.
1154 std::atomic<unsigned int> m_pool_fwd_lookup_tl_cache_count_hi_wmark = 0;
1155}; // struct Borrower_pool_lookup_global_stats
1156
1157/**
1158 * This info-`struct` contains basic properties of a SHM-pool (in a SHM-arena).
1159 *
1160 * The info included here is not *really* stats, per se, informally speaking. Nevertheless we declare it
1161 * as a `Stat_set` if only to get `ostream << print(...)` "for free." It is not really meant for use with other
1162 * `flow::util::stat::stats_*()` utilities, so you are on your own, if you try that.
1163 *
1164 * Each property is therefore declared as a GAUGE; as GAUGEs are, all else being equal, formally capable
1165 * of representing arbitrary values (until, that is, one tries to `stat_aggregate()` them or something).
1166 */
1168{
1169 // Data.
1170
1171 /**
1172 * Pool ID, trivially cast to a large unsigned integer type; identifies the SHM-pool with *machine-wide uniqueness*
1173 * (between successive boots); lower ID means earlier pool creation.
1174 *
1175 * @internal
1176 * Not using `pool_id_t`; that type does not as of this writing strictly need to be exposed
1177 * publicly (non-`detail/`).
1178 */
1179 uint64_t m_id = 0;
1180
1181 /// Pool size in bytes.
1182 size_t m_sz = 0;
1183
1184 /// Unique (machine-wide, between boots) ID of containing SHM-arena.
1186
1187 /**
1188 * If applicable (otherwise left at default value: 1), the use-count or ref-count of the particular
1189 * underlying SHM pool (<=> name in file-system) in the containing context. That's vague, but it just
1190 * depends on the context. As of this writing there is one known place where this has meaning:
1191 * - When reporting the global (per SHM-provider; e.g. for SHM-jemalloc) set of known borrowed SHM-pools,
1192 * `m_use_count` indicates the number of `Shm_session`s that are currently each borrowing that particular
1193 * pool; there is only one pool-handle + memory-mapped area, but `m_use_count` sessions are able to
1194 * access data therein.
1195 */
1196 size_t m_use_count = 1;
1197}; // struct Shm_pool_info
1198
1199/**
1200 * The memory-manager-level statistics for a single native allocator-arena (for SHM-jemalloc: one jemalloc
1201 * arena) backing an arena-lend `Shm_arena` (e.g. jemalloc::Ipc_arena).
1202 *
1203 * @see Pool_stats: A peer grouping of per-arena stats; it must be separate due to not being TL-sharded or
1204 * coming from an external data source when internally collected; therefore the method of return is different
1205 * from a `*this`.
1206 * @see Sharded_stats: A peer grouping of per-arena stats; we are separate from it due to coming from an
1207 * external data source (memory manager: e.g., jemalloc) as opposed to from internal algorithms/data.
1208 * (They could still be bundled together. Nevertheless the subject matter/data source are, we feel,
1209 * so sharply different that Memory_manager_stats simply should remain separate from all others.)
1210 *
1211 * To summarize: An arena-lending SHM-provider (at least: SHM-jemalloc) manages SHM pools and sessions, as well as
1212 * cross-process garbage-collectable *first-class objects* (as `.construct<T>()`ed and `.borrow_object<T>()`ed);
1213 * but when it is required to actually allocate (or later deallocate) a buffer in SHM, the SHM-provider uses
1214 * a *memory manager* (for SHM-jemalloc, that's jemalloc). (In the middle of an allocation, sometimes
1215 * the memory manager needs a new vaddr area, which it requests of SHM-jemalloc via customization hook, so we
1216 * create a SHM-pool and... so it goes; the memory manager and the leveraging SHM-provider thus cooperate.)
1217 *
1218 * So, Memory_manager_stats tracks stats that happen to have these general properties:
1219 * - They are about the memory manager.
1220 * - They are usually collected by the memory manager itself; so Flow-IPC queries its stats subsystem
1221 * (e.g., jemalloc's robust stats/profiling module) and organizes it in useful shapes here. (All of the
1222 * information -- and surely more -- is available directly if desired. Use, e.g.,
1223 * jemalloc::Ipc_arena::get_jemalloc_arena_id() to grab the equivalent of #m_native_arena_id; then
1224 * query the memory manager itself. We aim to give the most relevant stuff here, though, for convenience.)
1225 * - They include (but aren't limited to) stats such that accurately accounting them means some kind of
1226 * tracking inside every `allocate()` and `deallocate()` ([de]allocate buffer) call. We stay away from
1227 * doing this in Flow-IPC proper, due to the performance risk at least, but memory managers know how to do
1228 * it well.
1229 * - `_hi_wmark` (e.g.: Vaddr::m_mapped_sz_hi_wmark) fields are not reported by memory manager directly (if they
1230 * were, we'd take them!), so their semantics have the same limitation as within TL-sharded `struct`s (e.g.: see
1231 * Sharded_stats doc header).
1232 *
1233 * This `struct` is SHM-provider-agnostic by design; a given SHM-provider impl fills what it can and leaves the
1234 * rest at default (usually zero).
1235 *
1236 * The first (as of this writing only) arena-lend SHM-provider is SHM-jemalloc, which fills these from jemalloc's
1237 * `mallctl()` statistics surface.
1238 *
1239 * ### How it is consumed ###
1240 * Unlike the continuously-sampled (and often TL-sharded) stat-sets, these are *raw readings* obtained from the
1241 * provider at stat-consumption time -- a single consistent snapshot per consume (for SHM-jemalloc: one
1242 * `mallctl("epoch")` refresh, then a batch of reads, repeated if someone else refreshed meanwhile). (Internally
1243 * this uses `flow::util::stat::stats_since_reset_state()`/`stats_mark_reset_state()`.)
1244 *
1245 * Per-arena context: a `Shm_arena` exposes one Memory_manager_stats per native arena it maintains, each
1246 * self-identified by #m_uniq_arena_id (<=> `Shm_arena`) and #m_native_arena_id (<=> native arena), both emitted
1247 * as GAUGEs.
1248 *
1249 * For SHM-jemalloc, as of this writing:
1250 * - The `Shm_arena` is in fact jemalloc::Ipc_arena.
1251 * - There is exactly one jemalloc (native) arena per `Ipc_arena`. (There are as of this writing plans to
1252 * potentially add more, perhaps for better multi-core behavior.)
1253 * - It is available by itself via jemalloc::Ipc_arena::get_jemalloc_arena_id().
1254 */
1256{
1257 // Types.
1258
1259 /**
1260 * Sub-group: the arena's virtual-address-space / page-state footprint -- how much memory it currently holds
1261 * from the OS and in what state. All members are GAUGEs in bytes.
1262 *
1263 * Approximate relationships (sizes are post size-class quantization; see jemalloc docs for precise meanings):
1264 * - #m_mapped_sz ~= #m_active_sz + #m_inactive_resident_sz + #m_inactive_muzzy_sz + allocator-metadata.
1265 * - #m_resident_sz ~= #m_active_sz + #m_inactive_resident_sz + allocator-metadata (excludes muzzy + retained).
1266 * - #m_retained_sz is separate: space excluded from #m_mapped_sz book-keeping yet deliberately kept
1267 * mapped, saved for cheap reuse (see its doc header).
1268 */
1269 struct Vaddr
1270 {
1271 // Data.
1272
1273 /**
1274 * (Gauge) Total bytes currently mapped from the OS into the arena's address space. See sub-group doc header.
1275 *
1276 * Reminder: This is not RAM in use (taken from general OS use); a page becomes resident only once accessed.
1277 * E.g.: it is entirely possible and not even unusual for a particular vaddr area (in our case backed by a SHM
1278 * pool) to be, say, mapped for 100 megabytes -- but only 500 kilobytes of it correspond to actual RAM use (at
1279 * most #m_resident_sz).
1280 */
1281 size_t m_mapped_sz = 0;
1282
1283 /**
1284 * Max of #m_mapped_sz observed so far.
1285 *
1286 * @note For this and nearby `_hi_wmark`s: Due to the way these data are acquired from jemalloc-stats (or
1287 * equivalent), the high-water-mark values here cannot be true max-values since the last reset but merely
1288 * from among the times `*this` is sampled via stat-consumption, plus the last reset.
1289 */
1291
1292 /**
1293 * (Gauge) Roughly: the peak, since arena creation, of concurrently-allocated bytes (rounded up to jemalloc's
1294 * internal units), plus allocator metadata, minus whatever has since been purged (returned to the OS).
1295 * This approximates physically-resident bytes, as an upper bound: allocated bytes never actually touched count.
1296 * See sub-group doc header.
1297 */
1298 size_t m_resident_sz = 0;
1299
1300 /// Max of #m_resident_sz observed so far.
1302
1303 /**
1304 * (Gauge) Bytes in pages backing live (active) allocations: ~= live allocated bytes + unused space on those same
1305 * pages, set aside for future allocations of the same size. (jemalloc packs same-size small allocations together
1306 * into pages; such a page counts in full while at least one of them is live.) Excludes allocator metadata.
1307 *
1308 * @see Memory_manager_stats::Alloc subgroup doc header discussion may be relevant.
1309 */
1310 size_t m_active_sz = 0;
1311
1312 /// Max of #m_active_sz observed so far.
1314
1315 /**
1316 * (Gauge) Not-active (so freed from/not yet used for allocated user-buffers; contrast with #m_active_sz) bytes
1317 * not yet returned to the OS; hence physically resident (not available for general use by OS) -- to the extent
1318 * they were ever touched. This is cheaply reusable in-place (no syscall required; just read/write at vaddr).
1319 *
1320 * For SHM-jemalloc: jemalloc's *dirty* pages (`pdirty` x page-size). (This "dirty" is jemalloc's term: unused
1321 * but not yet purged. It is unrelated to the OS-kernel sense of "dirty page.")
1322 */
1324
1325 /// Max of #m_inactive_resident_sz observed so far.
1327
1328 /**
1329 * (Gauge) Not-active (see also #m_inactive_resident_sz) bytes that the memory manager has returned to the OS
1330 * *lazily*: the OS may reclaim them at its leisure, and until then they may remain resident.
1331 *
1332 * For SHM-jemalloc: jemalloc's *muzzy* pages (`pmuzzy` x page-size). SHM-jemalloc has no lazy-return ability
1333 * (it returns pages via SHM-object hole-punching; see #m_retained_sz), so this is always zero.
1334 */
1336
1337 /// Max of #m_inactive_muzzy_sz observed so far.
1339
1340 /**
1341 * (Gauge) Bytes of once-used vaddr space the memory-manager has excluded from #m_mapped_sz book-keeping
1342 * yet deliberately keeps mapped -- *not* returned to the OS -- saved for cheap reuse (no memory-map op
1343 * needed; the range is simply handed back out on a suitable future allocation). For SHM-jemalloc this
1344 * state is entered when jemalloc asks to remove unused extent (SHM-pool) space, and policy declines
1345 * (as of this writing in our impl we indeed always decline): the backing physical pages are then released
1346 * via SHM-object hole-punching -- `fallocate(FALLOC_FL_PUNCH_HOLE)` et al in Linux -- while the vaddr range
1347 * and its SHM-pool remain intact.
1348 *
1349 * @internal
1350 * SHM-jemalloc: See jemalloc::Ipc_arena::optional_remove_shm_pool().
1351 */
1352 size_t m_retained_sz = 0;
1353
1354 /// Max of #m_retained_sz observed so far.
1356 }; // struct Vaddr
1357
1358 /**
1359 * Sub-group: raw allocation activity -- cumulative counts of memory-manager allocate/deallocate operations
1360 * (e.g. each `Arena::construct<T>()` plus each nested container allocation), and the currently-live count/bytes
1361 * derived from them.
1362 *
1363 * These count *raw* (low-level) allocations, *not* first-class `construct()`ed objects (those are tracked
1364 * elsewhere, e.g. shm::stat::Owner_obj_stats): one `construct<T>()` of a container-bearing `T` may trigger many raw
1365 * allocations.
1366 *
1367 * The cumulative counts are ACCUMULATORs (reported relative to the last reset; see Memory_manager_stats "How it
1368 * is consumed"); the live count/bytes are GAUGEs (absolute), each with a high-water mark.
1369 *
1370 * Regarding HI_WMARKs: See note for Vaddr::m_mapped_sz_hi_wmark.
1371 *
1372 * SHM-jemalloc: How these stats are accounted
1373 * -------------------------------------------
1374 * ### Why discuss SHM-jemalloc specifically, here? ###
1375 * Formally speaking SHM-jemalloc is "merely" one SHM-provider of the arena-lend type. It is however the first
1376 * such SHM-provider; and as of this writing it is the only one. Therefore, out of pragmatism, we include
1377 * these notes here, even though this is the shm::arena_lend namespace as opposed to shm::arena_lend::jemalloc.
1378 * If or when more arena-lend SHM-providers are added, we may choose to reorganize this documentation; but
1379 * until then this discussion should be here for posterity.
1380 *
1381 * Now to the actual topic at hand: *How does (SHM-)jemalloc (de)allocation work + how is it accounted in `*this`*?
1382 *
1383 * ### Significant mechanism: jemalloc tcache ###
1384 * jemalloc -- and plausibly future arena-lend providers -- adds a *thread cache* (*tcache*) between the
1385 * "user" and the arena proper: a store of allocations kept on hand for quick reuse without arena-level locking.
1386 * SHM-jemalloc passes a tcache (one per thread, per-jemalloc-arena #m_native_arena_id) to every allocation;
1387 * jemalloc uses it for sizes up to its tcache maximum (32Ki bytes by default), while larger allocations bypass
1388 * it. (Build flag `IPC_SHM_ARENA_LEND_JEMALLOC_NO_TCACHE` skips tcache use entirely. Let us assume it is not
1389 * skipped, as tcache is a valuable perf mechanism.)
1390 *
1391 * Crucially, an allocation sitting in a tcache counts, from the arena's accounting standpoint, as *allocated* --
1392 * though no user holds it.
1393 *
1394 * @see Thread_cache doc header for a deeper look at how jemalloc tcache works.
1395 *
1396 * The upshot for the stats here is that jemalloc's counters (whether the `_sz` byte-counts or the `_count`s)
1397 * count allocations moving between the arena proper and the caches (or the user, when a cache is bypassed), not
1398 * individual user calls. A given `allocate()` or `deallocate()` serviced by the tcache does not update them.
1399 * Over time, though, the counters will be updated -- but in a lumpier/batchier way.
1400 *
1401 * Thus with the tcache in play:
1402 * - Cumulative #m_alloc_count / #m_alloc_sz (and the dealloc counterparts) measure allocations moving between
1403 * the arena and caches, not the count/bytes of user calls. They differ from the user-call counts in *either*
1404 * direction: a low-traffic size shows more than the calls (a cache refill fetches a batch, caching the
1405 * surplus), while a hot, recycled size can show fewer (allocations ping-pong between user and cache without
1406 * touching the arena). The size of the gap depends on the tcache churn pattern.
1407 * - The live #m_live_count / #m_live_sz similarly reflect what is currently taken from the arena, which
1408 * includes allocations in tcaches (therefore *not* used by actual user allocated-buffers). So `m_live_*`
1409 * members overstate what the user actually holds, by the current cache contents.
1410 * - Cf. Vaddr::m_active_sz: pages holding cached allocations stay "active" -- same effect, same cause.
1411 *
1412 * If the tcache use (by SHM-jemalloc) is compiled-out (see above), then all of this collapses: each counter
1413 * tracks user `allocate()`/`deallocate()` directly, and the live values are what the user holds. That's nice
1414 * for understanding the stats in real-time fashion, but in terms of perf it means more mutex-locking inside
1415 * jemalloc.
1416 *
1417 * @note jemalloc also counts per-call allocation requests (`nrequests`), but with no deallocation counterpart, so
1418 * it cannot feed the live values; hence all members here (including #m_histo_alloc_count_by_sz) use the
1419 * `nmalloc`/`ndalloc` view, with the above caveats. Nor do we offer an `nrequests`-based stat: with
1420 * explicit tcaches, jemalloc files part of those tallies under a different (automatic) arena, so
1421 * per-*our*-arena request counts would be an undercount of unknowable size.
1422 *
1423 * @internal
1424 * On `nrequests` (verified against jemalloc 5.3.0 and 5.4.0 source): tallies recorded at a tcache refill go to
1425 * the filling arena (ours); those recorded at a tcache flush or destruction go to the tcache's *associated* arena,
1426 * chosen internally at tcache creation; no public API can point it at our arena.
1427 */
1428 struct Alloc
1429 {
1430 // Data.
1431
1432 /// Cumulative count of allocate ops (for SHM-jemalloc: jemalloc small + large `nmalloc`).
1433 uint64_t m_alloc_count = 0;
1434
1435 /// Cumulative count of deallocate ops (for SHM-jemalloc: jemalloc small + large `ndalloc`).
1436 uint64_t m_dealloc_count = 0;
1437
1438 /**
1439 * Cumulative bytes allocated (for SHM-jemalloc: sum over size-classes of `nmalloc` x size-class-byte-size).
1440 *
1441 * @note Post size-class quantization (see #m_live_sz): bytes the heap handed out, not user-requested sizes.
1442 */
1443 uint64_t m_alloc_sz = 0;
1444
1445 /// Cumulative bytes deallocated (for SHM-jemalloc: sum over size-classes of `ndalloc` x size-class-byte-size).
1446 uint64_t m_dealloc_sz = 0;
1447
1448 /// (Gauge) Currently-live allocation count (= #m_alloc_count minus #m_dealloc_count).
1449 uint64_t m_live_count = 0;
1450
1451 /// Max of #m_live_count observed so far.
1453
1454 /**
1455 * (Gauge) Currently-live allocated bytes (for SHM-jemalloc: jemalloc small + large `allocated`).
1456 *
1457 * @note This is post size-class quantization -- i.e. "what the heap holds," not the sum of user-requested sizes.
1458 */
1459 size_t m_live_sz = 0;
1460
1461 /// Max of #m_live_sz observed so far.
1463
1464 /**
1465 * Cumulative allocation count broken down by allocation size-class, as a histogram: each bucket counts the
1466 * cumulative allocations of a certain size-class; so a user-request for allocation of N bytes results in
1467 * a `++` for the bucket whose `Histogram_counter` bounds are [N or lower, N or higher].
1468 *
1469 * A caveat here:
1470 *
1471 * @note The previous description ignores the tcache mechanism. See Alloc doc header which explains
1472 * how tcache can affect these semantics.
1473 *
1474 * For SHM-jemalloc this is jemalloc's small `bins` followed by its large `lextents`, concatenated into one
1475 * ascending sequence: the small/large split is a jemalloc implementation detail. We combine them into
1476 * this one histogram.
1477 *
1478 * @note The bucket structure of the histogram is determined by the memory manager's size classes which (1) are
1479 * process-constant but (2) reported at run-time by the memory manager (e.g.: jemalloc). This is
1480 * unknown at compile-time, so we use some placeholder values (`{2, 1}`) in the default-init near this
1481 * doc-comment; but this structure is never used. The placeholder bounds in the default-initializer
1482 * are overwritten when actually emitting stats into a `*this`, most notably at stat-consume (via public
1483 * `Shm_arena` API invoked by the user) time.
1484 */
1485 flow::util::stat::Histogram_counter m_histo_alloc_count_by_sz{2, 1};
1486 }; // struct Alloc
1487
1488 // Data.
1489
1490 /// Unique (machine-wide, between boots) ID of managing Flow-IPC SHM-arena. See also Shm_pool_info::m_uniq_arena_id.
1492
1493 /**
1494 * The memory-manager's native arena ID of arena described in `*this`. For SHM-jemalloc this is the
1495 * jemalloc arena ID.
1496 *
1497 * Stored as `uint64_t` rather than a provider-specific ID type which need not be exposed publicly here.
1498 */
1499 uint64_t m_native_arena_id = 0;
1500
1501 /// See Vaddr doc header.
1503
1504 /// See Alloc doc header.
1506}; // struct Memory_manager_stats
1507
1508// Template implementations.
1509
1510template<typename Visitor>
1511void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1512 const Owner_obj_stats* src_stats, Owner_obj_stats* target_stats,
1513 Visitor&& visitor)
1514{
1515 FLOW_UTIL_STAT_DECLARE(m_sync_destroy_count, ACCUMULATOR);
1516 FLOW_UTIL_STAT_DECLARE(m_drain_destroy_count, ACCUMULATOR);
1517}
1518
1519template<typename Visitor>
1520void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1521 const Zombie_obj_reaper_stats* src_stats, Zombie_obj_reaper_stats* target_stats, Visitor&& visitor)
1522{
1523 FLOW_UTIL_STAT_DECLARE(m_scans, ACCUMULATOR);
1524 FLOW_UTIL_STAT_DECLARE(m_exhaustive_scans, ACCUMULATOR);
1525 FLOW_UTIL_STAT_DECLARE(m_hints_sufficient_scans, ACCUMULATOR);
1526 FLOW_UTIL_STAT_DECLARE(m_fifo_scans, ACCUMULATOR);
1527 FLOW_UTIL_STAT_DECLARE(m_fifo_exhausted_scans, ACCUMULATOR);
1528 FLOW_UTIL_STAT_DECLARE(m_histo_zombie_ct_estimate, ACCUMULATOR);
1529 FLOW_UTIL_STAT_DECLARE(m_events.m_hints_checked, ACCUMULATOR);
1530 FLOW_UTIL_STAT_DECLARE(m_events.m_hints_stale, ACCUMULATOR);
1531 FLOW_UTIL_STAT_DECLARE(m_events.m_hints_stale_and_slots_reused, ACCUMULATOR);
1532 FLOW_UTIL_STAT_DECLARE(m_events.m_fifo_objs_checked, ACCUMULATOR);
1533 FLOW_UTIL_STAT_DECLARE(m_events.m_fifo_objs_live, ACCUMULATOR);
1534}
1535
1536template<typename Visitor>
1537void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1538 const Obj_db_aux_pool_stats* src_stats, Obj_db_aux_pool_stats* target_stats, Visitor&& visitor)
1539{
1540 FLOW_UTIL_STAT_DECLARE(m_aux_pool_count, GAUGE);
1541 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_aux_pool_count_hi_wmark, m_aux_pool_count);
1542 FLOW_UTIL_STAT_DECLARE(m_use_ct_active_quanta, GAUGE);
1543 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_use_ct_active_quanta_hi_wmark, m_use_ct_active_quanta);
1544 FLOW_UTIL_STAT_DECLARE(m_use_ct_quanta, GAUGE);
1545 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_use_ct_quanta_hi_wmark, m_use_ct_quanta);
1546 FLOW_UTIL_STAT_DECLARE(m_resident_sz, GAUGE);
1547 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_resident_sz_hi_wmark, m_resident_sz);
1548 FLOW_UTIL_STAT_DECLARE(m_mapped_sz, GAUGE);
1549 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_mapped_sz_hi_wmark, m_mapped_sz);
1550}
1551
1552template<typename Visitor>
1553void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1554 const Obj_db_aux_pool_global_stats* src_stats, Obj_db_aux_pool_global_stats* target_stats,
1555 Visitor&& visitor)
1556{
1557 FLOW_UTIL_STAT_DECLARE(m_client_tl_aux_pool_live_hndls, GAUGE);
1558 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_client_tl_aux_pool_live_hndls_hi_wmark, m_client_tl_aux_pool_live_hndls);
1559 FLOW_UTIL_STAT_DECLARE(m_client_tl_aux_pool_hndl_open_count, ACCUMULATOR);
1560 FLOW_UTIL_STAT_DECLARE(m_client_tl_aux_pool_hndl_open_fail_count, ACCUMULATOR);
1561 FLOW_UTIL_STAT_DECLARE(m_client_tl_aux_pool_hndl_reap_count, ACCUMULATOR);
1562 FLOW_UTIL_STAT_DECLARE(m_client_tl_aux_pool_hndl_reap_check_count, ACCUMULATOR);
1563}
1564
1565template<typename Visitor>
1566void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1567 const Owner_pool_stats* src_stats, Owner_pool_stats* target_stats, Visitor&& visitor)
1568{
1569 FLOW_UTIL_STAT_DECLARE(m_pool_create_count, ACCUMULATOR);
1570 FLOW_UTIL_STAT_DECLARE(m_pool_create_sz, ACCUMULATOR);
1571 FLOW_UTIL_STAT_DECLARE(m_pool_destroy_count, ACCUMULATOR);
1572 FLOW_UTIL_STAT_DECLARE(m_pool_destroy_sz, ACCUMULATOR);
1573 FLOW_UTIL_STAT_DECLARE(m_pool_optional_destroy_request_count, ACCUMULATOR);
1574}
1575
1576template<typename Visitor>
1577void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1578 const Pool_stats* src_stats, Pool_stats* target_stats, Visitor&& visitor)
1579{
1580 using flow::util::stat::Stat_name;
1581
1582 declare_stats(Stat_name{name_prefix, "owner."},
1583 src_stats ? &src_stats->m_owner_pool : nullptr,
1584 target_stats ? &target_stats->m_owner_pool : nullptr,
1585 visitor);
1586 declare_stats(Stat_name{name_prefix, "dbaux."},
1587 src_stats ? &src_stats->m_obj_db_aux_pool : nullptr,
1588 target_stats ? &target_stats->m_obj_db_aux_pool : nullptr,
1589 visitor);
1590}
1591
1592template<typename Visitor>
1593void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1594 const Borrower_pool_stats* src_stats, Borrower_pool_stats* target_stats, Visitor&& visitor)
1595{
1596 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id1, GAUGE);
1597 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id2, GAUGE);
1598 FLOW_UTIL_STAT_DECLARE(m_arena_register_count, ACCUMULATOR);
1599 FLOW_UTIL_STAT_DECLARE(m_arena_deregister_count, ACCUMULATOR);
1600 FLOW_UTIL_STAT_DECLARE(m_arena_first_register_count, ACCUMULATOR);
1601 FLOW_UTIL_STAT_DECLARE(m_arena_last_deregister_count, ACCUMULATOR);
1602 FLOW_UTIL_STAT_DECLARE(m_n_borrowed_arenas, GAUGE);
1603 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_n_borrowed_arenas_hi_wmark, m_n_borrowed_arenas);
1604 FLOW_UTIL_STAT_DECLARE(m_pool_register_count, ACCUMULATOR);
1605 FLOW_UTIL_STAT_DECLARE(m_pool_deregister_count, ACCUMULATOR);
1606 FLOW_UTIL_STAT_DECLARE(m_pool_open_count, ACCUMULATOR);
1607 FLOW_UTIL_STAT_DECLARE(m_pool_close_count, ACCUMULATOR);
1608 FLOW_UTIL_STAT_DECLARE(m_n_open_pools, GAUGE);
1609 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_n_open_pools_hi_wmark, m_n_open_pools);
1610 FLOW_UTIL_STAT_DECLARE(m_mapped_sz, GAUGE);
1611 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_mapped_sz_hi_wmark, m_mapped_sz);
1612}
1613
1614template<typename Visitor>
1615void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1616 const Owner_pool_lookup_global_stats* src_stats, Owner_pool_lookup_global_stats* target_stats,
1617 Visitor&& visitor)
1618{
1619 FLOW_UTIL_STAT_DECLARE(m_pool_rev_lookup_tl_cache_count, GAUGE);
1620 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_pool_rev_lookup_tl_cache_count_hi_wmark, m_pool_rev_lookup_tl_cache_count);
1621}
1622
1623template<typename Visitor>
1624void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1626 Visitor&& visitor)
1627{
1628 FLOW_UTIL_STAT_DECLARE(m_pool_rev_lookup_tl_cache_count, GAUGE);
1629 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_pool_rev_lookup_tl_cache_count_hi_wmark, m_pool_rev_lookup_tl_cache_count);
1630 FLOW_UTIL_STAT_DECLARE(m_pool_fwd_lookup_tl_cache_count, GAUGE);
1631 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_pool_fwd_lookup_tl_cache_count_hi_wmark, m_pool_fwd_lookup_tl_cache_count);
1632}
1633
1634template<typename Visitor>
1635void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1636 const Shm_pool_info* src_stats, Shm_pool_info* target_stats,
1637 Visitor&& visitor)
1638{
1639 FLOW_UTIL_STAT_DECLARE(m_id, GAUGE);
1640 FLOW_UTIL_STAT_DECLARE(m_sz, GAUGE);
1641 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id1, GAUGE);
1642 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id2, GAUGE);
1643 FLOW_UTIL_STAT_DECLARE(m_use_count, GAUGE);
1644}
1645
1646template<typename Visitor>
1647void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1648 const Memory_manager_stats* src_stats, Memory_manager_stats* target_stats, Visitor&& visitor)
1649{
1650 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id1, GAUGE);
1651 FLOW_UTIL_STAT_DECLARE(m_uniq_arena_id.m_id2, GAUGE);
1652 FLOW_UTIL_STAT_DECLARE(m_native_arena_id, GAUGE);
1653 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_mapped_sz, GAUGE);
1654 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_mapped_sz_hi_wmark, m_vaddr.m_mapped_sz);
1655 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_resident_sz, GAUGE);
1656 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_resident_sz_hi_wmark, m_vaddr.m_resident_sz);
1657 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_active_sz, GAUGE);
1658 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_active_sz_hi_wmark, m_vaddr.m_active_sz);
1659 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_inactive_resident_sz, GAUGE);
1660 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_inactive_resident_sz_hi_wmark, m_vaddr.m_inactive_resident_sz);
1661 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_inactive_muzzy_sz, GAUGE);
1662 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_inactive_muzzy_sz_hi_wmark, m_vaddr.m_inactive_muzzy_sz);
1663 FLOW_UTIL_STAT_DECLARE(m_vaddr.m_retained_sz, GAUGE);
1664 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_vaddr.m_retained_sz_hi_wmark, m_vaddr.m_retained_sz);
1665 FLOW_UTIL_STAT_DECLARE(m_alloc.m_alloc_count, ACCUMULATOR);
1666 FLOW_UTIL_STAT_DECLARE(m_alloc.m_dealloc_count, ACCUMULATOR);
1667 FLOW_UTIL_STAT_DECLARE(m_alloc.m_alloc_sz, ACCUMULATOR);
1668 FLOW_UTIL_STAT_DECLARE(m_alloc.m_dealloc_sz, ACCUMULATOR);
1669 FLOW_UTIL_STAT_DECLARE(m_alloc.m_live_count, GAUGE);
1670 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_alloc.m_live_count_hi_wmark, m_alloc.m_live_count);
1671 FLOW_UTIL_STAT_DECLARE(m_alloc.m_live_sz, GAUGE);
1672 FLOW_UTIL_STAT_DECLARE_HI_WMARK(m_alloc.m_live_sz_hi_wmark, m_alloc.m_live_sz);
1673 FLOW_UTIL_STAT_DECLARE(m_alloc.m_histo_alloc_count_by_sz, ACCUMULATOR);
1674}
1675
1676template<typename Visitor>
1677void declare_stats(const flow::util::stat::Stat_name& name_prefix,
1678 const Sharded_stats* src_stats, Sharded_stats* target_stats,
1679 Visitor&& visitor)
1680{
1681 using flow::util::stat::Stat_name;
1682
1683 // Similar rationale as in `declare_stats(shm::classic::stat::Arena_stats)`.
1684 const Stat_name obj_prefix{name_prefix, "obj."};
1685 declare_stats(Stat_name{obj_prefix, "own."},
1686 src_stats ? &src_stats->m_owner_obj : nullptr,
1687 target_stats ? &target_stats->m_owner_obj : nullptr,
1688 visitor);
1689 // Same prefix; the names in m_owner[_arena_lend] are chosen synergistically.
1690 declare_stats(Stat_name{obj_prefix, "own."},
1691 src_stats ? &src_stats->m_owner_obj_arena_lend : nullptr,
1692 target_stats ? &target_stats->m_owner_obj_arena_lend : nullptr,
1693 visitor);
1694 declare_stats(Stat_name{obj_prefix, "lnd."},
1695 src_stats ? &src_stats->m_lend_obj : nullptr,
1696 target_stats ? &target_stats->m_lend_obj : nullptr,
1697 visitor);
1698 declare_stats(obj_prefix, // Individual names are chosen so that "live." (or whatever) would be unnecessary.
1699 src_stats ? &src_stats->m_live_obj : nullptr,
1700 target_stats ? &target_stats->m_live_obj : nullptr,
1701 visitor);
1702 declare_stats(Stat_name{obj_prefix, "zombie."},
1703 src_stats ? &src_stats->m_zombie_obj_reaper_main : nullptr,
1704 target_stats ? &target_stats->m_zombie_obj_reaper_main : nullptr,
1705 visitor);
1706 declare_stats(Stat_name{obj_prefix, "zb.drn."},
1707 src_stats ? &src_stats->m_zombie_obj_reaper_drain : nullptr,
1708 target_stats ? &target_stats->m_zombie_obj_reaper_drain : nullptr,
1709 visitor);
1710}
1711
1712} // namespace ipc::shm::arena_lend::stat
static constexpr size_t S_N_UNUSED_IDX_HINTS
The max number of unused-slot hints emit_unused_ct_idx_hints() can emit.
Stats-related sub-namespace, for ADL segregation and general organization.
void declare_stats(const flow::util::stat::Stat_name &name_prefix, const Owner_obj_stats *src_stats, Owner_obj_stats *target_stats, Visitor &&visitor)
Declares the stats for Owner_obj_stats.
Analogous subject matter to Owner_pool_lookup_global_stats but borrower-side.
std::atomic< unsigned int > m_pool_fwd_lookup_tl_cache_count_hi_wmark
Max of m_pool_fwd_lookup_tl_cache_count observed so far.
std::atomic< unsigned int > m_pool_rev_lookup_tl_cache_count_hi_wmark
Max of m_pool_rev_lookup_tl_cache_count observed so far.
std::atomic< unsigned int > m_pool_rev_lookup_tl_cache_count
(Gauge) Number of thread-local vaddr-to-SHM-pool caches currently in existence; equals the number of ...
std::atomic< unsigned int > m_pool_fwd_lookup_tl_cache_count
(Gauge) Number of thread-local SHM-pool-to-vaddr caches currently in existence; equals the number of ...
Loosely similar subject matter to Owner_pool_stats but regarding the borrower-side view of SHM-pools,...
std::atomic< uint64_t > m_n_open_pools
(Gauge) Analogous to m_n_borrowed_arenas but counts individual in-arena SHM-pools.
std::atomic< uint64_t > m_arena_last_deregister_count
Of the unborrowings counted by m_arena_deregister_count, exclude those wherein arena was unborrowed w...
std::atomic< uint64_t > m_n_borrowed_arenas
(Gauge) Effectively m_arena_first_register_count minus m_arena_last_deregister_count.
std::atomic< uint64_t > m_pool_deregister_count
Analogous to m_arena_deregister_count but counts individual in-arena SHM-pools.
std::atomic< uint64_t > m_mapped_sz_hi_wmark
Max of m_mapped_sz observed so far.
Uniq_arena_id m_uniq_arena_id
Iff per-arena context (else zeroes): Unique (machine-wide, between boots) ID of containing SHM-arena.
std::atomic< uint64_t > m_n_borrowed_arenas_hi_wmark
Max of m_n_borrowed_arenas observed so far.
std::atomic< uint64_t > m_pool_register_count
Analogous to m_arena_register_count but counts individual in-arena SHM-pools.
std::atomic< uint64_t > m_mapped_sz
(Gauge) The combined vaddr-space, in bytes, across the SHM-pools counted in m_n_open_pools (modulo co...
std::atomic< uint64_t > m_arena_deregister_count
Of the borrowings counted by m_arena_register_count, how many have been undone (as of this writing: v...
std::atomic< uint64_t > m_n_open_pools_hi_wmark
Max of m_n_open_pools observed so far.
std::atomic< uint64_t > m_arena_register_count
How many times an arena was borrowed (opposing user did S.lend_arena(X)), including when already-bein...
std::atomic< uint64_t > m_pool_close_count
Analogous to m_arena_last_deregister_count but counts individual in-arena SHM-pools.
std::atomic< uint64_t > m_pool_open_count
Analogous to m_arena_first_register_count but counts individual in-arena SHM-pools.
std::atomic< uint64_t > m_arena_first_register_count
Of the borrowings counted by m_arena_register_count, exclude those wherein arena was borrowed while a...
Sub-group: raw allocation activity – cumulative counts of memory-manager allocate/deallocate operatio...
size_t m_live_sz_hi_wmark
Max of m_live_sz observed so far.
uint64_t m_live_count
(Gauge) Currently-live allocation count (= m_alloc_count minus m_dealloc_count).
flow::util::stat::Histogram_counter m_histo_alloc_count_by_sz
Cumulative allocation count broken down by allocation size-class, as a histogram: each bucket counts ...
uint64_t m_dealloc_count
Cumulative count of deallocate ops (for SHM-jemalloc: jemalloc small + large ndalloc).
uint64_t m_live_count_hi_wmark
Max of m_live_count observed so far.
size_t m_live_sz
(Gauge) Currently-live allocated bytes (for SHM-jemalloc: jemalloc small + large allocated).
uint64_t m_alloc_count
Cumulative count of allocate ops (for SHM-jemalloc: jemalloc small + large nmalloc).
uint64_t m_dealloc_sz
Cumulative bytes deallocated (for SHM-jemalloc: sum over size-classes of ndalloc x size-class-byte-si...
uint64_t m_alloc_sz
Cumulative bytes allocated (for SHM-jemalloc: sum over size-classes of nmalloc x size-class-byte-size...
Sub-group: the arena's virtual-address-space / page-state footprint – how much memory it currently ho...
size_t m_active_sz
(Gauge) Bytes in pages backing live (active) allocations: ~= live allocated bytes + unused space on t...
size_t m_retained_sz_hi_wmark
Max of m_retained_sz observed so far.
size_t m_inactive_resident_sz_hi_wmark
Max of m_inactive_resident_sz observed so far.
size_t m_inactive_muzzy_sz_hi_wmark
Max of m_inactive_muzzy_sz observed so far.
size_t m_retained_sz
(Gauge) Bytes of once-used vaddr space the memory-manager has excluded from m_mapped_sz book-keeping ...
size_t m_resident_sz_hi_wmark
Max of m_resident_sz observed so far.
size_t m_mapped_sz_hi_wmark
Max of m_mapped_sz observed so far.
size_t m_inactive_resident_sz
(Gauge) Not-active (so freed from/not yet used for allocated user-buffers; contrast with m_active_sz)...
size_t m_resident_sz
(Gauge) Roughly: the peak, since arena creation, of concurrently-allocated bytes (rounded up to jemal...
size_t m_mapped_sz
(Gauge) Total bytes currently mapped from the OS into the arena's address space.
size_t m_inactive_muzzy_sz
(Gauge) Not-active (see also m_inactive_resident_sz) bytes that the memory manager has returned to th...
size_t m_active_sz_hi_wmark
Max of m_active_sz observed so far.
The memory-manager-level statistics for a single native allocator-arena (for SHM-jemalloc: one jemall...
uint64_t m_native_arena_id
The memory-manager's native arena ID of arena described in *this.
Uniq_arena_id m_uniq_arena_id
Unique (machine-wide, between boots) ID of managing Flow-IPC SHM-arena. See also Shm_pool_info::m_uni...
Similar mission to Obj_db_aux_pool_stats but covers information that, by nature, is not per-arena but...
std::atomic< uint64_t > m_client_tl_aux_pool_hndl_open_count
In this process: number of times a client aux-SHM-pool handle (as described for m_client_tl_aux_pool_...
std::atomic< uint64_t > m_client_tl_aux_pool_live_hndls
(Gauge) In this process, the number of all per-owner-thread, per-arena thread-local aux SHM-pool hand...
std::atomic< uint64_t > m_client_tl_aux_pool_hndl_open_fail_count
In this process: number of times an attempt to open a client aux-SHM-pool handle (as described for m_...
std::atomic< uint64_t > m_client_tl_aux_pool_hndl_reap_count
In this process: number of times a client aux-SHM-pool handle (as described for m_client_tl_aux_pool_...
std::atomic< uint64_t > m_client_tl_aux_pool_live_hndls_hi_wmark
Max of m_client_tl_aux_pool_live_hndls observed so far.
std::atomic< uint64_t > m_client_tl_aux_pool_hndl_reap_check_count
In this process: number of times a client aux-SHM-pool handle (as described for m_client_tl_aux_pool_...
Stats about the RAM use of the arena-lending SHM-provider's auxiliary (non-user-data) SHM-pools which...
std::atomic< uint64_t > m_aux_pool_count
Number of aux SHM-pools currently in existence; incremented when a given owner-process thread first e...
std::atomic< uint64_t > m_aux_pool_count_hi_wmark
Max of m_aux_pool_count observed so far.
std::atomic< uint64_t > m_use_ct_quanta_hi_wmark
Max of m_use_ct_quanta observed so far.
std::atomic< uint64_t > m_use_ct_active_quanta_hi_wmark
Max of m_use_ct_active_quanta observed so far.
std::atomic< size_t > m_resident_sz
Of the mapped space tracked (in bytes) in m_mapped_sz, the approximate/ceiling size of resident RAM (...
std::atomic< size_t > m_mapped_sz
Total vaddr space, in bytes, reserved for SHM aux-pools.
std::atomic< uint64_t > m_use_ct_active_quanta
Of the quanta counted in m_use_ct_quanta, count of the ones in active use.
std::atomic< size_t > m_resident_sz_hi_wmark
Max of m_resident_sz observed so far.
std::atomic< size_t > m_mapped_sz_hi_wmark
Max of m_mapped_sz observed so far.
std::atomic< uint64_t > m_use_ct_quanta
Count of quanta available – not necessarily (or typically) all active – across the currently live aux...
Similar mission to shm::stat::Owner_obj_stats (which is relevant regardless of SHM-provider type); ad...
std::atomic< uint64_t > m_drain_destroy_count
Of the first-class object deletions counted in shm::stat::Owner_obj_stats::m_destroy_count,...
std::atomic< uint64_t > m_sync_destroy_count
Of the first-class object deletions counted in shm::stat::Owner_obj_stats::m_destroy_count,...
Similar mission to Owner_pool_stats but covers information that, by nature, is not per-arena but rath...
std::atomic< unsigned int > m_pool_rev_lookup_tl_cache_count_hi_wmark
Max of m_pool_rev_lookup_tl_cache_count observed so far.
std::atomic< unsigned int > m_pool_rev_lookup_tl_cache_count
(Gauge) Number of thread-local vaddr-to-SHM-pool caches currently in existence; equals the number of ...
Similar mission to shm::stat::Owner_obj_stats (which is relevant regardless of SHM-provider type) and...
std::atomic< uint64_t > m_pool_destroy_sz
Across the pool-destructions counted in m_pool_destroy_count, the sum of the actual sizes of those po...
std::atomic< uint64_t > m_pool_destroy_count
Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested something,...
std::atomic< uint64_t > m_pool_optional_destroy_request_count
Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested the optional removal (...
std::atomic< uint64_t > m_pool_create_count
Number of times the memory manager (e.g., jemalloc for SHM-jemalloc) requested the creation of a new ...
std::atomic< uint64_t > m_pool_create_sz
Across the pool-creations counted in m_pool_create_count, the sum of the requested/actual sizes of th...
Bundles together per-arena Owner_pool_stats and Obj_db_aux_pool_stats, so they can be accessed via on...
Obj_db_aux_pool_stats m_obj_db_aux_pool
Sub-struct with individual stats; see type's doc header.
Owner_pool_stats m_owner_pool
Sub-struct with individual stats; see type's doc header.
A bundle of several individually-documented stat-sets – object construct/dispose/live and zombie-reap...
shm::stat::Lender_obj_stats m_lend_obj
Sub-struct with individual stats; see type's doc header.
arena_lend::stat::Zombie_obj_reaper_stats m_zombie_obj_reaper_drain
(During drain-phase thread, after user thread exited) Sub-struct with individual stats; see type's do...
arena_lend::stat::Zombie_obj_reaper_stats m_zombie_obj_reaper_main
(During mainstream piggy-backed zombie reaping) Sub-struct with individual stats; see type's doc head...
arena_lend::stat::Owner_obj_stats m_owner_obj_arena_lend
Sub-struct with individual stats; see type's doc header.
shm::stat::Live_obj_stats m_live_obj
Sub-struct with individual stats; see type's doc header.
shm::stat::Owner_obj_stats m_owner_obj
Sub-struct with individual stats; see type's doc header.
This info-struct contains basic properties of a SHM-pool (in a SHM-arena).
size_t m_use_count
If applicable (otherwise left at default value: 1), the use-count or ref-count of the particular unde...
Uniq_arena_id m_uniq_arena_id
Unique (machine-wide, between boots) ID of containing SHM-arena.
uint64_t m_id
Pool ID, trivially cast to a large unsigned integer type; identifies the SHM-pool with machine-wide u...
IDs an arena, as a GAUGE in contexts like Shm_pool_info and per-arena Borrower_pool_stats,...
uint64_t m_id1
Iff per-arena context (else zero): Unique (machine-wide, between boots) ID of containing SHM-arena: f...
uint64_t m_id2
Iff per-arena context (else zero): Unique (machine-wide, between boots) ID of containing SHM-arena: s...
Groups stats that count events any of which can occur 0+ – not a maximum of one time – per scan.
std::conditional_t< S_ATOMIC_OR_NOT, std::atomic< uint64_t >, uint64_t > Counter
Used for all ACCUMULATORs in a *this.
Counter m_fifo_objs_checked
In the (typically-unnecessary, we hope) FIFO-zombie-search phases of the zombie-reaping scans,...
Counter m_fifo_objs_live
Of the checked-objects counted in m_fifo_objs_checked, count of those that were live – not zombies – ...
Counter m_hints_checked
The number of single-object hints scanned (each of which may or may not have resulted in 1 zombie-obj...
Counter m_hints_stale_and_slots_reused
Of the single-object hints counted in m_hints_checked, count of how many resulted in the finding that...
Counter m_hints_stale
Of the single-object hints counted in m_hints_checked, count of how many resulted in the finding that...
static constexpr bool S_ATOMIC_OR_NOT
The template parameter's value.
Cousin of arena_lend::stat::Owner_obj_stats, this tracks the zombie-reaper scan algorithm whose task ...
flow::util::stat::Histogram_counter m_histo_zombie_ct_estimate
Histogram that records, at the start of each run through the zombie-reaping scan algorithm (see m_sca...
std::atomic< uint64_t > m_scans
Number of times the zombie-reaper scan algorithm executed.
std::atomic< uint64_t > m_exhaustive_scans
Of the scans counted in m_scans, count of how many were exhaustive scans; that is performed up to onc...
std::atomic< uint64_t > m_fifo_exhausted_scans
Of the scans counted in m_scans, count of how many did not immediately exit due to the zombie-object-...
std::atomic< uint64_t > m_fifo_scans
Of the scans counted in m_scans, count of how many did not immediately exit due to the zombie-object-...
std::atomic< uint64_t > m_hints_sufficient_scans
Of the scans counted in m_scans, count of how many did not immediately exit due to the zombie-object-...
Flow-IPC SHM-module: SHM-provider-agnostic stats about an arena if viewed as being capable or lending...
Definition: shm_stats.hpp:257
Stats struct thematically relevant to gauging outstanding (not-yet-garbage-collected) first-class obj...
Definition: shm_stats.hpp:384
Flow-IPC SHM-module: SHM-provider-agnostic stats about an arena object if viewed as solely a memory-m...
Definition: shm_stats.hpp:120