| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/metrics/histogram_base.h" |
| |
| #include <limits.h> |
| |
| #include <memory> |
| #include <set> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/histogram_samples.h" |
| #include "base/metrics/sparse_histogram.h" |
| #include "base/metrics/statistics_recorder.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/pickle.h" |
| #include "base/process/process_handle.h" |
| #include "base/rand_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "base/values.h" |
| |
| namespace base { |
| |
| std::string HistogramTypeToString(HistogramType type) { |
| switch (type) { |
| case HISTOGRAM: |
| return "HISTOGRAM"; |
| case LINEAR_HISTOGRAM: |
| return "LINEAR_HISTOGRAM"; |
| case BOOLEAN_HISTOGRAM: |
| return "BOOLEAN_HISTOGRAM"; |
| case CUSTOM_HISTOGRAM: |
| return "CUSTOM_HISTOGRAM"; |
| case SPARSE_HISTOGRAM: |
| return "SPARSE_HISTOGRAM"; |
| case DUMMY_HISTOGRAM: |
| return "DUMMY_HISTOGRAM"; |
| } |
| NOTREACHED(); |
| return "UNKNOWN"; |
| } |
| |
| HistogramBase* DeserializeHistogramInfo(PickleIterator* iter) { |
| int type; |
| if (!iter->ReadInt(&type)) |
| return nullptr; |
| |
| switch (type) { |
| case HISTOGRAM: |
| return Histogram::DeserializeInfoImpl(iter); |
| case LINEAR_HISTOGRAM: |
| return LinearHistogram::DeserializeInfoImpl(iter); |
| case BOOLEAN_HISTOGRAM: |
| return BooleanHistogram::DeserializeInfoImpl(iter); |
| case CUSTOM_HISTOGRAM: |
| return CustomHistogram::DeserializeInfoImpl(iter); |
| case SPARSE_HISTOGRAM: |
| return SparseHistogram::DeserializeInfoImpl(iter); |
| default: |
| return nullptr; |
| } |
| } |
| |
| HistogramBase::CountAndBucketData::CountAndBucketData(Count count, |
| int64_t sum, |
| Value::List buckets) |
| : count(count), sum(sum), buckets(std::move(buckets)) {} |
| |
| HistogramBase::CountAndBucketData::~CountAndBucketData() = default; |
| |
| HistogramBase::CountAndBucketData::CountAndBucketData( |
| CountAndBucketData&& other) = default; |
| |
| HistogramBase::CountAndBucketData& HistogramBase::CountAndBucketData::operator=( |
| CountAndBucketData&& other) = default; |
| |
| const HistogramBase::Sample HistogramBase::kSampleType_MAX = INT_MAX; |
| |
| HistogramBase::HistogramBase(const char* name) |
| : histogram_name_(name), flags_(kNoFlags) {} |
| |
| HistogramBase::~HistogramBase() = default; |
| |
| void HistogramBase::CheckName(const StringPiece& name) const { |
| DCHECK_EQ(StringPiece(histogram_name()), name) |
| << "Provided histogram name doesn't match instance name. Are you using a " |
| "dynamic string in a macro?"; |
| } |
| |
| void HistogramBase::SetFlags(int32_t flags) { |
| flags_.fetch_or(flags, std::memory_order_relaxed); |
| } |
| |
| void HistogramBase::ClearFlags(int32_t flags) { |
| flags_.fetch_and(~flags, std::memory_order_relaxed); |
| } |
| |
| bool HistogramBase::HasFlags(int32_t flags) const { |
| // Check this->flags() is a superset of |flags|, i.e. every flag in |flags| is |
| // included. |
| return (this->flags() & flags) == flags; |
| } |
| |
| void HistogramBase::AddScaled(Sample value, int count, int scale) { |
| DCHECK_GT(scale, 0); |
| |
| // Convert raw count and probabilistically round up/down if the remainder |
| // is more than a random number [0, scale). This gives a more accurate |
| // count when there are a large number of records. RandInt is "inclusive", |
| // hence the -1 for the max value. |
| int count_scaled = count / scale; |
| if (count - (count_scaled * scale) > base::RandInt(0, scale - 1)) |
| ++count_scaled; |
| if (count_scaled <= 0) |
| return; |
| |
| AddCount(value, count_scaled); |
| } |
| |
| void HistogramBase::AddKilo(Sample value, int count) { |
| AddScaled(value, count, 1000); |
| } |
| |
| void HistogramBase::AddKiB(Sample value, int count) { |
| AddScaled(value, count, 1024); |
| } |
| |
| void HistogramBase::AddTimeMillisecondsGranularity(const TimeDelta& time) { |
| Add(saturated_cast<Sample>(time.InMilliseconds())); |
| } |
| |
| void HistogramBase::AddTimeMicrosecondsGranularity(const TimeDelta& time) { |
| // Intentionally drop high-resolution reports on clients with low-resolution |
| // clocks. High-resolution metrics cannot make use of low-resolution data and |
| // reporting it merely adds noise to the metric. https://crbug.com/807615#c16 |
| if (TimeTicks::IsHighResolution()) |
| Add(saturated_cast<Sample>(time.InMicroseconds())); |
| } |
| |
| void HistogramBase::AddBoolean(bool value) { |
| Add(value ? 1 : 0); |
| } |
| |
| void HistogramBase::SerializeInfo(Pickle* pickle) const { |
| pickle->WriteInt(GetHistogramType()); |
| SerializeInfoImpl(pickle); |
| } |
| |
| uint32_t HistogramBase::FindCorruption(const HistogramSamples& samples) const { |
| // Not supported by default. |
| return NO_INCONSISTENCIES; |
| } |
| |
| void HistogramBase::ValidateHistogramContents() const {} |
| |
| void HistogramBase::WriteJSON(std::string* output, |
| JSONVerbosityLevel verbosity_level) const { |
| CountAndBucketData count_and_bucket_data = GetCountAndBucketData(); |
| Value::Dict parameters = GetParameters(); |
| |
| JSONStringValueSerializer serializer(output); |
| Value::Dict root; |
| root.Set("name", histogram_name()); |
| root.Set("count", count_and_bucket_data.count); |
| root.Set("sum", static_cast<double>(count_and_bucket_data.sum)); |
| root.Set("flags", flags()); |
| root.Set("params", std::move(parameters)); |
| if (verbosity_level != JSON_VERBOSITY_LEVEL_OMIT_BUCKETS) |
| root.Set("buckets", std::move(count_and_bucket_data.buckets)); |
| root.Set("pid", static_cast<int>(GetUniqueIdForProcess().GetUnsafeValue())); |
| serializer.Serialize(root); |
| } |
| |
| void HistogramBase::FindAndRunCallbacks(HistogramBase::Sample sample) const { |
| StatisticsRecorder::GlobalSampleCallback global_sample_callback = |
| StatisticsRecorder::global_sample_callback(); |
| if (global_sample_callback) |
| global_sample_callback(histogram_name(), name_hash(), sample); |
| |
| // We check the flag first since it is very cheap and we can avoid the |
| // function call and lock overhead of FindAndRunHistogramCallbacks(). |
| if (!HasFlags(kCallbackExists)) { |
| return; |
| } |
| |
| StatisticsRecorder::FindAndRunHistogramCallbacks( |
| base::PassKey<HistogramBase>(), histogram_name(), name_hash(), sample); |
| } |
| |
| HistogramBase::CountAndBucketData HistogramBase::GetCountAndBucketData() const { |
| std::unique_ptr<HistogramSamples> snapshot = SnapshotSamples(); |
| Count count = snapshot->TotalCount(); |
| int64_t sum = snapshot->sum(); |
| std::unique_ptr<SampleCountIterator> it = snapshot->Iterator(); |
| |
| Value::List buckets; |
| while (!it->Done()) { |
| Sample bucket_min; |
| int64_t bucket_max; |
| Count bucket_count; |
| it->Get(&bucket_min, &bucket_max, &bucket_count); |
| |
| Value::Dict bucket_value; |
| bucket_value.Set("low", bucket_min); |
| // TODO(crbug.com/1334256): Make base::Value able to hold int64_t and remove |
| // this cast. |
| bucket_value.Set("high", static_cast<int>(bucket_max)); |
| bucket_value.Set("count", bucket_count); |
| buckets.Append(std::move(bucket_value)); |
| it->Next(); |
| } |
| |
| return CountAndBucketData(count, sum, std::move(buckets)); |
| } |
| |
| void HistogramBase::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(" "); |
| } |
| |
| const std::string HistogramBase::GetSimpleAsciiBucketRange( |
| Sample sample) const { |
| return StringPrintf("%d", sample); |
| } |
| |
| void HistogramBase::WriteAsciiBucketValue(Count current, |
| double scaled_sum, |
| std::string* output) const { |
| StringAppendF(output, " (%d = %3.1f%%)", current, current / scaled_sum); |
| } |
| |
| void HistogramBase::WriteAscii(std::string* output) const { |
| base::Value::Dict graph_dict = ToGraphDict(); |
| output->append(*graph_dict.FindString("header")); |
| output->append("\n"); |
| output->append(*graph_dict.FindString("body")); |
| } |
| |
| // static |
| char const* HistogramBase::GetPermanentName(const std::string& name) { |
| // A set of histogram names that provides the "permanent" lifetime required |
| // by histogram objects for those strings that are not already code constants |
| // or held in persistent memory. |
| static base::NoDestructor<std::set<std::string>> permanent_names; |
| static base::NoDestructor<Lock> permanent_names_lock; |
| |
| AutoLock lock(*permanent_names_lock); |
| auto result = permanent_names->insert(name); |
| return result.first->c_str(); |
| } |
| |
| } // namespace base |