1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <cstddef>
17 #include <cstdint>
18
19 #include "pw_allocator/allocator.h"
20 #include "pw_metric/metric.h"
21 #include "pw_status/status_with_size.h"
22
23 namespace pw::allocator {
24
25 /// Applies the given macro to each metric.
26 ///
27 /// This macro helps ensure consistency between definitions that involve every
28 /// metric.
29 ///
30 /// The recognized pw_allocator metrics are:
31 ///
32 /// - Metrics to track the current, peak, and cumulative number of bytes
33 /// requested to be allocated, respectively.
34 /// - requested_bytes
35 /// - peak_requested_bytes
36 /// - cumulative_requested_bytes
37 ///
38 /// - Metrics to track the current, peak, and cumulative number of bytes
39 /// actually allocated, respectively.
40 /// - allocated_bytes
41 /// - peak_allocated_bytes
42 /// - cumulative_allocated_bytes
43 ///
44 /// - Metrics to track the number of successful calls to each interface method.
45 /// - num_allocations
46 /// - num_deallocations
47 /// - num_resizes
48 /// - num_reallocations
49 ///
50 /// - Metrics to track block-related numbers, including the number of free
51 /// blocks, as well as the sizes of the smallest and largest blocks.
52 /// - num_free_blocks
53 /// - smallest_free_block_size
54 /// - largest_free_block_size
55 ///
56 /// - Metrics to tracks the number of interface calls that failed, and the
57 /// number of bytes requested in those calls.
58 /// - num_failures
59 /// - unfulfilled_bytes
60 #define PW_ALLOCATOR_METRICS_FOREACH(fn) \
61 fn(requested_bytes); \
62 fn(peak_requested_bytes); \
63 fn(cumulative_requested_bytes); \
64 fn(allocated_bytes); \
65 fn(peak_allocated_bytes); \
66 fn(cumulative_allocated_bytes); \
67 fn(num_allocations); \
68 fn(num_deallocations); \
69 fn(num_resizes); \
70 fn(num_reallocations); \
71 fn(num_free_blocks); \
72 fn(smallest_free_block_size); \
73 fn(largest_free_block_size); \
74 fn(num_failures); \
75 fn(unfulfilled_bytes)
76
77 #define PW_ALLOCATOR_ABSORB_SEMICOLON() static_assert(true)
78
79 /// Declares the names of metrics used by `pw::allocator::Metrics`.
80 ///
81 /// Only the names of declared metrics may be passed to
82 /// ``PW_ALLOCATOR_METRICS_ENABLE`` as part of a metrics struct definition.
83 ///
84 /// This macro generates trait types that are used by ``Metrics`` to
85 /// conditionally include metric-related code.
86 ///
87 /// Note: if enabling ``peak_allocated_bytes` or `cumulative_allocated_bytes`,
88 /// `allocated_bytes` should also be enabled.
89 #define PW_ALLOCATOR_METRICS_DECLARE(metric_name) \
90 static constexpr bool has_##metric_name() { return false; } \
91 ::pw::StatusWithSize get_##metric_name() const { \
92 return ::pw::StatusWithSize::NotFound(); \
93 } \
94 static constexpr bool metric_name##_enabled() { return false; } \
95 PW_ALLOCATOR_ABSORB_SEMICOLON()
96
97 /// A predefined metric struct that enables no allocator metrics.
98 ///
99 /// This struct declares all valid metrics, but enables none of them. Other
100 /// metrics struct MUST derive from this type, and should use
101 /// `PW_ALLOCATOR_METRICS_INCLUDE` and/or `PW_ALLOCATOR_METRICS_ENABLE`.
102 struct NoMetrics {
103 PW_ALLOCATOR_METRICS_FOREACH(PW_ALLOCATOR_METRICS_DECLARE);
104
105 /// Updates metrics by querying an allocator directly.
106 ///
107 /// Metrics are typically updated by an allocator when the pw::Allocator API
108 /// is invoked. In some cases, there may be metrics that cannot be determined
109 /// at the interface or are too expensive to do so, e.g. determining the
110 /// smallest free block. This method provides a way for metrics structs to
111 /// request these values on-demand.
112 ///
113 /// For this empty base struct, this is simply a no-op.
UpdateDeferredNoMetrics114 void UpdateDeferred(Allocator&) {}
115 };
116
117 #undef PW_ALLOCATOR_METRICS_DECLARE
118
119 /// Includes a metric in a metrics struct.
120 ///
121 /// The ``pw::allocator::TrackingAllocator`` template takes a struct that
122 /// includes zero or more of the metrics enumerated by
123 /// ``PW_ALLOCATOR_METRICS_DECLARE```.
124 ///
125 /// This struct may be one of ``AllMetrics`` or ``NoMetrics``, or may be a
126 /// custom struct that selects a subset of metrics.
127 ///
128 /// A metric that is only included, and not enabled, is expected to only be
129 /// updated when `UpdatedDeferred` is invoked.
130 #define PW_ALLOCATOR_METRICS_INCLUDE(metric_name) \
131 static_assert(!::pw::allocator::NoMetrics::has_##metric_name()); \
132 static constexpr bool has_##metric_name() { return true; } \
133 inline ::pw::StatusWithSize get_##metric_name() const { \
134 return ::pw::StatusWithSize(metric_name.value()); \
135 } \
136 PW_METRIC(metric_name, #metric_name, 0U)
137
138 /// Enables a metric for in a metrics struct.
139 ///
140 /// The ``pw::allocator::TrackingAllocator`` template takes a struct that
141 /// enables zero or more of the metrics enumerated by
142 /// ``PW_ALLOCATOR_METRICS_DECLARE```.
143 ///
144 /// This struct may be one of ``AllMetrics`` or ``NoMetrics``, or may be a
145 /// custom struct that selects a subset of metrics.
146 ///
147 /// A metric that is enabled is expected to be updated automatically by using a
148 /// tracking allocator's API.
149 ///
150 /// Example:
151 /// @code{.cpp}
152 /// struct MyMetrics {
153 /// PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes);
154 /// PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes);
155 /// PW_ALLOCATOR_METRICS_INCLUDE(num_free_blocks);
156 ///
157 /// void UpdateDeferred(Allocator& allocator);
158 /// };
159 /// @endcode
160 #define PW_ALLOCATOR_METRICS_ENABLE(metric_name) \
161 static constexpr bool metric_name##_enabled() { return true; } \
162 PW_ALLOCATOR_METRICS_INCLUDE(metric_name)
163
164 namespace internal {
165
166 /// A metrics type that enables all metrics for testing.
167 ///
168 /// Warning! Do not use in production code. As metrics are added to it later,
169 /// code using this struct may unexpectedly grow in code size, memory usage,
170 /// and/or performance overhead.
171 struct AllMetrics : public NoMetrics {
172 PW_ALLOCATOR_METRICS_FOREACH(PW_ALLOCATOR_METRICS_ENABLE);
173 };
174
175 /// Returns whether any metric is enabled. If not, metric collection can be
176 /// skipped.
177 template <typename MetricsType>
178 constexpr bool AnyEnabled();
179
180 /// Copies the values enabled for a given `MetricsType` from `src` to `dst`.
181 template <typename MetricsType>
182 void CopyMetrics(const MetricsType& src, MetricsType& dst);
183
184 /// Encapsulates the metrics struct for ``pw::allocator::TrackingAllocator``.
185 ///
186 /// This class uses the type traits from ``PW_ALLOCATOR_METRICS_DECLARE`` to
187 /// conditionally include or exclude code to update metrics based on calls to
188 /// the ``pw::Allocator`` API. This minimizes code size without adding
189 /// additional conditions to be evaluated at runtime.
190 ///
191 /// @tparam MetricsType The struct defining which metrics are enabled.
192 template <typename MetricsType>
193 class Metrics final {
194 public:
195 Metrics(metric::Token token);
196 ~Metrics() = default;
197
group()198 const metric::Group& group() const { return group_; }
group()199 metric::Group& group() { return group_; }
200
metrics()201 const MetricsType& metrics() const { return metrics_; }
202
203 /// Updates how much memory was requested and successfully allocated.
204 ///
205 /// This will update the current, peak, and cumulative amounts of memory
206 /// requests that were satisfied by an allocator.
207 ///
208 /// @param increase How much memory was requested to be allocated.
209 /// @param decrease How much memory was requested to be freed.
210 void ModifyRequested(size_t increase, size_t decrease);
211
212 /// Updates how much memory is allocated.
213 ///
214 /// This will update the current, peak, and cumulative amounts of memory that
215 /// has been actually allocated or freed. This method acts as if it frees
216 /// memory before allocating. If a routine suchas `Reallocate` allocates
217 /// before freeing, the update should be separated into two calls, e.g.
218 ///
219 /// @code{.cpp}
220 /// ModifyAllocated(increase, 0);
221 /// ModifyAllocated(0, decrease);
222 /// @endcode
223 ///
224 /// @param increase How much memory was allocated.
225 /// @param decrease How much memory was freed.
226 void ModifyAllocated(size_t increase, size_t decrease);
227
228 /// Records that a call to `Allocate` was made.
229 void IncrementAllocations();
230
231 /// Records that a call to `Deallocate` was made.
232 void IncrementDeallocations();
233
234 /// Records that a call to `Resize` was made.
235 void IncrementResizes();
236
237 /// Records that a call to `Reallocate` was made.
238 void IncrementReallocations();
239
240 /// Records that a call to `Allocate`, `Resize`, or `Reallocate` failed.
241 ///
242 /// This may indicated that memory becoming exhausted and/or highly
243 /// fragmented.
244 ///
245 /// @param requested How much memory was requested in the failed
246 /// call.
247 void RecordFailure(size_t requested);
248
249 /// Updates metrics by querying an allocator directly.
250 ///
251 /// See also `NoMetrics::UpdateDeferred`.
UpdateDeferred(Allocator & allocator)252 void UpdateDeferred(Allocator& allocator) {
253 metrics_.UpdateDeferred(allocator);
254 }
255
256 private:
257 metric::Group group_;
258 MetricsType metrics_;
259 };
260
261 /// Helper method for converting `size_t`s to `uint32_t`s.
ClampU32(size_t size)262 inline uint32_t ClampU32(size_t size) {
263 return static_cast<uint32_t>(std::min(
264 size, static_cast<size_t>(std::numeric_limits<uint32_t>::max())));
265 }
266
267 // Template method implementations.
268
269 template <typename MetricsType>
AnyEnabled()270 constexpr bool AnyEnabled() {
271 #define PW_ALLOCATOR_RETURN_IF_ENABLED(metric_name) \
272 if (MetricsType::metric_name##_enabled()) { \
273 return true; \
274 } \
275 PW_ALLOCATOR_ABSORB_SEMICOLON()
276
277 PW_ALLOCATOR_METRICS_FOREACH(PW_ALLOCATOR_RETURN_IF_ENABLED);
278 return false;
279
280 #undef PW_ALLOCATOR_RETURN_IF_ENABLED
281 }
282
283 template <typename MetricsType>
CopyMetrics(const MetricsType & src,MetricsType & dst)284 void CopyMetrics(const MetricsType& src, MetricsType& dst) {
285 #define PW_ALLOCATOR_COPY_METRIC(metric_name) \
286 if constexpr (MetricsType::has_##metric_name()) { \
287 dst.metric_name.Set(src.metric_name.value()); \
288 } \
289 PW_ALLOCATOR_ABSORB_SEMICOLON()
290
291 PW_ALLOCATOR_METRICS_FOREACH(PW_ALLOCATOR_COPY_METRIC);
292
293 #undef PW_ALLOCATOR_COPY_METRIC
294 }
295
296 template <typename MetricsType>
Metrics(metric::Token token)297 Metrics<MetricsType>::Metrics(metric::Token token) : group_(token) {
298 #define PW_ALLOCATOR_ADD_TO_GROUP(metric_name) \
299 if constexpr (MetricsType::has_##metric_name()) { \
300 group_.Add(metrics_.metric_name); \
301 } \
302 PW_ALLOCATOR_ABSORB_SEMICOLON()
303
304 PW_ALLOCATOR_METRICS_FOREACH(PW_ALLOCATOR_ADD_TO_GROUP);
305
306 #undef PW_ALLOCATOR_ADD_TO_GROUP
307 }
308
309 template <typename MetricsType>
ModifyRequested(size_t increase,size_t decrease)310 void Metrics<MetricsType>::ModifyRequested(size_t increase, size_t decrease) {
311 if constexpr (MetricsType::requested_bytes_enabled()) {
312 metrics_.requested_bytes.Increment(internal::ClampU32(increase));
313 metrics_.requested_bytes.Decrement(internal::ClampU32(decrease));
314 if constexpr (MetricsType::peak_requested_bytes_enabled()) {
315 uint32_t requested_bytes = metrics_.requested_bytes.value();
316 if (metrics_.peak_requested_bytes.value() < requested_bytes) {
317 metrics_.peak_requested_bytes.Set(requested_bytes);
318 }
319 }
320 if constexpr (MetricsType::cumulative_requested_bytes_enabled()) {
321 if (increase > decrease) {
322 metrics_.cumulative_requested_bytes.Increment(
323 internal::ClampU32(increase - decrease));
324 }
325 }
326 }
327 }
328
329 template <typename MetricsType>
ModifyAllocated(size_t increase,size_t decrease)330 void Metrics<MetricsType>::ModifyAllocated(size_t increase, size_t decrease) {
331 if constexpr (MetricsType::allocated_bytes_enabled()) {
332 metrics_.allocated_bytes.Increment(internal::ClampU32(increase));
333 metrics_.allocated_bytes.Decrement(internal::ClampU32(decrease));
334 if constexpr (MetricsType::peak_allocated_bytes_enabled()) {
335 uint32_t allocated_bytes = metrics_.allocated_bytes.value();
336 if (metrics_.peak_allocated_bytes.value() < allocated_bytes) {
337 metrics_.peak_allocated_bytes.Set(allocated_bytes);
338 }
339 }
340 if constexpr (MetricsType::cumulative_allocated_bytes_enabled()) {
341 if (increase > decrease) {
342 metrics_.cumulative_allocated_bytes.Increment(
343 internal::ClampU32(increase - decrease));
344 }
345 }
346 }
347 }
348
349 template <typename MetricsType>
IncrementAllocations()350 void Metrics<MetricsType>::IncrementAllocations() {
351 if constexpr (MetricsType::num_allocations_enabled()) {
352 metrics_.num_allocations.Increment();
353 }
354 }
355
356 template <typename MetricsType>
IncrementDeallocations()357 void Metrics<MetricsType>::IncrementDeallocations() {
358 if constexpr (MetricsType::num_deallocations_enabled()) {
359 metrics_.num_deallocations.Increment();
360 }
361 }
362
363 template <typename MetricsType>
IncrementResizes()364 void Metrics<MetricsType>::IncrementResizes() {
365 if constexpr (MetricsType::num_resizes_enabled()) {
366 metrics_.num_resizes.Increment();
367 }
368 }
369
370 template <typename MetricsType>
IncrementReallocations()371 void Metrics<MetricsType>::IncrementReallocations() {
372 if constexpr (MetricsType::num_reallocations_enabled()) {
373 metrics_.num_reallocations.Increment();
374 }
375 }
376
377 template <typename MetricsType>
RecordFailure(size_t requested)378 void Metrics<MetricsType>::RecordFailure(size_t requested) {
379 if constexpr (MetricsType::num_failures_enabled()) {
380 metrics_.num_failures.Increment();
381 }
382 if constexpr (MetricsType::unfulfilled_bytes_enabled()) {
383 metrics_.unfulfilled_bytes.Increment(internal::ClampU32(requested));
384 }
385 }
386
387 #undef PW_ALLOCATOR_ABSORB_SEMICOLON
388
389 } // namespace internal
390 } // namespace pw::allocator
391