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