• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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