• 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 #include <cstring>
19 
20 #include "pw_allocator/allocator.h"
21 #include "pw_allocator/capability.h"
22 #include "pw_allocator/metrics.h"
23 #include "pw_assert/assert.h"
24 #include "pw_metric/metric.h"
25 #include "pw_preprocessor/compiler.h"
26 #include "pw_result/result.h"
27 #include "pw_status/status.h"
28 #include "pw_status/status_with_size.h"
29 
30 namespace pw::allocator {
31 
32 /// This tag type is used to explicitly select the constructor which adds
33 /// the tracking allocator's metrics group as a child of the info
34 /// allocator it is wrapping.
35 static constexpr struct AddTrackingAllocatorAsChild {
36 } kAddTrackingAllocatorAsChild = {};
37 
38 /// Wraps an `Allocator` and records details of its usage.
39 ///
40 /// Metric collection is performed using the provided template parameter type.
41 /// Callers can not instantiate this class directly, as it lacks a public
42 /// constructor. Instead, callers should use derived classes which provide the
43 /// template parameter type, such as `TrackingAllocator` which uses the
44 /// default metrics implementation, or `TrackingAllocatorForTest` which
45 /// always uses the real metrics implementation.
46 template <typename MetricsType>
47 class TrackingAllocator : public Allocator {
48  public:
TrackingAllocator(metric::Token token,Allocator & allocator)49   TrackingAllocator(metric::Token token, Allocator& allocator)
50       : Allocator(allocator.capabilities() | kImplementsGetRequestedLayout),
51         allocator_(allocator),
52         metrics_(token) {}
53 
54   template <typename OtherMetrics>
TrackingAllocator(metric::Token token,TrackingAllocator<OtherMetrics> & parent,const AddTrackingAllocatorAsChild &)55   TrackingAllocator(metric::Token token,
56                     TrackingAllocator<OtherMetrics>& parent,
57                     const AddTrackingAllocatorAsChild&)
58       : TrackingAllocator(token, parent) {
59     parent.metric_group().Add(metric_group());
60   }
61 
metric_group()62   const metric::Group& metric_group() const { return metrics_.group(); }
metric_group()63   metric::Group& metric_group() { return metrics_.group(); }
64 
metrics()65   const MetricsType& metrics() const { return metrics_.metrics(); }
66 
67   /// Requests to update out-of-band metrics, if any.
68   ///
69   /// See also `NoMetrics::UpdateDeferred`.
UpdateDeferred()70   void UpdateDeferred() const { metrics_.UpdateDeferred(allocator_); }
71 
72  private:
73   /// @copydoc Allocator::Allocate
74   void* DoAllocate(Layout layout) override;
75 
76   /// @copydoc Allocator::Deallocate
77   void DoDeallocate(void* ptr) override;
78 
79   /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr,Layout)80   void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
81 
82   /// @copydoc Allocator::Resize
83   bool DoResize(void* ptr, size_t new_size) override;
84 
85   /// @copydoc Allocator::Reallocate
86   void* DoReallocate(void* ptr, Layout new_layout) override;
87 
88   /// @copydoc Allocator::GetAllocated
DoGetAllocated()89   size_t DoGetAllocated() const override { return allocator_.GetAllocated(); }
90 
91   /// @copydoc Deallocator::GetInfo
DoGetInfo(InfoType info_type,const void * ptr)92   Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override {
93     return GetInfo(allocator_, info_type, ptr);
94   }
95 
96   Allocator& allocator_;
97   mutable internal::Metrics<MetricsType> metrics_;
98 };
99 
100 // Template method implementation.
101 
102 template <typename MetricsType>
DoAllocate(Layout layout)103 void* TrackingAllocator<MetricsType>::DoAllocate(Layout layout) {
104   if constexpr (internal::AnyEnabled<MetricsType>()) {
105     Layout requested = layout;
106     size_t allocated = allocator_.GetAllocated();
107     void* new_ptr = allocator_.Allocate(requested);
108     if (new_ptr == nullptr) {
109       metrics_.RecordFailure(requested.size());
110       return nullptr;
111     }
112     metrics_.IncrementAllocations();
113     metrics_.ModifyRequested(requested.size(), 0);
114     metrics_.ModifyAllocated(allocator_.GetAllocated(), allocated);
115     return new_ptr;
116   } else {
117     return allocator_.Allocate(layout);
118   }
119 }
120 
121 template <typename MetricsType>
DoDeallocate(void * ptr)122 void TrackingAllocator<MetricsType>::DoDeallocate(void* ptr) {
123   if constexpr (internal::AnyEnabled<MetricsType>()) {
124     Layout requested = Layout::Unwrap(GetRequestedLayout(ptr));
125     size_t allocated = allocator_.GetAllocated();
126     allocator_.Deallocate(ptr);
127     metrics_.IncrementDeallocations();
128     metrics_.ModifyRequested(0, requested.size());
129     metrics_.ModifyAllocated(allocator_.GetAllocated(), allocated);
130   } else {
131     allocator_.Deallocate(ptr);
132   }
133 }
134 
135 template <typename MetricsType>
DoResize(void * ptr,size_t new_size)136 bool TrackingAllocator<MetricsType>::DoResize(void* ptr, size_t new_size) {
137   if constexpr (internal::AnyEnabled<MetricsType>()) {
138     Layout requested = Layout::Unwrap(GetRequestedLayout(ptr));
139     size_t allocated = allocator_.GetAllocated();
140     if (!allocator_.Resize(ptr, new_size)) {
141       metrics_.RecordFailure(new_size);
142       return false;
143     }
144     metrics_.IncrementResizes();
145     metrics_.ModifyRequested(new_size, requested.size());
146     metrics_.ModifyAllocated(allocator_.GetAllocated(), allocated);
147     return true;
148   } else {
149     return allocator_.Resize(ptr, new_size);
150   }
151 }
152 
153 template <typename MetricsType>
DoReallocate(void * ptr,Layout new_layout)154 void* TrackingAllocator<MetricsType>::DoReallocate(void* ptr,
155                                                    Layout new_layout) {
156   if constexpr (internal::AnyEnabled<MetricsType>()) {
157     // Check if possible to resize in place with no additional overhead.
158     Layout requested = Layout::Unwrap(GetRequestedLayout(ptr));
159     size_t allocated = allocator_.GetAllocated();
160     size_t new_size = new_layout.size();
161     if (allocator_.Resize(ptr, new_size)) {
162       metrics_.IncrementReallocations();
163       metrics_.ModifyRequested(new_size, requested.size());
164       metrics_.ModifyAllocated(allocator_.GetAllocated(), allocated);
165       return ptr;
166     }
167 
168     // Need to move data to a brand new allocation.
169     // In order to properly record the peak allocation, this method needs to
170     // perform the steps of allocating, copying, and deallocating memory, and
171     // recording metrics in the interim steps.
172     Result<Layout> old_layout = GetUsableLayout(ptr);
173     if (!old_layout.ok()) {
174       metrics_.RecordFailure(new_size);
175       return nullptr;
176     }
177     void* new_ptr = allocator_.Allocate(new_layout);
178     if (new_ptr == nullptr) {
179       metrics_.RecordFailure(new_size);
180       return nullptr;
181     }
182     // Update with transient allocation to ensure peak metrics are correct.
183     size_t transient_allocated = allocator_.GetAllocated();
184     metrics_.ModifyAllocated(transient_allocated, allocated);
185     if (ptr != nullptr) {
186       std::memcpy(new_ptr, ptr, std::min(new_size, old_layout->size()));
187       allocator_.Deallocate(ptr);
188     }
189     metrics_.IncrementReallocations();
190     metrics_.ModifyRequested(new_size, requested.size());
191     metrics_.ModifyAllocated(allocator_.GetAllocated(), transient_allocated);
192     return new_ptr;
193   } else {
194     return allocator_.Reallocate(ptr, new_layout);
195   }
196 }
197 
198 // TODO(b/326509341): This is an interim alias to facilitate refactoring
199 // downstream consumers of `TrackingAllocator` to add a template parameter.
200 //
201 // The following migration steps are complete:
202 // 1. Downstream consumers will be updated to use `TrackingAllocatorImpl<...>`.
203 // 2. The iterim `TrackingAllocator` class will be removed.
204 // 3. `TrackingAllocatorImpl<...>` will be renamed to `TrackingAllocator<...>`,
205 //    with a `TrackingAllocatorImpl<...>` alias pointing to it.
206 //
207 // The following migration steps remain:
208 // 4. Downstream consumers will be updated to use `TrackingAllocator<...>`.
209 // 5. The `TrackingAllocatorImpl<...>` alias will be removed.
210 template <typename MetricsType>
211 using TrackingAllocatorImpl = TrackingAllocator<MetricsType>;
212 
213 }  // namespace pw::allocator
214