blob: fdb95b9018042a9df5c29fff2f327ac1bba5981a [file] [log] [blame]
// 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/statistics_recorder.h"
#include <memory>
#include "base/at_exit.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/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
namespace base {
namespace {
bool HistogramNameLesser(const base::HistogramBase* a,
const base::HistogramBase* b) {
return strcmp(a->histogram_name(), b->histogram_name()) < 0;
}
} // namespace
// static
LazyInstance<Lock>::Leaky StatisticsRecorder::lock_;
// static
StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
// static
bool StatisticsRecorder::is_vlog_initialized_ = false;
size_t StatisticsRecorder::BucketRangesHash::operator()(
const BucketRanges* const a) const {
return a->checksum();
}
bool StatisticsRecorder::BucketRangesEqual::operator()(
const BucketRanges* const a,
const BucketRanges* const b) const {
return a->Equals(b);
}
StatisticsRecorder::~StatisticsRecorder() {
const AutoLock auto_lock(lock_.Get());
DCHECK_EQ(this, top_);
DCHECK_NE(this, previous_);
top_ = previous_;
// previous_ is only used for testing purpose to create temporary clean
// environment, sometimes multiple temporary environment can be messy and
// we want to make sure at least the last temporary StatisticsRecorder clears
// the static StatisticsRecorder's previous_.
if (top_ && top_->previous_) {
top_->previous_ = nullptr;
}
}
// static
void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
lock_.Get().AssertAcquired();
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 AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
top_->providers_.push_back(provider);
}
// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
// Declared before |auto_lock| to ensure correct destruction order.
std::unique_ptr<HistogramBase> histogram_deleter;
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
const char* const name = histogram->histogram_name();
HistogramBase*& registered = top_->histograms_[name];
if (!registered) {
// |name| is guaranteed to never change or be deallocated so long
// as the histogram is alive (which is forever).
registered = histogram;
ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
// If there are callbacks for this histogram, we set the kCallbackExists
// flag.
const auto callback_iterator = top_->callbacks_.find(name);
if (callback_iterator != top_->callbacks_.end()) {
if (!callback_iterator->second.is_null())
histogram->SetFlags(HistogramBase::kCallbackExists);
else
histogram->ClearFlags(HistogramBase::kCallbackExists);
}
return histogram;
}
if (histogram == registered) {
// The histogram was registered before.
return histogram;
}
// We already have one histogram with this name.
histogram_deleter.reset(histogram);
return registered;
}
// static
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
DCHECK(ranges->HasValidChecksum());
// Declared before |auto_lock| to ensure correct destruction order.
std::unique_ptr<const BucketRanges> ranges_deleter;
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
const BucketRanges* const registered = *top_->ranges_.insert(ranges).first;
if (registered == ranges) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
} else {
ranges_deleter.reset(ranges);
}
return registered;
}
// static
void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
std::string* output) {
for (const HistogramBase* const histogram :
Sort(WithName(GetHistograms(), query))) {
histogram->WriteHTMLGraph(output);
*output += "<br><hr><br>";
}
}
// 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() {
std::vector<const BucketRanges*> out;
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
out.reserve(top_->ranges_.size());
out.assign(top_->ranges_.begin(), top_->ranges_.end());
return out;
}
// static
HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
// 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();
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
const HistogramMap::const_iterator it = top_->histograms_.find(name);
return it != top_->histograms_.end() ? it->second : nullptr;
}
// static
StatisticsRecorder::HistogramProviders
StatisticsRecorder::GetHistogramProviders() {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
return top_->providers_;
}
// static
void StatisticsRecorder::ImportProvidedHistograms() {
// Merge histogram data from each provider in turn.
for (const WeakPtr<HistogramProvider>& provider : GetHistogramProviders()) {
// Weak-pointer may be invalid if the provider was destructed, though they
// generally never are.
if (provider)
provider->MergeHistogramDeltas();
}
}
// static
void StatisticsRecorder::PrepareDeltas(
bool include_persistent,
HistogramBase::Flags flags_to_set,
HistogramBase::Flags required_flags,
HistogramSnapshotManager* snapshot_manager) {
Histograms histograms = GetHistograms();
if (!include_persistent)
histograms = NonPersistent(std::move(histograms));
snapshot_manager->PrepareDeltas(Sort(std::move(histograms)), flags_to_set,
required_flags);
}
// static
void StatisticsRecorder::InitLogOnShutdown() {
const AutoLock auto_lock(lock_.Get());
InitLogOnShutdownWhileLocked();
}
// static
bool StatisticsRecorder::SetCallback(
const std::string& name,
const StatisticsRecorder::OnSampleCallback& cb) {
DCHECK(!cb.is_null());
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
if (!top_->callbacks_.insert({name, cb}).second)
return false;
const HistogramMap::const_iterator it = top_->histograms_.find(name);
if (it != top_->histograms_.end())
it->second->SetFlags(HistogramBase::kCallbackExists);
return true;
}
// static
void StatisticsRecorder::ClearCallback(const std::string& name) {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
top_->callbacks_.erase(name);
// We also clear the flag from the histogram (if it exists).
const HistogramMap::const_iterator it = top_->histograms_.find(name);
if (it != top_->histograms_.end())
it->second->ClearFlags(HistogramBase::kCallbackExists);
}
// static
StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
const std::string& name) {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
const auto it = top_->callbacks_.find(name);
return it != top_->callbacks_.end() ? it->second : OnSampleCallback();
}
// static
size_t StatisticsRecorder::GetHistogramCount() {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
return top_->histograms_.size();
}
// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
const HistogramMap::iterator found = top_->histograms_.find(name);
if (found == top_->histograms_.end())
return;
HistogramBase* const base = found->second;
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);
}
top_->histograms_.erase(found);
}
// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
const AutoLock auto_lock(lock_.Get());
return WrapUnique(new StatisticsRecorder());
}
// static
void StatisticsRecorder::SetRecordChecker(
std::unique_ptr<RecordHistogramChecker> record_checker) {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
top_->record_checker_ = std::move(record_checker);
}
// static
bool StatisticsRecorder::ShouldRecordHistogram(uint64_t histogram_hash) {
const AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
return !top_->record_checker_ ||
top_->record_checker_->ShouldRecord(histogram_hash);
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms() {
// 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 AutoLock auto_lock(lock_.Get());
EnsureGlobalRecorderWhileLocked();
out.reserve(top_->histograms_.size());
for (const auto& entry : top_->histograms_)
out.push_back(entry.second);
return out;
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
std::sort(histograms.begin(), histograms.end(), &HistogramNameLesser);
return histograms;
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::WithName(
Histograms histograms,
const std::string& query) {
// Need a C-string query for comparisons against C-string histogram name.
const char* const query_string = query.c_str();
histograms.erase(std::remove_if(histograms.begin(), histograms.end(),
[query_string](const HistogramBase* const h) {
return !strstr(h->histogram_name(),
query_string);
}),
histograms.end());
return histograms;
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::NonPersistent(
Histograms histograms) {
histograms.erase(
std::remove_if(histograms.begin(), histograms.end(),
[](const HistogramBase* const h) {
return (h->flags() & HistogramBase::kIsPersistent) != 0;
}),
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();
}
// This singleton instance should be started during the single threaded portion
// of main(), and hence it is not thread safe. It initializes globals to provide
// support for all future calls.
StatisticsRecorder::StatisticsRecorder() {
lock_.Get().AssertAcquired();
previous_ = top_;
top_ = this;
InitLogOnShutdownWhileLocked();
}
// static
void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
lock_.Get().AssertAcquired();
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