// 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 #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::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_); top_ = previous_; } // 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& 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 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 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 += "


"; } } // 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 StatisticsRecorder::GetBucketRanges() { std::vector 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& 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(base)->bucket_ranges()->set_persistent_reference(0); } top_->histograms_.erase(found); } // static std::unique_ptr StatisticsRecorder::CreateTemporaryForTesting() { const AutoLock auto_lock(lock_.Get()); return WrapUnique(new StatisticsRecorder()); } // static void StatisticsRecorder::SetRecordChecker( std::unique_ptr 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