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.
883 lines
31 KiB
883 lines
31 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/histogram.h"
|
|
|
|
#include <limits.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <climits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "base/lazy_instance.h"
|
|
#include "base/logging.h"
|
|
#include "base/metrics/bucket_ranges.h"
|
|
#include "base/metrics/dummy_histogram.h"
|
|
#include "base/metrics/histogram_macros.h"
|
|
#include "base/metrics/metrics_hashes.h"
|
|
#include "base/metrics/persistent_histogram_allocator.h"
|
|
#include "base/metrics/persistent_memory_allocator.h"
|
|
#include "base/metrics/record_histogram_checker.h"
|
|
#include "base/metrics/sample_vector.h"
|
|
#include "base/metrics/statistics_recorder.h"
|
|
#include "base/pickle.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/test/gtest_util.h"
|
|
#include "base/time/time.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
namespace {
|
|
|
|
const char kExpiredHistogramName[] = "ExpiredHistogram";
|
|
|
|
// Test implementation of RecordHistogramChecker interface.
|
|
class TestRecordHistogramChecker : public RecordHistogramChecker {
|
|
public:
|
|
~TestRecordHistogramChecker() override = default;
|
|
|
|
// RecordHistogramChecker:
|
|
bool ShouldRecord(uint64_t histogram_hash) const override {
|
|
return histogram_hash != HashMetricName(kExpiredHistogramName);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Test parameter indicates if a persistent memory allocator should be used
|
|
// for histogram allocation. False will allocate histograms from the process
|
|
// heap.
|
|
class HistogramTest : public testing::TestWithParam<bool> {
|
|
protected:
|
|
const int32_t kAllocatorMemorySize = 8 << 20; // 8 MiB
|
|
|
|
HistogramTest() : use_persistent_histogram_allocator_(GetParam()) {}
|
|
|
|
void SetUp() override {
|
|
if (use_persistent_histogram_allocator_)
|
|
CreatePersistentHistogramAllocator();
|
|
|
|
// 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();
|
|
DestroyPersistentHistogramAllocator();
|
|
}
|
|
|
|
void InitializeStatisticsRecorder() {
|
|
DCHECK(!statistics_recorder_);
|
|
statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
|
|
}
|
|
|
|
void UninitializeStatisticsRecorder() {
|
|
statistics_recorder_.reset();
|
|
}
|
|
|
|
void CreatePersistentHistogramAllocator() {
|
|
GlobalHistogramAllocator::CreateWithLocalMemory(
|
|
kAllocatorMemorySize, 0, "HistogramAllocatorTest");
|
|
allocator_ = GlobalHistogramAllocator::Get()->memory_allocator();
|
|
}
|
|
|
|
void DestroyPersistentHistogramAllocator() {
|
|
allocator_ = nullptr;
|
|
GlobalHistogramAllocator::ReleaseForTesting();
|
|
}
|
|
|
|
std::unique_ptr<SampleVector> SnapshotAllSamples(Histogram* h) {
|
|
return h->SnapshotAllSamples();
|
|
}
|
|
|
|
const bool use_persistent_histogram_allocator_;
|
|
|
|
std::unique_ptr<StatisticsRecorder> statistics_recorder_;
|
|
std::unique_ptr<char[]> allocator_memory_;
|
|
PersistentMemoryAllocator* allocator_ = nullptr;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(HistogramTest);
|
|
};
|
|
|
|
// Run all HistogramTest cases with both heap and persistent memory.
|
|
INSTANTIATE_TEST_CASE_P(HeapAndPersistent, HistogramTest, testing::Bool());
|
|
|
|
|
|
// Check for basic syntax and use.
|
|
TEST_P(HistogramTest, BasicTest) {
|
|
// Try basic construction
|
|
HistogramBase* histogram = Histogram::FactoryGet(
|
|
"TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(histogram);
|
|
|
|
HistogramBase* linear_histogram = LinearHistogram::FactoryGet(
|
|
"TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(linear_histogram);
|
|
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(1);
|
|
custom_ranges.push_back(5);
|
|
HistogramBase* custom_histogram = CustomHistogram::FactoryGet(
|
|
"TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(custom_histogram);
|
|
|
|
// Macros that create histograms have an internal static variable which will
|
|
// continue to point to those from the very first run of this method even
|
|
// during subsequent runs.
|
|
static bool already_run = false;
|
|
if (already_run)
|
|
return;
|
|
already_run = true;
|
|
|
|
// Use standard macros (but with fixed samples)
|
|
LOCAL_HISTOGRAM_TIMES("Test2Histogram", TimeDelta::FromDays(1));
|
|
LOCAL_HISTOGRAM_COUNTS("Test3Histogram", 30);
|
|
|
|
LOCAL_HISTOGRAM_ENUMERATION("Test6Histogram", 129, 130);
|
|
}
|
|
|
|
// Check that the macro correctly matches histograms by name and records their
|
|
// data together.
|
|
TEST_P(HistogramTest, NameMatchTest) {
|
|
// Macros that create histograms have an internal static variable which will
|
|
// continue to point to those from the very first run of this method even
|
|
// during subsequent runs.
|
|
static bool already_run = false;
|
|
if (already_run)
|
|
return;
|
|
already_run = true;
|
|
|
|
LOCAL_HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10);
|
|
LOCAL_HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10);
|
|
HistogramBase* histogram = LinearHistogram::FactoryGet(
|
|
"DuplicatedHistogram", 1, 101, 102, HistogramBase::kNoFlags);
|
|
|
|
std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
EXPECT_EQ(2, samples->GetCount(10));
|
|
}
|
|
|
|
// Check that delta calculations work correctly.
|
|
TEST_P(HistogramTest, DeltaTest) {
|
|
HistogramBase* histogram =
|
|
Histogram::FactoryGet("DeltaHistogram", 1, 64, 8,
|
|
HistogramBase::kNoFlags);
|
|
histogram->Add(1);
|
|
histogram->Add(10);
|
|
histogram->Add(50);
|
|
|
|
std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
|
|
EXPECT_EQ(3, samples->TotalCount());
|
|
EXPECT_EQ(1, samples->GetCount(1));
|
|
EXPECT_EQ(1, samples->GetCount(10));
|
|
EXPECT_EQ(1, samples->GetCount(50));
|
|
EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
|
|
|
|
samples = histogram->SnapshotDelta();
|
|
EXPECT_EQ(0, samples->TotalCount());
|
|
|
|
histogram->Add(10);
|
|
histogram->Add(10);
|
|
samples = histogram->SnapshotDelta();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
EXPECT_EQ(2, samples->GetCount(10));
|
|
|
|
samples = histogram->SnapshotDelta();
|
|
EXPECT_EQ(0, samples->TotalCount());
|
|
}
|
|
|
|
// Check that final-delta calculations work correctly.
|
|
TEST_P(HistogramTest, FinalDeltaTest) {
|
|
HistogramBase* histogram =
|
|
Histogram::FactoryGet("FinalDeltaHistogram", 1, 64, 8,
|
|
HistogramBase::kNoFlags);
|
|
histogram->Add(1);
|
|
histogram->Add(10);
|
|
histogram->Add(50);
|
|
|
|
std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
|
|
EXPECT_EQ(3, samples->TotalCount());
|
|
EXPECT_EQ(1, samples->GetCount(1));
|
|
EXPECT_EQ(1, samples->GetCount(10));
|
|
EXPECT_EQ(1, samples->GetCount(50));
|
|
EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
|
|
|
|
histogram->Add(2);
|
|
histogram->Add(50);
|
|
|
|
samples = histogram->SnapshotFinalDelta();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
EXPECT_EQ(1, samples->GetCount(2));
|
|
EXPECT_EQ(1, samples->GetCount(50));
|
|
EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
|
|
}
|
|
|
|
TEST_P(HistogramTest, ExponentialRangesTest) {
|
|
// Check that we got a nice exponential when there was enough room.
|
|
BucketRanges ranges(9);
|
|
Histogram::InitializeBucketRanges(1, 64, &ranges);
|
|
EXPECT_EQ(0, ranges.range(0));
|
|
int power_of_2 = 1;
|
|
for (int i = 1; i < 8; i++) {
|
|
EXPECT_EQ(power_of_2, ranges.range(i));
|
|
power_of_2 *= 2;
|
|
}
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8));
|
|
|
|
// Check the corresponding Histogram will use the correct ranges.
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags));
|
|
EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges()));
|
|
|
|
// When bucket count is limited, exponential ranges will partially look like
|
|
// linear.
|
|
BucketRanges ranges2(16);
|
|
Histogram::InitializeBucketRanges(1, 32, &ranges2);
|
|
|
|
EXPECT_EQ(0, ranges2.range(0));
|
|
EXPECT_EQ(1, ranges2.range(1));
|
|
EXPECT_EQ(2, ranges2.range(2));
|
|
EXPECT_EQ(3, ranges2.range(3));
|
|
EXPECT_EQ(4, ranges2.range(4));
|
|
EXPECT_EQ(5, ranges2.range(5));
|
|
EXPECT_EQ(6, ranges2.range(6));
|
|
EXPECT_EQ(7, ranges2.range(7));
|
|
EXPECT_EQ(9, ranges2.range(8));
|
|
EXPECT_EQ(11, ranges2.range(9));
|
|
EXPECT_EQ(14, ranges2.range(10));
|
|
EXPECT_EQ(17, ranges2.range(11));
|
|
EXPECT_EQ(21, ranges2.range(12));
|
|
EXPECT_EQ(26, ranges2.range(13));
|
|
EXPECT_EQ(32, ranges2.range(14));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(15));
|
|
|
|
// Check the corresponding Histogram will use the correct ranges.
|
|
Histogram* histogram2 = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram2", 1, 32, 15, HistogramBase::kNoFlags));
|
|
EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges()));
|
|
}
|
|
|
|
TEST_P(HistogramTest, LinearRangesTest) {
|
|
BucketRanges ranges(9);
|
|
LinearHistogram::InitializeBucketRanges(1, 7, &ranges);
|
|
// Gets a nice linear set of bucket ranges.
|
|
for (int i = 0; i < 8; i++)
|
|
EXPECT_EQ(i, ranges.range(i));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8));
|
|
|
|
// The correspoding LinearHistogram should use the correct ranges.
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
LinearHistogram::FactoryGet("Linear", 1, 7, 8, HistogramBase::kNoFlags));
|
|
EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges()));
|
|
|
|
// Linear ranges are not divisible.
|
|
BucketRanges ranges2(6);
|
|
LinearHistogram::InitializeBucketRanges(1, 6, &ranges2);
|
|
EXPECT_EQ(0, ranges2.range(0));
|
|
EXPECT_EQ(1, ranges2.range(1));
|
|
EXPECT_EQ(3, ranges2.range(2));
|
|
EXPECT_EQ(4, ranges2.range(3));
|
|
EXPECT_EQ(6, ranges2.range(4));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(5));
|
|
// The correspoding LinearHistogram should use the correct ranges.
|
|
Histogram* histogram2 = static_cast<Histogram*>(
|
|
LinearHistogram::FactoryGet("Linear2", 1, 6, 5, HistogramBase::kNoFlags));
|
|
EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges()));
|
|
}
|
|
|
|
TEST_P(HistogramTest, ArrayToCustomEnumRangesTest) {
|
|
const HistogramBase::Sample ranges[3] = {5, 10, 20};
|
|
std::vector<HistogramBase::Sample> ranges_vec =
|
|
CustomHistogram::ArrayToCustomEnumRanges(ranges);
|
|
ASSERT_EQ(6u, ranges_vec.size());
|
|
EXPECT_EQ(5, ranges_vec[0]);
|
|
EXPECT_EQ(6, ranges_vec[1]);
|
|
EXPECT_EQ(10, ranges_vec[2]);
|
|
EXPECT_EQ(11, ranges_vec[3]);
|
|
EXPECT_EQ(20, ranges_vec[4]);
|
|
EXPECT_EQ(21, ranges_vec[5]);
|
|
}
|
|
|
|
TEST_P(HistogramTest, CustomHistogramTest) {
|
|
// A well prepared custom ranges.
|
|
std::vector<HistogramBase::Sample> custom_ranges;
|
|
custom_ranges.push_back(1);
|
|
custom_ranges.push_back(2);
|
|
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet("TestCustomHistogram1", custom_ranges,
|
|
HistogramBase::kNoFlags));
|
|
const BucketRanges* ranges = histogram->bucket_ranges();
|
|
ASSERT_EQ(4u, ranges->size());
|
|
EXPECT_EQ(0, ranges->range(0)); // Auto added.
|
|
EXPECT_EQ(1, ranges->range(1));
|
|
EXPECT_EQ(2, ranges->range(2));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); // Auto added.
|
|
|
|
// A unordered custom ranges.
|
|
custom_ranges.clear();
|
|
custom_ranges.push_back(2);
|
|
custom_ranges.push_back(1);
|
|
histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet("TestCustomHistogram2", custom_ranges,
|
|
HistogramBase::kNoFlags));
|
|
ranges = histogram->bucket_ranges();
|
|
ASSERT_EQ(4u, ranges->size());
|
|
EXPECT_EQ(0, ranges->range(0));
|
|
EXPECT_EQ(1, ranges->range(1));
|
|
EXPECT_EQ(2, ranges->range(2));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3));
|
|
|
|
// A custom ranges with duplicated values.
|
|
custom_ranges.clear();
|
|
custom_ranges.push_back(4);
|
|
custom_ranges.push_back(1);
|
|
custom_ranges.push_back(4);
|
|
histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet("TestCustomHistogram3", custom_ranges,
|
|
HistogramBase::kNoFlags));
|
|
ranges = histogram->bucket_ranges();
|
|
ASSERT_EQ(4u, ranges->size());
|
|
EXPECT_EQ(0, ranges->range(0));
|
|
EXPECT_EQ(1, ranges->range(1));
|
|
EXPECT_EQ(4, ranges->range(2));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3));
|
|
}
|
|
|
|
TEST_P(HistogramTest, CustomHistogramWithOnly2Buckets) {
|
|
// This test exploits the fact that the CustomHistogram can have 2 buckets,
|
|
// while the base class Histogram is *supposed* to have at least 3 buckets.
|
|
// We should probably change the restriction on the base class (or not inherit
|
|
// the base class!).
|
|
|
|
std::vector<HistogramBase::Sample> custom_ranges;
|
|
custom_ranges.push_back(4);
|
|
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet("2BucketsCustomHistogram", custom_ranges,
|
|
HistogramBase::kNoFlags));
|
|
const BucketRanges* ranges = histogram->bucket_ranges();
|
|
ASSERT_EQ(3u, ranges->size());
|
|
EXPECT_EQ(0, ranges->range(0));
|
|
EXPECT_EQ(4, ranges->range(1));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2));
|
|
}
|
|
|
|
TEST_P(HistogramTest, AddCountTest) {
|
|
const size_t kBucketCount = 50;
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("AddCountHistogram", 10, 100, kBucketCount,
|
|
HistogramBase::kNoFlags));
|
|
|
|
histogram->AddCount(20, 15);
|
|
histogram->AddCount(30, 14);
|
|
|
|
std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
|
|
EXPECT_EQ(29, samples->TotalCount());
|
|
EXPECT_EQ(15, samples->GetCount(20));
|
|
EXPECT_EQ(14, samples->GetCount(30));
|
|
|
|
histogram->AddCount(20, 25);
|
|
histogram->AddCount(30, 24);
|
|
|
|
std::unique_ptr<HistogramSamples> samples2 = histogram->SnapshotSamples();
|
|
EXPECT_EQ(78, samples2->TotalCount());
|
|
EXPECT_EQ(40, samples2->GetCount(20));
|
|
EXPECT_EQ(38, samples2->GetCount(30));
|
|
}
|
|
|
|
TEST_P(HistogramTest, AddCount_LargeValuesDontOverflow) {
|
|
const size_t kBucketCount = 50;
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("AddCountHistogram", 10, 1000000000, kBucketCount,
|
|
HistogramBase::kNoFlags));
|
|
|
|
histogram->AddCount(200000000, 15);
|
|
histogram->AddCount(300000000, 14);
|
|
|
|
std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
|
|
EXPECT_EQ(29, samples->TotalCount());
|
|
EXPECT_EQ(15, samples->GetCount(200000000));
|
|
EXPECT_EQ(14, samples->GetCount(300000000));
|
|
|
|
histogram->AddCount(200000000, 25);
|
|
histogram->AddCount(300000000, 24);
|
|
|
|
std::unique_ptr<HistogramSamples> samples2 = histogram->SnapshotSamples();
|
|
EXPECT_EQ(78, samples2->TotalCount());
|
|
EXPECT_EQ(40, samples2->GetCount(200000000));
|
|
EXPECT_EQ(38, samples2->GetCount(300000000));
|
|
EXPECT_EQ(19400000000LL, samples2->sum());
|
|
}
|
|
|
|
// Some metrics are designed so that they are guaranteed not to overflow between
|
|
// snapshots, but could overflow over a long-running session.
|
|
// Make sure that counts returned by Histogram::SnapshotDelta do not overflow
|
|
// even when a total count (returned by Histogram::SnapshotSample) does.
|
|
TEST_P(HistogramTest, AddCount_LargeCountsDontOverflow) {
|
|
const size_t kBucketCount = 10;
|
|
Histogram* histogram = static_cast<Histogram*>(Histogram::FactoryGet(
|
|
"AddCountHistogram", 10, 50, kBucketCount, HistogramBase::kNoFlags));
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
// Make sure histogram handles out-of-bounds data gracefully.
|
|
TEST_P(HistogramTest, BoundsTest) {
|
|
const size_t kBucketCount = 50;
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Bounded", 10, 100, kBucketCount,
|
|
HistogramBase::kNoFlags));
|
|
|
|
// Put two samples "out of bounds" above and below.
|
|
histogram->Add(5);
|
|
histogram->Add(-50);
|
|
|
|
histogram->Add(100);
|
|
histogram->Add(10000);
|
|
|
|
// Verify they landed in the underflow, and overflow buckets.
|
|
std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
|
|
EXPECT_EQ(2, samples->GetCountAtIndex(0));
|
|
EXPECT_EQ(0, samples->GetCountAtIndex(1));
|
|
size_t array_size = histogram->bucket_count();
|
|
EXPECT_EQ(kBucketCount, array_size);
|
|
EXPECT_EQ(0, samples->GetCountAtIndex(array_size - 2));
|
|
EXPECT_EQ(2, samples->GetCountAtIndex(array_size - 1));
|
|
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(10);
|
|
custom_ranges.push_back(50);
|
|
custom_ranges.push_back(100);
|
|
Histogram* test_custom_histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet("TestCustomRangeBoundedHistogram",
|
|
custom_ranges, HistogramBase::kNoFlags));
|
|
|
|
// Put two samples "out of bounds" above and below.
|
|
test_custom_histogram->Add(5);
|
|
test_custom_histogram->Add(-50);
|
|
test_custom_histogram->Add(100);
|
|
test_custom_histogram->Add(1000);
|
|
test_custom_histogram->Add(INT_MAX);
|
|
|
|
// Verify they landed in the underflow, and overflow buckets.
|
|
std::unique_ptr<SampleVector> custom_samples =
|
|
test_custom_histogram->SnapshotAllSamples();
|
|
EXPECT_EQ(2, custom_samples->GetCountAtIndex(0));
|
|
EXPECT_EQ(0, custom_samples->GetCountAtIndex(1));
|
|
size_t bucket_count = test_custom_histogram->bucket_count();
|
|
EXPECT_EQ(0, custom_samples->GetCountAtIndex(bucket_count - 2));
|
|
EXPECT_EQ(3, custom_samples->GetCountAtIndex(bucket_count - 1));
|
|
}
|
|
|
|
// Check to be sure samples land as expected is "correct" buckets.
|
|
TEST_P(HistogramTest, BucketPlacementTest) {
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags));
|
|
|
|
// Add i+1 samples to the i'th bucket.
|
|
histogram->Add(0);
|
|
int power_of_2 = 1;
|
|
for (int i = 1; i < 8; i++) {
|
|
for (int j = 0; j <= i; j++)
|
|
histogram->Add(power_of_2);
|
|
power_of_2 *= 2;
|
|
}
|
|
|
|
// Check to see that the bucket counts reflect our additions.
|
|
std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
|
|
for (int i = 0; i < 8; i++)
|
|
EXPECT_EQ(i + 1, samples->GetCountAtIndex(i));
|
|
}
|
|
|
|
TEST_P(HistogramTest, CorruptSampleCounts) {
|
|
// The internal code creates histograms via macros and thus keeps static
|
|
// pointers to them. If those pointers are to persistent memory which will
|
|
// be free'd then any following calls to that code will crash with a
|
|
// segmentation violation.
|
|
if (use_persistent_histogram_allocator_)
|
|
return;
|
|
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags));
|
|
|
|
// Add some samples.
|
|
histogram->Add(20);
|
|
histogram->Add(40);
|
|
|
|
std::unique_ptr<SampleVector> snapshot = histogram->SnapshotAllSamples();
|
|
EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
|
|
histogram->FindCorruption(*snapshot));
|
|
EXPECT_EQ(2, snapshot->redundant_count());
|
|
EXPECT_EQ(2, snapshot->TotalCount());
|
|
|
|
snapshot->counts()[3] += 100; // Sample count won't match redundant count.
|
|
EXPECT_EQ(HistogramBase::COUNT_LOW_ERROR,
|
|
histogram->FindCorruption(*snapshot));
|
|
snapshot->counts()[2] -= 200;
|
|
EXPECT_EQ(HistogramBase::COUNT_HIGH_ERROR,
|
|
histogram->FindCorruption(*snapshot));
|
|
|
|
// But we can't spot a corruption if it is compensated for.
|
|
snapshot->counts()[1] += 100;
|
|
EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
|
|
histogram->FindCorruption(*snapshot));
|
|
}
|
|
|
|
TEST_P(HistogramTest, CorruptBucketBounds) {
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags));
|
|
|
|
std::unique_ptr<HistogramSamples> snapshot = histogram->SnapshotSamples();
|
|
EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
|
|
histogram->FindCorruption(*snapshot));
|
|
|
|
BucketRanges* bucket_ranges =
|
|
const_cast<BucketRanges*>(histogram->bucket_ranges());
|
|
HistogramBase::Sample tmp = bucket_ranges->range(1);
|
|
bucket_ranges->set_range(1, bucket_ranges->range(2));
|
|
bucket_ranges->set_range(2, tmp);
|
|
EXPECT_EQ(
|
|
HistogramBase::BUCKET_ORDER_ERROR | HistogramBase::RANGE_CHECKSUM_ERROR,
|
|
histogram->FindCorruption(*snapshot));
|
|
|
|
bucket_ranges->set_range(2, bucket_ranges->range(1));
|
|
bucket_ranges->set_range(1, tmp);
|
|
EXPECT_EQ(0U, histogram->FindCorruption(*snapshot));
|
|
|
|
// Show that two simple changes don't offset each other
|
|
bucket_ranges->set_range(3, bucket_ranges->range(3) + 1);
|
|
EXPECT_EQ(HistogramBase::RANGE_CHECKSUM_ERROR,
|
|
histogram->FindCorruption(*snapshot));
|
|
|
|
bucket_ranges->set_range(4, bucket_ranges->range(4) - 1);
|
|
EXPECT_EQ(HistogramBase::RANGE_CHECKSUM_ERROR,
|
|
histogram->FindCorruption(*snapshot));
|
|
|
|
// Repair histogram so that destructor won't DCHECK().
|
|
bucket_ranges->set_range(3, bucket_ranges->range(3) - 1);
|
|
bucket_ranges->set_range(4, bucket_ranges->range(4) + 1);
|
|
}
|
|
|
|
TEST_P(HistogramTest, HistogramSerializeInfo) {
|
|
Histogram* histogram = static_cast<Histogram*>(
|
|
Histogram::FactoryGet("Histogram", 1, 64, 8,
|
|
HistogramBase::kIPCSerializationSourceFlag));
|
|
Pickle pickle;
|
|
histogram->SerializeInfo(&pickle);
|
|
|
|
PickleIterator iter(pickle);
|
|
|
|
int type;
|
|
EXPECT_TRUE(iter.ReadInt(&type));
|
|
EXPECT_EQ(HISTOGRAM, type);
|
|
|
|
std::string name;
|
|
EXPECT_TRUE(iter.ReadString(&name));
|
|
EXPECT_EQ("Histogram", name);
|
|
|
|
int flag;
|
|
EXPECT_TRUE(iter.ReadInt(&flag));
|
|
EXPECT_EQ(HistogramBase::kIPCSerializationSourceFlag,
|
|
flag & ~HistogramBase::kIsPersistent);
|
|
|
|
int min;
|
|
EXPECT_TRUE(iter.ReadInt(&min));
|
|
EXPECT_EQ(1, min);
|
|
|
|
int max;
|
|
EXPECT_TRUE(iter.ReadInt(&max));
|
|
EXPECT_EQ(64, max);
|
|
|
|
uint32_t bucket_count;
|
|
EXPECT_TRUE(iter.ReadUInt32(&bucket_count));
|
|
EXPECT_EQ(8u, bucket_count);
|
|
|
|
uint32_t checksum;
|
|
EXPECT_TRUE(iter.ReadUInt32(&checksum));
|
|
EXPECT_EQ(histogram->bucket_ranges()->checksum(), checksum);
|
|
|
|
// No more data in the pickle.
|
|
EXPECT_FALSE(iter.SkipBytes(1));
|
|
}
|
|
|
|
TEST_P(HistogramTest, CustomHistogramSerializeInfo) {
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(10);
|
|
custom_ranges.push_back(100);
|
|
|
|
HistogramBase* custom_histogram = CustomHistogram::FactoryGet(
|
|
"TestCustomRangeBoundedHistogram",
|
|
custom_ranges,
|
|
HistogramBase::kNoFlags);
|
|
Pickle pickle;
|
|
custom_histogram->SerializeInfo(&pickle);
|
|
|
|
// Validate the pickle.
|
|
PickleIterator iter(pickle);
|
|
|
|
int i;
|
|
std::string s;
|
|
uint32_t bucket_count;
|
|
uint32_t ui32;
|
|
EXPECT_TRUE(iter.ReadInt(&i) && iter.ReadString(&s) && iter.ReadInt(&i) &&
|
|
iter.ReadInt(&i) && iter.ReadInt(&i) &&
|
|
iter.ReadUInt32(&bucket_count) && iter.ReadUInt32(&ui32));
|
|
EXPECT_EQ(3u, bucket_count);
|
|
|
|
int range;
|
|
EXPECT_TRUE(iter.ReadInt(&range));
|
|
EXPECT_EQ(10, range);
|
|
EXPECT_TRUE(iter.ReadInt(&range));
|
|
EXPECT_EQ(100, range);
|
|
|
|
// No more data in the pickle.
|
|
EXPECT_FALSE(iter.SkipBytes(1));
|
|
}
|
|
|
|
TEST_P(HistogramTest, BadConstruction) {
|
|
HistogramBase* histogram = Histogram::FactoryGet(
|
|
"BadConstruction", 0, 100, 8, HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(histogram->HasConstructionArguments(1, 100, 8));
|
|
|
|
// Try to get the same histogram name with different arguments.
|
|
HistogramBase* bad_histogram = Histogram::FactoryGet(
|
|
"BadConstruction", 0, 100, 7, HistogramBase::kNoFlags);
|
|
EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
|
|
bad_histogram = Histogram::FactoryGet(
|
|
"BadConstruction", 0, 99, 8, HistogramBase::kNoFlags);
|
|
EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
|
|
|
|
HistogramBase* linear_histogram = LinearHistogram::FactoryGet(
|
|
"BadConstructionLinear", 0, 100, 8, HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(linear_histogram->HasConstructionArguments(1, 100, 8));
|
|
|
|
// Try to get the same histogram name with different arguments.
|
|
bad_histogram = LinearHistogram::FactoryGet(
|
|
"BadConstructionLinear", 0, 100, 7, HistogramBase::kNoFlags);
|
|
EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
|
|
bad_histogram = LinearHistogram::FactoryGet(
|
|
"BadConstructionLinear", 10, 100, 8, HistogramBase::kNoFlags);
|
|
EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
|
|
}
|
|
|
|
TEST_P(HistogramTest, FactoryTime) {
|
|
const int kTestCreateCount = 1 << 14; // Must be power-of-2.
|
|
const int kTestLookupCount = 100000;
|
|
const int kTestAddCount = 1000000;
|
|
|
|
// 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) {
|
|
Histogram::FactoryGet(histogram_names[i], 1, 100, 10,
|
|
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);
|
|
Histogram::FactoryGet(histogram_names[index], 1, 100, 10,
|
|
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 = Histogram::FactoryGet(
|
|
histogram_names[0], 1, 100, 10, 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(HistogramTest, ScaledLinearHistogram) {
|
|
ScaledLinearHistogram scaled("SLH", 1, 5, 6, 100, HistogramBase::kNoFlags);
|
|
|
|
scaled.AddScaledCount(0, 1);
|
|
scaled.AddScaledCount(1, 49);
|
|
scaled.AddScaledCount(2, 50);
|
|
scaled.AddScaledCount(3, 101);
|
|
scaled.AddScaledCount(4, 160);
|
|
scaled.AddScaledCount(5, 130);
|
|
scaled.AddScaledCount(6, 140);
|
|
|
|
std::unique_ptr<SampleVector> samples =
|
|
SnapshotAllSamples(scaled.histogram());
|
|
EXPECT_EQ(0, samples->GetCountAtIndex(0));
|
|
EXPECT_EQ(0, samples->GetCountAtIndex(1));
|
|
EXPECT_EQ(1, samples->GetCountAtIndex(2));
|
|
EXPECT_EQ(1, samples->GetCountAtIndex(3));
|
|
EXPECT_EQ(2, samples->GetCountAtIndex(4));
|
|
EXPECT_EQ(3, samples->GetCountAtIndex(5));
|
|
|
|
// Make sure the macros compile properly. This can only be run when
|
|
// there is no persistent allocator which can be discarded and leave
|
|
// dangling pointers.
|
|
if (!use_persistent_histogram_allocator_) {
|
|
enum EnumWithMax {
|
|
kA = 0,
|
|
kB = 1,
|
|
kC = 2,
|
|
kMaxValue = kC,
|
|
};
|
|
UMA_HISTOGRAM_SCALED_EXACT_LINEAR("h1", 1, 5000, 5, 100);
|
|
UMA_HISTOGRAM_SCALED_ENUMERATION("h2", kB, 5000, 100);
|
|
}
|
|
}
|
|
|
|
// For Histogram, LinearHistogram and CustomHistogram, the minimum for a
|
|
// declared range is 1, while the maximum is (HistogramBase::kSampleType_MAX -
|
|
// 1). But we accept ranges exceeding those limits, and silently clamped to
|
|
// those limits. This is for backwards compatibility.
|
|
TEST(HistogramDeathTest, BadRangesTest) {
|
|
HistogramBase* histogram = Histogram::FactoryGet(
|
|
"BadRanges", 0, HistogramBase::kSampleType_MAX, 8,
|
|
HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(
|
|
histogram->HasConstructionArguments(
|
|
1, HistogramBase::kSampleType_MAX - 1, 8));
|
|
|
|
HistogramBase* linear_histogram = LinearHistogram::FactoryGet(
|
|
"BadRangesLinear", 0, HistogramBase::kSampleType_MAX, 8,
|
|
HistogramBase::kNoFlags);
|
|
EXPECT_TRUE(
|
|
linear_histogram->HasConstructionArguments(
|
|
1, HistogramBase::kSampleType_MAX - 1, 8));
|
|
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(0);
|
|
custom_ranges.push_back(5);
|
|
Histogram* custom_histogram = static_cast<Histogram*>(
|
|
CustomHistogram::FactoryGet(
|
|
"BadRangesCustom", custom_ranges, HistogramBase::kNoFlags));
|
|
const BucketRanges* ranges = custom_histogram->bucket_ranges();
|
|
ASSERT_EQ(3u, ranges->size());
|
|
EXPECT_EQ(0, ranges->range(0));
|
|
EXPECT_EQ(5, ranges->range(1));
|
|
EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2));
|
|
|
|
// CustomHistogram does not accepts kSampleType_MAX as range.
|
|
custom_ranges.push_back(HistogramBase::kSampleType_MAX);
|
|
EXPECT_DEATH_IF_SUPPORTED(
|
|
CustomHistogram::FactoryGet("BadRangesCustom2", custom_ranges,
|
|
HistogramBase::kNoFlags),
|
|
"");
|
|
|
|
// CustomHistogram needs at least 1 valid range.
|
|
custom_ranges.clear();
|
|
custom_ranges.push_back(0);
|
|
EXPECT_DEATH_IF_SUPPORTED(
|
|
CustomHistogram::FactoryGet("BadRangesCustom3", custom_ranges,
|
|
HistogramBase::kNoFlags),
|
|
"");
|
|
}
|
|
|
|
TEST_P(HistogramTest, ExpiredHistogramTest) {
|
|
auto record_checker = std::make_unique<TestRecordHistogramChecker>();
|
|
StatisticsRecorder::SetRecordChecker(std::move(record_checker));
|
|
|
|
HistogramBase* expired = Histogram::FactoryGet(kExpiredHistogramName, 1, 1000,
|
|
10, HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(expired);
|
|
expired->Add(5);
|
|
expired->Add(500);
|
|
auto samples = expired->SnapshotDelta();
|
|
EXPECT_EQ(0, samples->TotalCount());
|
|
|
|
HistogramBase* linear_expired = LinearHistogram::FactoryGet(
|
|
kExpiredHistogramName, 1, 1000, 10, HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(linear_expired);
|
|
linear_expired->Add(5);
|
|
linear_expired->Add(500);
|
|
samples = linear_expired->SnapshotDelta();
|
|
EXPECT_EQ(0, samples->TotalCount());
|
|
|
|
std::vector<int> custom_ranges;
|
|
custom_ranges.push_back(1);
|
|
custom_ranges.push_back(5);
|
|
HistogramBase* custom_expired = CustomHistogram::FactoryGet(
|
|
kExpiredHistogramName, custom_ranges, HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(custom_expired);
|
|
custom_expired->Add(2);
|
|
custom_expired->Add(4);
|
|
samples = custom_expired->SnapshotDelta();
|
|
EXPECT_EQ(0, samples->TotalCount());
|
|
|
|
HistogramBase* valid = Histogram::FactoryGet("ValidHistogram", 1, 1000, 10,
|
|
HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(valid);
|
|
valid->Add(5);
|
|
valid->Add(500);
|
|
samples = valid->SnapshotDelta();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
|
|
HistogramBase* linear_valid = LinearHistogram::FactoryGet(
|
|
"LinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(linear_valid);
|
|
linear_valid->Add(5);
|
|
linear_valid->Add(500);
|
|
samples = linear_valid->SnapshotDelta();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
|
|
HistogramBase* custom_valid = CustomHistogram::FactoryGet(
|
|
"CustomHistogram", custom_ranges, HistogramBase::kNoFlags);
|
|
ASSERT_TRUE(custom_valid);
|
|
custom_valid->Add(2);
|
|
custom_valid->Add(4);
|
|
samples = custom_valid->SnapshotDelta();
|
|
EXPECT_EQ(2, samples->TotalCount());
|
|
}
|
|
|
|
} // namespace base
|