| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| #include "pw_allocator/tracking_allocator.h" |
| |
| #include <cstdint> |
| |
| #include "pw_allocator/allocator.h" |
| #include "pw_allocator/first_fit.h" |
| #include "pw_allocator/metrics.h" |
| #include "pw_allocator/testing.h" |
| #include "pw_log/log.h" |
| #include "pw_metric/metric.h" |
| #include "pw_unit_test/framework.h" |
| |
| namespace { |
| |
| // Test fixtures. |
| |
| using ::pw::allocator::Layout; |
| using ::pw::allocator::TrackingAllocator; |
| using TestMetrics = ::pw::allocator::internal::AllMetrics; |
| |
| class TrackingAllocatorForTest : public TrackingAllocator<TestMetrics> { |
| public: |
| TrackingAllocatorForTest(pw::metric::Token token, pw::Allocator& allocator) |
| : TrackingAllocator<TestMetrics>(token, allocator) {} |
| |
| // Expose the protected allocated ``Layout`` method for test purposes. |
| pw::Result<Layout> GetAllocatedLayout(const void* ptr) const { |
| return TrackingAllocator<TestMetrics>::GetAllocatedLayout(ptr); |
| } |
| }; |
| |
| class TrackingAllocatorTest : public ::testing::Test { |
| protected: |
| using BlockType = ::pw::allocator::FirstFitBlock<uint32_t>; |
| using AllocatorType = ::pw::allocator::FirstFitAllocator<BlockType>; |
| static_assert(sizeof(uintptr_t) >= BlockType::kAlignment); |
| |
| constexpr static size_t kCapacity = 256; |
| constexpr static pw::metric::Token kToken = 1U; |
| |
| TrackingAllocatorTest() : ::testing::Test(), tracker_(kToken, *allocator_) {} |
| |
| void SetUp() override { allocator_->Init(allocator_.as_bytes()); } |
| |
| void TearDown() override { |
| pw::allocator::test::FreeAll<BlockType>(allocator_->blocks()); |
| } |
| |
| pw::allocator::WithBuffer<AllocatorType, kCapacity, BlockType::kAlignment> |
| allocator_; |
| TrackingAllocatorForTest tracker_; |
| }; |
| |
| struct ExpectedValues { |
| #define INCLUDE_EXPECTED_METRIC(metric_name) uint32_t metric_name = 0 |
| |
| PW_ALLOCATOR_METRICS_FOREACH(INCLUDE_EXPECTED_METRIC); |
| |
| #undef INCLUDE_EXPECTED_METRIC |
| |
| void AddRequestedBytes(size_t requested_bytes_) { |
| requested_bytes += static_cast<uint32_t>(requested_bytes_); |
| peak_requested_bytes = std::max(requested_bytes, peak_requested_bytes); |
| cumulative_requested_bytes += requested_bytes_; |
| } |
| |
| void AddAllocatedBytes(size_t allocated_bytes_) { |
| allocated_bytes += static_cast<uint32_t>(allocated_bytes_); |
| peak_allocated_bytes = std::max(allocated_bytes, peak_allocated_bytes); |
| cumulative_allocated_bytes += allocated_bytes_; |
| } |
| |
| void Check(const TestMetrics& metrics, int line) { |
| #define EXPECT_METRIC_EQ(metric_name) \ |
| EXPECT_EQ(metrics.metric_name.value(), metric_name); |
| |
| PW_ALLOCATOR_METRICS_FOREACH(EXPECT_METRIC_EQ); |
| if (testing::Test::HasFailure()) { |
| PW_LOG_ERROR("Metrics comparison failed at line %d.", line); |
| } |
| |
| #undef EXPECT_METRIC_EQ |
| } |
| }; |
| |
| #define EXPECT_METRICS_EQ(expected, metrics) expected.Check(metrics, __LINE__) |
| |
| // Unit tests. |
| |
| TEST_F(TrackingAllocatorTest, InitialValues) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; // Initially all 0. |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, GetCapacity) { |
| pw::StatusWithSize capacity = tracker_.GetCapacity(); |
| EXPECT_EQ(capacity.status(), pw::OkStatus()); |
| EXPECT_EQ(capacity.size(), kCapacity); |
| } |
| |
| TEST_F(TrackingAllocatorTest, AddTrackingAllocatorAsChild) { |
| constexpr static pw::metric::Token kChildToken = 2U; |
| TrackingAllocator<::pw::allocator::NoMetrics> child( |
| kChildToken, tracker_, pw::allocator::kAddTrackingAllocatorAsChild); |
| pw::IntrusiveList<pw::metric::Group>& children = |
| tracker_.metric_group().children(); |
| ASSERT_FALSE(children.empty()); |
| EXPECT_EQ(children.size(), 1U); |
| EXPECT_EQ(&(children.front()), &(child.metric_group())); |
| } |
| |
| TEST_F(TrackingAllocatorTest, AllocateDeallocate) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout1 = Layout::Of<uintptr_t[2]>(); |
| void* ptr1 = tracker_.Allocate(layout1); |
| size_t ptr1_allocated = tracker_.GetAllocatedLayout(ptr1)->size(); |
| ASSERT_NE(ptr1, nullptr); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr1); |
| expected.requested_bytes -= layout1.size(); |
| expected.allocated_bytes -= ptr1_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, AllocateFailure) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout = Layout::Of<uintptr_t[0x1000000U]>(); |
| void* ptr = tracker_.Allocate(layout); |
| EXPECT_EQ(ptr, nullptr); |
| expected.num_failures += 1; |
| expected.unfulfilled_bytes += layout.size(); |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, AllocateDeallocateMultiple) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| Layout layout1 = Layout::Of<uintptr_t[3]>(); |
| void* ptr1 = tracker_.Allocate(layout1); |
| ASSERT_NE(ptr1, nullptr); |
| size_t ptr1_allocated = tracker_.GetAllocatedLayout(ptr1)->size(); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| Layout layout2 = Layout::Of<uintptr_t[2]>(); |
| void* ptr2 = tracker_.Allocate(layout2); |
| ASSERT_NE(ptr2, nullptr); |
| size_t ptr2_allocated = tracker_.GetAllocatedLayout(ptr2)->size(); |
| expected.AddRequestedBytes(layout2.size()); |
| expected.AddAllocatedBytes(ptr2_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr1); |
| expected.requested_bytes -= layout1.size(); |
| expected.allocated_bytes -= ptr1_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| Layout layout3 = Layout::Of<uintptr_t>(); |
| void* ptr3 = tracker_.Allocate(layout3); |
| ASSERT_NE(ptr3, nullptr); |
| size_t ptr3_allocated = tracker_.GetAllocatedLayout(ptr3)->size(); |
| expected.AddRequestedBytes(layout3.size()); |
| expected.AddAllocatedBytes(ptr3_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr3); |
| expected.requested_bytes -= layout3.size(); |
| expected.allocated_bytes -= ptr3_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr2); |
| expected.requested_bytes -= layout2.size(); |
| expected.allocated_bytes -= ptr2_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, ResizeLarger) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout1 = Layout::Of<uintptr_t[3]>(); |
| void* ptr = tracker_.Allocate(layout1); |
| size_t ptr_allocated1 = tracker_.GetAllocatedLayout(ptr)->size(); |
| ASSERT_NE(ptr, nullptr); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr_allocated1); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| constexpr size_t size2 = sizeof(uintptr_t[5]); |
| EXPECT_TRUE(tracker_.Resize(ptr, size2)); |
| size_t ptr_allocated2 = tracker_.GetAllocatedLayout(ptr)->size(); |
| expected.AddRequestedBytes(size2 - layout1.size()); |
| expected.AddAllocatedBytes(ptr_allocated2 - ptr_allocated1); |
| expected.num_resizes += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr); |
| expected.requested_bytes -= size2; |
| expected.allocated_bytes -= ptr_allocated2; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, ResizeSmaller) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout1 = Layout::Of<uintptr_t[2]>(); |
| void* ptr = tracker_.Allocate(layout1); |
| size_t ptr_allocated1 = tracker_.GetAllocatedLayout(ptr)->size(); |
| ASSERT_NE(ptr, nullptr); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr_allocated1); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| constexpr size_t size2 = sizeof(uintptr_t[1]); |
| EXPECT_TRUE(tracker_.Resize(ptr, size2)); |
| size_t ptr_allocated2 = tracker_.GetAllocatedLayout(ptr)->size(); |
| expected.requested_bytes -= layout1.size() - size2; |
| expected.allocated_bytes -= ptr_allocated1 - ptr_allocated2; |
| expected.num_resizes += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr); |
| expected.requested_bytes -= size2; |
| expected.allocated_bytes -= ptr_allocated2; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, ResizeFailure) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout = Layout::Of<uintptr_t[4]>(); |
| void* ptr1 = tracker_.Allocate(layout); |
| ASSERT_NE(ptr1, nullptr); |
| size_t ptr1_allocated = tracker_.GetAllocatedLayout(ptr1)->size(); |
| expected.AddRequestedBytes(layout.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| void* ptr2 = tracker_.Allocate(layout); |
| ASSERT_NE(ptr2, nullptr); |
| size_t ptr2_allocated = tracker_.GetAllocatedLayout(ptr2)->size(); |
| expected.AddRequestedBytes(layout.size()); |
| expected.AddAllocatedBytes(ptr2_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| EXPECT_FALSE(tracker_.Resize(ptr1, layout.size() * 2)); |
| expected.num_failures += 1; |
| expected.unfulfilled_bytes += layout.size() * 2; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, Reallocate) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout1 = Layout::Of<uintptr_t[2]>(); |
| void* ptr1 = tracker_.Allocate(layout1); |
| ASSERT_NE(ptr1, nullptr); |
| size_t ptr1_allocated = tracker_.GetAllocatedLayout(ptr1)->size(); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // If `Reallocate` just resizes, no extra memory is allocated |
| constexpr Layout layout2 = Layout::Of<uintptr_t[4]>(); |
| void* ptr2 = tracker_.Reallocate(ptr1, layout2); |
| EXPECT_EQ(ptr2, ptr1); |
| size_t ptr2_allocated = tracker_.GetAllocatedLayout(ptr2)->size(); |
| expected.AddRequestedBytes(layout2.size() - layout1.size()); |
| expected.AddAllocatedBytes(ptr2_allocated - ptr1_allocated); |
| expected.num_reallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Make a second allocation to force reallocation. |
| constexpr Layout layout3 = layout1; |
| void* ptr3 = tracker_.Allocate(layout1); |
| ASSERT_NE(ptr3, nullptr); |
| size_t ptr3_allocated = tracker_.GetAllocatedLayout(ptr3)->size(); |
| expected.AddRequestedBytes(layout3.size()); |
| expected.AddAllocatedBytes(ptr3_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // If `Reallocate` must copy to a new location, it allocates before |
| // deallocating and results in higher peaks. |
| constexpr Layout layout4 = Layout::Of<uintptr_t[8]>(); |
| void* ptr4 = tracker_.Reallocate(ptr2, layout4); |
| EXPECT_NE(ptr4, ptr2); |
| size_t ptr4_allocated = tracker_.GetAllocatedLayout(ptr4)->size(); |
| expected.AddRequestedBytes(layout4.size() - layout2.size()); |
| expected.AddAllocatedBytes(ptr4_allocated); |
| expected.allocated_bytes -= ptr2_allocated; |
| expected.num_reallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr3); |
| expected.requested_bytes -= layout3.size(); |
| expected.allocated_bytes -= ptr3_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| tracker_.Deallocate(ptr4); |
| expected.requested_bytes -= layout4.size(); |
| expected.allocated_bytes -= ptr4_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, ReallocateFailure) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| constexpr Layout layout1 = Layout::Of<uintptr_t[4]>(); |
| void* ptr1 = tracker_.Allocate(layout1); |
| ASSERT_NE(ptr1, nullptr); |
| size_t ptr1_allocated = tracker_.GetAllocatedLayout(ptr1)->size(); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| constexpr Layout layout2 = Layout(0x10000000U, 1); |
| void* ptr2 = tracker_.Reallocate(ptr1, layout2); |
| EXPECT_EQ(ptr2, nullptr); |
| expected.num_failures += 1; |
| expected.unfulfilled_bytes += layout2.size(); |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| TEST_F(TrackingAllocatorTest, CorrectlyAccountsForShiftedBytes) { |
| const TestMetrics& metrics = tracker_.metrics(); |
| ExpectedValues expected; |
| |
| // Find an alignment greater than two block headers. |
| size_t alignment = 1; |
| while (alignment <= (BlockType::kBlockOverhead * 2)) { |
| alignment *= 2; |
| } |
| |
| // Allocate one block to align the usable space of the following block. |
| Layout layout1(alignment - BlockType::kBlockOverhead, alignment); |
| void* ptr1 = tracker_.Allocate(layout1); |
| ASSERT_NE(ptr1, nullptr); |
| auto* block1 = BlockType::FromUsableSpace(ptr1); |
| size_t ptr1_allocated = block1->OuterSize(); |
| expected.AddRequestedBytes(layout1.size()); |
| expected.AddAllocatedBytes(ptr1_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Allocate a second block that ends two block headers from an alignment |
| // boundary. |
| Layout layout2(alignment - (BlockType::kBlockOverhead * 2), alignment); |
| void* ptr2 = tracker_.Allocate(layout2); |
| ASSERT_NE(ptr2, nullptr); |
| auto* block2 = BlockType::FromUsableSpace(ptr2); |
| EXPECT_EQ(block2->InnerSize(), layout2.size()); |
| size_t ptr2_allocated = block2->OuterSize(); |
| expected.AddRequestedBytes(layout2.size()); |
| expected.AddAllocatedBytes(ptr2_allocated); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Allocate a third block to prevent the second block from being coalesced. |
| // The extra bytes should be appended to the second block. |
| Layout layout3(1, alignment); |
| void* ptr3 = tracker_.Allocate(layout3); |
| ASSERT_NE(ptr3, nullptr); |
| auto* block3 = BlockType::FromUsableSpace(ptr3); |
| size_t ptr3_allocated = block3->OuterSize(); |
| size_t shifted = block2->OuterSize() - ptr2_allocated; |
| expected.AddRequestedBytes(layout3.size()); |
| expected.AddAllocatedBytes(ptr3_allocated + shifted); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Free the second block, which is larger than when it was allocated. |
| tracker_.Deallocate(ptr2); |
| expected.requested_bytes -= layout2.size(); |
| expected.allocated_bytes -= ptr2_allocated + shifted; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Allocate the second block again. The trailing space should be appended. |
| ptr2 = tracker_.Allocate(layout2); |
| ASSERT_NE(ptr2, nullptr); |
| block2 = BlockType::FromUsableSpace(ptr2); |
| EXPECT_EQ(block2->OuterSize(), ptr2_allocated + shifted); |
| expected.AddRequestedBytes(layout2.size()); |
| expected.AddAllocatedBytes(ptr2_allocated + shifted); |
| expected.num_allocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Free the third block, which should reclaim space from the second block. |
| tracker_.Deallocate(ptr3); |
| expected.requested_bytes -= layout3.size(); |
| expected.allocated_bytes -= ptr3_allocated + shifted; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| |
| // Free the second block, which is now smaller than when it was allocated. |
| tracker_.Deallocate(ptr2); |
| expected.requested_bytes -= layout2.size(); |
| expected.allocated_bytes -= ptr2_allocated; |
| expected.num_deallocations += 1; |
| EXPECT_METRICS_EQ(expected, metrics); |
| } |
| |
| } // namespace |