xref: /aosp_15_r20/external/pigweed/pw_allocator/public/pw_allocator/metrics.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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_metric/metric.h"
20 
21 namespace pw::allocator {
22 
23 /// Declares the names of metrics used by `pw::allocator::Metrics`.
24 ///
25 /// Only the names of declared metrics may be passed to
26 /// ``PW_ALLOCATOR_METRICS_ENABLE`` as part of a metrics struct definition.
27 ///
28 /// This macro generates trait types that are used by ``Metrics`` to
29 /// conditionally include metric-related code.
30 ///
31 /// Note: if enabling ``peak_allocated_bytes` or `cumulative_allocated_bytes`,
32 /// `allocated_bytes` should also be enabled.
33 #define PW_ALLOCATOR_METRICS_DECLARE(metric_name)                           \
34   template <typename MetricsType, typename = void>                          \
35   struct has_##metric_name : std::false_type {};                            \
36   template <typename MetricsType>                                           \
37   struct has_##metric_name<MetricsType,                                     \
38                            std::void_t<decltype(MetricsType::metric_name)>> \
39       : std::true_type {}
40 
41 // Tracks the current, peak, and cumulative number of bytes requested to be
42 // allocated, respectively.
43 PW_ALLOCATOR_METRICS_DECLARE(requested_bytes);
44 PW_ALLOCATOR_METRICS_DECLARE(peak_requested_bytes);
45 PW_ALLOCATOR_METRICS_DECLARE(cumulative_requested_bytes);
46 
47 // Tracks the current, peak, and cumulative number of bytes actually allocated,
48 // respectively.
49 PW_ALLOCATOR_METRICS_DECLARE(allocated_bytes);
50 PW_ALLOCATOR_METRICS_DECLARE(peak_allocated_bytes);
51 PW_ALLOCATOR_METRICS_DECLARE(cumulative_allocated_bytes);
52 
53 // Tracks the number of successful calls to each interface method.
54 PW_ALLOCATOR_METRICS_DECLARE(num_allocations);
55 PW_ALLOCATOR_METRICS_DECLARE(num_deallocations);
56 PW_ALLOCATOR_METRICS_DECLARE(num_resizes);
57 PW_ALLOCATOR_METRICS_DECLARE(num_reallocations);
58 
59 // Tracks the number of interface calls that failed, and the number of bytes
60 // requested in those calls.
61 PW_ALLOCATOR_METRICS_DECLARE(num_failures);
62 PW_ALLOCATOR_METRICS_DECLARE(unfulfilled_bytes);
63 
64 #undef PW_ALLOCATOR_METRICS_DECLARE
65 
66 /// Enables a metric for in a metrics struct.
67 ///
68 /// The ``pw::allocator::TrackingAllocator`` template takes a struct that
69 /// enables zero or more of the metrics enumerated by
70 /// ``PW_ALLOCATOR_METRICS_DECLARE```.
71 ///
72 /// This struct may be one of ``AllMetrics`` or ``NoMetrics``, or may be a
73 /// custom struct that selects a subset of metrics.
74 ///
75 /// Note that this must be fully-qualified since the metric struct may be
76 /// defined in any namespace.
77 ///
78 /// Example:
79 /// @code{.cpp}
80 ///   struct MyMetrics {
81 ///     PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes);
82 ///     PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes);
83 ///     PW_ALLOCATOR_METRICS_ENABLE(num_failures);
84 ///   };
85 /// @endcode
86 #define PW_ALLOCATOR_METRICS_ENABLE(metric_name)                   \
87   static_assert(!::pw::allocator::has_##metric_name<void>::value); \
88   PW_METRIC(metric_name, #metric_name, 0U)
89 
90 /// A predefined metric struct that enables no allocator metrics.
91 struct NoMetrics {};
92 
93 namespace internal {
94 
95 /// A metrics type that enables all metrics for testing.
96 ///
97 /// Warning! Do not use in production code. If metrics are added to it later,
98 /// code using this struct may unexpected grow in code size, memory usage,
99 /// and/or performance overhead.
100 struct AllMetrics {
101   PW_ALLOCATOR_METRICS_ENABLE(requested_bytes);
102   PW_ALLOCATOR_METRICS_ENABLE(peak_requested_bytes);
103   PW_ALLOCATOR_METRICS_ENABLE(cumulative_requested_bytes);
104   PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes);
105   PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes);
106   PW_ALLOCATOR_METRICS_ENABLE(cumulative_allocated_bytes);
107   PW_ALLOCATOR_METRICS_ENABLE(num_allocations);
108   PW_ALLOCATOR_METRICS_ENABLE(num_deallocations);
109   PW_ALLOCATOR_METRICS_ENABLE(num_resizes);
110   PW_ALLOCATOR_METRICS_ENABLE(num_reallocations);
111   PW_ALLOCATOR_METRICS_ENABLE(num_failures);
112   PW_ALLOCATOR_METRICS_ENABLE(unfulfilled_bytes);
113 };
114 
115 /// Encapsulates the metrics struct for ``pw::allocator::TrackingAllocator``.
116 ///
117 /// This class uses the type traits from ``PW_ALLOCATOR_METRICS_DECLARE`` to
118 /// conditionally include or exclude code to update metrics based on calls to
119 /// the ``pw::Allocator`` API. This minimizes code size without adding
120 /// additional conditions to be evaluated at runtime.
121 ///
122 /// @tparam   MetricsType   The struct defining which metrics are enabled.
123 template <typename MetricsType>
124 class Metrics final {
125  public:
126   Metrics(metric::Token token);
127   ~Metrics() = default;
128 
group()129   const metric::Group& group() const { return group_; }
group()130   metric::Group& group() { return group_; }
131 
metrics()132   const MetricsType& metrics() const { return metrics_; }
133 
134   /// Updates how much memory was requested and successfully allocated.
135   ///
136   /// This will update the current, peak, and cumulative amounts of memory
137   /// requests that were satisfied by an allocator.
138   ///
139   /// @param  increase         How much memory was requested to be allocated.
140   /// @param  decrease         How much memory was requested to be freed.
141   void ModifyRequested(size_t increase, size_t decrease);
142 
143   /// Updates how much memory is allocated.
144   ///
145   /// This will update the current, peak, and cumulative amounts of memory that
146   /// has been actually allocated or freed. This method acts as if it frees
147   /// memory before allocating. If a routine suchas `Reallocate` allocates
148   /// before freeing, the update should be separated into two calls, e.g.
149   ///
150   /// @code{.cpp}
151   /// ModifyAllocated(increase, 0);
152   /// ModifyAllocated(0, decrease);
153   /// @endcode
154   ///
155   /// @param  increase         How much memory was allocated.
156   /// @param  decrease         How much memory was freed.
157   void ModifyAllocated(size_t increase, size_t decrease);
158 
159   /// Records that a call to `Allocate` was made.
160   void IncrementAllocations();
161 
162   /// Records that a call to `Deallocate` was made.
163   void IncrementDeallocations();
164 
165   /// Records that a call to `Resize` was made.
166   void IncrementResizes();
167 
168   /// Records that a call to `Reallocate` was made.
169   void IncrementReallocations();
170 
171   /// Records that a call to `Allocate`, `Resize`, or `Reallocate` failed.
172   ///
173   /// This may indicated that memory becoming exhausted and/or highly
174   /// fragmented.
175   ///
176   /// @param  requested         How much memory was requested in the failed
177   ///                           call.
178   void RecordFailure(size_t requested);
179 
180  private:
181   metric::Group group_;
182   MetricsType metrics_;
183 };
184 
185 // Helper method for converting `size_t`s to `uint32_t`s.
ClampU32(size_t size)186 inline uint32_t ClampU32(size_t size) {
187   return static_cast<uint32_t>(std::min(
188       size, static_cast<size_t>(std::numeric_limits<uint32_t>::max())));
189 }
190 
191 // Template method implementation.
192 
193 template <typename MetricsType>
Metrics(metric::Token token)194 Metrics<MetricsType>::Metrics(metric::Token token) : group_(token) {
195   if constexpr (has_requested_bytes<MetricsType>::value) {
196     group_.Add(metrics_.requested_bytes);
197   }
198   if constexpr (has_peak_requested_bytes<MetricsType>::value) {
199     group_.Add(metrics_.peak_requested_bytes);
200   }
201   if constexpr (has_cumulative_requested_bytes<MetricsType>::value) {
202     group_.Add(metrics_.cumulative_requested_bytes);
203   }
204   if constexpr (has_allocated_bytes<MetricsType>::value) {
205     group_.Add(metrics_.allocated_bytes);
206   }
207   if constexpr (has_peak_allocated_bytes<MetricsType>::value) {
208     group_.Add(metrics_.peak_allocated_bytes);
209   }
210   if constexpr (has_cumulative_allocated_bytes<MetricsType>::value) {
211     group_.Add(metrics_.cumulative_allocated_bytes);
212   }
213   if constexpr (has_num_allocations<MetricsType>::value) {
214     group_.Add(metrics_.num_allocations);
215   }
216   if constexpr (has_num_deallocations<MetricsType>::value) {
217     group_.Add(metrics_.num_deallocations);
218   }
219   if constexpr (has_num_resizes<MetricsType>::value) {
220     group_.Add(metrics_.num_resizes);
221   }
222   if constexpr (has_num_reallocations<MetricsType>::value) {
223     group_.Add(metrics_.num_reallocations);
224   }
225   if constexpr (has_num_failures<MetricsType>::value) {
226     group_.Add(metrics_.num_failures);
227   }
228   if constexpr (has_unfulfilled_bytes<MetricsType>::value) {
229     group_.Add(metrics_.unfulfilled_bytes);
230   }
231 }
232 
233 template <typename MetricsType>
ModifyRequested(size_t increase,size_t decrease)234 void Metrics<MetricsType>::ModifyRequested(size_t increase, size_t decrease) {
235   if constexpr (has_requested_bytes<MetricsType>::value) {
236     metrics_.requested_bytes.Increment(internal::ClampU32(increase));
237     metrics_.requested_bytes.Decrement(internal::ClampU32(decrease));
238     if constexpr (has_peak_requested_bytes<MetricsType>::value) {
239       uint32_t requested_bytes = metrics_.requested_bytes.value();
240       if (metrics_.peak_requested_bytes.value() < requested_bytes) {
241         metrics_.peak_requested_bytes.Set(requested_bytes);
242       }
243     }
244     if constexpr (has_cumulative_requested_bytes<MetricsType>::value) {
245       if (increase > decrease) {
246         metrics_.cumulative_requested_bytes.Increment(
247             internal::ClampU32(increase - decrease));
248       }
249     }
250   }
251 }
252 
253 template <typename MetricsType>
ModifyAllocated(size_t increase,size_t decrease)254 void Metrics<MetricsType>::ModifyAllocated(size_t increase, size_t decrease) {
255   if constexpr (has_allocated_bytes<MetricsType>::value) {
256     metrics_.allocated_bytes.Increment(internal::ClampU32(increase));
257     metrics_.allocated_bytes.Decrement(internal::ClampU32(decrease));
258     if constexpr (has_peak_allocated_bytes<MetricsType>::value) {
259       uint32_t allocated_bytes = metrics_.allocated_bytes.value();
260       if (metrics_.peak_allocated_bytes.value() < allocated_bytes) {
261         metrics_.peak_allocated_bytes.Set(allocated_bytes);
262       }
263     }
264     if constexpr (has_cumulative_allocated_bytes<MetricsType>::value) {
265       if (increase > decrease) {
266         metrics_.cumulative_allocated_bytes.Increment(
267             internal::ClampU32(increase - decrease));
268       }
269     }
270   }
271 }
272 
273 template <typename MetricsType>
IncrementAllocations()274 void Metrics<MetricsType>::IncrementAllocations() {
275   if constexpr (has_num_allocations<MetricsType>::value) {
276     metrics_.num_allocations.Increment();
277   }
278 }
279 
280 template <typename MetricsType>
IncrementDeallocations()281 void Metrics<MetricsType>::IncrementDeallocations() {
282   if constexpr (has_num_deallocations<MetricsType>::value) {
283     metrics_.num_deallocations.Increment();
284   }
285 }
286 
287 template <typename MetricsType>
IncrementResizes()288 void Metrics<MetricsType>::IncrementResizes() {
289   if constexpr (has_num_resizes<MetricsType>::value) {
290     metrics_.num_resizes.Increment();
291   }
292 }
293 
294 template <typename MetricsType>
IncrementReallocations()295 void Metrics<MetricsType>::IncrementReallocations() {
296   if constexpr (has_num_reallocations<MetricsType>::value) {
297     metrics_.num_reallocations.Increment();
298   }
299 }
300 
301 template <typename MetricsType>
RecordFailure(size_t requested)302 void Metrics<MetricsType>::RecordFailure(size_t requested) {
303   if constexpr (has_num_failures<MetricsType>::value) {
304     metrics_.num_failures.Increment();
305   }
306   if constexpr (has_unfulfilled_bytes<MetricsType>::value) {
307     metrics_.unfulfilled_bytes.Increment(internal::ClampU32(requested));
308   }
309 }
310 
311 }  // namespace internal
312 }  // namespace pw::allocator
313