You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
14 KiB
389 lines
14 KiB
// 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
|