#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_libc_calls
#endif
#include "base/metrics/histogram_samples.h"
#include <limits>
#include <string_view>
#include <utility>
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
#include "base/pickle.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
namespace base {
namespace {
constexpr size_t kSizeMax = std::numeric_limits<size_t>::max();
constexpr int32_t kDisabledSingleSample = -1;
class SampleCountPickleIterator : public SampleCountIterator {
public:
explicit SampleCountPickleIterator(PickleIterator* iter);
bool Done() const override;
void Next() override;
void Get(HistogramBase::Sample32* min,
int64_t* max,
HistogramBase::Count32* count) override;
private:
const raw_ptr<PickleIterator> iter_;
HistogramBase::Sample32 min_;
int64_t max_;
HistogramBase::Count32 count_;
bool is_done_ = false;
};
SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter)
: iter_(iter) {
Next();
}
bool SampleCountPickleIterator::Done() const {
return is_done_;
}
void SampleCountPickleIterator::Next() {
DCHECK(!Done());
if (!iter_->ReadInt(&min_) || !iter_->ReadInt64(&max_) ||
!iter_->ReadInt(&count_)) {
is_done_ = true;
}
}
void SampleCountPickleIterator::Get(HistogramBase::Sample32* min,
int64_t* max,
HistogramBase::Count32* count) {
DCHECK(!Done());
*min = min_;
*max = max_;
*count = count_;
}
}
static_assert(sizeof(HistogramSamples::AtomicSingleSample) ==
sizeof(subtle::Atomic32),
"AtomicSingleSample isn't 32 bits");
HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Load()
const {
AtomicSingleSample single_sample(subtle::Acquire_Load(&as_atomic));
if (single_sample.as_atomic == kDisabledSingleSample) {
single_sample.as_atomic = 0;
}
return single_sample.as_parts;
}
HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Extract(
AtomicSingleSample new_value) {
DCHECK(new_value.as_atomic != kDisabledSingleSample)
<< "Disabling an AtomicSingleSample should be done through "
"ExtractAndDisable().";
AtomicSingleSample old_value;
while (true) {
old_value.as_atomic = subtle::Acquire_Load(&as_atomic);
if (old_value.as_atomic == kDisabledSingleSample) {
old_value.as_atomic = 0;
return old_value.as_parts;
}
subtle::Atomic32 existing = subtle::Release_CompareAndSwap(
&as_atomic, old_value.as_atomic, new_value.as_atomic);
if (existing == old_value.as_atomic) {
return old_value.as_parts;
}
}
}
HistogramSamples::SingleSample
HistogramSamples::AtomicSingleSample::ExtractAndDisable() {
AtomicSingleSample old_value(
subtle::NoBarrier_AtomicExchange(&as_atomic, kDisabledSingleSample));
if (old_value.as_atomic == kDisabledSingleSample) {
old_value.as_atomic = 0;
}
return old_value.as_parts;
}
bool HistogramSamples::AtomicSingleSample::Accumulate(
size_t bucket,
HistogramBase::Count32 count) {
if (count == 0) {
return true;
}
if (count < -std::numeric_limits<uint16_t>::max() ||
count > std::numeric_limits<uint16_t>::max() ||
bucket > std::numeric_limits<uint16_t>::max()) {
return false;
}
bool count_is_negative = count < 0;
uint16_t count16 = static_cast<uint16_t>(count_is_negative ? -count : count);
uint16_t bucket16 = static_cast<uint16_t>(bucket);
AtomicSingleSample single_sample;
bool sample_updated;
do {
subtle::Atomic32 original = subtle::Acquire_Load(&as_atomic);
if (original == kDisabledSingleSample) {
return false;
}
single_sample.as_atomic = original;
if (single_sample.as_atomic != 0) {
if (single_sample.as_parts.bucket != bucket16) {
return false;
}
} else {
single_sample.as_parts.bucket = bucket16;
}
CheckedNumeric<uint16_t> new_count(single_sample.as_parts.count);
if (count_is_negative) {
new_count -= count16;
} else {
new_count += count16;
}
if (!new_count.AssignIfValid(&single_sample.as_parts.count)) {
return false;
}
if (single_sample.as_atomic == kDisabledSingleSample) {
return false;
}
subtle::Atomic32 existing = subtle::Release_CompareAndSwap(
&as_atomic, original, single_sample.as_atomic);
sample_updated = (existing == original);
} while (!sample_updated);
return true;
}
bool HistogramSamples::AtomicSingleSample::IsDisabled() const {
return subtle::Acquire_Load(&as_atomic) == kDisabledSingleSample;
}
HistogramSamples::LocalMetadata::LocalMetadata() {
memset(this, 0, sizeof(*this));
}
HistogramSamples::HistogramSamples(uint64_t id, Metadata* meta) : meta_(meta) {
DCHECK(meta_->id == 0 || meta_->id == id);
if (!meta_->id) {
meta_->id = id;
}
}
HistogramSamples::HistogramSamples(uint64_t id, std::unique_ptr<Metadata> meta)
: HistogramSamples(id, meta.get()) {
meta_owned_ = std::move(meta);
}
HistogramSamples::~HistogramSamples() = default;
bool HistogramSamples::Add(const HistogramSamples& other) {
IncreaseSumAndCount(other.sum(), other.redundant_count());
std::unique_ptr<SampleCountIterator> it = other.Iterator();
return AddSubtractImpl(it.get(), ADD);
}
bool HistogramSamples::AddFromPickle(PickleIterator* iter) {
int64_t sum;
HistogramBase::Count32 redundant_count;
if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count)) {
return false;
}
IncreaseSumAndCount(sum, redundant_count);
SampleCountPickleIterator pickle_iter(iter);
return AddSubtractImpl(&pickle_iter, ADD);
}
bool HistogramSamples::Subtract(const HistogramSamples& other) {
IncreaseSumAndCount(-other.sum(), -other.redundant_count());
std::unique_ptr<SampleCountIterator> it = other.Iterator();
return AddSubtractImpl(it.get(), SUBTRACT);
}
bool HistogramSamples::Extract(HistogramSamples& other) {
static_assert(sizeof(other.meta_->sum) == 8);
#ifdef ARCH_CPU_64_BITS
subtle::Atomic64 other_sum =
subtle::NoBarrier_AtomicExchange(&other.meta_->sum, 0);
#else
volatile int64_t other_sum = other.meta_->sum;
other.meta_->sum -= other_sum;
#endif
HistogramBase::AtomicCount other_redundant_count =
subtle::NoBarrier_AtomicExchange(&other.meta_->redundant_count, 0);
IncreaseSumAndCount(other_sum, other_redundant_count);
std::unique_ptr<SampleCountIterator> it = other.ExtractingIterator();
return AddSubtractImpl(it.get(), ADD);
}
bool HistogramSamples::IsDefinitelyEmpty() const {
return sum() == 0 && redundant_count() == 0;
}
void HistogramSamples::Serialize(Pickle* pickle) const {
pickle->WriteInt64(sum());
pickle->WriteInt(redundant_count());
HistogramBase::Sample32 min;
int64_t max;
HistogramBase::Count32 count;
for (std::unique_ptr<SampleCountIterator> it = Iterator(); !it->Done();
it->Next()) {
it->Get(&min, &max, &count);
pickle->WriteInt(min);
pickle->WriteInt64(max);
pickle->WriteInt(count);
}
}
bool HistogramSamples::AccumulateSingleSample(HistogramBase::Sample32 value,
HistogramBase::Count32 count,
size_t bucket) {
if (single_sample().Accumulate(bucket, count)) {
IncreaseSumAndCount(strict_cast<int64_t>(value) * count, count);
return true;
}
return false;
}
void HistogramSamples::IncreaseSumAndCount(int64_t sum,
HistogramBase::Count32 count) {
#ifdef ARCH_CPU_64_BITS
subtle::NoBarrier_AtomicIncrement(&meta_->sum, sum);
#else
meta_->sum += sum;
#endif
subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, count);
}
void HistogramSamples::RecordNegativeSample(NegativeSampleReason reason,
HistogramBase::Count32 increment) {
UMA_HISTOGRAM_ENUMERATION("UMA.NegativeSamples.Reason", reason,
MAX_NEGATIVE_SAMPLE_REASONS);
UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.NegativeSamples.Increment", increment, 1,
1 << 30, 100);
UmaHistogramSparse("UMA.NegativeSamples.Histogram",
static_cast<int32_t>(id()));
}
base::Value::Dict HistogramSamples::ToGraphDict(std::string_view histogram_name,
int32_t flags) const {
base::Value::Dict dict;
dict.Set("name", histogram_name);
dict.Set("header", GetAsciiHeader(histogram_name, flags));
dict.Set("body", GetAsciiBody());
return dict;
}
std::string HistogramSamples::GetAsciiHeader(std::string_view histogram_name,
int32_t flags) const {
std::string output;
StrAppend(&output, {"Histogram: ", histogram_name, " recorded ",
NumberToString(TotalCount()), " samples"});
if (flags) {
StringAppendF(&output, " (flags = 0x%x)", flags);
}
return output;
}
std::string HistogramSamples::GetAsciiBody() const {
HistogramBase::Count32 total_count = TotalCount();
double scaled_total_count = total_count / 100.0;
HistogramBase::Count32 largest_count = 0;
HistogramBase::Sample32 largest_sample = 0;
std::unique_ptr<SampleCountIterator> it = Iterator();
while (!it->Done()) {
HistogramBase::Sample32 min;
int64_t max;
HistogramBase::Count32 count;
it->Get(&min, &max, &count);
if (min > largest_sample) {
largest_sample = min;
}
if (count > largest_count) {
largest_count = count;
}
it->Next();
}
const double kLineLength = 72;
double scaling_factor = 1;
if (largest_count > kLineLength) {
scaling_factor = kLineLength / largest_count;
}
size_t print_width = GetSimpleAsciiBucketRange(largest_sample).size() + 1;
it = Iterator();
std::string output;
while (!it->Done()) {
HistogramBase::Sample32 min;
int64_t max;
HistogramBase::Count32 count;
it->Get(&min, &max, &count);
std::string range = GetSimpleAsciiBucketRange(min);
output.append(range);
if (const auto range_size = range.size(); print_width >= range_size) {
output.append(print_width + 1 - range_size, ' ');
}
HistogramBase::Count32 current_size = round(count * scaling_factor);
WriteAsciiBucketGraph(current_size, kLineLength, &output);
WriteAsciiBucketValue(count, scaled_total_count, &output);
output.append(1, '\n');
it->Next();
}
return output;
}
void HistogramSamples::WriteAsciiBucketGraph(double x_count,
int line_length,
std::string* output) {
output->reserve(ClampAdd(output->size(), ClampAdd(line_length, 1)));
const size_t count = ClampRound<size_t>(x_count);
output->append(count, '-');
output->append(1, 'O');
if (const auto len = as_unsigned(line_length); count < len) {
output->append(len - count, ' ');
}
}
void HistogramSamples::WriteAsciiBucketValue(HistogramBase::Count32 current,
double scaled_sum,
std::string* output) const {
StringAppendF(output, " (%d = %3.1f%%)", current, current / scaled_sum);
}
const std::string HistogramSamples::GetSimpleAsciiBucketRange(
HistogramBase::Sample32 sample) const {
return StringPrintf("%d", sample);
}
SampleCountIterator::~SampleCountIterator() = default;
bool SampleCountIterator::GetBucketIndex(size_t* index) const {
DCHECK(!Done());
return false;
}
SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample32 min,
int64_t max,
HistogramBase::Count32 count,
size_t bucket_index,
bool value_was_extracted)
: min_(min),
max_(max),
bucket_index_(bucket_index),
count_(count),
value_was_extracted_(value_was_extracted) {}
SingleSampleIterator::~SingleSampleIterator() {
DCHECK(!value_was_extracted_ || Done());
}
bool SingleSampleIterator::Done() const {
return count_ == 0;
}
void SingleSampleIterator::Next() {
DCHECK(!Done());
count_ = 0;
}
void SingleSampleIterator::Get(HistogramBase::Sample32* min,
int64_t* max,
HistogramBase::Count32* count) {
DCHECK(!Done());
*min = min_;
*max = max_;
*count = count_;
}
bool SingleSampleIterator::GetBucketIndex(size_t* index) const {
DCHECK(!Done());
if (bucket_index_ == kSizeMax) {
return false;
}
*index = bucket_index_;
return true;
}
}