Commit b86ebc88 authored by François Degros's avatar François Degros Committed by Commit Bot

Simplified and optimized StatisticsRecorder.

StatisticsRecorder is now an actual container of collections.

There is only one global recorder at any time, referenced by
StatisticsRecorder::top_.

Each StatisticsRecorder also has a pointer to the previous global recorder.

We now have a stack (singly linked list) of StatisticsRecorder objects, where
the top of the stack (StatisticsRecorder::top_) is the current global recorder.

This pattern is clearer and cleaner. It avoids a bunch of static variables,
indirections, unique_ptrs and calls to operator new. It gets rid of the ugly
hacks of UninitializeForTesting. It reduces memory fragmentation by having fewer
dynamically allocated objects.

Removed methods Reset, UninitializeForTesting and DumpHistogramsToVlog. They are
now unnecessary.

Reviewed, clarified and documented the thread safety status of each method.

Added comments.


Change-Id: Ia1a7611905009d0449068c801464ad0df7813c0d
Bug: 
Reviewed-on: https://chromium-review.googlesource.com/830997
Commit-Queue: François Degros <fdegros@chromium.org>
Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/master@{#526570}
parent 041e24cc
......@@ -20,12 +20,9 @@
#include "base/strings/stringprintf.h"
#include "base/values.h"
namespace base {
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 strcmp(a->histogram_name(), b->histogram_name()) < 0;
......@@ -33,7 +30,14 @@ bool HistogramNameLesser(const base::HistogramBase* a,
} // namespace
namespace base {
// 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 {
......@@ -47,67 +51,55 @@ bool StatisticsRecorder::BucketRangesEqual::operator()(
}
StatisticsRecorder::~StatisticsRecorder() {
DCHECK(histograms_);
DCHECK(ranges_);
// Clean out what this object created and then restore what existed before.
Reset();
base::AutoLock auto_lock(lock_.Get());
histograms_ = existing_histograms_.release();
callbacks_ = existing_callbacks_.release();
ranges_ = existing_ranges_.release();
providers_ = existing_providers_.release();
record_checker_ = existing_record_checker_.release();
const AutoLock auto_lock(lock_.Get());
DCHECK_EQ(this, top_);
top_ = previous_;
}
// static
void StatisticsRecorder::Initialize() {
// Tests sometimes create local StatisticsRecorders in order to provide a
// contained environment of histograms that can be later discarded. If a
// true global instance gets created in this environment then it will
// eventually get disconnected when the local instance destructs and
// restores the previous state, resulting in no StatisticsRecorder at all.
// The global lazy instance, however, will remain valid thus ensuring that
// another never gets installed via this method. If a |histograms_| map
// exists then assume the StatisticsRecorder is already "initialized".
if (histograms_)
const AutoLock auto_lock(lock_.Get());
if (top_)
return;
// Ensure that an instance of the StatisticsRecorder object is created.
g_statistics_recorder_.Get();
const StatisticsRecorder* const p = new StatisticsRecorder;
// The global recorder is never deleted.
ANNOTATE_LEAKING_OBJECT_PTR(p);
DCHECK_EQ(p, top_);
}
// static
bool StatisticsRecorder::IsActive() {
base::AutoLock auto_lock(lock_.Get());
return histograms_ != nullptr;
const AutoLock auto_lock(lock_.Get());
return top_ != nullptr;
}
// static
void StatisticsRecorder::RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider) {
providers_->push_back(provider);
const AutoLock auto_lock(lock_.Get());
top_->providers_.push_back(provider);
}
// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
// Declared before auto_lock to ensure correct destruction order.
// Declared before |auto_lock| to ensure correct destruction order.
std::unique_ptr<HistogramBase> histogram_deleter;
base::AutoLock auto_lock(lock_.Get());
const AutoLock auto_lock(lock_.Get());
if (!histograms_) {
if (!top_) {
// 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 (!histograms_)|.
// twice |if (!top_)|.
ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
return histogram;
}
const char* const name = histogram->histogram_name();
HistogramBase*& registered = (*histograms_)[name];
HistogramBase*& registered = top_->histograms_[name];
if (!registered) {
// |name| is guaranteed to never change or be deallocated so long
......@@ -116,8 +108,8 @@ HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
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 = callbacks_->find(name);
if (callback_iterator != callbacks_->end()) {
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
......@@ -141,16 +133,16 @@ const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
DCHECK(ranges->HasValidChecksum());
// Declared before auto_lock to ensure correct destruction order.
// Declared before |auto_lock| to ensure correct destruction order.
std::unique_ptr<const BucketRanges> ranges_deleter;
base::AutoLock auto_lock(lock_.Get());
const AutoLock auto_lock(lock_.Get());
if (!ranges_) {
if (!top_) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
return ranges;
}
const BucketRanges* const registered = *ranges_->insert(ranges).first;
const BucketRanges* const registered = *top_->ranges_.insert(ranges).first;
if (registered == ranges) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
} else {
......@@ -163,9 +155,6 @@ const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
// 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);
......@@ -178,8 +167,6 @@ void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
// 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
......@@ -196,9 +183,6 @@ void StatisticsRecorder::WriteGraph(const std::string& query,
// static
std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
if (!IsActive())
return std::string();
Histograms snapshot;
GetSnapshot(std::string(), &snapshot);
......@@ -217,11 +201,11 @@ std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
// static
void StatisticsRecorder::GetHistograms(Histograms* output) {
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return;
for (const auto& entry : *histograms_) {
for (const auto& entry : top_->histograms_) {
output->push_back(entry.second);
}
}
......@@ -229,11 +213,11 @@ void StatisticsRecorder::GetHistograms(Histograms* output) {
// static
void StatisticsRecorder::GetBucketRanges(
std::vector<const BucketRanges*>* output) {
base::AutoLock auto_lock(lock_.Get());
if (!ranges_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return;
for (const BucketRanges* const p : *ranges_) {
for (const BucketRanges* const p : top_->ranges_) {
output->push_back(p);
}
}
......@@ -245,21 +229,28 @@ HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
// will acquire the lock at that time.
ImportGlobalPersistentHistograms();
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return nullptr;
const HistogramMap::const_iterator it = histograms_->find(name);
return it != histograms_->end() ? it->second : nullptr;
const HistogramMap::const_iterator it = top_->histograms_.find(name);
return it != top_->histograms_.end() ? it->second : nullptr;
}
// static
void StatisticsRecorder::ImportProvidedHistograms() {
if (!providers_)
return;
StatisticsRecorder::HistogramProviders
StatisticsRecorder::GetHistogramProviders() {
const AutoLock auto_lock(lock_.Get());
if (!top_)
return {};
return top_->providers_;
}
// static
void StatisticsRecorder::ImportProvidedHistograms() {
// Merge histogram data from each provider in turn.
for (const WeakPtr<HistogramProvider>& provider : *providers_) {
for (const WeakPtr<HistogramProvider>& provider : GetHistogramProviders()) {
// Weak-pointer may be invalid if the provider was destructed, though they
// generally never are.
if (provider)
......@@ -283,11 +274,8 @@ void StatisticsRecorder::PrepareDeltas(
// static
void StatisticsRecorder::InitLogOnShutdown() {
if (!histograms_)
return;
base::AutoLock auto_lock(lock_.Get());
g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock();
const AutoLock auto_lock(lock_.Get());
InitLogOnShutdownWhileLocked();
}
// static
......@@ -298,14 +286,14 @@ void StatisticsRecorder::GetSnapshot(const std::string& query,
// will acquire the lock at that time.
ImportGlobalPersistentHistograms();
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return;
// Need a c-string query for comparisons against c-string histogram name.
const char* query_string = query.c_str();
for (const auto& entry : *histograms_) {
for (const auto& entry : top_->histograms_) {
if (strstr(entry.second->histogram_name(), query_string) != nullptr)
snapshot->push_back(entry.second);
}
......@@ -316,15 +304,15 @@ bool StatisticsRecorder::SetCallback(
const std::string& name,
const StatisticsRecorder::OnSampleCallback& cb) {
DCHECK(!cb.is_null());
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return false;
if (!callbacks_->insert({name, cb}).second)
if (!top_->callbacks_.insert({name, cb}).second)
return false;
const HistogramMap::const_iterator it = histograms_->find(name);
if (it != histograms_->end())
const HistogramMap::const_iterator it = top_->histograms_.find(name);
if (it != top_->histograms_.end())
it->second->SetFlags(HistogramBase::kCallbackExists);
return true;
......@@ -332,42 +320,43 @@ bool StatisticsRecorder::SetCallback(
// static
void StatisticsRecorder::ClearCallback(const std::string& name) {
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return;
callbacks_->erase(name);
top_->callbacks_.erase(name);
// We also clear the flag from the histogram (if it exists).
const HistogramMap::const_iterator it = histograms_->find(name);
if (it != histograms_->end())
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) {
base::AutoLock auto_lock(lock_.Get());
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return OnSampleCallback();
const auto it = callbacks_->find(name);
return it != callbacks_->end() ? it->second : OnSampleCallback();
const auto it = top_->callbacks_.find(name);
return it != top_->callbacks_.end() ? it->second : OnSampleCallback();
}
// static
size_t StatisticsRecorder::GetHistogramCount() {
base::AutoLock auto_lock(lock_.Get());
return histograms_ ? histograms_->size() : 0;
const AutoLock auto_lock(lock_.Get());
return top_ ? top_->histograms_.size() : 0;
}
// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
if (!histograms_)
const AutoLock auto_lock(lock_.Get());
if (!top_)
return;
const HistogramMap::iterator found = histograms_->find(name);
if (found == histograms_->end())
const HistogramMap::iterator found = top_->histograms_.find(name);
if (found == top_->histograms_.end())
return;
HistogramBase* const base = found->second;
......@@ -379,53 +368,40 @@ void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
}
histograms_->erase(found);
top_->histograms_.erase(found);
}
// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
const AutoLock auto_lock(lock_.Get());
return WrapUnique(new StatisticsRecorder());
}
// static
void StatisticsRecorder::UninitializeForTesting() {
// Stop now if it's never been initialized.
if (!histograms_)
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::SetRecordChecker(
std::unique_ptr<RecordHistogramChecker> record_checker) {
record_checker_ = record_checker.release();
const AutoLock auto_lock(lock_.Get());
top_->record_checker_ = std::move(record_checker);
}
// static
bool StatisticsRecorder::ShouldRecordHistogram(uint64_t histogram_hash) {
return !record_checker_ || record_checker_->ShouldRecord(histogram_hash);
const AutoLock auto_lock(lock_.Get());
return !top_ || !top_->record_checker_ ||
top_->record_checker_->ShouldRecord(histogram_hash);
}
// static
StatisticsRecorder::Histograms StatisticsRecorder::GetKnownHistograms(
bool include_persistent) {
Histograms known;
base::AutoLock auto_lock(lock_.Get());
if (!histograms_ || histograms_->empty())
const AutoLock auto_lock(lock_.Get());
if (!top_ || top_->histograms_.empty())
return known;
known.reserve(histograms_->size());
for (const auto& h : *histograms_) {
known.reserve(top_->histograms_.size());
for (const auto& h : top_->histograms_) {
if (include_persistent ||
(h.second->flags() & HistogramBase::kIsPersistent) == 0)
known.push_back(h.second);
......@@ -436,9 +412,6 @@ StatisticsRecorder::Histograms StatisticsRecorder::GetKnownHistograms(
// static
void StatisticsRecorder::ImportGlobalPersistentHistograms() {
if (!histograms_)
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
......@@ -451,73 +424,24 @@ void StatisticsRecorder::ImportGlobalPersistentHistograms() {
// of main(), and hence it is not thread safe. It initializes globals to provide
// support for all future calls.
StatisticsRecorder::StatisticsRecorder() {
base::AutoLock auto_lock(lock_.Get());
existing_histograms_.reset(histograms_);
existing_callbacks_.reset(callbacks_);
existing_ranges_.reset(ranges_);
existing_providers_.reset(providers_);
existing_record_checker_.reset(record_checker_);
histograms_ = new HistogramMap;
callbacks_ = new CallbackMap;
ranges_ = new RangesMap;
providers_ = new HistogramProviders;
record_checker_ = nullptr;
InitLogOnShutdownWithoutLock();
}
void StatisticsRecorder::InitLogOnShutdownWithoutLock() {
if (!vlog_initialized_ && VLOG_IS_ON(1)) {
vlog_initialized_ = true;
AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
}
lock_.Get().AssertAcquired();
previous_ = top_;
top_ = this;
InitLogOnShutdownWhileLocked();
}
// static
void StatisticsRecorder::Reset() {
// Declared before auto_lock to ensure correct destruction order.
std::unique_ptr<HistogramMap> histograms_deleter;
std::unique_ptr<CallbackMap> callbacks_deleter;
std::unique_ptr<RangesMap> ranges_deleter;
std::unique_ptr<HistogramProviders> providers_deleter;
std::unique_ptr<RecordHistogramChecker> record_checker_deleter;
base::AutoLock auto_lock(lock_.Get());
histograms_deleter.reset(histograms_);
callbacks_deleter.reset(callbacks_);
ranges_deleter.reset(ranges_);
providers_deleter.reset(providers_);
record_checker_deleter.reset(record_checker_);
histograms_ = nullptr;
callbacks_ = nullptr;
ranges_ = nullptr;
providers_ = nullptr;
record_checker_ = nullptr;
// We are going to leak the histograms and the ranges.
}
// static
void StatisticsRecorder::DumpHistogramsToVlog(void* instance) {
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;
StatisticsRecorder::WriteGraph(std::string(), &output);
WriteGraph("", &output);
VLOG(1) << output;
};
AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
}
}
// static
StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = nullptr;
// static
StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = nullptr;
// static
StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = nullptr;
// static
StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_ =
nullptr;
// static
RecordHistogramChecker* StatisticsRecorder::record_checker_ = nullptr;
// static
base::LazyInstance<base::Lock>::Leaky StatisticsRecorder::lock_ =
LAZY_INSTANCE_INITIALIZER;
} // namespace base
......@@ -34,6 +34,18 @@ namespace base {
class BucketRanges;
class HistogramSnapshotManager;
// In-memory recorder of usage statistics (aka metrics, aka histograms).
//
// Most of the methods are static and act on a global recorder. This global
// recorder must first be initialized using Initialize() before being used. This
// global recorder is internally synchronized and all the static methods are
// thread safe.
//
// StatisticsRecorder doesn't have any public constructor. For testing purpose,
// you can create a temporary recorder using the factory method
// CreateTemporaryForTesting(). This temporary recorder becomes the global one
// until deleted. When this temporary recorder is deleted, it restores the
// previous global one.
class BASE_EXPORT StatisticsRecorder {
public:
// An interface class that allows the StatisticsRecorder to forcibly merge
......@@ -46,58 +58,83 @@ class BASE_EXPORT StatisticsRecorder {
typedef std::vector<HistogramBase*> Histograms;
// Restores the previous global recorder.
//
// When several temporary recorders are created using
// CreateTemporaryForTesting(), these recorders must be deleted in reverse
// order of creation.
//
// This method is thread safe.
//
// Precondition: The recorder being deleted is the current global recorder.
~StatisticsRecorder();
// Initializes the StatisticsRecorder system. Safe to call multiple times.
// Initializes the global recorder. Safe to call multiple times.
//
// This method is thread safe.
static void Initialize();
// Find out if histograms can now be registered into our list.
// Finds out if histograms can now be registered into our list.
//
// This method is thread safe.
static bool IsActive();
// Register a provider of histograms that can be called to merge those into
// the global StatisticsRecorder. Calls to ImportProvidedHistograms() will
// fetch from registered providers.
// Registers a provider of histograms that can be called to merge those into
// the global recorder. Calls to ImportProvidedHistograms() will fetch from
// registered providers.
//
// This method is thread safe.
static void RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider);
// Register, or add a new histogram to the collection of statistics. If an
// Registers or adds a new histogram to the collection of statistics. If an
// identically named histogram is already registered, then the argument
// |histogram| will deleted. The returned value is always the registered
// |histogram| will be deleted. The returned value is always the registered
// histogram (either the argument, or the pre-existing registered histogram).
//
// This method is thread safe.
static HistogramBase* RegisterOrDeleteDuplicate(HistogramBase* histogram);
// Register, or add a new BucketRanges. If an identically BucketRanges is
// already registered, then the argument |ranges| will deleted. The returned
// value is always the registered BucketRanges (either the argument, or the
// pre-existing one).
// Registers or adds a new BucketRanges. If an equivalent BucketRanges is
// already registered, then the argument |ranges| will be deleted. The
// returned value is always the registered BucketRanges (either the argument,
// or the pre-existing one).
//
// This method is thread safe.
static const BucketRanges* RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges);
// Methods for appending histogram data to a string. Only histograms which
// have |query| as a substring are written to |output| (an empty string will
// process all registered histograms).
//
// These methods are thread safe.
static void WriteHTMLGraph(const std::string& query, std::string* output);
static void WriteGraph(const std::string& query, std::string* output);
// Returns the histograms with |verbosity_level| as the serialization
// verbosity.
//
// This method is thread safe.
static std::string ToJSON(JSONVerbosityLevel verbosity_level);
// Method for extracting histograms which were marked for use by UMA.
// Extracts histograms which were marked for use by UMA.
//
// This method is thread safe.
static void GetHistograms(Histograms* output);
// Method for extracting BucketRanges used by all histograms registered.
// Extracts BucketRanges used by all histograms registered.
static void GetBucketRanges(std::vector<const BucketRanges*>* output);
// Find a histogram by name. It matches the exact name.
// It returns NULL if a matching histogram is not found.
// Finds a histogram by name. Matches the exact name. Returns a null pointer
// if a matching histogram is not found.
//
// This method is thread safe.
static HistogramBase* FindHistogram(base::StringPiece name);
// Imports histograms from providers. This must be called on the UI thread.
// Imports histograms from providers.
//
// This method must be called on the UI thread.
static void ImportProvidedHistograms();
// Snapshots all histograms via |snapshot_manager|. |flags_to_set| is used to
......@@ -110,71 +147,81 @@ class BASE_EXPORT StatisticsRecorder {
HistogramBase::Flags required_flags,
HistogramSnapshotManager* snapshot_manager);
// GetSnapshot copies some of the pointers to registered histograms into the
// caller supplied vector (Histograms). Only histograms which have |query| as
// a substring are copied (an empty string will process all registered
// histograms).
// Extracts registered histograms. Only histograms which have |query| as a
// substring are extracted. An empty query will extract all registered
// histograms.
//
// This method is thread safe.
static void GetSnapshot(const std::string& query, Histograms* snapshot);
typedef base::Callback<void(HistogramBase::Sample)> OnSampleCallback;
// SetCallback sets the callback to notify when a new sample is recorded on
// the histogram referred to by |histogram_name|. The call to this method can
// be be done before or after the histogram is created. This method is thread
// safe. The return value is whether or not the callback was successfully set.
// Sets the callback to notify when a new sample is recorded on the histogram
// referred to by |histogram_name|. Can be called before or after the
// histogram is created. Returns whether the callback was successfully set.
//
// This method is thread safe.
static bool SetCallback(const std::string& histogram_name,
const OnSampleCallback& callback);
// ClearCallback clears any callback set on the histogram referred to by
// |histogram_name|. This method is thread safe.
// Clears any callback set on the histogram referred to by |histogram_name|.
//
// This method is thread safe.
static void ClearCallback(const std::string& histogram_name);
// FindCallback retrieves the callback for the histogram referred to by
// |histogram_name|, or a null callback if no callback exists for this
// histogram. This method is thread safe.
// Retrieves the callback for the histogram referred to by |histogram_name|,
// or a null callback if no callback exists for this histogram.
//
// This method is thread safe.
static OnSampleCallback FindCallback(const std::string& histogram_name);
// Returns the number of known histograms.
//
// This method is thread safe.
static size_t GetHistogramCount();
// Initializes logging histograms with --v=1. Safe to call multiple times.
// Is called from ctor but for browser it seems that it is more useful to
// start logging after statistics recorder, so we need to init log-on-shutdown
// later.
//
// This method is thread safe.
static void InitLogOnShutdown();
// Removes a histogram from the internal set of known ones. This can be
// necessary during testing persistent histograms where the underlying
// memory is being released.
//
// This method is thread safe.
static void ForgetHistogramForTesting(base::StringPiece name);
// Creates a local StatisticsRecorder object for testing purposes. All new
// histograms will be registered in it until it is destructed or pushed
// aside for the lifetime of yet another SR object. The destruction of the
// returned object will re-activate the previous one. Always release SR
// objects in the opposite order to which they're created.
// Creates a temporary StatisticsRecorder object for testing purposes. All new
// histograms will be registered in it until it is destructed or pushed aside
// for the lifetime of yet another StatisticsRecorder object. The destruction
// of the returned object will re-activate the previous one.
// StatisticsRecorder objects must be deleted in the opposite order to which
// they're created.
//
// This method is thread safe.
static std::unique_ptr<StatisticsRecorder> CreateTemporaryForTesting()
WARN_UNUSED_RESULT;
// Resets any global instance of the statistics-recorder that was created
// by a call to Initialize().
static void UninitializeForTesting();
// Sets the record checker for determining if a histogram should be recorded.
// Record checker doesn't affect any already recorded histograms, so this
// method must be called very early, before any threads have started.
// Record checker methods can be called on any thread, so they shouldn't
// mutate any state.
//
// TODO(iburak): This is not yet hooked up to histogram recording
// infrastructure.
static void SetRecordChecker(
std::unique_ptr<RecordHistogramChecker> record_checker);
// Returns true iff the given histogram should be recorded based on
// the ShouldRecord() method of the record checker.
// If the record checker is not set, returns true.
// Checks if the given histogram should be recorded based on the
// ShouldRecord() method of the record checker. If the record checker is not
// set, returns true.
//
// This method is thread safe.
static bool ShouldRecordHistogram(uint64_t histogram_hash);
private:
......@@ -199,52 +246,57 @@ class BASE_EXPORT StatisticsRecorder {
unordered_set<const BucketRanges*, BucketRangesHash, BucketRangesEqual>
RangesMap;
friend struct LazyInstanceTraitsBase<StatisticsRecorder>;
friend class StatisticsRecorderTest;
FRIEND_TEST_ALL_PREFIXES(StatisticsRecorderTest, IterationTest);
// Fetch set of existing histograms. Ownership of the individual histograms
// Fetches set of existing histograms. Ownership of the individual histograms
// remains with the StatisticsRecorder.
//
// This method is thread safe.
static Histograms GetKnownHistograms(bool include_persistent);
// Imports histograms from global persistent memory. The global lock must
// not be held during this call.
// Gets histogram providers.
//
// This method is thread safe.
static HistogramProviders GetHistogramProviders();
// Imports histograms from global persistent memory.
//
// Precondition: The global lock must not be held during this call.
static void ImportGlobalPersistentHistograms();
// The constructor just initializes static members. Usually client code should
// use Initialize to do this. But in test code, you can friend this class and
// call the constructor to get a clean StatisticsRecorder.
// Constructs a new StatisticsRecorder and sets it as the current global
// recorder.
//
// Precondition: The global lock is already acquired.
StatisticsRecorder();
// Initialize implementation but without lock. Caller should guard
// StatisticsRecorder by itself if needed (it isn't in unit tests).
void InitLogOnShutdownWithoutLock();
// These are copies of everything that existed when the (test) Statistics-
// Recorder was created. The global ones have to be moved aside to create a
// clean environment.
std::unique_ptr<HistogramMap> existing_histograms_;
std::unique_ptr<CallbackMap> existing_callbacks_;
std::unique_ptr<RangesMap> existing_ranges_;
std::unique_ptr<HistogramProviders> existing_providers_;
std::unique_ptr<RecordHistogramChecker> existing_record_checker_;
bool vlog_initialized_ = false;
static void Reset();
static void DumpHistogramsToVlog(void* instance);
static HistogramMap* histograms_;
static CallbackMap* callbacks_;
static RangesMap* ranges_;
static HistogramProviders* providers_;
static RecordHistogramChecker* record_checker_;
// Lock protects access to above maps. This is a LazyInstance to avoid races
// when the above methods are used before Initialize(). Previously each method
// would do |if (!lock_) return;| which would race with
// |lock_ = new Lock;| in StatisticsRecorder(). http://crbug.com/672852.
static base::LazyInstance<base::Lock>::Leaky lock_;
//
// Precondition: The global lock is already acquired.
static void InitLogOnShutdownWhileLocked();
HistogramMap histograms_;
CallbackMap callbacks_;
RangesMap ranges_;
HistogramProviders providers_;
std::unique_ptr<RecordHistogramChecker> record_checker_;
// Previous global recorder that existed when this one was created.
StatisticsRecorder* previous_ = nullptr;
// Global lock for internal synchronization.
static LazyInstance<Lock>::Leaky lock_;
// Current global recorder. This recorder is used by static methods. When a
// new global recorder is created by CreateTemporaryForTesting(), then the
// previous global recorder is referenced by top_->previous_.
static StatisticsRecorder* top_;
// Tracks whether InitLogOnShutdownWhileLocked() has registered a logging
// function that will be called when the program finishes.
static bool is_vlog_initialized_;
DISALLOW_COPY_AND_ASSIGN(StatisticsRecorder);
};
......
......@@ -30,9 +30,7 @@ class LogStateSaver {
public:
LogStateSaver() : old_min_log_level_(logging::GetMinLogLevel()) {}
~LogStateSaver() {
logging::SetMinLogLevel(old_min_log_level_);
}
~LogStateSaver() { logging::SetMinLogLevel(old_min_log_level_); }
private:
int old_min_log_level_;
......@@ -70,8 +68,8 @@ class StatisticsRecorderTest : public testing::TestWithParam<bool> {
// Use persistent memory for histograms if so indicated by test parameter.
if (use_persistent_histogram_allocator_) {
GlobalHistogramAllocator::CreateWithLocalMemory(
kAllocatorMemorySize, 0, "StatisticsRecorderTest");
GlobalHistogramAllocator::CreateWithLocalMemory(kAllocatorMemorySize, 0,
"StatisticsRecorderTest");
}
}
......@@ -82,14 +80,10 @@ class StatisticsRecorderTest : public testing::TestWithParam<bool> {
void InitializeStatisticsRecorder() {
DCHECK(!statistics_recorder_);
StatisticsRecorder::UninitializeForTesting();
statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
}
void UninitializeStatisticsRecorder() {
statistics_recorder_.reset();
StatisticsRecorder::UninitializeForTesting();
}
void UninitializeStatisticsRecorder() { statistics_recorder_.reset(); }
Histogram* CreateHistogram(const char* name,
HistogramBase::Sample min,
......@@ -102,18 +96,15 @@ class StatisticsRecorderTest : public testing::TestWithParam<bool> {
return new Histogram(name, min, max, registered_ranges);
}
void DeleteHistogram(HistogramBase* histogram) {
delete histogram;
}
void DeleteHistogram(HistogramBase* histogram) { delete histogram; }
void InitLogOnShutdown() {
DCHECK(statistics_recorder_);
statistics_recorder_->InitLogOnShutdownWithoutLock();
}
void InitLogOnShutdown() { StatisticsRecorder::InitLogOnShutdown(); }
bool IsVLogInitialized() { return StatisticsRecorder::is_vlog_initialized_; }
bool VLogInitialized() {
DCHECK(statistics_recorder_);
return statistics_recorder_->vlog_initialized_;
void ResetVLogInitialized() {
UninitializeStatisticsRecorder();
StatisticsRecorder::is_vlog_initialized_ = false;
}
const bool use_persistent_histogram_allocator_;
......@@ -277,8 +268,8 @@ TEST_P(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) {
ASSERT_EQ(0u, registered_histograms.size());
// Create a histogram.
HistogramBase* histogram = Histogram::FactoryGet(
"TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
HistogramBase* histogram = Histogram::FactoryGet("TestHistogram", 1, 1000, 10,
HistogramBase::kNoFlags);
registered_histograms.clear();
StatisticsRecorder::GetHistograms(&registered_histograms);
EXPECT_EQ(1u, registered_histograms.size());
......@@ -292,15 +283,15 @@ TEST_P(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) {
EXPECT_EQ(histogram, histogram2);
// Create a LinearHistogram.
histogram = LinearHistogram::FactoryGet(
"TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
histogram = LinearHistogram::FactoryGet("TestLinearHistogram", 1, 1000, 10,
HistogramBase::kNoFlags);
registered_histograms.clear();
StatisticsRecorder::GetHistograms(&registered_histograms);
EXPECT_EQ(2u, registered_histograms.size());
// Create a BooleanHistogram.
histogram = BooleanHistogram::FactoryGet(
"TestBooleanHistogram", HistogramBase::kNoFlags);
histogram = BooleanHistogram::FactoryGet("TestBooleanHistogram",
HistogramBase::kNoFlags);
registered_histograms.clear();
StatisticsRecorder::GetHistograms(&registered_histograms);
EXPECT_EQ(3u, registered_histograms.size());
......@@ -309,8 +300,8 @@ TEST_P(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) {
std::vector<int> custom_ranges;
custom_ranges.push_back(1);
custom_ranges.push_back(5);
histogram = CustomHistogram::FactoryGet(
"TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags);
histogram = CustomHistogram::FactoryGet("TestCustomHistogram", custom_ranges,
HistogramBase::kNoFlags);
registered_histograms.clear();
StatisticsRecorder::GetHistograms(&registered_histograms);
EXPECT_EQ(4u, registered_histograms.size());
......@@ -624,33 +615,33 @@ TEST_P(StatisticsRecorderTest, CallbackUsedBeforeHistogramCreatedTest) {
}
TEST_P(StatisticsRecorderTest, LogOnShutdownNotInitialized) {
UninitializeStatisticsRecorder();
ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_WARNING);
InitializeStatisticsRecorder();
EXPECT_FALSE(VLOG_IS_ON(1));
EXPECT_FALSE(VLogInitialized());
EXPECT_FALSE(IsVLogInitialized());
InitLogOnShutdown();
EXPECT_FALSE(VLogInitialized());
EXPECT_FALSE(IsVLogInitialized());
}
TEST_P(StatisticsRecorderTest, LogOnShutdownInitializedExplicitly) {
UninitializeStatisticsRecorder();
ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_WARNING);
InitializeStatisticsRecorder();
EXPECT_FALSE(VLOG_IS_ON(1));
EXPECT_FALSE(VLogInitialized());
EXPECT_FALSE(IsVLogInitialized());
logging::SetMinLogLevel(logging::LOG_VERBOSE);
EXPECT_TRUE(VLOG_IS_ON(1));
InitLogOnShutdown();
EXPECT_TRUE(VLogInitialized());
EXPECT_TRUE(IsVLogInitialized());
}
TEST_P(StatisticsRecorderTest, LogOnShutdownInitialized) {
UninitializeStatisticsRecorder();
ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_VERBOSE);
InitializeStatisticsRecorder();
EXPECT_TRUE(VLOG_IS_ON(1));
EXPECT_TRUE(VLogInitialized());
EXPECT_TRUE(IsVLogInitialized());
}
class TestHistogramProvider : public StatisticsRecorder::HistogramProvider {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment