Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
stat_substrate.cpp
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
27#include <flow/util/util.hpp>
28#include <vector>
29#include <algorithm>
30#include <array>
31#include <cstdint>
32#include <cassert>
33#include <flow/util/stat/histo.hpp>
34
36{
37
38// Implementations.
39
41{
42 /* config.stats is a compile-time constant within a given jemalloc build, so read it once and cache.
43 * Note: the config.* paths are available regardless of build flags (jemalloc ctl.c); a read failure here
44 * would thus be unexpected, but we conservatively treat any such failure as "stats unavailable." */
45 static const bool s_enabled = ([]() -> bool
46 {
47 bool enabled = false;
48 return (mallctl_read("config.stats", &enabled) == 0) && enabled;
49 })();
50 return s_enabled;
51}
52
53size_t page_size()
54{
55 static const size_t s_page_size = ([]() -> size_t
56 {
57 size_t page = 0;
58 mallctl_read("arenas.page", &page);
59 return page;
60 })();
61 return s_page_size;
62}
63
64uint64_t epoch_refresh()
65{
66 /* Writing any value to "epoch" refreshes jemalloc's stats snapshot. The read-back happens in the same mallctl()
67 * call -- under jemalloc's ctl lock, after the refresh -- so it yields the epoch of the snapshot we just took. */
68 uint64_t epoch_in = 1;
69 uint64_t epoch_out = 0;
70 size_t epoch_out_sz = sizeof(epoch_out);
71 IPC_SHM_ARENA_LEND_JEMALLOC_API(mallctl)("epoch", &epoch_out, &epoch_out_sz, &epoch_in, sizeof(epoch_in));
72 return epoch_out;
73}
74
75uint64_t epoch()
76{
77 uint64_t epoch_out = 0;
78 mallctl_read("epoch", &epoch_out);
79 return epoch_out;
80}
81
82bool mallctl_name_to_mib(const char* name, size_t* mib, size_t* mib_len)
83{
84 return IPC_SHM_ARENA_LEND_JEMALLOC_API(mallctlnametomib)(name, mib, mib_len) == 0;
85}
86
87void cumulative_alloc_dealloc_sz(arena_id_t arena_id, uint64_t* alloc_sz_ptr, uint64_t* dealloc_sz_ptr,
88 flow::util::stat::Histogram_counter* alloc_count_by_size_ptr)
89{
90 using flow::util::ostream_op_string;
91 using flow::util::stat::Histogram_counter;
92 using value_t = Histogram_counter::value_t;
93 using std::vector;
94 using std::array;
95 using std::copy_n;
96 using uint = unsigned int;
97
98 static_assert(sizeof(value_t) == sizeof(size_t),
99 "We read size_t-typed size-class byte-sizes (arenas.bin.<j>.size, etc.) directly into value_t "
100 "elements; mallctl() checks only the byte-size of the target.");
101
102 assert(alloc_sz_ptr && dealloc_sz_ptr && alloc_count_by_size_ptr);
103 auto& alloc_sz = *alloc_sz_ptr;
104 auto& dealloc_sz = *dealloc_sz_ptr;
105 auto& histo = *alloc_count_by_size_ptr;
106
107 /* Init-once (process-wide constants): per-size-class byte-sizes + the resolved MIBs for the per-arena
108 * per-size-class cumulative counters. jemalloc's size classes (and their sizes) are fixed for the process;
109 * only the arena/class indices vary per read -- mutated into copies of the cached MIBs below. Small classes
110 * are "bins"; large classes are "lextents"; their byte-sizes are contiguous and strictly increasing across the
111 * join (bins end ~14 KiB, lextents start ~16 KiB). We cache their concatenation (m_all_szs) -- used both for
112 * the cumulative-bytes math and to derive the histogram's request-range bucket bounds (m_histo_lo_bounds +
113 * m_histo_last_bucket_sz; see the by-size histogram member docs in arena_lend_stats.hpp). (Max MIB depth for
114 * these = 6, e.g. stats.arenas.i.bins.j.nmalloc.) */
115 struct Tables
116 {
117 vector<value_t> m_bin_szs; // small (bin) size-class byte-sizes.
118 vector<value_t> m_lext_szs; // large (lextent) size-class byte-sizes.
119 vector<value_t> m_all_szs; // m_bin_szs plus m_lext_szs: per-class byte-sizes for the cumulative-bytes math.
120 vector<value_t> m_histo_lo_bounds; // Request-range histogram bucket lower-bounds: [1, s0+1, s1+1, ...].
121 value_t m_histo_last_bucket_sz = 1; // Last histogram bucket's width (spans up to the top class size).
122 array<size_t, 6> m_bin_nmalloc_mib{}, m_bin_ndalloc_mib{};
123 array<size_t, 6> m_lext_nmalloc_mib{}, m_lext_ndalloc_mib{};
124 size_t m_bin_mib_len = 6, m_lext_mib_len = 6;
125 };
126 static const Tables s_tables = ([]() -> Tables
127 {
128 Tables tables;
129
130 uint n_bins = 0;
131 mallctl_read("arenas.nbins", &n_bins);
132 tables.m_bin_szs.resize(n_bins);
133 for (uint idx = 0; idx != n_bins; ++idx)
134 {
135 mallctl_read(ostream_op_string("arenas.bin.", idx, ".size").c_str(), &tables.m_bin_szs[idx]);
136 }
137
138 uint n_lext = 0;
139 mallctl_read("arenas.nlextents", &n_lext);
140 tables.m_lext_szs.resize(n_lext);
141 for (uint idx = 0; idx != n_lext; ++idx)
142 {
143 mallctl_read(ostream_op_string("arenas.lextent.", idx, ".size").c_str(), &tables.m_lext_szs[idx]);
144 }
145
146 tables.m_all_szs.reserve(tables.m_bin_szs.size() + tables.m_lext_szs.size());
147 tables.m_all_szs.insert(tables.m_all_szs.end(), tables.m_bin_szs.begin(), tables.m_bin_szs.end());
148 tables.m_all_szs.insert(tables.m_all_szs.end(), tables.m_lext_szs.begin(), tables.m_lext_szs.end());
149
150 /* Histogram bucket bounds = request ranges (The Law): bucket i counts allocations whose requested size N
151 * rounded up to size-class i, i.e. N in (prev-class, this-class]. So the lower bound of bucket i = (prev
152 * class size) + 1: [1, s0+1, s1+1, ..., s_{n-2}+1]; the last bucket spans up to the top class size. */
153 const auto n_all = tables.m_all_szs.size();
154 tables.m_histo_lo_bounds.resize(n_all);
155 tables.m_histo_lo_bounds[0] = 1;
156 for (size_t idx = 1; idx != n_all; ++idx)
157 {
158 tables.m_histo_lo_bounds[idx] = (tables.m_all_szs[idx - 1] + 1);
159 }
160 tables.m_histo_last_bucket_sz = (tables.m_all_szs[n_all - 1] - tables.m_all_szs[n_all - 2]);
161
162 /* Resolve the MIB templates (placeholder indices 0). Per the jemalloc MIB convention the arena index lives
163 * at slot [2] and the size-class index at slot [4]; we mutate those per read below. */
164 mallctl_name_to_mib("stats.arenas.0.bins.0.nmalloc", tables.m_bin_nmalloc_mib.data(), &tables.m_bin_mib_len);
165 auto len = size_t(6);
166 mallctl_name_to_mib("stats.arenas.0.bins.0.ndalloc", tables.m_bin_ndalloc_mib.data(), &len);
167 mallctl_name_to_mib("stats.arenas.0.lextents.0.nmalloc", tables.m_lext_nmalloc_mib.data(), &tables.m_lext_mib_len);
168 len = 6;
169 mallctl_name_to_mib("stats.arenas.0.lextents.0.ndalloc", tables.m_lext_ndalloc_mib.data(), &len);
170
171 return tables;
172 })(); // static const Tables s_tables =
173
174 alloc_sz = dealloc_sz = 0;
175 // The histogram: fresh, zero-count, with the constant request-range bounds (see by-size histogram docs).
176 histo = Histogram_counter{s_tables.m_histo_lo_bounds, s_tables.m_histo_last_bucket_sz};
177
178 /* For each size-class idx: sum (count x size-class-byte-size) into the scalar totals, with mib[2] = arena and
179 * mib[4] = class index; AND overwrite the matching `histo` bucket from the nmalloc count. The histogram's
180 * buckets are [bins | lextents] in order, so bins occupy [0, nbins) and lextents [nbins, nbins + nlext) --
181 * hence the per-walk bucket_offset (0 for bins, nbins for lextents). */
182 const auto walk = [&](const auto& nmalloc_mib, const auto& ndalloc_mib,
183 size_t mib_len, const vector<value_t>& szs, size_t bucket_offset)
184 {
185 array<size_t, 6> mib_n{};
186 array<size_t, 6> mib_d{};
187 copy_n(nmalloc_mib.data(), mib_len, mib_n.data());
188 copy_n(ndalloc_mib.data(), mib_len, mib_d.data());
189 mib_n[2] = mib_d[2] = arena_id;
190 for (size_t idx = 0; idx != szs.size(); ++idx)
191 {
192 mib_n[4] = mib_d[4] = idx;
193 uint64_t count_n = 0;
194 uint64_t count_d = 0;
195 mallctl_read_mib(mib_n.data(), mib_len, &count_n);
196 mallctl_read_mib(mib_d.data(), mib_len, &count_d);
197 const auto sz = uint64_t(szs[idx]);
198 alloc_sz += (count_n * sz);
199 dealloc_sz += (count_d * sz);
200 histo.overwrite_count_for_bucket(bucket_offset + idx, count_n);
201 }
202 }; // const auto walk =
203
204 walk(s_tables.m_bin_nmalloc_mib, s_tables.m_bin_ndalloc_mib,
205 s_tables.m_bin_mib_len, s_tables.m_bin_szs, 0);
206 walk(s_tables.m_lext_nmalloc_mib, s_tables.m_lext_ndalloc_mib,
207 s_tables.m_lext_mib_len, s_tables.m_lext_szs, s_tables.m_bin_szs.size());
208} // cumulative_alloc_dealloc_sz()
209
210} // namespace ipc::shm::arena_lend::jemalloc::detail::stat
Stats-related sub-namespace within SHM-jemalloc's detail, for general organization (and ADL segregati...
size_t page_size()
Returns the jemalloc page size in bytes (arenas.page), read once and cached (constant for the process...
bool config_stats_enabled()
Returns whether the linked jemalloc was built with statistics support (its config....
uint64_t epoch_refresh()
Refreshes jemalloc's statistics snapshot (writes mallctl("epoch")), so that subsequently-read "stats....
int mallctl_read_mib(const size_t *mib, size_t mib_len, T *val)
Like mallctl_read() but addresses the value by a previously-resolved MIB (see mallctl_name_to_mib()) ...
bool mallctl_name_to_mib(const char *name, size_t *mib, size_t *mib_len)
Resolves a mallctl name – which may contain integer components (e.g.
uint64_t epoch()
Returns jemalloc's current statistics epoch (reads mallctl("epoch")), without refreshing anything.
void cumulative_alloc_dealloc_sz(arena_id_t arena_id, uint64_t *alloc_sz, uint64_t *dealloc_sz, flow::util::stat::Histogram_counter *alloc_count_by_size)
Computes, for the given jemalloc-arena, the cumulative bytes-ever-allocated and bytes-ever-deallocate...
int mallctl_read(const char *name, T *val)
Reads a single scalar mallctl value, by name, into *val (using sizeof(T) as the value size).
unsigned int arena_id_t
Arena ID as dictated by jemalloc.
#define IPC_SHM_ARENA_LEND_JEMALLOC_API(ARG_api_name)
To be used when invoking jemalloc public API functions, this evaluates to the given argument (which s...
Definition: jemalloc.hpp:87