| // 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 |