#include "base/metrics/statistics_recorder.h"
#include <algorithm>
#include <string_view>
#include "base/at_exit.h"
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/debug/leak_annotations.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_snapshot_manager.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/record_histogram_checker.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
namespace base {
namespace {
bool HistogramNameLesser(const base::HistogramBase* a,
const base::HistogramBase* b) {
return a->histogram_name() < b->histogram_name();
}
}
StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
bool StatisticsRecorder::is_vlog_initialized_ = false;
std::atomic<bool> StatisticsRecorder::have_active_callbacks_{false};
std::atomic<StatisticsRecorder::GlobalSampleCallback>
StatisticsRecorder::global_sample_callback_{nullptr};
StatisticsRecorder::ScopedHistogramSampleObserver::
ScopedHistogramSampleObserver(std::string_view name,
OnSampleCallback callback)
: histogram_name_(name),
callback_(
base::IgnoreArgs<std::optional<uint64_t>>(std::move(callback))) {
StatisticsRecorder::AddHistogramSampleObserver(histogram_name_, this);
}
StatisticsRecorder::ScopedHistogramSampleObserver::
ScopedHistogramSampleObserver(std::string_view name,
OnSampleWithEventCallback callback)
: histogram_name_(name), callback_(std::move(callback)) {
StatisticsRecorder::AddHistogramSampleObserver(histogram_name_, this);
}
StatisticsRecorder::ScopedHistogramSampleObserver::
ScopedHistogramSampleObserver(std::string_view name,
base::RepeatingClosure callback)
: histogram_name_(name),
callback_(
base::IgnoreArgs<std::optional<uint64_t>,
std::string_view,
uint64_t,
HistogramBase::Sample32>(std::move(callback))) {
StatisticsRecorder::AddHistogramSampleObserver(histogram_name_, this);
}
StatisticsRecorder::ScopedHistogramSampleObserver::
~ScopedHistogramSampleObserver() {
StatisticsRecorder::RemoveHistogramSampleObserver(histogram_name_, this);
}
void StatisticsRecorder::ScopedHistogramSampleObserver::RunCallback(
std::string_view histogram_name,
uint64_t name_hash,
HistogramBase::Sample32 sample,
std::optional<uint64_t> event_id) {
callback_.Run(event_id, histogram_name, name_hash, sample);
}
StatisticsRecorder::HistogramWaiter::HistogramWaiter(std::string_view metric_name) {
histogram_observer_ =
std::make_unique<base::StatisticsRecorder::ScopedHistogramSampleObserver>(
metric_name,
run_loop_.QuitClosure());
}
StatisticsRecorder::HistogramWaiter::~HistogramWaiter() = default;
void StatisticsRecorder::HistogramWaiter::Wait() {
run_loop_.Run();
}
StatisticsRecorder::~StatisticsRecorder() {
const AutoLock auto_lock(GetLock());
DCHECK_EQ(this, top_);
top_ = previous_;
}
void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
AssertLockHeld();
if (top_) {
return;
}
const StatisticsRecorder* const p = new StatisticsRecorder;
ANNOTATE_LEAKING_OBJECT_PTR(p);
DCHECK_EQ(p, top_);
}
void StatisticsRecorder::RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider) {
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
top_->providers_.push_back(provider);
}
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
CHECK(histogram);
uint64_t hash = histogram->name_hash();
DCHECK_EQ(hash, HashMetricName(histogram->histogram_name()));
std::unique_ptr<HistogramBase> histogram_deleter;
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
HistogramBase*& registered = top_->histograms_[hash];
if (!registered) {
registered = histogram;
ANNOTATE_LEAKING_OBJECT_PTR(histogram);
if (base::Contains(top_->observers_, hash)) {
histogram->SetFlags(HistogramBase::kCallbackExists);
}
return histogram;
}
DCHECK_EQ(histogram->histogram_name(), registered->histogram_name())
<< "Histogram name hash collision between " << histogram->histogram_name()
<< " and " << registered->histogram_name() << " (hash = " << hash << ")";
if (histogram == registered) {
return histogram;
}
histogram_deleter.reset(histogram);
return registered;
}
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
const BucketRanges* registered;
{
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
registered = top_->ranges_manager_.GetOrRegisterCanonicalRanges(ranges);
}
if (registered != ranges) {
delete ranges;
} else {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
}
return registered;
}
void StatisticsRecorder::WriteGraph(const std::string& query,
std::string* output) {
if (query.length()) {
StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
} else {
output->append("Collections of all histograms\n");
}
for (const HistogramBase* const histogram :
Sort(WithName(GetHistograms(), query))) {
histogram->WriteAscii(output);
output->append("\n");
}
}
std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
std::string output = "{\"histograms\":[";
const char* sep = "";
for (const HistogramBase* const histogram : Sort(GetHistograms())) {
output += sep;
sep = ",";
std::string json;
histogram->WriteJSON(&json, verbosity_level);
output += json;
}
output += "]}";
return output;
}
std::vector<const BucketRanges*> StatisticsRecorder::GetBucketRanges() {
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return std::vector<const BucketRanges*>();
}
return const_top->ranges_manager_.GetBucketRanges();
}
HistogramBase* StatisticsRecorder::FindHistogram(std::string_view name) {
return FindHistogram(HashMetricName(name), name);
}
HistogramBase* StatisticsRecorder::FindHistogram(uint64_t hash,
std::string_view name) {
DCHECK_EQ(hash, HashMetricName(name)) << "Hash does not match name.";
ImportGlobalPersistentHistograms();
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return nullptr;
}
return const_top->FindHistogramByHashInternal(hash, name);
}
StatisticsRecorder::HistogramProviders
StatisticsRecorder::GetHistogramProviders() {
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return StatisticsRecorder::HistogramProviders();
}
return const_top->providers_;
}
void StatisticsRecorder::ImportProvidedHistograms(bool async,
OnceClosure done_callback) {
HistogramProviders providers = GetHistogramProviders();
auto barrier_callback =
BarrierClosure(providers.size(), std::move(done_callback));
for (const WeakPtr<HistogramProvider>& provider : providers) {
if (!provider) {
barrier_callback.Run();
continue;
}
provider->MergeHistogramDeltas(async, barrier_callback);
}
}
void StatisticsRecorder::ImportProvidedHistogramsSync() {
ImportProvidedHistograms(false, DoNothing());
}
void StatisticsRecorder::PrepareDeltas(
bool include_persistent,
HistogramBase::Flags flags_to_set,
HistogramBase::Flags required_flags,
HistogramSnapshotManager* snapshot_manager) {
Histograms histograms =
Sort(GetHistograms(include_persistent, HistogramBase::Flags::kNoFlags));
snapshot_manager->PrepareDeltas(std::move(histograms), flags_to_set,
required_flags);
}
void StatisticsRecorder::InitLogOnShutdown() {
const AutoLock auto_lock(GetLock());
InitLogOnShutdownWhileLocked();
}
Lock& StatisticsRecorder::GetLock() {
static base::NoDestructor<Lock> lock;
return *lock;
}
void StatisticsRecorder::AssertLockHeld() {
GetLock().AssertAcquired();
}
HistogramBase* StatisticsRecorder::FindHistogramByHashInternal(
uint64_t hash,
std::string_view name) const {
AssertLockHeld();
const HistogramMap::const_iterator it = histograms_.find(hash);
if (it == histograms_.end()) {
return nullptr;
}
DCHECK_EQ(name, it->second->histogram_name())
<< "Histogram name hash collision between " << name << " and "
<< it->second->histogram_name() << " (hash = " << hash << ")";
return it->second;
}
void StatisticsRecorder::AddHistogramSampleObserver(
const std::string& name,
StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
DCHECK(observer);
uint64_t hash = HashMetricName(name);
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
auto iter = top_->observers_.find(hash);
if (iter == top_->observers_.end()) {
top_->observers_.insert(
{hash, base::MakeRefCounted<HistogramSampleObserverList>(
ObserverListPolicy::EXISTING_ONLY)});
}
top_->observers_[hash]->AddObserver(observer);
HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
if (histogram) {
histogram->SetFlags(HistogramBase::kCallbackExists);
}
have_active_callbacks_.store(
global_sample_callback() || !top_->observers_.empty(),
std::memory_order_relaxed);
}
void StatisticsRecorder::RemoveHistogramSampleObserver(
const std::string& name,
StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
uint64_t hash = HashMetricName(name);
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
auto iter = top_->observers_.find(hash);
CHECK(iter != top_->observers_.end());
auto result = iter->second->RemoveObserver(observer);
if (result ==
HistogramSampleObserverList::RemoveObserverResult::kWasOrBecameEmpty) {
top_->observers_.erase(hash);
HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
if (histogram) {
histogram->ClearFlags(HistogramBase::kCallbackExists);
}
}
have_active_callbacks_.store(
global_sample_callback() || !top_->observers_.empty(),
std::memory_order_relaxed);
}
void StatisticsRecorder::FindAndRunHistogramCallbacks(
base::PassKey<HistogramBase>,
std::string_view histogram_name,
uint64_t name_hash,
HistogramBase::Sample32 sample,
std::optional<uint64_t> event_id) {
DCHECK_EQ(name_hash, HashMetricName(histogram_name));
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return;
}
auto it = const_top->observers_.find(name_hash);
if (it == const_top->observers_.end()) {
return;
}
it->second->Notify(FROM_HERE, &ScopedHistogramSampleObserver::RunCallback,
histogram_name, name_hash, sample, event_id);
}
void StatisticsRecorder::SetGlobalSampleCallback(
const GlobalSampleCallback& new_global_sample_callback) {
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
DCHECK(!global_sample_callback() || !new_global_sample_callback);
global_sample_callback_.store(new_global_sample_callback);
have_active_callbacks_.store(
new_global_sample_callback || !top_->observers_.empty(),
std::memory_order_relaxed);
}
size_t StatisticsRecorder::GetHistogramCount() {
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return 0;
}
return const_top->histograms_.size();
}
void StatisticsRecorder::ForgetHistogramForTesting(std::string_view name) {
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
uint64_t hash = HashMetricName(name);
HistogramBase* base = top_->FindHistogramByHashInternal(hash, name);
if (!base) {
return;
}
if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
}
top_->histograms_.erase(hash);
}
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
const AutoLock auto_lock(GetLock());
std::unique_ptr<StatisticsRecorder> temporary_recorder =
WrapUnique(new StatisticsRecorder());
temporary_recorder->ranges_manager_
.DoNotReleaseRangesOnDestroyForTesting();
return temporary_recorder;
}
void StatisticsRecorder::SetRecordChecker(
std::unique_ptr<RecordHistogramChecker> record_checker) {
const AutoLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
top_->record_checker_ = std::move(record_checker);
}
bool StatisticsRecorder::ShouldRecordHistogram(uint32_t histogram_hash) {
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
return !const_top || !const_top->record_checker_ ||
const_top->record_checker_->ShouldRecord(histogram_hash);
}
StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms(
bool include_persistent,
int32_t exclude_flags) {
ImportGlobalPersistentHistograms();
Histograms out;
const AutoLock auto_lock(GetLock());
const auto* const_top = top_;
if (!const_top) {
return out;
}
out.reserve(const_top->histograms_.size());
for (const auto& entry : const_top->histograms_) {
bool is_persistent = entry.second->HasFlags(HistogramBase::kIsPersistent);
if (!include_persistent && is_persistent) {
continue;
}
if (exclude_flags & entry.second->flags()) {
continue;
}
out.push_back(entry.second);
}
return out;
}
StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
std::ranges::sort(histograms, &HistogramNameLesser);
return histograms;
}
StatisticsRecorder::Histograms StatisticsRecorder::WithName(
Histograms histograms,
std::string_view query,
bool case_sensitive) {
auto comparator = [case_sensitive](char a, char b) {
return case_sensitive ? a == b : std::toupper(a) == std::toupper(b);
};
auto histogram_name_does_not_contain_query =
[comparator, query](const HistogramBase* const h) {
const auto& name = h->histogram_name();
return std::search(name.begin(), name.end(), query.begin(), query.end(),
comparator) == name.end();
};
std::erase_if(histograms, histogram_name_does_not_contain_query);
return histograms;
}
void StatisticsRecorder::ImportGlobalPersistentHistograms() {
if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get()) {
allocator->ImportHistogramsToStatisticsRecorder();
}
}
StatisticsRecorder::StatisticsRecorder() {
AssertLockHeld();
previous_ = top_;
top_ = this;
InitLogOnShutdownWhileLocked();
}
void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
AssertLockHeld();
if (!is_vlog_initialized_ && VLOG_IS_ON(1)) {
is_vlog_initialized_ = true;
const auto dump_to_vlog = [](void*) {
std::string output;
WriteGraph("", &output);
VLOG(1) << output;
};
AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
}
}
}