blob: a53bca8328c56425c94905670697d3a293e4fae6 [file] [log] [blame]
// 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 <memory>
#include <string>
#include <vector>
#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<HistogramData>& GetRecordedDeltaHistograms() {
return recorded_delta_histograms_;
}
private:
std::vector<HistogramData> 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<TestPersistentHistogramAllocator> CreateDuplicateAllocator(
base::PersistentHistogramAllocator* allocator) {
// Just wrap around the data segment in-use by the passed allocator.
return std::make_unique<TestPersistentHistogramAllocator>(
std::make_unique<base::PersistentMemoryAllocator>(
const_cast<void*>(allocator->data()), allocator->length(), 0, 0, "",
base::PersistentMemoryAllocator::kReadWrite));
}
std::vector<HistogramData> 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<base::PersistentHistogramAllocator> 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<base::StatisticsRecorder> 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