| // Copyright (c) 2012 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/sparse_histogram.h" | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 |  | 
 | #include "base/metrics/histogram_base.h" | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/metrics/histogram_samples.h" | 
 | #include "base/metrics/metrics_hashes.h" | 
 | #include "base/metrics/persistent_histogram_allocator.h" | 
 | #include "base/metrics/persistent_memory_allocator.h" | 
 | #include "base/metrics/sample_map.h" | 
 | #include "base/metrics/statistics_recorder.h" | 
 | #include "base/pickle.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | // Test parameter indicates if a persistent memory allocator should be used | 
 | // for histogram allocation. False will allocate histograms from the process | 
 | // heap. | 
 | class SparseHistogramTest : public testing::TestWithParam<bool> { | 
 |  protected: | 
 |   const int32_t kAllocatorMemorySize = 8 << 20;  // 8 MiB | 
 |  | 
 |   SparseHistogramTest() : use_persistent_histogram_allocator_(GetParam()) {} | 
 |  | 
 |   void SetUp() override { | 
 |     if (use_persistent_histogram_allocator_) | 
 |       CreatePersistentMemoryAllocator(); | 
 |  | 
 |     // Each test will have a clean state (no Histogram / BucketRanges | 
 |     // registered). | 
 |     InitializeStatisticsRecorder(); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     if (allocator_) { | 
 |       ASSERT_FALSE(allocator_->IsFull()); | 
 |       ASSERT_FALSE(allocator_->IsCorrupt()); | 
 |     } | 
 |     UninitializeStatisticsRecorder(); | 
 |     DestroyPersistentMemoryAllocator(); | 
 |   } | 
 |  | 
 |   void InitializeStatisticsRecorder() { | 
 |     DCHECK(!statistics_recorder_); | 
 |     statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   } | 
 |  | 
 |   void UninitializeStatisticsRecorder() { | 
 |     statistics_recorder_.reset(); | 
 |   } | 
 |  | 
 |   void CreatePersistentMemoryAllocator() { | 
 |     GlobalHistogramAllocator::CreateWithLocalMemory( | 
 |         kAllocatorMemorySize, 0, "SparseHistogramAllocatorTest"); | 
 |     allocator_ = GlobalHistogramAllocator::Get()->memory_allocator(); | 
 |   } | 
 |  | 
 |   void DestroyPersistentMemoryAllocator() { | 
 |     allocator_ = nullptr; | 
 |     GlobalHistogramAllocator::ReleaseForTesting(); | 
 |   } | 
 |  | 
 |   std::unique_ptr<SparseHistogram> NewSparseHistogram(const char* name) { | 
 |     // std::make_unique can't access protected ctor so do it manually. This | 
 |     // test class is a friend so can access it. | 
 |     return std::unique_ptr<SparseHistogram>(new SparseHistogram(name)); | 
 |   } | 
 |  | 
 |   const bool use_persistent_histogram_allocator_; | 
 |  | 
 |   std::unique_ptr<StatisticsRecorder> statistics_recorder_; | 
 |   PersistentMemoryAllocator* allocator_ = nullptr; | 
 |  | 
 |  private: | 
 |   DISALLOW_COPY_AND_ASSIGN(SparseHistogramTest); | 
 | }; | 
 |  | 
 | // Run all HistogramTest cases with both heap and persistent memory. | 
 | INSTANTIATE_TEST_CASE_P(HeapAndPersistent, | 
 |                         SparseHistogramTest, | 
 |                         testing::Bool()); | 
 |  | 
 |  | 
 | TEST_P(SparseHistogramTest, BasicTest) { | 
 |   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse")); | 
 |   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(0, snapshot->TotalCount()); | 
 |   EXPECT_EQ(0, snapshot->sum()); | 
 |  | 
 |   histogram->Add(100); | 
 |   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(1, snapshot1->TotalCount()); | 
 |   EXPECT_EQ(1, snapshot1->GetCount(100)); | 
 |  | 
 |   histogram->Add(100); | 
 |   histogram->Add(101); | 
 |   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(3, snapshot2->TotalCount()); | 
 |   EXPECT_EQ(2, snapshot2->GetCount(100)); | 
 |   EXPECT_EQ(1, snapshot2->GetCount(101)); | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, BasicTestAddCount) { | 
 |   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse")); | 
 |   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(0, snapshot->TotalCount()); | 
 |   EXPECT_EQ(0, snapshot->sum()); | 
 |  | 
 |   histogram->AddCount(100, 15); | 
 |   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(15, snapshot1->TotalCount()); | 
 |   EXPECT_EQ(15, snapshot1->GetCount(100)); | 
 |  | 
 |   histogram->AddCount(100, 15); | 
 |   histogram->AddCount(101, 25); | 
 |   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(55, snapshot2->TotalCount()); | 
 |   EXPECT_EQ(30, snapshot2->GetCount(100)); | 
 |   EXPECT_EQ(25, snapshot2->GetCount(101)); | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, AddCount_LargeValuesDontOverflow) { | 
 |   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse")); | 
 |   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(0, snapshot->TotalCount()); | 
 |   EXPECT_EQ(0, snapshot->sum()); | 
 |  | 
 |   histogram->AddCount(1000000000, 15); | 
 |   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(15, snapshot1->TotalCount()); | 
 |   EXPECT_EQ(15, snapshot1->GetCount(1000000000)); | 
 |  | 
 |   histogram->AddCount(1000000000, 15); | 
 |   histogram->AddCount(1010000000, 25); | 
 |   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(55, snapshot2->TotalCount()); | 
 |   EXPECT_EQ(30, snapshot2->GetCount(1000000000)); | 
 |   EXPECT_EQ(25, snapshot2->GetCount(1010000000)); | 
 |   EXPECT_EQ(55250000000LL, snapshot2->sum()); | 
 | } | 
 |  | 
 | // Make sure that counts returned by Histogram::SnapshotDelta do not overflow | 
 | // even when a total count (returned by Histogram::SnapshotSample) does. | 
 | TEST_P(SparseHistogramTest, AddCount_LargeCountsDontOverflow) { | 
 |   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse")); | 
 |   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); | 
 |   EXPECT_EQ(0, snapshot->TotalCount()); | 
 |   EXPECT_EQ(0, snapshot->sum()); | 
 |  | 
 |   const int count = (1 << 30) - 1; | 
 |  | 
 |   // Repeat N times to make sure that there is no internal value overflow. | 
 |   for (int i = 0; i < 10; ++i) { | 
 |     histogram->AddCount(42, count); | 
 |     std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta(); | 
 |     EXPECT_EQ(count, samples->TotalCount()); | 
 |     EXPECT_EQ(count, samples->GetCount(42)); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, MacroBasicTest) { | 
 |   UmaHistogramSparse("Sparse", 100); | 
 |   UmaHistogramSparse("Sparse", 200); | 
 |   UmaHistogramSparse("Sparse", 100); | 
 |  | 
 |   const StatisticsRecorder::Histograms histograms = | 
 |       StatisticsRecorder::GetHistograms(); | 
 |  | 
 |   ASSERT_THAT(histograms, testing::SizeIs(1)); | 
 |   const HistogramBase* const sparse_histogram = histograms[0]; | 
 |  | 
 |   EXPECT_EQ(SPARSE_HISTOGRAM, sparse_histogram->GetHistogramType()); | 
 |   EXPECT_EQ("Sparse", StringPiece(sparse_histogram->histogram_name())); | 
 |   EXPECT_EQ( | 
 |       HistogramBase::kUmaTargetedHistogramFlag | | 
 |           (use_persistent_histogram_allocator_ ? HistogramBase::kIsPersistent | 
 |                                                : 0), | 
 |       sparse_histogram->flags()); | 
 |  | 
 |   std::unique_ptr<HistogramSamples> samples = | 
 |       sparse_histogram->SnapshotSamples(); | 
 |   EXPECT_EQ(3, samples->TotalCount()); | 
 |   EXPECT_EQ(2, samples->GetCount(100)); | 
 |   EXPECT_EQ(1, samples->GetCount(200)); | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, MacroInLoopTest) { | 
 |   // Unlike the macros in histogram.h, SparseHistogram macros can have a | 
 |   // variable as histogram name. | 
 |   for (int i = 0; i < 2; i++) { | 
 |     UmaHistogramSparse(StringPrintf("Sparse%d", i), 100); | 
 |   } | 
 |  | 
 |   const StatisticsRecorder::Histograms histograms = | 
 |       StatisticsRecorder::Sort(StatisticsRecorder::GetHistograms()); | 
 |   ASSERT_THAT(histograms, testing::SizeIs(2)); | 
 |   EXPECT_STREQ(histograms[0]->histogram_name(), "Sparse0"); | 
 |   EXPECT_STREQ(histograms[1]->histogram_name(), "Sparse1"); | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, Serialize) { | 
 |   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse")); | 
 |   histogram->SetFlags(HistogramBase::kIPCSerializationSourceFlag); | 
 |  | 
 |   Pickle pickle; | 
 |   histogram->SerializeInfo(&pickle); | 
 |  | 
 |   PickleIterator iter(pickle); | 
 |  | 
 |   int type; | 
 |   EXPECT_TRUE(iter.ReadInt(&type)); | 
 |   EXPECT_EQ(SPARSE_HISTOGRAM, type); | 
 |  | 
 |   std::string name; | 
 |   EXPECT_TRUE(iter.ReadString(&name)); | 
 |   EXPECT_EQ("Sparse", name); | 
 |  | 
 |   int flag; | 
 |   EXPECT_TRUE(iter.ReadInt(&flag)); | 
 |   EXPECT_EQ(HistogramBase::kIPCSerializationSourceFlag, flag); | 
 |  | 
 |   // No more data in the pickle. | 
 |   EXPECT_FALSE(iter.SkipBytes(1)); | 
 | } | 
 |  | 
 | // Ensure that race conditions that cause multiple, identical sparse histograms | 
 | // to be created will safely resolve to a single one. | 
 | TEST_P(SparseHistogramTest, DuplicationSafety) { | 
 |   const char histogram_name[] = "Duplicated"; | 
 |   size_t histogram_count = StatisticsRecorder::GetHistogramCount(); | 
 |  | 
 |   // Create a histogram that we will later duplicate. | 
 |   HistogramBase* original = | 
 |       SparseHistogram::FactoryGet(histogram_name, HistogramBase::kNoFlags); | 
 |   ++histogram_count; | 
 |   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount()); | 
 |   original->Add(1); | 
 |  | 
 |   // Create a duplicate. This has to happen differently depending on where the | 
 |   // memory is taken from. | 
 |   if (use_persistent_histogram_allocator_) { | 
 |     // To allocate from persistent memory, clear the last_created reference in | 
 |     // the GlobalHistogramAllocator. This will cause an Import to recreate | 
 |     // the just-created histogram which will then be released as a duplicate. | 
 |     GlobalHistogramAllocator::Get()->ClearLastCreatedReferenceForTesting(); | 
 |     // Creating a different histogram will first do an Import to ensure it | 
 |     // hasn't been created elsewhere, triggering the duplication and release. | 
 |     SparseHistogram::FactoryGet("something.new", HistogramBase::kNoFlags); | 
 |     ++histogram_count; | 
 |   } else { | 
 |     // To allocate from the heap, just call the (private) constructor directly. | 
 |     // Delete it immediately like would have happened within FactoryGet(); | 
 |     std::unique_ptr<SparseHistogram> something = | 
 |         NewSparseHistogram(histogram_name); | 
 |     DCHECK_NE(original, something.get()); | 
 |   } | 
 |   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount()); | 
 |  | 
 |   // Re-creating the histogram via FactoryGet() will return the same one. | 
 |   HistogramBase* duplicate = | 
 |       SparseHistogram::FactoryGet(histogram_name, HistogramBase::kNoFlags); | 
 |   DCHECK_EQ(original, duplicate); | 
 |   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount()); | 
 |   duplicate->Add(2); | 
 |  | 
 |   // Ensure that original histograms are still cross-functional. | 
 |   original->Add(2); | 
 |   duplicate->Add(1); | 
 |   std::unique_ptr<HistogramSamples> snapshot_orig = original->SnapshotSamples(); | 
 |   std::unique_ptr<HistogramSamples> snapshot_dup = duplicate->SnapshotSamples(); | 
 |   DCHECK_EQ(2, snapshot_orig->GetCount(2)); | 
 |   DCHECK_EQ(2, snapshot_dup->GetCount(1)); | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, FactoryTime) { | 
 |   const int kTestCreateCount = 1 << 10;  // Must be power-of-2. | 
 |   const int kTestLookupCount = 100000; | 
 |   const int kTestAddCount = 100000; | 
 |  | 
 |   // Create all histogram names in advance for accurate timing below. | 
 |   std::vector<std::string> histogram_names; | 
 |   for (int i = 0; i < kTestCreateCount; ++i) { | 
 |     histogram_names.push_back( | 
 |         StringPrintf("TestHistogram.%d", i % kTestCreateCount)); | 
 |   } | 
 |  | 
 |   // Calculate cost of creating histograms. | 
 |   TimeTicks create_start = TimeTicks::Now(); | 
 |   for (int i = 0; i < kTestCreateCount; ++i) | 
 |     SparseHistogram::FactoryGet(histogram_names[i], HistogramBase::kNoFlags); | 
 |   TimeDelta create_ticks = TimeTicks::Now() - create_start; | 
 |   int64_t create_ms = create_ticks.InMilliseconds(); | 
 |  | 
 |   VLOG(1) << kTestCreateCount << " histogram creations took " << create_ms | 
 |           << "ms or about " | 
 |           << (create_ms * 1000000) / kTestCreateCount | 
 |           << "ns each."; | 
 |  | 
 |   // Calculate cost of looking up existing histograms. | 
 |   TimeTicks lookup_start = TimeTicks::Now(); | 
 |   for (int i = 0; i < kTestLookupCount; ++i) { | 
 |     // 6007 is co-prime with kTestCreateCount and so will do lookups in an | 
 |     // order less likely to be cacheable (but still hit them all) should the | 
 |     // underlying storage use the exact histogram name as the key. | 
 |     const int i_mult = 6007; | 
 |     static_assert(i_mult < INT_MAX / kTestCreateCount, "Multiplier too big"); | 
 |     int index = (i * i_mult) & (kTestCreateCount - 1); | 
 |     SparseHistogram::FactoryGet(histogram_names[index], | 
 |                                 HistogramBase::kNoFlags); | 
 |   } | 
 |   TimeDelta lookup_ticks = TimeTicks::Now() - lookup_start; | 
 |   int64_t lookup_ms = lookup_ticks.InMilliseconds(); | 
 |  | 
 |   VLOG(1) << kTestLookupCount << " histogram lookups took " << lookup_ms | 
 |           << "ms or about " | 
 |           << (lookup_ms * 1000000) / kTestLookupCount | 
 |           << "ns each."; | 
 |  | 
 |   // Calculate cost of accessing histograms. | 
 |   HistogramBase* histogram = | 
 |       SparseHistogram::FactoryGet(histogram_names[0], HistogramBase::kNoFlags); | 
 |   ASSERT_TRUE(histogram); | 
 |   TimeTicks add_start = TimeTicks::Now(); | 
 |   for (int i = 0; i < kTestAddCount; ++i) | 
 |     histogram->Add(i & 127); | 
 |   TimeDelta add_ticks = TimeTicks::Now() - add_start; | 
 |   int64_t add_ms = add_ticks.InMilliseconds(); | 
 |  | 
 |   VLOG(1) << kTestAddCount << " histogram adds took " << add_ms | 
 |           << "ms or about " | 
 |           << (add_ms * 1000000) / kTestAddCount | 
 |           << "ns each."; | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, ExtremeValues) { | 
 |   static const struct { | 
 |     Histogram::Sample sample; | 
 |     int64_t expected_max; | 
 |   } cases[] = { | 
 |       // Note: We use -2147483647 - 1 rather than -2147483648 because the later | 
 |       // is interpreted as - operator applied to 2147483648 and the latter can't | 
 |       // be represented as an int32 and causes a warning. | 
 |       {-2147483647 - 1, -2147483647LL}, | 
 |       {0, 1}, | 
 |       {2147483647, 2147483648LL}, | 
 |   }; | 
 |  | 
 |   for (size_t i = 0; i < arraysize(cases); ++i) { | 
 |     HistogramBase* histogram = | 
 |         SparseHistogram::FactoryGet(StringPrintf("ExtremeValues_%zu", i), | 
 |                                     HistogramBase::kUmaTargetedHistogramFlag); | 
 |     histogram->Add(cases[i].sample); | 
 |  | 
 |     std::unique_ptr<HistogramSamples> snapshot = histogram->SnapshotSamples(); | 
 |     std::unique_ptr<SampleCountIterator> it = snapshot->Iterator(); | 
 |     ASSERT_FALSE(it->Done()); | 
 |  | 
 |     base::Histogram::Sample min; | 
 |     int64_t max; | 
 |     base::Histogram::Count count; | 
 |     it->Get(&min, &max, &count); | 
 |  | 
 |     EXPECT_EQ(1, count); | 
 |     EXPECT_EQ(cases[i].sample, min); | 
 |     EXPECT_EQ(cases[i].expected_max, max); | 
 |  | 
 |     it->Next(); | 
 |     EXPECT_TRUE(it->Done()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_P(SparseHistogramTest, HistogramNameHash) { | 
 |   const char kName[] = "TestName"; | 
 |   HistogramBase* histogram = SparseHistogram::FactoryGet( | 
 |       kName, HistogramBase::kUmaTargetedHistogramFlag); | 
 |   EXPECT_EQ(histogram->name_hash(), HashMetricName(kName)); | 
 | } | 
 |  | 
 | }  // namespace base |