#include "base/metrics/sample_vector.h"
#include <ostream>
#include "base/check_op.h"
#include "base/debug/crash_logging.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
namespace base {
typedef HistogramBase::Count Count;
typedef HistogramBase::Sample Sample;
namespace {
template <typename T>
class IteratorTemplate : public SampleCountIterator {
public:
IteratorTemplate(T* counts,
size_t counts_size,
const BucketRanges* bucket_ranges)
: counts_(counts),
counts_size_(counts_size),
bucket_ranges_(bucket_ranges) {
DCHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
SkipEmptyBuckets();
}
~IteratorTemplate() override;
bool Done() const override { return index_ >= counts_size_; }
void Next() override {
DCHECK(!Done());
index_++;
SkipEmptyBuckets();
}
void Get(HistogramBase::Sample* min,
int64_t* max,
HistogramBase::Count* count) override;
bool GetBucketIndex(size_t* index) const override {
DCHECK(!Done());
if (index != nullptr) {
*index = index_;
}
return true;
}
private:
void SkipEmptyBuckets() {
if (Done()) {
return;
}
while (index_ < counts_size_) {
if (subtle::NoBarrier_Load(&counts_[index_]) != 0) {
return;
}
index_++;
}
}
raw_ptr<T> counts_;
size_t counts_size_;
raw_ptr<const BucketRanges> bucket_ranges_;
size_t index_ = 0;
};
typedef IteratorTemplate<const HistogramBase::AtomicCount> SampleVectorIterator;
template <>
SampleVectorIterator::~IteratorTemplate() = default;
template <>
void SampleVectorIterator::Get(HistogramBase::Sample* min,
int64_t* max,
HistogramBase::Count* count) {
DCHECK(!Done());
*min = bucket_ranges_->range(index_);
*max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
*count = subtle::NoBarrier_Load(&counts_[index_]);
}
typedef IteratorTemplate<HistogramBase::AtomicCount>
ExtractingSampleVectorIterator;
template <>
ExtractingSampleVectorIterator::~IteratorTemplate() {
DCHECK(Done());
}
template <>
void ExtractingSampleVectorIterator::Get(HistogramBase::Sample* min,
int64_t* max,
HistogramBase::Count* count) {
DCHECK(!Done());
*min = bucket_ranges_->range(index_);
*max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
*count = subtle::NoBarrier_AtomicExchange(&counts_[index_], 0);
}
}
SampleVectorBase::SampleVectorBase(uint64_t id,
Metadata* meta,
const BucketRanges* bucket_ranges)
: HistogramSamples(id, meta), bucket_ranges_(bucket_ranges) {
CHECK_GE(bucket_ranges_->bucket_count(), 1u);
}
SampleVectorBase::SampleVectorBase(uint64_t id,
std::unique_ptr<Metadata> meta,
const BucketRanges* bucket_ranges)
: HistogramSamples(id, std::move(meta)), bucket_ranges_(bucket_ranges) {
CHECK_GE(bucket_ranges_->bucket_count(), 1u);
}
SampleVectorBase::~SampleVectorBase() = default;
void SampleVectorBase::Accumulate(Sample value, Count count) {
const size_t bucket_index = GetBucketIndex(value);
if (!counts()) {
if (AccumulateSingleSample(value, count, bucket_index)) {
if (counts())
MoveSingleSampleToCounts();
return;
}
MountCountsStorageAndMoveSingleSample();
}
Count new_value =
subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count);
IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
Count old_value = new_value - count;
if ((new_value >= 0) != (old_value >= 0) && count > 0)
RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
}
Count SampleVectorBase::GetCount(Sample value) const {
return GetCountAtIndex(GetBucketIndex(value));
}
Count SampleVectorBase::TotalCount() const {
SingleSample sample = single_sample().Load();
if (sample.count != 0)
return sample.count;
if (counts() || MountExistingCountsStorage()) {
Count count = 0;
size_t size = counts_size();
const HistogramBase::AtomicCount* counts_array = counts();
for (size_t i = 0; i < size; ++i) {
count += subtle::NoBarrier_Load(&counts_array[i]);
}
return count;
}
return 0;
}
Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
DCHECK(bucket_index < counts_size());
SingleSample sample = single_sample().Load();
if (sample.count != 0)
return sample.bucket == bucket_index ? sample.count : 0;
if (counts() || MountExistingCountsStorage())
return subtle::NoBarrier_Load(&counts()[bucket_index]);
return 0;
}
std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
SingleSample sample = single_sample().Load();
if (sample.count != 0) {
return std::make_unique<SingleSampleIterator>(
bucket_ranges_->range(sample.bucket),
bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
false);
}
if (counts() || MountExistingCountsStorage()) {
return std::make_unique<SampleVectorIterator>(counts(), counts_size(),
bucket_ranges_);
}
return std::make_unique<SampleVectorIterator>(nullptr, 0, bucket_ranges_);
}
std::unique_ptr<SampleCountIterator> SampleVectorBase::ExtractingIterator() {
SingleSample sample = single_sample().Extract();
if (sample.count != 0) {
return std::make_unique<SingleSampleIterator>(
bucket_ranges_->range(sample.bucket),
bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
true);
}
if (counts() || MountExistingCountsStorage()) {
return std::make_unique<ExtractingSampleVectorIterator>(
counts(), counts_size(), bucket_ranges_);
}
return std::make_unique<ExtractingSampleVectorIterator>(nullptr, 0,
bucket_ranges_);
}
bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
HistogramSamples::Operator op) {
if (iter->Done())
return true;
HistogramBase::Sample min;
int64_t max;
HistogramBase::Count count;
iter->Get(&min, &max, &count);
size_t dest_index = GetBucketIndex(min);
size_t index_offset = 0;
size_t iter_index;
if (iter->GetBucketIndex(&iter_index))
index_offset = dest_index - iter_index;
if (dest_index >= counts_size())
return false;
iter->Next();
if (!counts()) {
if (iter->Done()) {
if (single_sample().Accumulate(
dest_index, op == HistogramSamples::ADD ? count : -count)) {
if (counts())
MoveSingleSampleToCounts();
return true;
}
}
MountCountsStorageAndMoveSingleSample();
}
while (true) {
if (min != bucket_ranges_->range(dest_index) ||
max != bucket_ranges_->range(dest_index + 1)) {
#if !BUILDFLAG(IS_NACL)
SCOPED_CRASH_KEY_NUMBER("SampleVector", "min", min);
SCOPED_CRASH_KEY_NUMBER("SampleVector", "max", max);
SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_min",
bucket_ranges_->range(dest_index));
SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_max",
bucket_ranges_->range(dest_index + 1));
#endif
NOTREACHED() << "sample=" << min << "," << max
<< "; range=" << bucket_ranges_->range(dest_index) << ","
<< bucket_ranges_->range(dest_index + 1);
return false;
}
subtle::NoBarrier_AtomicIncrement(
&counts()[dest_index], op == HistogramSamples::ADD ? count : -count);
if (iter->Done())
return true;
iter->Get(&min, &max, &count);
if (iter->GetBucketIndex(&iter_index)) {
dest_index = iter_index + index_offset;
} else {
dest_index = GetBucketIndex(min);
}
if (dest_index >= counts_size())
return false;
iter->Next();
}
}
size_t SampleVectorBase::GetBucketIndex(Sample value) const {
size_t bucket_count = bucket_ranges_->bucket_count();
CHECK_GE(bucket_count, 1u);
CHECK_GE(value, bucket_ranges_->range(0));
CHECK_LT(value, bucket_ranges_->range(bucket_count));
Sample maximum = bucket_ranges_->range(bucket_count - 1);
if (maximum == static_cast<Sample>(bucket_count - 1)) {
if (value < 1)
return 0;
if (value > maximum)
return bucket_count - 1;
return static_cast<size_t>(value);
}
size_t under = 0;
size_t over = bucket_count;
size_t mid;
do {
DCHECK_GE(over, under);
mid = under + (over - under)/2;
if (mid == under)
break;
if (bucket_ranges_->range(mid) <= value)
under = mid;
else
over = mid;
} while (true);
DCHECK_LE(bucket_ranges_->range(mid), value);
CHECK_GT(bucket_ranges_->range(mid + 1), value);
return mid;
}
void SampleVectorBase::MoveSingleSampleToCounts() {
DCHECK(counts());
SingleSample sample = single_sample().ExtractAndDisable();
if (sample.count == 0)
return;
if (sample.bucket >= counts_size()) {
return;
}
subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count);
}
void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
if (!counts_.load(std::memory_order_relaxed)) {
AutoLock lock(counts_lock.Get());
if (!counts_.load(std::memory_order_relaxed)) {
HistogramBase::Count* counts = CreateCountsStorageWhileLocked();
DCHECK(counts);
set_counts(counts);
}
}
MoveSingleSampleToCounts();
}
SampleVector::SampleVector(const BucketRanges* bucket_ranges)
: SampleVector(0, bucket_ranges) {}
SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
: SampleVectorBase(id, std::make_unique<LocalMetadata>(), bucket_ranges) {}
SampleVector::~SampleVector() = default;
bool SampleVector::MountExistingCountsStorage() const {
return counts() != nullptr;
}
std::string SampleVector::GetAsciiHeader(StringPiece histogram_name,
int32_t flags) const {
Count sample_count = TotalCount();
std::string output;
StringAppendF(&output, "Histogram: %.*s recorded %d samples",
static_cast<int>(histogram_name.size()), histogram_name.data(),
sample_count);
if (sample_count == 0) {
DCHECK_EQ(sum(), 0);
} else {
double mean = static_cast<float>(sum()) / sample_count;
StringAppendF(&output, ", mean = %.1f", mean);
}
if (flags)
StringAppendF(&output, " (flags = 0x%x)", flags);
return output;
}
std::string SampleVector::GetAsciiBody() const {
Count sample_count = TotalCount();
double max_size = 0;
double scaling_factor = 1;
max_size = GetPeakBucketSize();
const double kLineLength = 72;
if (max_size > kLineLength)
scaling_factor = kLineLength / max_size;
size_t print_width = 1;
for (uint32_t i = 0; i < bucket_count(); ++i) {
if (GetCountAtIndex(i)) {
size_t width =
GetSimpleAsciiBucketRange(bucket_ranges()->range(i)).size() + 1;
if (width > print_width)
print_width = width;
}
}
int64_t remaining = sample_count;
int64_t past = 0;
std::string output;
for (uint32_t i = 0; i < bucket_count(); ++i) {
Count current = GetCountAtIndex(i);
remaining -= current;
std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i));
output.append(range);
for (size_t j = 0; range.size() + j < print_width + 1; ++j)
output.push_back(' ');
if (0 == current && i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
while (i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
++i;
}
output.append("... \n");
continue;
}
Count current_size = round(current * scaling_factor);
WriteAsciiBucketGraph(current_size, kLineLength, &output);
WriteAsciiBucketContext(past, current, remaining, i, &output);
output.append("\n");
past += current;
}
DCHECK_EQ(sample_count, past);
return output;
}
double SampleVector::GetPeakBucketSize() const {
Count max = 0;
for (uint32_t i = 0; i < bucket_count(); ++i) {
Count current = GetCountAtIndex(i);
if (current > max)
max = current;
}
return max;
}
void SampleVector::WriteAsciiBucketContext(int64_t past,
Count current,
int64_t remaining,
uint32_t current_bucket_index,
std::string* output) const {
double scaled_sum = (past + current + remaining) / 100.0;
WriteAsciiBucketValue(current, scaled_sum, output);
if (0 < current_bucket_index) {
double percentage = past / scaled_sum;
StringAppendF(output, " {%3.1f%%}", percentage);
}
}
HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() {
local_counts_.resize(counts_size());
return &local_counts_[0];
}
PersistentSampleVector::PersistentSampleVector(
uint64_t id,
const BucketRanges* bucket_ranges,
Metadata* meta,
const DelayedPersistentAllocation& counts)
: SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
if (single_sample().IsDisabled()) {
bool success = MountExistingCountsStorage();
DCHECK(success);
}
}
PersistentSampleVector::~PersistentSampleVector() = default;
bool PersistentSampleVector::MountExistingCountsStorage() const {
if (!persistent_counts_.reference())
return false;
set_counts(
static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get()));
return counts() != nullptr;
}
HistogramBase::AtomicCount*
PersistentSampleVector::CreateCountsStorageWhileLocked() {
void* mem = persistent_counts_.Get();
if (!mem) {
return new HistogramBase::AtomicCount[counts_size()];
}
return static_cast<HistogramBase::AtomicCount*>(mem);
}
}