// 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_samples.h" #include #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/safe_conversions.h" #include "base/numerics/safe_math.h" #include "base/pickle.h" #include "base/strings/stringprintf.h" namespace base { namespace { // A shorthand constant for the max value of size_t. constexpr size_t kSizeMax = std::numeric_limits::max(); // A constant stored in an AtomicSingleSample (as_atomic) to indicate that the // sample is "disabled" and no further accumulation should be done with it. The // value is chosen such that it will be MAX_UINT16 for both |bucket| & |count|, // and thus less likely to conflict with real use. Conflicts are explicitly // handled in the code but it's worth making them as unlikely as possible. constexpr int32_t kDisabledSingleSample = -1; class SampleCountPickleIterator : public SampleCountIterator { public: explicit SampleCountPickleIterator(PickleIterator* iter); bool Done() const override; void Next() override; void Get(HistogramBase::Sample* min, int64_t* max, HistogramBase::Count* count) const override; private: const raw_ptr iter_; HistogramBase::Sample min_; int64_t max_; HistogramBase::Count count_; bool is_done_; }; SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter) : iter_(iter), is_done_(false) { 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::Sample* min, int64_t* max, HistogramBase::Count* count) const { DCHECK(!Done()); *min = min_; *max = max_; *count = count_; } } // namespace 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 the sample was extracted/disabled, it's still zero to the outside. if (single_sample.as_atomic == kDisabledSingleSample) single_sample.as_atomic = 0; return single_sample.as_parts; } HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Extract( bool disable) { AtomicSingleSample single_sample = subtle::NoBarrier_AtomicExchange( &as_atomic, disable ? kDisabledSingleSample : 0); if (single_sample.as_atomic == kDisabledSingleSample) single_sample.as_atomic = 0; return single_sample.as_parts; } bool HistogramSamples::AtomicSingleSample::Accumulate( size_t bucket, HistogramBase::Count count) { if (count == 0) return true; // Convert the parameters to 16-bit variables because it's all 16-bit below. // To support decrements/subtractions, divide the |count| into sign/value and // do the proper operation below. The alternative is to change the single- // sample's count to be a signed integer (int16_t) and just add an int16_t // |count16| but that is somewhat wasteful given that the single-sample is // never expected to have a count less than zero. if (count < -std::numeric_limits::max() || count > std::numeric_limits::max() || bucket > std::numeric_limits::max()) { return false; } bool count_is_negative = count < 0; uint16_t count16 = static_cast(count_is_negative ? -count : count); uint16_t bucket16 = static_cast(bucket); // A local, unshared copy of the single-sample is necessary so the parts // can be manipulated without worrying about atomicity. 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) { // Only the same bucket (parameter and stored) can be counted multiple // times. if (single_sample.as_parts.bucket != bucket16) return false; } else { // The |single_ sample| was zero so becomes the |bucket| parameter, the // contents of which were checked above to fit in 16 bits. single_sample.as_parts.bucket = bucket16; } // Update count, making sure that it doesn't overflow. CheckedNumeric 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; // Don't let this become equivalent to the "disabled" value. if (single_sample.as_atomic == kDisabledSingleSample) return false; // Store the updated single-sample back into memory. |existing| is what // was in that memory location at the time of the call; if it doesn't // match |original| then the swap didn't happen so loop again. 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() { // This is the same way it's done for persistent metadata since no ctor // is called for the data members in that case. memset(this, 0, sizeof(*this)); } HistogramSamples::HistogramSamples(uint64_t id, Metadata* meta) : meta_(meta) { DCHECK(meta_->id == 0 || meta_->id == id); // It's possible that |meta| is contained in initialized, read-only memory // so it's essential that no write be done in that case. if (!meta_->id) meta_->id = id; } HistogramSamples::HistogramSamples(uint64_t id, std::unique_ptr meta) : HistogramSamples(id, meta.get()) { meta_owned_ = std::move(meta); } // This mustn't do anything with |meta_|. It was passed to the ctor and may // be invalid by the time this dtor gets called. HistogramSamples::~HistogramSamples() = default; void HistogramSamples::Add(const HistogramSamples& other) { IncreaseSumAndCount(other.sum(), other.redundant_count()); std::unique_ptr it = other.Iterator(); bool success = AddSubtractImpl(it.get(), ADD); DCHECK(success); } bool HistogramSamples::AddFromPickle(PickleIterator* iter) { int64_t sum; HistogramBase::Count 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); } void HistogramSamples::Subtract(const HistogramSamples& other) { IncreaseSumAndCount(-other.sum(), -other.redundant_count()); std::unique_ptr it = other.Iterator(); bool success = AddSubtractImpl(it.get(), SUBTRACT); DCHECK(success); } void HistogramSamples::Serialize(Pickle* pickle) const { pickle->WriteInt64(sum()); pickle->WriteInt(redundant_count()); HistogramBase::Sample min; int64_t max; HistogramBase::Count count; for (std::unique_ptr it = Iterator(); !it->Done(); it->Next()) { it->Get(&min, &max, &count); pickle->WriteInt(min); pickle->WriteInt64(max); pickle->WriteInt(count); } } bool HistogramSamples::AccumulateSingleSample(HistogramBase::Sample value, HistogramBase::Count count, size_t bucket) { if (single_sample().Accumulate(bucket, count)) { // Success. Update the (separate) sum and redundant-count. IncreaseSumAndCount(strict_cast(value) * count, count); return true; } return false; } void HistogramSamples::IncreaseSumAndCount(int64_t sum, HistogramBase::Count 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::Count 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(id())); } base::Value::Dict HistogramSamples::ToGraphDict(StringPiece 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(StringPiece histogram_name, int32_t flags) const { std::string output; StringAppendF(&output, "Histogram: %.*s recorded %d samples", static_cast(histogram_name.size()), histogram_name.data(), TotalCount()); if (flags) StringAppendF(&output, " (flags = 0x%x)", flags); return output; } std::string HistogramSamples::GetAsciiBody() const { HistogramBase::Count total_count = TotalCount(); double scaled_total_count = total_count / 100.0; // Determine how wide the largest bucket range is (how many digits to print), // so that we'll be able to right-align starts for the graphical bars. // Determine which bucket has the largest sample count so that we can // normalize the graphical bar-width relative to that sample count. HistogramBase::Count largest_count = 0; HistogramBase::Sample largest_sample = 0; std::unique_ptr it = Iterator(); while (!it->Done()) { HistogramBase::Sample min; int64_t max; HistogramBase::Count count; it->Get(&min, &max, &count); if (min > largest_sample) largest_sample = min; if (count > largest_count) largest_count = count; it->Next(); } // Scale histogram bucket counts to take at most 72 characters. // Note: Keep in sync w/ kLineLength sample_vector.cc 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; // iterate over each item and display them it = Iterator(); std::string output; while (!it->Done()) { HistogramBase::Sample min; int64_t max; HistogramBase::Count count; it->Get(&min, &max, &count); // value is min, so display it std::string range = GetSimpleAsciiBucketRange(min); output.append(range); for (size_t j = 0; range.size() + j < print_width + 1; ++j) output.push_back(' '); HistogramBase::Count current_size = round(count * scaling_factor); WriteAsciiBucketGraph(current_size, kLineLength, &output); WriteAsciiBucketValue(count, scaled_total_count, &output); StringAppendF(&output, "\n"); it->Next(); } return output; } void HistogramSamples::WriteAsciiBucketGraph(double x_count, int line_length, std::string* output) const { int x_remainder = line_length - x_count; while (0 < x_count--) output->append("-"); output->append("O"); while (0 < x_remainder--) output->append(" "); } void HistogramSamples::WriteAsciiBucketValue(HistogramBase::Count current, double scaled_sum, std::string* output) const { StringAppendF(output, " (%d = %3.1f%%)", current, current / scaled_sum); } const std::string HistogramSamples::GetSimpleAsciiBucketRange( HistogramBase::Sample sample) const { return StringPrintf("%d", sample); } SampleCountIterator::~SampleCountIterator() = default; bool SampleCountIterator::GetBucketIndex(size_t* index) const { DCHECK(!Done()); return false; } SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min, int64_t max, HistogramBase::Count count) : SingleSampleIterator(min, max, count, kSizeMax) {} SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min, int64_t max, HistogramBase::Count count, size_t bucket_index) : min_(min), max_(max), bucket_index_(bucket_index), count_(count) {} SingleSampleIterator::~SingleSampleIterator() = default; bool SingleSampleIterator::Done() const { return count_ == 0; } void SingleSampleIterator::Next() { DCHECK(!Done()); count_ = 0; } void SingleSampleIterator::Get(HistogramBase::Sample* min, int64_t* max, HistogramBase::Count* count) const { DCHECK(!Done()); if (min != nullptr) *min = min_; if (max != nullptr) *max = max_; if (count != nullptr) *count = count_; } bool SingleSampleIterator::GetBucketIndex(size_t* index) const { DCHECK(!Done()); if (bucket_index_ == kSizeMax) return false; *index = bucket_index_; return true; } } // namespace base