// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/metrics/content/subprocess_metrics_provider.h" #include #include #include #include "base/metrics/histogram.h" #include "base/metrics/histogram_flattener.h" #include "base/metrics/histogram_snapshot_manager.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/metrics/persistent_memory_allocator.h" #include "base/metrics/sparse_histogram.h" #include "base/metrics/statistics_recorder.h" #include "base/test/bind.h" #include "content/public/test/browser_task_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; namespace metrics { namespace { const uint32_t TEST_MEMORY_SIZE = 64 << 10; // 64 KiB struct HistogramData { const std::string histogram_name; const base::HistogramBase::Count total_count; const int64_t sum; bool operator==(const HistogramData& other) const { return histogram_name == other.histogram_name && total_count == other.total_count && sum == other.sum; } }; class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener { public: HistogramFlattenerDeltaRecorder() = default; HistogramFlattenerDeltaRecorder(const HistogramFlattenerDeltaRecorder&) = delete; HistogramFlattenerDeltaRecorder& operator=( const HistogramFlattenerDeltaRecorder&) = delete; ~HistogramFlattenerDeltaRecorder() override = default; void RecordDelta(const base::HistogramBase& histogram, const base::HistogramSamples& snapshot) override { // Only record histograms that start with '_' (e.g., _foo, _bar, _baz) to // not record histograms from outside what is being tested. if (histogram.histogram_name()[0] == '_') { recorded_delta_histograms_.emplace_back( histogram.histogram_name(), snapshot.TotalCount(), snapshot.sum()); } } const std::vector& GetRecordedDeltaHistograms() { return recorded_delta_histograms_; } private: std::vector recorded_delta_histograms_; }; // Same as PersistentHistogramAllocator, but will call a callback on being // destroyed. class TestPersistentHistogramAllocator : public base::PersistentHistogramAllocator { public: using base::PersistentHistogramAllocator::PersistentHistogramAllocator; TestPersistentHistogramAllocator(const TestPersistentHistogramAllocator&) = delete; TestPersistentHistogramAllocator& operator=( const TestPersistentHistogramAllocator&) = delete; ~TestPersistentHistogramAllocator() override { if (!destroyed_callback_.is_null()) { std::move(destroyed_callback_).Run(); } } void SetDestroyedCallback(base::OnceClosure destroyed_callback) { destroyed_callback_ = std::move(destroyed_callback); } private: base::OnceClosure destroyed_callback_; }; } // namespace class SubprocessMetricsProviderTest : public testing::Test { public: SubprocessMetricsProviderTest(const SubprocessMetricsProviderTest&) = delete; SubprocessMetricsProviderTest& operator=( const SubprocessMetricsProviderTest&) = delete; protected: SubprocessMetricsProviderTest() : task_environment_( // Use ThreadPoolExecutionMode::QUEUED so that tests can decide // exactly when tasks posted to ThreadPool start running. base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) { SubprocessMetricsProvider::CreateInstance(); // Need to recreate a task runner, otherwise it may be a stale one from a // previous test (it's possible the global instance is re-used across // tests). RecreateTaskRunnerForTesting(); // MergeHistogramDeltas needs to be called because it uses a histogram // macro which caches a pointer to a histogram. If not done before setting // a persistent global allocator, then it would point into memory that // will go away. SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); // Create a dedicated StatisticsRecorder for this test. test_recorder_ = base::StatisticsRecorder::CreateTemporaryForTesting(); // Create a global allocator using a block of memory from the heap. base::GlobalHistogramAllocator::CreateWithLocalMemory(TEST_MEMORY_SIZE, 0, ""); } ~SubprocessMetricsProviderTest() override { base::GlobalHistogramAllocator::ReleaseForTesting(); } std::unique_ptr CreateDuplicateAllocator( base::PersistentHistogramAllocator* allocator) { // Just wrap around the data segment in-use by the passed allocator. return std::make_unique( std::make_unique( const_cast(allocator->data()), allocator->length(), 0, 0, "", base::PersistentMemoryAllocator::kReadWrite)); } std::vector GetSnapshotHistograms() { // Flatten what is known to see what has changed since the last time. HistogramFlattenerDeltaRecorder flattener; base::HistogramSnapshotManager snapshot_manager(&flattener); // "true" to the begin() includes histograms held in persistent storage. base::StatisticsRecorder::PrepareDeltas(true, base::Histogram::kNoFlags, base::Histogram::kNoFlags, &snapshot_manager); return flattener.GetRecordedDeltaHistograms(); } void RegisterSubprocessAllocator( int id, std::unique_ptr allocator) { SubprocessMetricsProvider::GetInstance()->RegisterSubprocessAllocator( id, std::move(allocator)); } void DeregisterSubprocessAllocator(int id) { SubprocessMetricsProvider::GetInstance()->DeregisterSubprocessAllocator(id); } void RecreateTaskRunnerForTesting() { SubprocessMetricsProvider::GetInstance()->RecreateTaskRunnerForTesting(); } content::BrowserTaskEnvironment* task_environment() { return &task_environment_; } private: // A thread-bundle makes the tests appear on the UI thread, something that is // checked in methods called from the SubprocessMetricsProvider class under // test. This must be constructed before the |provider_| field. content::BrowserTaskEnvironment task_environment_; std::unique_ptr test_recorder_; }; TEST_F(SubprocessMetricsProviderTest, SnapshotMetrics) { base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0); base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0); base::HistogramBase* baz = base::Histogram::FactoryGet("_baz", 1, 100, 10, 0); base::HistogramBase* foobar = base::SparseHistogram::FactoryGet( "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag); foo->Add(42); bar->Add(84); foobar->Add(1); foobar->Add(2); foobar->Add(3); // Register a new allocator that duplicates the global one. base::GlobalHistogramAllocator* global_allocator( base::GlobalHistogramAllocator::ReleaseForTesting()); auto duplicate_allocator = CreateDuplicateAllocator(global_allocator); bool duplicate_allocator_destroyed = false; duplicate_allocator->SetDestroyedCallback(base::BindLambdaForTesting( [&] { duplicate_allocator_destroyed = true; })); RegisterSubprocessAllocator(123, std::move(duplicate_allocator)); // Recording should find the two histograms created in persistent memory. SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/42}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/84}, HistogramData{"_foobar", /*total_count=*/3, /*sum=*/6})); // A second run should have nothing to produce. SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); // Create a new histogram and update existing ones. Should now report 3 items. baz->Add(1969); foo->Add(10); bar->Add(20); foobar->Add(4); SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/10}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/20}, HistogramData{"_baz", /*total_count=*/1, /*sum=*/1969}, HistogramData{"_foobar", /*total_count=*/1, /*sum=*/4})); // Ensure that deregistering does a final merge of the data. foo->Add(10); bar->Add(20); DeregisterSubprocessAllocator(123); // Do not call MergeHistogramDeltas() here, because the call to // DeregisterSubprocessAllocator() should have already scheduled a task to // merge the histograms. EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilIdle(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/10}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/20})); // The allocator should have been released after deregistering. EXPECT_TRUE(duplicate_allocator_destroyed); // Further snapshots should be empty even if things have changed. foo->Add(10); bar->Add(20); SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); } TEST_F(SubprocessMetricsProviderTest, SnapshotMetricsAsync) { base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0); base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0); base::HistogramBase* baz = base::Histogram::FactoryGet("_baz", 1, 100, 10, 0); base::HistogramBase* foobar = base::SparseHistogram::FactoryGet( "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag); foo->Add(42); bar->Add(84); foobar->Add(1); foobar->Add(2); foobar->Add(3); // Register a new allocator that duplicates the global one. base::GlobalHistogramAllocator* global_allocator( base::GlobalHistogramAllocator::ReleaseForTesting()); RegisterSubprocessAllocator(123, CreateDuplicateAllocator(global_allocator)); // Recording should find the two histograms created in persistent memory. SubprocessMetricsProvider::MergeHistogramDeltasForTesting( /*async=*/true, /*done_callback=*/task_environment()->QuitClosure()); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilQuit(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/42}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/84}, HistogramData{"_foobar", /*total_count=*/3, /*sum=*/6})); // A second run should have nothing to produce. SubprocessMetricsProvider::MergeHistogramDeltasForTesting( /*async=*/true, /*done_callback=*/task_environment()->QuitClosure()); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilQuit(); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); // Create a new histogram and update existing ones. Should now report 3 items. baz->Add(1969); foo->Add(10); bar->Add(20); foobar->Add(4); SubprocessMetricsProvider::MergeHistogramDeltasForTesting( /*async=*/true, /*done_callback=*/task_environment()->QuitClosure()); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilQuit(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/10}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/20}, HistogramData{"_baz", /*total_count=*/1, /*sum=*/1969}, HistogramData{"_foobar", /*total_count=*/1, /*sum=*/4})); // Ensure that deregistering does a final merge of the data. foo->Add(10); bar->Add(20); DeregisterSubprocessAllocator(123); // Do not call MergeHistogramDeltas() here, because the call to // DeregisterSubprocessAllocator() should have already scheduled a task to // merge the histograms. EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilIdle(); EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/10}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/20})); // Further snapshots should be empty even if things have changed. foo->Add(10); bar->Add(20); SubprocessMetricsProvider::MergeHistogramDeltasForTesting( /*async=*/true, /*done_callback=*/task_environment()->QuitClosure()); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); task_environment()->RunUntilQuit(); EXPECT_THAT(GetSnapshotHistograms(), IsEmpty()); } // Verifies that it is fine to deregister an allocator even if background tasks // that access it are still pending/running. TEST_F(SubprocessMetricsProviderTest, AllocatorRefCounted) { base::HistogramBase* foo = base::Histogram::FactoryGet("_foo", 1, 100, 10, 0); base::HistogramBase* bar = base::Histogram::FactoryGet("_bar", 1, 100, 10, 0); base::HistogramBase* baz = base::SparseHistogram::FactoryGet( "_baz", base::HistogramBase::kUmaTargetedHistogramFlag); base::HistogramBase* foobar = base::SparseHistogram::FactoryGet( "_foobar", base::HistogramBase::kUmaTargetedHistogramFlag); foo->Add(42); bar->Add(84); baz->Add(1); baz->Add(2); baz->Add(3); foobar->Add(4); foobar->Add(5); foobar->Add(6); // Register a new allocator that duplicates the global one. base::GlobalHistogramAllocator* global_allocator( base::GlobalHistogramAllocator::ReleaseForTesting()); auto duplicate_allocator = CreateDuplicateAllocator(global_allocator); bool duplicate_allocator_destroyed = false; duplicate_allocator->SetDestroyedCallback(base::BindLambdaForTesting( [&] { duplicate_allocator_destroyed = true; })); RegisterSubprocessAllocator(123, std::move(duplicate_allocator)); // Merge histogram deltas. This will be done asynchronously. SubprocessMetricsProvider::MergeHistogramDeltasForTesting( /*async=*/true, /*done_callback=*/base::DoNothing()); // Deregister the allocator. This will be done asynchronously. DeregisterSubprocessAllocator(123); // The call to DeregisterSubprocessAllocator() above will have removed the // allocator from the internal map. However, the allocator should not have // been freed yet as there are still background tasks pending/running // that have a reference to it (i.e., the tasks from MergeHistogramDeltas() // and DeregisterSubprocessAllocator()). ASSERT_FALSE(duplicate_allocator_destroyed); // Run tasks. task_environment()->RunUntilIdle(); // After all the tasks have finished, the allocator should have been released. EXPECT_TRUE(duplicate_allocator_destroyed); // Verify that the histograms were merged. EXPECT_THAT(GetSnapshotHistograms(), UnorderedElementsAre( HistogramData{"_foo", /*total_count=*/1, /*sum=*/42}, HistogramData{"_bar", /*total_count=*/1, /*sum=*/84}, HistogramData{"_baz", /*total_count=*/3, /*sum=*/6}, HistogramData{"_foobar", /*total_count=*/3, /*sum=*/15})); } } // namespace metrics