| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/profiler/metadata_recorder.h" |
| |
| #include "base/ranges/algorithm.h" |
| #include "base/test/gtest_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace base { |
| |
| bool operator==(const MetadataRecorder::Item& lhs, |
| const MetadataRecorder::Item& rhs) { |
| return lhs.name_hash == rhs.name_hash && lhs.value == rhs.value; |
| } |
| |
| bool operator<(const MetadataRecorder::Item& lhs, |
| const MetadataRecorder::Item& rhs) { |
| return lhs.name_hash < rhs.name_hash; |
| } |
| |
| TEST(MetadataRecorderTest, GetItems_Empty) { |
| MetadataRecorder recorder; |
| MetadataRecorder::ItemArray items; |
| |
| EXPECT_EQ(0u, MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items)); |
| EXPECT_EQ(0u, MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items)); |
| } |
| |
| TEST(MetadataRecorderTest, Set_NewNameHash) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, absl::nullopt, absl::nullopt, 20); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count; |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| EXPECT_FALSE(items[0].thread_id.has_value()); |
| EXPECT_EQ(20, items[0].value); |
| } |
| |
| recorder.Set(20, absl::nullopt, absl::nullopt, 30); |
| |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(2u, item_count); |
| EXPECT_EQ(20u, items[1].name_hash); |
| EXPECT_FALSE(items[1].key.has_value()); |
| EXPECT_FALSE(items[1].thread_id.has_value()); |
| EXPECT_EQ(30, items[1].value); |
| } |
| } |
| |
| TEST(MetadataRecorderTest, Set_ExistingNameNash) { |
| MetadataRecorder recorder; |
| recorder.Set(10, absl::nullopt, absl::nullopt, 20); |
| recorder.Set(10, absl::nullopt, absl::nullopt, 30); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| EXPECT_FALSE(items[0].thread_id.has_value()); |
| EXPECT_EQ(30, items[0].value); |
| } |
| |
| TEST(MetadataRecorderTest, Set_ReAddRemovedNameNash) { |
| MetadataRecorder recorder; |
| MetadataRecorder::ItemArray items; |
| std::vector<MetadataRecorder::Item> expected; |
| for (size_t i = 0; i < items.size(); ++i) { |
| expected.emplace_back(i, absl::nullopt, absl::nullopt, 0); |
| recorder.Set(i, absl::nullopt, absl::nullopt, 0); |
| } |
| |
| // By removing an item from a full recorder, re-setting the same item, and |
| // verifying that the item is returned, we can verify that the recorder is |
| // reusing the inactive slot for the same name hash instead of trying (and |
| // failing) to allocate a new slot. |
| recorder.Remove(3, absl::nullopt, absl::nullopt); |
| recorder.Set(3, absl::nullopt, absl::nullopt, 0); |
| |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| EXPECT_EQ(items.size(), item_count); |
| EXPECT_THAT(expected, ::testing::UnorderedElementsAreArray(items)); |
| } |
| |
| TEST(MetadataRecorderTest, Set_AddPastMaxCount) { |
| MetadataRecorder recorder; |
| MetadataRecorder::ItemArray items; |
| for (size_t i = 0; i < items.size(); ++i) { |
| recorder.Set(i, absl::nullopt, absl::nullopt, 0); |
| } |
| |
| // This should fail silently. |
| recorder.Set(items.size(), absl::nullopt, absl::nullopt, 0); |
| } |
| |
| TEST(MetadataRecorderTest, Set_NulloptKeyIsIndependentOfNonNulloptKey) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, 100, absl::nullopt, 20); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count; |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| ASSERT_TRUE(items[0].key.has_value()); |
| EXPECT_EQ(100, *items[0].key); |
| EXPECT_EQ(20, items[0].value); |
| } |
| |
| recorder.Set(10, absl::nullopt, absl::nullopt, 30); |
| |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(2u, item_count); |
| |
| EXPECT_EQ(10u, items[0].name_hash); |
| ASSERT_TRUE(items[0].key.has_value()); |
| EXPECT_EQ(100, *items[0].key); |
| EXPECT_EQ(20, items[0].value); |
| |
| EXPECT_EQ(10u, items[1].name_hash); |
| EXPECT_FALSE(items[1].key.has_value()); |
| EXPECT_EQ(30, items[1].value); |
| } |
| } |
| |
| TEST(MetadataRecorderTest, Set_ThreadIdIsScoped) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, absl::nullopt, PlatformThread::CurrentId(), 20); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count; |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, |
| PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| ASSERT_TRUE(items[0].thread_id.has_value()); |
| EXPECT_EQ(PlatformThread::CurrentId(), *items[0].thread_id); |
| EXPECT_EQ(20, items[0].value); |
| } |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder, kInvalidThreadId) |
| .GetItems(&items); |
| EXPECT_EQ(0U, item_count); |
| } |
| } |
| |
| TEST(MetadataRecorderTest, Set_NulloptThreadAndNonNulloptThread) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, absl::nullopt, PlatformThread::CurrentId(), 20); |
| recorder.Set(10, absl::nullopt, absl::nullopt, 30); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(2u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| ASSERT_TRUE(items[0].thread_id.has_value()); |
| EXPECT_EQ(PlatformThread::CurrentId(), *items[0].thread_id); |
| EXPECT_EQ(20, items[0].value); |
| |
| EXPECT_EQ(10u, items[1].name_hash); |
| EXPECT_FALSE(items[1].key.has_value()); |
| EXPECT_FALSE(items[1].thread_id.has_value()); |
| EXPECT_EQ(30, items[1].value); |
| } |
| |
| TEST(MetadataRecorderTest, Remove) { |
| MetadataRecorder recorder; |
| recorder.Set(10, absl::nullopt, absl::nullopt, 20); |
| recorder.Set(30, absl::nullopt, absl::nullopt, 40); |
| recorder.Set(50, absl::nullopt, absl::nullopt, 60); |
| recorder.Remove(30, absl::nullopt, absl::nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(2u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| EXPECT_EQ(20, items[0].value); |
| EXPECT_EQ(50u, items[1].name_hash); |
| EXPECT_FALSE(items[1].key.has_value()); |
| EXPECT_EQ(60, items[1].value); |
| } |
| |
| TEST(MetadataRecorderTest, Remove_DoesntExist) { |
| MetadataRecorder recorder; |
| recorder.Set(10, absl::nullopt, absl::nullopt, 20); |
| recorder.Remove(20, absl::nullopt, absl::nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| EXPECT_EQ(20, items[0].value); |
| } |
| |
| TEST(MetadataRecorderTest, Remove_NulloptKeyIsIndependentOfNonNulloptKey) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, 100, absl::nullopt, 20); |
| recorder.Set(10, absl::nullopt, absl::nullopt, 30); |
| |
| recorder.Remove(10, absl::nullopt, absl::nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| ASSERT_TRUE(items[0].key.has_value()); |
| EXPECT_EQ(100, *items[0].key); |
| EXPECT_EQ(20, items[0].value); |
| } |
| |
| TEST(MetadataRecorderTest, |
| Remove_NulloptThreadIsIndependentOfNonNulloptThread) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, absl::nullopt, PlatformThread::CurrentId(), 20); |
| recorder.Set(10, absl::nullopt, absl::nullopt, 30); |
| |
| recorder.Remove(10, absl::nullopt, absl::nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.has_value()); |
| ASSERT_TRUE(items[0].thread_id.has_value()); |
| EXPECT_EQ(PlatformThread::CurrentId(), *items[0].thread_id); |
| EXPECT_EQ(20, items[0].value); |
| } |
| |
| TEST(MetadataRecorderTest, ReclaimInactiveSlots) { |
| MetadataRecorder recorder; |
| |
| std::set<MetadataRecorder::Item> items_set; |
| // Fill up the metadata map. |
| for (size_t i = 0; i < MetadataRecorder::MAX_METADATA_COUNT; ++i) { |
| recorder.Set(i, absl::nullopt, absl::nullopt, i); |
| items_set.insert(MetadataRecorder::Item{i, absl::nullopt, absl::nullopt, |
| static_cast<int64_t>(i)}); |
| } |
| |
| // Remove every fourth entry to fragment the data. |
| size_t entries_removed = 0; |
| for (size_t i = 3; i < MetadataRecorder::MAX_METADATA_COUNT; i += 4) { |
| recorder.Remove(i, absl::nullopt, absl::nullopt); |
| ++entries_removed; |
| items_set.erase(MetadataRecorder::Item{i, absl::nullopt, absl::nullopt, |
| static_cast<int64_t>(i)}); |
| } |
| |
| // Ensure that the inactive slots are reclaimed to make room for more entries. |
| for (size_t i = 1; i <= entries_removed; ++i) { |
| recorder.Set(i * 100, absl::nullopt, absl::nullopt, i * 100); |
| items_set.insert(MetadataRecorder::Item{ |
| i * 100, absl::nullopt, absl::nullopt, static_cast<int64_t>(i * 100)}); |
| } |
| |
| MetadataRecorder::ItemArray items_arr; |
| ranges::copy(items_set, items_arr.begin()); |
| |
| MetadataRecorder::ItemArray recorder_items; |
| size_t recorder_item_count = |
| MetadataRecorder::MetadataProvider(&recorder, PlatformThread::CurrentId()) |
| .GetItems(&recorder_items); |
| EXPECT_EQ(recorder_item_count, MetadataRecorder::MAX_METADATA_COUNT); |
| EXPECT_THAT(recorder_items, ::testing::UnorderedElementsAreArray(items_arr)); |
| } |
| |
| } // namespace base |