| // 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_); | 
 |   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<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 |