blob: 8069a516ca12f13deec49690e52e42889f687877 [file] [log] [blame]
// 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/statistics_recorder.h"
#include "base/at_exit.h"
#include "base/barrier_closure.h"
#include "base/command_line.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_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/rand_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
namespace base {
namespace {
// Whether a 50/50 trial for using a R/W lock should be run.
// Restrict it to Windows for now as other platforms show poor results.
#if BUILDFLAG(IS_WIN)
constexpr bool kRunRwLockTrial = true;
#else
constexpr bool kRunRwLockTrial = false;
#endif // BUILDFLAG(IS_WIN)
// Whether the R/W lock should be used when the trial is not active.
// Only enabled on Windows for now, since other platforms show poor results.
#if BUILDFLAG(IS_WIN)
constexpr bool kUseRwLockByDefault = true;
#else
constexpr bool kUseRwLockByDefault = false;
#endif // BUILDFLAG(IS_WIN)
bool EnableBenchmarking() {
// TODO(asvitkine): If this code ends up not being temporary, refactor it to
// not duplicate the constant name. (Right now it's at a different layer.)
return CommandLine::InitializedForCurrentProcess() &&
CommandLine::ForCurrentProcess()->HasSwitch("enable-benchmarking");
}
bool HistogramNameLesser(const base::HistogramBase* a,
const base::HistogramBase* b) {
return strcmp(a->histogram_name(), b->histogram_name()) < 0;
}
} // namespace
// static
LazyInstance<StatisticsRecorder::SrLock>::Leaky StatisticsRecorder::lock_ =
LAZY_INSTANCE_INITIALIZER;
// static
LazyInstance<base::Lock>::Leaky StatisticsRecorder::snapshot_lock_ =
LAZY_INSTANCE_INITIALIZER;
// static
StatisticsRecorder::SnapshotTransactionId
StatisticsRecorder::last_snapshot_transaction_id_ = 0;
// static
StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
// static
bool StatisticsRecorder::is_vlog_initialized_ = false;
// static
std::atomic<bool> StatisticsRecorder::have_active_callbacks_{false};
// static
std::atomic<StatisticsRecorder::GlobalSampleCallback>
StatisticsRecorder::global_sample_callback_{nullptr};
StatisticsRecorder::ScopedHistogramSampleObserver::
ScopedHistogramSampleObserver(const std::string& name,
OnSampleCallback callback)
: histogram_name_(name), callback_(callback) {
StatisticsRecorder::AddHistogramSampleObserver(histogram_name_, this);
}
StatisticsRecorder::ScopedHistogramSampleObserver::
~ScopedHistogramSampleObserver() {
StatisticsRecorder::RemoveHistogramSampleObserver(histogram_name_, this);
}
void StatisticsRecorder::ScopedHistogramSampleObserver::RunCallback(
const char* histogram_name,
uint64_t name_hash,
HistogramBase::Sample sample) {
callback_.Run(histogram_name, name_hash, sample);
}
StatisticsRecorder::~StatisticsRecorder() {
const SrAutoWriterLock auto_lock(GetLock());
DCHECK_EQ(this, top_);
top_ = previous_;
}
// static
void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
AssertLockHeld();
if (top_) {
return;
}
const StatisticsRecorder* const p = new StatisticsRecorder;
// The global recorder is never deleted.
ANNOTATE_LEAKING_OBJECT_PTR(p);
DCHECK_EQ(p, top_);
}
// static
void StatisticsRecorder::RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider) {
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
top_->providers_.push_back(provider);
}
// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
uint64_t hash = histogram->name_hash();
// Ensure that histograms use HashMetricName() to compute their hash, since
// that function is used to look up histograms.
DCHECK_EQ(hash, HashMetricName(histogram->histogram_name()));
// Declared before |auto_lock| so that the histogram is deleted after the lock
// is released (no point in holding the lock longer than needed).
std::unique_ptr<HistogramBase> histogram_deleter;
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
HistogramBase*& registered = top_->histograms_[hash];
if (!registered) {
registered = histogram;
ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
// If there are callbacks for this histogram, we set the kCallbackExists
// flag.
if (base::Contains(top_->observers_, hash)) {
// Note: SetFlags() does not write to persistent memory, it only writes to
// an in-memory version of the flags.
histogram->SetFlags(HistogramBase::kCallbackExists);
}
return histogram;
}
// Assert that there was no collision. Note that this is intentionally a
// DCHECK because 1) this is expensive to call repeatedly, and 2) this
// comparison may cause a read in persistent memory, which can cause I/O (this
// is bad because |lock_| is currently being held).
//
// If you are a developer adding a new histogram and this DCHECK is being hit,
// you are unluckily a victim of a hash collision. For now, the best solution
// is to rename the histogram. Reach out to chrome-metrics-team@google.com if
// you are unsure!
DCHECK_EQ(strcmp(histogram->histogram_name(), registered->histogram_name()),
0)
<< "Histogram name hash collision between " << histogram->histogram_name()
<< " and " << registered->histogram_name() << " (hash = " << hash << ")";
if (histogram == registered) {
// The histogram was registered before.
return histogram;
}
// We already have a histogram with this name.
histogram_deleter.reset(histogram);
return registered;
}
// static
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
const BucketRanges* registered;
{
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
registered = top_->ranges_manager_.GetOrRegisterCanonicalRanges(ranges);
}
// Delete the duplicate ranges outside the lock to reduce contention.
if (registered != ranges) {
delete ranges;
} else {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
}
return registered;
}
// static
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");
}
}
// static
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;
}
// static
std::vector<const BucketRanges*> StatisticsRecorder::GetBucketRanges() {
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return std::vector<const BucketRanges*>();
}
return const_top->ranges_manager_.GetBucketRanges();
}
// static
HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
uint64_t hash = HashMetricName(name);
// This must be called *before* the lock is acquired below because it may call
// back into StatisticsRecorder to register histograms. Those called methods
// will acquire the lock at that time.
ImportGlobalPersistentHistograms();
// Acquire the lock in "read" mode since we're only reading the data, not
// modifying anything. This allows multiple readers to look up histograms
// concurrently.
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return nullptr;
}
return const_top->FindHistogramByHashInternal(hash, name);
}
// static
StatisticsRecorder::HistogramProviders
StatisticsRecorder::GetHistogramProviders() {
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return StatisticsRecorder::HistogramProviders();
}
return const_top->providers_;
}
// static
void StatisticsRecorder::ImportProvidedHistograms(bool async,
OnceClosure done_callback) {
// Merge histogram data from each provider in turn.
HistogramProviders providers = GetHistogramProviders();
auto barrier_callback =
BarrierClosure(providers.size(), std::move(done_callback));
for (const WeakPtr<HistogramProvider>& provider : providers) {
// Weak-pointer may be invalid if the provider was destructed, though they
// generally never are.
if (!provider) {
barrier_callback.Run();
continue;
}
provider->MergeHistogramDeltas(async, barrier_callback);
}
}
// static
void StatisticsRecorder::ImportProvidedHistogramsSync() {
ImportProvidedHistograms(/*async=*/false, /*done_callback=*/DoNothing());
}
// static
StatisticsRecorder::SnapshotTransactionId StatisticsRecorder::PrepareDeltas(
bool include_persistent,
HistogramBase::Flags flags_to_set,
HistogramBase::Flags required_flags,
HistogramSnapshotManager* snapshot_manager) {
Histograms histograms = Sort(GetHistograms(include_persistent));
base::AutoLock lock(snapshot_lock_.Get());
snapshot_manager->PrepareDeltas(std::move(histograms), flags_to_set,
required_flags);
return ++last_snapshot_transaction_id_;
}
// static
StatisticsRecorder::SnapshotTransactionId
StatisticsRecorder::SnapshotUnloggedSamples(
HistogramBase::Flags required_flags,
HistogramSnapshotManager* snapshot_manager) {
Histograms histograms = Sort(GetHistograms());
base::AutoLock lock(snapshot_lock_.Get());
snapshot_manager->SnapshotUnloggedSamples(std::move(histograms),
required_flags);
return ++last_snapshot_transaction_id_;
}
// static
StatisticsRecorder::SnapshotTransactionId
StatisticsRecorder::GetLastSnapshotTransactionId() {
base::AutoLock lock(snapshot_lock_.Get());
return last_snapshot_transaction_id_;
}
// static
void StatisticsRecorder::InitLogOnShutdown() {
const SrAutoWriterLock auto_lock(GetLock());
InitLogOnShutdownWhileLocked();
}
// static
StringPiece StatisticsRecorder::GetLockTrialGroup() {
if (kRunRwLockTrial && !EnableBenchmarking()) {
return lock_.Get().use_shared_mutex() ? "Enabled" : "Disabled";
}
return StringPiece();
}
// static
bool StatisticsRecorder::SrLock::ShouldUseSharedMutex() {
// Force deterministic results for benchmarks.
if (kRunRwLockTrial && !EnableBenchmarking()) {
return RandInt(0, 1) == 1;
}
return kUseRwLockByDefault;
}
HistogramBase* StatisticsRecorder::FindHistogramByHashInternal(
uint64_t hash,
StringPiece name) const {
AssertLockHeld();
const HistogramMap::const_iterator it = histograms_.find(hash);
if (it == histograms_.end()) {
return nullptr;
}
// Assert that there was no collision. Note that this is intentionally a
// DCHECK because 1) this is expensive to call repeatedly, and 2) this
// comparison may cause a read in persistent memory, which can cause I/O (this
// is bad because |lock_| is currently being held).
//
// If you are a developer adding a new histogram and this DCHECK is being hit,
// you are unluckily a victim of a hash collision. For now, the best solution
// is to rename the histogram. Reach out to chrome-metrics-team@google.com if
// you are unsure!
DCHECK_EQ(name, it->second->histogram_name())
<< "Histogram name hash collision between " << name << " and "
<< it->second->histogram_name() << " (hash = " << hash << ")";
return it->second;
}
// static
void StatisticsRecorder::AddHistogramSampleObserver(
const std::string& name,
StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
DCHECK(observer);
uint64_t hash = HashMetricName(name);
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
auto iter = top_->observers_.find(hash);
if (iter == top_->observers_.end()) {
top_->observers_.insert(
{hash, base::MakeRefCounted<HistogramSampleObserverList>()});
}
top_->observers_[hash]->AddObserver(observer);
HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
if (histogram) {
// Note: SetFlags() does not write to persistent memory, it only writes to
// an in-memory version of the flags.
histogram->SetFlags(HistogramBase::kCallbackExists);
}
have_active_callbacks_.store(
global_sample_callback() || !top_->observers_.empty(),
std::memory_order_relaxed);
}
// static
void StatisticsRecorder::RemoveHistogramSampleObserver(
const std::string& name,
StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
uint64_t hash = HashMetricName(name);
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
auto iter = top_->observers_.find(hash);
DCHECK(iter != top_->observers_.end());
auto result = iter->second->RemoveObserver(observer);
if (result ==
HistogramSampleObserverList::RemoveObserverResult::kWasOrBecameEmpty) {
top_->observers_.erase(hash);
// We also clear the flag from the histogram (if it exists).
HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
if (histogram) {
// Note: ClearFlags() does not write to persistent memory, it only writes
// to an in-memory version of the flags.
histogram->ClearFlags(HistogramBase::kCallbackExists);
}
}
have_active_callbacks_.store(
global_sample_callback() || !top_->observers_.empty(),
std::memory_order_relaxed);
}
// static
void StatisticsRecorder::FindAndRunHistogramCallbacks(
base::PassKey<HistogramBase>,
const char* histogram_name,
uint64_t name_hash,
HistogramBase::Sample sample) {
DCHECK_EQ(name_hash, HashMetricName(histogram_name));
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return;
}
auto it = const_top->observers_.find(name_hash);
// Ensure that this observer is still registered, as it might have been
// unregistered before we acquired the lock.
if (it == const_top->observers_.end()) {
return;
}
it->second->Notify(FROM_HERE, &ScopedHistogramSampleObserver::RunCallback,
histogram_name, name_hash, sample);
}
// static
void StatisticsRecorder::SetGlobalSampleCallback(
const GlobalSampleCallback& new_global_sample_callback) {
const SrAutoWriterLock 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);
}
// static
size_t StatisticsRecorder::GetHistogramCount() {
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return 0;
}
return const_top->histograms_.size();
}
// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
uint64_t hash = HashMetricName(name);
HistogramBase* base = top_->FindHistogramByHashInternal(hash, name);
if (!base) {
return;
}
if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
// When forgetting a histogram, it's likely that other information is also
// becoming invalid. Clear the persistent reference that may no longer be
// valid. There's no danger in this as, at worst, duplicates will be created
// in persistent memory.
static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
}
// This performs another lookup in the map, but this is fine since this is
// only used in tests.
top_->histograms_.erase(hash);
}
// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
const SrAutoWriterLock auto_lock(GetLock());
std::unique_ptr<StatisticsRecorder> temporary_recorder =
WrapUnique(new StatisticsRecorder());
temporary_recorder->ranges_manager_
.DoNotReleaseRangesOnDestroyForTesting(); // IN-TEST
return temporary_recorder;
}
// static
void StatisticsRecorder::SetRecordChecker(
std::unique_ptr<RecordHistogramChecker> record_checker) {
const SrAutoWriterLock auto_lock(GetLock());
EnsureGlobalRecorderWhileLocked();
top_->record_checker_ = std::move(record_checker);
}
// static
bool StatisticsRecorder::ShouldRecordHistogram(uint32_t histogram_hash) {
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
return !const_top || !const_top->record_checker_ ||
const_top->record_checker_->ShouldRecord(histogram_hash);
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms(
bool include_persistent) {
// This must be called *before* the lock is acquired below because it will
// call back into this object to register histograms. Those called methods
// will acquire the lock at that time.
ImportGlobalPersistentHistograms();
Histograms out;
const SrAutoReaderLock auto_lock(GetLock());
// Manipulate |top_| through a const variable to ensure it is not mutated.
const auto* const_top = top_;
if (!const_top) {
return out;
}
out.reserve(const_top->histograms_.size());
for (const auto& entry : const_top->histograms_) {
// Note: HasFlags() does not read to persistent memory, it only reads an
// in-memory version of the flags.
bool is_persistent = entry.second->HasFlags(HistogramBase::kIsPersistent);
if (!include_persistent && is_persistent) {
continue;
}
out.push_back(entry.second);
}
return out;
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
ranges::sort(histograms, &HistogramNameLesser);
return histograms;
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::WithName(
Histograms histograms,
const std::string& query,
bool case_sensitive) {
// Need a C-string query for comparisons against C-string histogram name.
std::string lowercase_query;
const char* query_string;
if (case_sensitive) {
query_string = query.c_str();
} else {
lowercase_query = base::ToLowerASCII(query);
query_string = lowercase_query.c_str();
}
histograms.erase(
ranges::remove_if(
histograms,
[query_string, case_sensitive](const HistogramBase* const h) {
return !strstr(
case_sensitive
? h->histogram_name()
: base::ToLowerASCII(h->histogram_name()).c_str(),
query_string);
}),
histograms.end());
return histograms;
}
// static
void StatisticsRecorder::ImportGlobalPersistentHistograms() {
// Import histograms from known persistent storage. Histograms could have been
// added by other processes and they must be fetched and recognized locally.
// If the persistent memory segment is not shared between processes, this call
// does nothing.
if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get())
allocator->ImportHistogramsToStatisticsRecorder();
}
StatisticsRecorder::StatisticsRecorder() {
AssertLockHeld();
previous_ = top_;
top_ = this;
InitLogOnShutdownWhileLocked();
}
// static
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);
}
}
} // namespace base