#include <array>
#include <memory>
#include <string>
#include <vector>
#include "base/atomicops.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/memory/raw_span.h"
#include "base/metrics/bucket_ranges.h"
#include "base/metrics/histogram.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/sparse_histogram.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
size_t GetBucketIndex(HistogramBase::Sample32 value, const BucketRanges* ranges) {
size_t bucket_count = ranges->bucket_count();
EXPECT_GE(bucket_count, 1U);
for (size_t i = 0; i < bucket_count; ++i) {
if (ranges->range(i) > value) {
return i - 1;
}
}
return bucket_count - 1;
}
class SnapshotDeltaThread : public SimpleThread {
public:
SnapshotDeltaThread(const std::string& name,
size_t num_emissions,
span<HistogramBase*> histograms,
HistogramBase::Sample32 histogram_max,
subtle::Atomic32* real_total_samples_count,
span<subtle::Atomic32> real_bucket_counts,
subtle::Atomic32* snapshots_total_samples_count,
span<subtle::Atomic32> snapshots_bucket_counts)
: SimpleThread(name, Options()),
num_emissions_(num_emissions),
histograms_(histograms),
histogram_max_(histogram_max),
real_total_samples_count_(real_total_samples_count),
real_bucket_counts_(real_bucket_counts),
snapshots_total_samples_count_(snapshots_total_samples_count),
snapshots_bucket_counts_(snapshots_bucket_counts) {}
SnapshotDeltaThread(const SnapshotDeltaThread&) = delete;
SnapshotDeltaThread& operator=(const SnapshotDeltaThread&) = delete;
~SnapshotDeltaThread() override = default;
void Run() override {
for (size_t i = 0; i < num_emissions_; ++i) {
for (HistogramBase* histogram : histograms_) {
Histogram::Sample32 sample = rand() % histogram_max_;
histogram->Add(sample);
std::unique_ptr<HistogramSamples> snapshot = histogram->SnapshotDelta();
StoreActualSample(histogram, sample);
StoreSnapshot(std::move(snapshot));
}
}
}
private:
void StoreActualSample(HistogramBase* histogram, Histogram::Sample32 sample) {
subtle::NoBarrier_AtomicIncrement(real_total_samples_count_, 1);
switch (histogram->GetHistogramType()) {
case HISTOGRAM: {
const BucketRanges* ranges =
static_cast<Histogram*>(histogram)->bucket_ranges();
size_t bucket_index = GetBucketIndex(sample, ranges);
size_t bucket_min = ranges->range(bucket_index);
subtle::NoBarrier_AtomicIncrement(&real_bucket_counts_[bucket_min], 1);
break;
}
case SPARSE_HISTOGRAM:
subtle::NoBarrier_AtomicIncrement(
&real_bucket_counts_[checked_cast<size_t>(sample)], 1);
break;
case LINEAR_HISTOGRAM:
case BOOLEAN_HISTOGRAM:
case CUSTOM_HISTOGRAM:
case DUMMY_HISTOGRAM:
NOTREACHED();
}
}
void StoreSnapshot(std::unique_ptr<HistogramSamples> snapshot) {
HistogramBase::Count32 snapshot_samples_count = snapshot->TotalCount();
subtle::NoBarrier_AtomicIncrement(snapshots_total_samples_count_,
snapshot_samples_count);
for (auto it = snapshot->Iterator(); !it->Done(); it->Next()) {
HistogramBase::Sample32 min;
int64_t max;
HistogramBase::Count32 count;
it->Get(&min, &max, &count);
ASSERT_GE(count, 0);
subtle::NoBarrier_AtomicIncrement(
&snapshots_bucket_counts_[checked_cast<size_t>(min)], count);
}
}
const size_t num_emissions_;
raw_span<HistogramBase*> histograms_;
const HistogramBase::Sample32 histogram_max_;
raw_ptr<subtle::Atomic32> real_total_samples_count_;
raw_span<subtle::Atomic32> real_bucket_counts_;
raw_ptr<subtle::Atomic32> snapshots_total_samples_count_;
raw_span<subtle::Atomic32> snapshots_bucket_counts_;
};
}
class HistogramThreadsafeTest : public testing::Test {
public:
HistogramThreadsafeTest() = default;
HistogramThreadsafeTest(const HistogramThreadsafeTest&) = delete;
HistogramThreadsafeTest& operator=(const HistogramThreadsafeTest&) = delete;
~HistogramThreadsafeTest() override = default;
void SetUp() override {
GlobalHistogramAllocator::CreateWithLocalMemory(4 << 20, 0,
"");
ASSERT_TRUE(GlobalHistogramAllocator::Get());
PersistentMemoryAllocator* allocator =
GlobalHistogramAllocator::Get()->memory_allocator();
std::unique_ptr<PersistentMemoryAllocator> memory_view =
std::make_unique<PersistentMemoryAllocator>(
const_cast<void*>(allocator->data()), allocator->size(),
0, 0,
"GlobalHistogramAllocatorView",
PersistentMemoryAllocator::kReadWrite);
allocator_view_ =
std::make_unique<PersistentHistogramAllocator>(std::move(memory_view));
}
void TearDown() override {
histograms_.clear();
allocator_view_.reset();
GlobalHistogramAllocator::ReleaseForTesting();
ASSERT_FALSE(GlobalHistogramAllocator::Get());
}
std::vector<HistogramBase*> CreateHistograms(size_t suffix,
HistogramBase::Sample32 max,
size_t bucket_count) {
std::vector<HistogramBase*> histograms;
std::string numeric_histogram_name =
StringPrintf("NumericHistogram%zu", suffix);
Histogram* numeric_histogram = static_cast<Histogram*>(
Histogram::FactoryGet(numeric_histogram_name, 1, max,
bucket_count, HistogramBase::kNoFlags));
histograms.push_back(numeric_histogram);
std::string sparse_histogram_name =
StringPrintf("SparseHistogram%zu", suffix);
HistogramBase* sparse_histogram =
SparseHistogram::FactoryGet(sparse_histogram_name,
HistogramBase::kNoFlags);
histograms.push_back(sparse_histogram);
std::string local_heap_histogram_name =
StringPrintf("LocalHeapNumericHistogram%zu", suffix);
auto& local_heap_histogram = histograms_.emplace_back(new Histogram(
HistogramBase::GetPermanentName(local_heap_histogram_name),
numeric_histogram->bucket_ranges()));
histograms.push_back(local_heap_histogram.get());
std::string local_heap_sparse_histogram_name =
StringPrintf("LocalHeapSparseHistogram%zu", suffix);
auto& local_heap_sparse_histogram =
histograms_.emplace_back(new SparseHistogram(
HistogramBase::GetPermanentName(local_heap_sparse_histogram_name)));
histograms.push_back(local_heap_sparse_histogram.get());
PersistentHistogramAllocator::Iterator hist_it(allocator_view_.get());
std::unique_ptr<HistogramBase> subprocess_numeric_histogram;
std::unique_ptr<HistogramBase> subprocess_sparse_histogram;
while (true) {
std::unique_ptr<HistogramBase> histogram = hist_it.GetNext();
if (!histogram) {
break;
}
EXPECT_NE(local_heap_histogram_name, histogram->histogram_name());
EXPECT_NE(local_heap_sparse_histogram_name, histogram->histogram_name());
if (histogram->histogram_name() == numeric_histogram_name) {
subprocess_numeric_histogram = std::move(histogram);
} else if (histogram->histogram_name() == sparse_histogram_name) {
subprocess_sparse_histogram = std::move(histogram);
}
}
EXPECT_TRUE(subprocess_numeric_histogram);
EXPECT_TRUE(subprocess_sparse_histogram);
histograms.push_back(subprocess_numeric_histogram.get());
histograms.push_back(subprocess_sparse_histogram.get());
EXPECT_NE(numeric_histogram, subprocess_numeric_histogram.get());
EXPECT_NE(sparse_histogram, subprocess_sparse_histogram.get());
histograms_.emplace_back(std::move(subprocess_numeric_histogram));
histograms_.emplace_back(std::move(subprocess_sparse_histogram));
PersistentHistogramAllocator::Iterator hist_it2(
GlobalHistogramAllocator::Get());
std::unique_ptr<HistogramBase> numeric_histogram2;
std::unique_ptr<HistogramBase> sparse_histogram2;
while (true) {
std::unique_ptr<HistogramBase> histogram = hist_it2.GetNext();
if (!histogram) {
break;
}
EXPECT_NE(local_heap_histogram_name, histogram->histogram_name());
EXPECT_NE(local_heap_sparse_histogram_name, histogram->histogram_name());
if (histogram->histogram_name() == numeric_histogram_name) {
numeric_histogram2 = std::move(histogram);
} else if (histogram->histogram_name() == sparse_histogram_name) {
sparse_histogram2 = std::move(histogram);
}
}
EXPECT_TRUE(numeric_histogram2);
EXPECT_TRUE(sparse_histogram2);
histograms.push_back(numeric_histogram2.get());
histograms.push_back(sparse_histogram2.get());
EXPECT_NE(numeric_histogram, numeric_histogram2.get());
EXPECT_NE(sparse_histogram, sparse_histogram2.get());
histograms_.emplace_back(std::move(numeric_histogram2));
histograms_.emplace_back(std::move(sparse_histogram2));
return histograms;
}
private:
std::unique_ptr<PersistentHistogramAllocator> allocator_view_;
std::vector<std::unique_ptr<HistogramBase>> histograms_;
};
TEST_F(HistogramThreadsafeTest, SnapshotDeltaThreadsafe) {
constexpr size_t kNumIterations = 50;
for (size_t iteration = 0; iteration < kNumIterations; ++iteration) {
const HistogramBase::Sample32 kHistogramMax = (iteration % 2 == 0) ? 2 : 50;
const size_t kBucketCount = (iteration % 2 == 0) ? 3 : 10;
std::vector<HistogramBase*> histograms =
CreateHistograms(iteration, kHistogramMax, kBucketCount);
constexpr size_t kNumThreads = 2;
constexpr size_t kNumEmissions = 1000;
subtle::Atomic32 real_total_samples_count = 0;
std::vector<subtle::Atomic32> real_bucket_counts(kHistogramMax, 0);
subtle::Atomic32 snapshots_total_samples_count = 0;
std::vector<subtle::Atomic32> snapshots_bucket_counts(kHistogramMax, 0);
std::array<std::unique_ptr<SnapshotDeltaThread>, kNumThreads> threads;
for (size_t i = 0; i < kNumThreads; ++i) {
threads[i] = std::make_unique<SnapshotDeltaThread>(
StringPrintf("SnapshotDeltaThread.%zu.%zu", iteration, i),
kNumEmissions, histograms, kHistogramMax, &real_total_samples_count,
real_bucket_counts, &snapshots_total_samples_count,
snapshots_bucket_counts);
threads[i]->Start();
}
for (auto& thread : threads) {
thread->Join();
}
ASSERT_EQ(static_cast<size_t>(real_total_samples_count),
kNumThreads * kNumEmissions * histograms.size());
ASSERT_EQ(snapshots_total_samples_count, real_total_samples_count);
for (HistogramBase::Sample32 i = 0; i < kHistogramMax; ++i) {
ASSERT_EQ(snapshots_bucket_counts[i], real_bucket_counts[i]);
}
HistogramBase::Count32 logged_total_samples_count = 0;
std::vector<HistogramBase::Count32> logged_bucket_counts(
kHistogramMax, 0);
for (size_t i = 0; i < histograms.size() - 4; ++i) {
HistogramBase* histogram = histograms[i];
ASSERT_EQ(histogram->SnapshotDelta()->TotalCount(), 0);
std::unique_ptr<HistogramSamples> logged_samples =
histogram->SnapshotSamples();
size_t expected_logged_samples_count = kNumThreads * kNumEmissions;
if (!Contains(histogram->histogram_name(), "LocalHeap")) {
expected_logged_samples_count *= 3;
}
ASSERT_EQ(static_cast<size_t>(logged_samples->TotalCount()),
expected_logged_samples_count);
for (auto it = logged_samples->Iterator(); !it->Done(); it->Next()) {
HistogramBase::Sample32 min;
int64_t max;
HistogramBase::Count32 count;
it->Get(&min, &max, &count);
ASSERT_GE(count, 0);
logged_total_samples_count += count;
logged_bucket_counts[min] += count;
}
}
ASSERT_EQ(logged_total_samples_count, real_total_samples_count);
for (HistogramBase::Sample32 i = 0; i < kHistogramMax; ++i) {
ASSERT_EQ(logged_bucket_counts[i], real_bucket_counts[i]);
}
HistogramBase* numeric_histogram = histograms[0];
HistogramBase* subprocess_numeric_histogram = histograms[4];
HistogramBase* sparse_histogram = histograms[1];
HistogramBase* subprocess_sparse_histogram = histograms[5];
ASSERT_EQ(subprocess_numeric_histogram->SnapshotDelta()->TotalCount(), 0);
ASSERT_EQ(subprocess_sparse_histogram->SnapshotDelta()->TotalCount(), 0);
numeric_histogram->Add(0);
sparse_histogram->Add(0);
ASSERT_EQ(subprocess_numeric_histogram->SnapshotDelta()->TotalCount(), 1);
ASSERT_EQ(subprocess_sparse_histogram->SnapshotDelta()->TotalCount(), 1);
ASSERT_EQ(numeric_histogram->SnapshotDelta()->TotalCount(), 0);
ASSERT_EQ(sparse_histogram->SnapshotDelta()->TotalCount(), 0);
HistogramBase* numeric_histogram2 = histograms[6];
HistogramBase* sparse_histogram2 = histograms[7];
ASSERT_EQ(numeric_histogram2->SnapshotDelta()->TotalCount(), 0);
ASSERT_EQ(sparse_histogram2->SnapshotDelta()->TotalCount(), 0);
numeric_histogram->Add(0);
sparse_histogram->Add(0);
ASSERT_EQ(numeric_histogram2->SnapshotDelta()->TotalCount(), 1);
ASSERT_EQ(sparse_histogram2->SnapshotDelta()->TotalCount(), 1);
ASSERT_EQ(numeric_histogram->SnapshotDelta()->TotalCount(), 0);
ASSERT_EQ(sparse_histogram->SnapshotDelta()->TotalCount(), 0);
}
}
}