|  | // 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 |