376 lines
14 KiB
C++
376 lines
14 KiB
C++
// Copyright 2016 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/metrics/persistent_histogram_allocator.h"
|
|
|
|
#include "base/files/file.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/files/scoped_temp_dir.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/metrics/bucket_ranges.h"
|
|
#include "base/metrics/histogram_macros.h"
|
|
#include "base/metrics/persistent_memory_allocator.h"
|
|
#include "base/metrics/statistics_recorder.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
|
|
class PersistentHistogramAllocatorTest : public testing::Test {
|
|
protected:
|
|
const int32_t kAllocatorMemorySize = 64 << 10; // 64 KiB
|
|
|
|
PersistentHistogramAllocatorTest()
|
|
: statistics_recorder_(StatisticsRecorder::CreateTemporaryForTesting()) {
|
|
CreatePersistentHistogramAllocator();
|
|
}
|
|
~PersistentHistogramAllocatorTest() override {
|
|
DestroyPersistentHistogramAllocator();
|
|
}
|
|
|
|
void CreatePersistentHistogramAllocator() {
|
|
allocator_memory_.reset(new char[kAllocatorMemorySize]);
|
|
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
memset(allocator_memory_.get(), 0, kAllocatorMemorySize);
|
|
GlobalHistogramAllocator::CreateWithPersistentMemory(
|
|
allocator_memory_.get(), kAllocatorMemorySize, 0, 0,
|
|
"PersistentHistogramAllocatorTest");
|
|
allocator_ = GlobalHistogramAllocator::Get()->memory_allocator();
|
|
}
|
|
|
|
void DestroyPersistentHistogramAllocator() {
|
|
allocator_ = nullptr;
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
}
|
|
|
|
std::unique_ptr<StatisticsRecorder> statistics_recorder_;
|
|
std::unique_ptr<char[]> allocator_memory_;
|
|
PersistentMemoryAllocator* allocator_ = nullptr;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(PersistentHistogramAllocatorTest);
|
|
};
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, CreateAndIterate) {
|
|
PersistentMemoryAllocator::MemoryInfo meminfo0;
|
|
allocator_->GetMemoryInfo(&meminfo0);
|
|
|
|
// Try basic construction
|
|
HistogramBase* histogram = Histogram::FactoryGet(
|
|
"TestHistogram", 1, 1000, 10, HistogramBase::kIsPersistent);
|
|
EXPECT_TRUE(histogram);
|
|
histogram->CheckName("TestHistogram");
|
|
PersistentMemoryAllocator::MemoryInfo meminfo1;
|
|
allocator_->GetMemoryInfo(&meminfo1);
|
|
EXPECT_GT(meminfo0.free, meminfo1.free);
|
|
|
|
HistogramBase* linear_histogram = LinearHistogram::FactoryGet(
|
|
"TestLinearHistogram", 1, 1000, 10, HistogramBase::kIsPersistent);
|
|
EXPECT_TRUE(linear_histogram);
|
|
linear_histogram->CheckName("TestLinearHistogram");
|
|
PersistentMemoryAllocator::MemoryInfo meminfo2;
|
|
allocator_->GetMemoryInfo(&meminfo2);
|
|
EXPECT_GT(meminfo1.free, meminfo2.free);
|
|
|
|
HistogramBase* boolean_histogram = BooleanHistogram::FactoryGet(
|
|
"TestBooleanHistogram", HistogramBase::kIsPersistent);
|
|
EXPECT_TRUE(boolean_histogram);
|
|
boolean_histogram->CheckName("TestBooleanHistogram");
|
|
PersistentMemoryAllocator::MemoryInfo meminfo3;
|
|
allocator_->GetMemoryInfo(&meminfo3);
|
|
EXPECT_GT(meminfo2.free, meminfo3.free);
|
|
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(1);
|
|
custom_ranges.push_back(5);
|
|
HistogramBase* custom_histogram = CustomHistogram::FactoryGet(
|
|
"TestCustomHistogram", custom_ranges, HistogramBase::kIsPersistent);
|
|
EXPECT_TRUE(custom_histogram);
|
|
custom_histogram->CheckName("TestCustomHistogram");
|
|
PersistentMemoryAllocator::MemoryInfo meminfo4;
|
|
allocator_->GetMemoryInfo(&meminfo4);
|
|
EXPECT_GT(meminfo3.free, meminfo4.free);
|
|
|
|
PersistentMemoryAllocator::Iterator iter(allocator_);
|
|
uint32_t type;
|
|
EXPECT_NE(0U, iter.GetNext(&type)); // Histogram
|
|
EXPECT_NE(0U, iter.GetNext(&type)); // LinearHistogram
|
|
EXPECT_NE(0U, iter.GetNext(&type)); // BooleanHistogram
|
|
EXPECT_NE(0U, iter.GetNext(&type)); // CustomHistogram
|
|
EXPECT_EQ(0U, iter.GetNext(&type));
|
|
|
|
// Create a second allocator and have it access the memory of the first.
|
|
std::unique_ptr<HistogramBase> recovered;
|
|
PersistentHistogramAllocator recovery(
|
|
std::make_unique<PersistentMemoryAllocator>(
|
|
allocator_memory_.get(), kAllocatorMemorySize, 0, 0, "", false));
|
|
PersistentHistogramAllocator::Iterator histogram_iter(&recovery);
|
|
|
|
recovered = histogram_iter.GetNext();
|
|
ASSERT_TRUE(recovered);
|
|
recovered->CheckName("TestHistogram");
|
|
|
|
recovered = histogram_iter.GetNext();
|
|
ASSERT_TRUE(recovered);
|
|
recovered->CheckName("TestLinearHistogram");
|
|
|
|
recovered = histogram_iter.GetNext();
|
|
ASSERT_TRUE(recovered);
|
|
recovered->CheckName("TestBooleanHistogram");
|
|
|
|
recovered = histogram_iter.GetNext();
|
|
ASSERT_TRUE(recovered);
|
|
recovered->CheckName("TestCustomHistogram");
|
|
|
|
recovered = histogram_iter.GetNext();
|
|
EXPECT_FALSE(recovered);
|
|
}
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, ConstructPaths) {
|
|
const FilePath dir_path(FILE_PATH_LITERAL("foo/"));
|
|
const std::string dir_string =
|
|
dir_path.NormalizePathSeparators().AsUTF8Unsafe();
|
|
|
|
FilePath path = GlobalHistogramAllocator::ConstructFilePath(dir_path, "bar");
|
|
EXPECT_EQ(dir_string + "bar.pma", path.AsUTF8Unsafe());
|
|
|
|
std::string name;
|
|
Time stamp;
|
|
ProcessId pid;
|
|
EXPECT_FALSE(
|
|
GlobalHistogramAllocator::ParseFilePath(path, &name, nullptr, nullptr));
|
|
EXPECT_FALSE(
|
|
GlobalHistogramAllocator::ParseFilePath(path, nullptr, &stamp, nullptr));
|
|
EXPECT_FALSE(
|
|
GlobalHistogramAllocator::ParseFilePath(path, nullptr, nullptr, &pid));
|
|
|
|
path = GlobalHistogramAllocator::ConstructFilePathForUploadDir(
|
|
dir_path, "bar", Time::FromTimeT(12345), 6789);
|
|
EXPECT_EQ(dir_string + "bar-3039-1A85.pma", path.AsUTF8Unsafe());
|
|
ASSERT_TRUE(
|
|
GlobalHistogramAllocator::ParseFilePath(path, &name, &stamp, &pid));
|
|
EXPECT_EQ(name, "bar");
|
|
EXPECT_EQ(Time::FromTimeT(12345), stamp);
|
|
EXPECT_EQ(static_cast<ProcessId>(6789), pid);
|
|
}
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, CreateWithFile) {
|
|
const char temp_name[] = "CreateWithFileTest";
|
|
ScopedTempDir temp_dir;
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name);
|
|
const size_t temp_size = 64 << 10; // 64 KiB
|
|
|
|
// Test creation of a new file.
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
GlobalHistogramAllocator::CreateWithFile(temp_file, temp_size, 0, temp_name);
|
|
EXPECT_EQ(std::string(temp_name),
|
|
GlobalHistogramAllocator::Get()->memory_allocator()->Name());
|
|
|
|
// Test re-open of a possibly-existing file.
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
GlobalHistogramAllocator::CreateWithFile(temp_file, temp_size, 0, "");
|
|
EXPECT_EQ(std::string(temp_name),
|
|
GlobalHistogramAllocator::Get()->memory_allocator()->Name());
|
|
|
|
// Test re-open of an known-existing file.
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
GlobalHistogramAllocator::CreateWithFile(temp_file, 0, 0, "");
|
|
EXPECT_EQ(std::string(temp_name),
|
|
GlobalHistogramAllocator::Get()->memory_allocator()->Name());
|
|
|
|
// Final release so file and temp-dir can be removed.
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
}
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, CreateSpareFile) {
|
|
const char temp_name[] = "CreateSpareFileTest.pma";
|
|
ScopedTempDir temp_dir;
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name);
|
|
const size_t temp_size = 64 << 10; // 64 KiB
|
|
|
|
ASSERT_TRUE(GlobalHistogramAllocator::CreateSpareFile(temp_file, temp_size));
|
|
|
|
File file(temp_file, File::FLAG_OPEN | File::FLAG_READ);
|
|
ASSERT_TRUE(file.IsValid());
|
|
EXPECT_EQ(static_cast<int64_t>(temp_size), file.GetLength());
|
|
|
|
char buffer[256];
|
|
for (size_t pos = 0; pos < temp_size; pos += sizeof(buffer)) {
|
|
ASSERT_EQ(static_cast<int>(sizeof(buffer)),
|
|
file.ReadAtCurrentPos(buffer, sizeof(buffer)));
|
|
for (size_t i = 0; i < sizeof(buffer); ++i)
|
|
EXPECT_EQ(0, buffer[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMerge) {
|
|
const char LinearHistogramName[] = "SRTLinearHistogram";
|
|
const char SparseHistogramName[] = "SRTSparseHistogram";
|
|
const size_t starting_sr_count = StatisticsRecorder::GetHistogramCount();
|
|
|
|
// Create a local StatisticsRecorder in which the newly created histogram
|
|
// will be recorded. The global allocator must be replaced after because the
|
|
// act of releasing will cause the active SR to forget about all histograms
|
|
// in the relased memory.
|
|
std::unique_ptr<StatisticsRecorder> local_sr =
|
|
StatisticsRecorder::CreateTemporaryForTesting();
|
|
EXPECT_EQ(0U, StatisticsRecorder::GetHistogramCount());
|
|
std::unique_ptr<GlobalHistogramAllocator> old_allocator =
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
GlobalHistogramAllocator::CreateWithLocalMemory(kAllocatorMemorySize, 0, "");
|
|
ASSERT_TRUE(GlobalHistogramAllocator::Get());
|
|
|
|
// Create a linear histogram for merge testing.
|
|
HistogramBase* histogram1 =
|
|
LinearHistogram::FactoryGet(LinearHistogramName, 1, 10, 10, 0);
|
|
ASSERT_TRUE(histogram1);
|
|
EXPECT_EQ(1U, StatisticsRecorder::GetHistogramCount());
|
|
histogram1->Add(3);
|
|
histogram1->Add(1);
|
|
histogram1->Add(4);
|
|
histogram1->AddCount(1, 4);
|
|
histogram1->Add(6);
|
|
|
|
// Create a sparse histogram for merge testing.
|
|
HistogramBase* histogram2 =
|
|
SparseHistogram::FactoryGet(SparseHistogramName, 0);
|
|
ASSERT_TRUE(histogram2);
|
|
EXPECT_EQ(2U, StatisticsRecorder::GetHistogramCount());
|
|
histogram2->Add(3);
|
|
histogram2->Add(1);
|
|
histogram2->Add(4);
|
|
histogram2->AddCount(1, 4);
|
|
histogram2->Add(6);
|
|
|
|
// Destroy the local SR and ensure that we're back to the initial state and
|
|
// restore the global allocator. Histograms created in the local SR will
|
|
// become unmanaged.
|
|
std::unique_ptr<GlobalHistogramAllocator> new_allocator =
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
local_sr.reset();
|
|
EXPECT_EQ(starting_sr_count, StatisticsRecorder::GetHistogramCount());
|
|
GlobalHistogramAllocator::Set(std::move(old_allocator));
|
|
|
|
// Create a "recovery" allocator using the same memory as the local one.
|
|
PersistentHistogramAllocator recovery1(
|
|
std::make_unique<PersistentMemoryAllocator>(
|
|
const_cast<void*>(new_allocator->memory_allocator()->data()),
|
|
new_allocator->memory_allocator()->size(), 0, 0, "", false));
|
|
PersistentHistogramAllocator::Iterator histogram_iter1(&recovery1);
|
|
|
|
// Get the histograms that were created locally (and forgotten) and merge
|
|
// them into the global SR. New objects will be created.
|
|
std::unique_ptr<HistogramBase> recovered;
|
|
while (true) {
|
|
recovered = histogram_iter1.GetNext();
|
|
if (!recovered)
|
|
break;
|
|
|
|
recovery1.MergeHistogramDeltaToStatisticsRecorder(recovered.get());
|
|
HistogramBase* found =
|
|
StatisticsRecorder::FindHistogram(recovered->histogram_name());
|
|
EXPECT_NE(recovered.get(), found);
|
|
};
|
|
EXPECT_EQ(starting_sr_count + 2, StatisticsRecorder::GetHistogramCount());
|
|
|
|
// Check the merged histograms for accuracy.
|
|
HistogramBase* found = StatisticsRecorder::FindHistogram(LinearHistogramName);
|
|
ASSERT_TRUE(found);
|
|
std::unique_ptr<HistogramSamples> snapshot = found->SnapshotSamples();
|
|
EXPECT_EQ(found->SnapshotSamples()->TotalCount(), snapshot->TotalCount());
|
|
EXPECT_EQ(1, snapshot->GetCount(3));
|
|
EXPECT_EQ(5, snapshot->GetCount(1));
|
|
EXPECT_EQ(1, snapshot->GetCount(4));
|
|
EXPECT_EQ(1, snapshot->GetCount(6));
|
|
|
|
found = StatisticsRecorder::FindHistogram(SparseHistogramName);
|
|
ASSERT_TRUE(found);
|
|
snapshot = found->SnapshotSamples();
|
|
EXPECT_EQ(found->SnapshotSamples()->TotalCount(), snapshot->TotalCount());
|
|
EXPECT_EQ(1, snapshot->GetCount(3));
|
|
EXPECT_EQ(5, snapshot->GetCount(1));
|
|
EXPECT_EQ(1, snapshot->GetCount(4));
|
|
EXPECT_EQ(1, snapshot->GetCount(6));
|
|
|
|
// Perform additional histogram increments.
|
|
histogram1->AddCount(1, 3);
|
|
histogram1->Add(6);
|
|
histogram2->AddCount(1, 3);
|
|
histogram2->Add(7);
|
|
|
|
// Do another merge.
|
|
PersistentHistogramAllocator recovery2(
|
|
std::make_unique<PersistentMemoryAllocator>(
|
|
const_cast<void*>(new_allocator->memory_allocator()->data()),
|
|
new_allocator->memory_allocator()->size(), 0, 0, "", false));
|
|
PersistentHistogramAllocator::Iterator histogram_iter2(&recovery2);
|
|
while (true) {
|
|
recovered = histogram_iter2.GetNext();
|
|
if (!recovered)
|
|
break;
|
|
recovery2.MergeHistogramDeltaToStatisticsRecorder(recovered.get());
|
|
};
|
|
EXPECT_EQ(starting_sr_count + 2, StatisticsRecorder::GetHistogramCount());
|
|
|
|
// And verify.
|
|
found = StatisticsRecorder::FindHistogram(LinearHistogramName);
|
|
snapshot = found->SnapshotSamples();
|
|
EXPECT_EQ(found->SnapshotSamples()->TotalCount(), snapshot->TotalCount());
|
|
EXPECT_EQ(1, snapshot->GetCount(3));
|
|
EXPECT_EQ(8, snapshot->GetCount(1));
|
|
EXPECT_EQ(1, snapshot->GetCount(4));
|
|
EXPECT_EQ(2, snapshot->GetCount(6));
|
|
|
|
found = StatisticsRecorder::FindHistogram(SparseHistogramName);
|
|
snapshot = found->SnapshotSamples();
|
|
EXPECT_EQ(found->SnapshotSamples()->TotalCount(), snapshot->TotalCount());
|
|
EXPECT_EQ(1, snapshot->GetCount(3));
|
|
EXPECT_EQ(8, snapshot->GetCount(1));
|
|
EXPECT_EQ(1, snapshot->GetCount(4));
|
|
EXPECT_EQ(1, snapshot->GetCount(6));
|
|
EXPECT_EQ(1, snapshot->GetCount(7));
|
|
}
|
|
|
|
TEST_F(PersistentHistogramAllocatorTest, RangesDeDuplication) {
|
|
// This corresponds to the "ranges_ref" field of the PersistentHistogramData
|
|
// structure defined (privately) inside persistent_histogram_allocator.cc.
|
|
const int kRangesRefIndex = 5;
|
|
|
|
// Create two histograms with the same ranges.
|
|
HistogramBase* histogram1 =
|
|
Histogram::FactoryGet("TestHistogram1", 1, 1000, 10, 0);
|
|
HistogramBase* histogram2 =
|
|
Histogram::FactoryGet("TestHistogram2", 1, 1000, 10, 0);
|
|
const uint32_t ranges_ref = static_cast<Histogram*>(histogram1)
|
|
->bucket_ranges()
|
|
->persistent_reference();
|
|
ASSERT_NE(0U, ranges_ref);
|
|
EXPECT_EQ(ranges_ref, static_cast<Histogram*>(histogram2)
|
|
->bucket_ranges()
|
|
->persistent_reference());
|
|
|
|
// Make sure that the persistent data record is also correct. Two histograms
|
|
// will be fetched; other allocations are not "iterable".
|
|
PersistentMemoryAllocator::Iterator iter(allocator_);
|
|
uint32_t type;
|
|
uint32_t ref1 = iter.GetNext(&type);
|
|
uint32_t ref2 = iter.GetNext(&type);
|
|
EXPECT_EQ(0U, iter.GetNext(&type));
|
|
EXPECT_NE(0U, ref1);
|
|
EXPECT_NE(0U, ref2);
|
|
EXPECT_NE(ref1, ref2);
|
|
|
|
uint32_t* data1 =
|
|
allocator_->GetAsArray<uint32_t>(ref1, 0, kRangesRefIndex + 1);
|
|
uint32_t* data2 =
|
|
allocator_->GetAsArray<uint32_t>(ref2, 0, kRangesRefIndex + 1);
|
|
EXPECT_EQ(ranges_ref, data1[kRangesRefIndex]);
|
|
EXPECT_EQ(ranges_ref, data2[kRangesRefIndex]);
|
|
}
|
|
|
|
} // namespace base
|