| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // 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/test/gtest_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.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; |
| |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); |
| |
| EXPECT_EQ(0u, item_count); |
| } |
| |
| TEST(MetadataRecorderTest, Set_NewNameHash) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, nullopt, 20); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count; |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder).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); |
| } |
| |
| recorder.Set(20, nullopt, 30); |
| |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); |
| ASSERT_EQ(2u, item_count); |
| EXPECT_EQ(20u, items[1].name_hash); |
| EXPECT_FALSE(items[1].key.has_value()); |
| EXPECT_EQ(30, items[1].value); |
| } |
| } |
| |
| TEST(MetadataRecorderTest, Set_ExistingNameNash) { |
| MetadataRecorder recorder; |
| recorder.Set(10, nullopt, 20); |
| recorder.Set(10, nullopt, 30); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); |
| ASSERT_EQ(1u, item_count); |
| EXPECT_EQ(10u, items[0].name_hash); |
| EXPECT_FALSE(items[0].key.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.push_back(MetadataRecorder::Item{i, nullopt, 0}); |
| recorder.Set(i, 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, nullopt); |
| recorder.Set(3, nullopt, 0); |
| |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).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, nullopt, 0); |
| } |
| |
| // This should fail silently. |
| recorder.Set(items.size(), nullopt, 0); |
| } |
| |
| TEST(MetadataRecorderTest, Set_NulloptKeyIsIndependentOfNonNulloptKey) { |
| MetadataRecorder recorder; |
| |
| recorder.Set(10, 100, 20); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count; |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder).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, nullopt, 30); |
| |
| { |
| item_count = MetadataRecorder::MetadataProvider(&recorder).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, Remove) { |
| MetadataRecorder recorder; |
| recorder.Set(10, nullopt, 20); |
| recorder.Set(30, nullopt, 40); |
| recorder.Set(50, nullopt, 60); |
| recorder.Remove(30, nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).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, nullopt, 20); |
| recorder.Remove(20, nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).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, 20); |
| recorder.Set(10, nullopt, 30); |
| |
| recorder.Remove(10, nullopt); |
| |
| MetadataRecorder::ItemArray items; |
| size_t item_count = |
| MetadataRecorder::MetadataProvider(&recorder).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, 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, nullopt, i); |
| items_set.insert(MetadataRecorder::Item{i, nullopt, 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, nullopt); |
| ++entries_removed; |
| items_set.erase(MetadataRecorder::Item{i, nullopt, 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, nullopt, i * 100); |
| items_set.insert(MetadataRecorder::Item{i * 100, nullopt, i * 100}); |
| } |
| |
| MetadataRecorder::ItemArray items_arr; |
| std::copy(items_set.begin(), items_set.end(), items_arr.begin()); |
| |
| MetadataRecorder::ItemArray recorder_items; |
| size_t recorder_item_count = |
| MetadataRecorder::MetadataProvider(&recorder).GetItems(&recorder_items); |
| EXPECT_EQ(recorder_item_count, MetadataRecorder::MAX_METADATA_COUNT); |
| EXPECT_THAT(recorder_items, ::testing::UnorderedElementsAreArray(items_arr)); |
| } |
| |
| TEST(MetadataRecorderTest, MetadataSlotsUsedUmaHistogram) { |
| MetadataRecorder recorder; |
| HistogramTester histogram_tester; |
| |
| for (size_t i = 0; i < MetadataRecorder::MAX_METADATA_COUNT; ++i) { |
| recorder.Set(i * 10, nullopt, i * 100); |
| } |
| |
| EXPECT_THAT( |
| histogram_tester.GetAllSamples("StackSamplingProfiler.MetadataSlotsUsed"), |
| testing::ElementsAre(Bucket(1, 1), Bucket(2, 1), Bucket(3, 1), |
| Bucket(4, 1), Bucket(5, 1), Bucket(6, 1), |
| Bucket(7, 1), Bucket(8, 2), Bucket(10, 2), |
| Bucket(12, 2), Bucket(14, 3), Bucket(17, 3), |
| Bucket(20, 4), Bucket(24, 5), Bucket(29, 5), |
| Bucket(34, 6), Bucket(40, 8), Bucket(48, 3))); |
| } |
| |
| } // namespace base |