// 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/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/values.h"
namespace {
// Initialize histogram statistics gathering system.
base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
LAZY_INSTANCE_INITIALIZER;
bool HistogramNameLesser(const base::HistogramBase* a,
const base::HistogramBase* b) {
return a->histogram_name() < b->histogram_name();
}
} // namespace
namespace base {
StatisticsRecorder::HistogramIterator::HistogramIterator(
const HistogramMap::iterator& iter, bool include_persistent)
: iter_(iter),
include_persistent_(include_persistent) {
// The starting location could point to a persistent histogram when such
// is not wanted. If so, skip it.
if (!include_persistent_ && iter_ != histograms_->end() &&
(iter_->second->flags() & HistogramBase::kIsPersistent)) {
// This operator will continue to skip until a non-persistent histogram
// is found.
operator++();
}
}
StatisticsRecorder::HistogramIterator::HistogramIterator(
const HistogramIterator& rhs)
: iter_(rhs.iter_),
include_persistent_(rhs.include_persistent_) {
}
StatisticsRecorder::HistogramIterator::~HistogramIterator() {}
StatisticsRecorder::HistogramIterator&
StatisticsRecorder::HistogramIterator::operator++() {
const HistogramMap::iterator histograms_end = histograms_->end();
if (iter_ == histograms_end || lock_ == NULL)
return *this;
base::AutoLock auto_lock(*lock_);
for (;;) {
++iter_;
if (iter_ == histograms_end)
break;
if (!include_persistent_ && (iter_->second->flags() &
HistogramBase::kIsPersistent)) {
continue;
}
break;
}
return *this;
}
StatisticsRecorder::~StatisticsRecorder() {
DCHECK(lock_);
DCHECK(histograms_);
DCHECK(ranges_);
// Clean out what this object created and then restore what existed before.
Reset();
base::AutoLock auto_lock(*lock_);
histograms_ = existing_histograms_.release();
callbacks_ = existing_callbacks_.release();
ranges_ = existing_ranges_.release();
}
// static
void StatisticsRecorder::Initialize() {
// Ensure that an instance of the StatisticsRecorder object is created.
g_statistics_recorder_.Get();
}
// static
bool StatisticsRecorder::IsActive() {
if (lock_ == NULL)
return false;
base::AutoLock auto_lock(*lock_);
return NULL != histograms_;
}
// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
// As per crbug.com/79322 the histograms are intentionally leaked, so we need
// to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used only once
// for an object, the duplicates should not be annotated.
// Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
// twice if (lock_ == NULL) || (!histograms_).
if (lock_ == NULL) {
ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
return histogram;
}
HistogramBase* histogram_to_delete = NULL;
HistogramBase* histogram_to_return = NULL;
{
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL) {
histogram_to_return = histogram;
} else {
const std::string& name = histogram->histogram_name();
HistogramMap::iterator it = histograms_->find(name);
if (histograms_->end() == it) {
// The StringKey references the name within |histogram| rather than
// making a copy.
(*histograms_)[name] = histogram;
ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
// If there are callbacks for this histogram, we set the kCallbackExists
// flag.
auto callback_iterator = callbacks_->find(name);
if (callback_iterator != callbacks_->end()) {
if (!callback_iterator->second.is_null())
histogram->SetFlags(HistogramBase::kCallbackExists);
else
histogram->ClearFlags(HistogramBase::kCallbackExists);
}
histogram_to_return = histogram;
} else if (histogram == it->second) {
// The histogram was registered before.
histogram_to_return = histogram;
} else {
// We already have one histogram with this name.
DCHECK_EQ(histogram->histogram_name(),
it->second->histogram_name()) << "hash collision";
histogram_to_return = it->second;
histogram_to_delete = histogram;
}
}
}
delete histogram_to_delete;
return histogram_to_return;
}
// static
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
DCHECK(ranges->HasValidChecksum());
std::unique_ptr<const BucketRanges> ranges_deleter;
if (lock_ == NULL) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
return ranges;
}
base::AutoLock auto_lock(*lock_);
if (ranges_ == NULL) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
return ranges;
}
std::list<const BucketRanges*>* checksum_matching_list;
RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
if (ranges_->end() == ranges_it) {
// Add a new matching list to map.
checksum_matching_list = new std::list<const BucketRanges*>();
ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list);
(*ranges_)[ranges->checksum()] = checksum_matching_list;
} else {
checksum_matching_list = ranges_it->second;
}
for (const BucketRanges* existing_ranges : *checksum_matching_list) {
if (existing_ranges->Equals(ranges)) {
if (existing_ranges == ranges) {
return ranges;
} else {
ranges_deleter.reset(ranges);
return existing_ranges;
}
}
}
// We haven't found a BucketRanges which has the same ranges. Register the
// new BucketRanges.
checksum_matching_list->push_front(ranges);
return ranges;
}
// static
void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
std::string* output) {
if (!IsActive())
return;
Histograms snapshot;
GetSnapshot(query, &snapshot);
std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
for (const HistogramBase* histogram : snapshot) {
histogram->WriteHTMLGraph(output);
output->append("<br><hr><br>");
}
}
// static
void StatisticsRecorder::WriteGraph(const std::string& query,
std::string* output) {
if (!IsActive())
return;
if (query.length())
StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
else
output->append("Collections of all histograms\n");
Histograms snapshot;
GetSnapshot(query, &snapshot);
std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
for (const HistogramBase* histogram : snapshot) {
histogram->WriteAscii(output);
output->append("\n");
}
}
// static
std::string StatisticsRecorder::ToJSON(const std::string& query) {
if (!IsActive())
return std::string();
std::string output("{");
if (!query.empty()) {
output += "\"query\":";
EscapeJSONString(query, true, &output);
output += ",";
}
Histograms snapshot;
GetSnapshot(query, &snapshot);
output += "\"histograms\":[";
bool first_histogram = true;
for (const HistogramBase* histogram : snapshot) {
if (first_histogram)
first_histogram = false;
else
output += ",";
std::string json;
histogram->WriteJSON(&json);
output += json;
}
output += "]}";
return output;
}
// static
void StatisticsRecorder::GetHistograms(Histograms* output) {
if (lock_ == NULL)
return;
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return;
for (const auto& entry : *histograms_) {
output->push_back(entry.second);
}
}
// static
void StatisticsRecorder::GetBucketRanges(
std::vector<const BucketRanges*>* output) {
if (lock_ == NULL)
return;
base::AutoLock auto_lock(*lock_);
if (ranges_ == NULL)
return;
for (const auto& entry : *ranges_) {
for (auto* range_entry : *entry.second) {
output->push_back(range_entry);
}
}
}
// 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();
if (lock_ == NULL)
return NULL;
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return NULL;
HistogramMap::iterator it = histograms_->find(name);
if (histograms_->end() == it)
return NULL;
return it->second;
}
// static
StatisticsRecorder::HistogramIterator StatisticsRecorder::begin(
bool include_persistent) {
DCHECK(histograms_);
ImportGlobalPersistentHistograms();
HistogramMap::iterator iter_begin;
{
base::AutoLock auto_lock(*lock_);
iter_begin = histograms_->begin();
}
return HistogramIterator(iter_begin, include_persistent);
}
// static
StatisticsRecorder::HistogramIterator StatisticsRecorder::end() {
HistogramMap::iterator iter_end;
{
base::AutoLock auto_lock(*lock_);
iter_end = histograms_->end();
}
return HistogramIterator(iter_end, true);
}
// static
void StatisticsRecorder::InitLogOnShutdown() {
if (lock_ == nullptr)
return;
base::AutoLock auto_lock(*lock_);
g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock();
}
// static
void StatisticsRecorder::GetSnapshot(const std::string& query,
Histograms* snapshot) {
if (lock_ == NULL)
return;
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return;
for (const auto& entry : *histograms_) {
if (entry.second->histogram_name().find(query) != std::string::npos)
snapshot->push_back(entry.second);
}
}
// static
bool StatisticsRecorder::SetCallback(
const std::string& name,
const StatisticsRecorder::OnSampleCallback& cb) {
DCHECK(!cb.is_null());
if (lock_ == NULL)
return false;
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return false;
if (ContainsKey(*callbacks_, name))
return false;
callbacks_->insert(std::make_pair(name, cb));
auto it = histograms_->find(name);
if (it != histograms_->end())
it->second->SetFlags(HistogramBase::kCallbackExists);
return true;
}
// static
void StatisticsRecorder::ClearCallback(const std::string& name) {
if (lock_ == NULL)
return;
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return;
callbacks_->erase(name);
// We also clear the flag from the histogram (if it exists).
auto it = histograms_->find(name);
if (it != histograms_->end())
it->second->ClearFlags(HistogramBase::kCallbackExists);
}
// static
StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
const std::string& name) {
if (lock_ == NULL)
return OnSampleCallback();
base::AutoLock auto_lock(*lock_);
if (histograms_ == NULL)
return OnSampleCallback();
auto callback_iterator = callbacks_->find(name);
return callback_iterator != callbacks_->end() ? callback_iterator->second
: OnSampleCallback();
}
// static
size_t StatisticsRecorder::GetHistogramCount() {
if (!lock_)
return 0;
base::AutoLock auto_lock(*lock_);
if (!histograms_)
return 0;
return histograms_->size();
}
// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
if (histograms_)
histograms_->erase(name);
}
// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
return WrapUnique(new StatisticsRecorder());
}
// static
void StatisticsRecorder::UninitializeForTesting() {
// Stop now if it's never been initialized.
if (lock_ == NULL || histograms_ == NULL)
return;
// Get the global instance and destruct it. It's held in static memory so
// can't "delete" it; call the destructor explicitly.
DCHECK(g_statistics_recorder_.private_instance_);
g_statistics_recorder_.Get().~StatisticsRecorder();
// Now the ugly part. There's no official way to release a LazyInstance once
// created so it's necessary to clear out an internal variable which
// shouldn't be publicly visible but is for initialization reasons.
g_statistics_recorder_.private_instance_ = 0;
}
// static
void StatisticsRecorder::ImportGlobalPersistentHistograms() {
if (lock_ == NULL)
return;
// 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.
GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
if (allocator)
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() {
if (lock_ == NULL) {
// This will leak on purpose. It's the only way to make sure we won't race
// against the static uninitialization of the module while one of our
// static methods relying on the lock get called at an inappropriate time
// during the termination phase. Since it's a static data member, we will
// leak one per process, which would be similar to the instance allocated
// during static initialization and released only on process termination.
lock_ = new base::Lock;
}
base::AutoLock auto_lock(*lock_);
existing_histograms_.reset(histograms_);
existing_callbacks_.reset(callbacks_);
existing_ranges_.reset(ranges_);
histograms_ = new HistogramMap;
callbacks_ = new CallbackMap;
ranges_ = new RangesMap;
InitLogOnShutdownWithoutLock();
}
void StatisticsRecorder::InitLogOnShutdownWithoutLock() {
if (!vlog_initialized_ && VLOG_IS_ON(1)) {
vlog_initialized_ = true;
AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
}
}
// static
void StatisticsRecorder::Reset() {
// If there's no lock then there is nothing to reset.
if (!lock_)
return;
std::unique_ptr<HistogramMap> histograms_deleter;
std::unique_ptr<CallbackMap> callbacks_deleter;
std::unique_ptr<RangesMap> ranges_deleter;
// We don't delete lock_ on purpose to avoid having to properly protect
// against it going away after we checked for NULL in the static methods.
{
base::AutoLock auto_lock(*lock_);
histograms_deleter.reset(histograms_);
callbacks_deleter.reset(callbacks_);
ranges_deleter.reset(ranges_);
histograms_ = NULL;
callbacks_ = NULL;
ranges_ = NULL;
}
// We are going to leak the histograms and the ranges.
}
// static
void StatisticsRecorder::DumpHistogramsToVlog(void* /*instance*/) {
std::string output;
StatisticsRecorder::WriteGraph(std::string(), &output);
VLOG(1) << output;
}
// static
StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL;
// static
StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = NULL;
// static
StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL;
// static
base::Lock* StatisticsRecorder::lock_ = NULL;
} // namespace base