diff options
Diffstat (limited to 'base/metrics/statistics_recorder.cc')
-rw-r--r-- | base/metrics/statistics_recorder.cc | 582 |
1 files changed, 228 insertions, 354 deletions
diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc index ba2101bccf..28773a13de 100644 --- a/base/metrics/statistics_recorder.cc +++ b/base/metrics/statistics_recorder.cc @@ -12,259 +12,162 @@ #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 { -// 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(); + return strcmp(a->histogram_name(), b->histogram_name()) < 0; } } // 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() {} +// static +LazyInstance<Lock>::Leaky StatisticsRecorder::lock_; -StatisticsRecorder::HistogramIterator& -StatisticsRecorder::HistogramIterator::operator++() { - const HistogramMap::iterator histograms_end = histograms_->end(); - if (iter_ == histograms_end) - return *this; +// static +StatisticsRecorder* StatisticsRecorder::top_ = nullptr; - base::AutoLock auto_lock(lock_.Get()); +// static +bool StatisticsRecorder::is_vlog_initialized_ = false; - for (;;) { - ++iter_; - if (iter_ == histograms_end) - break; - if (!include_persistent_ && (iter_->second->flags() & - HistogramBase::kIsPersistent)) { - continue; - } - break; - } +size_t StatisticsRecorder::BucketRangesHash::operator()( + const BucketRanges* const a) const { + return a->checksum(); +} - return *this; +bool StatisticsRecorder::BucketRangesEqual::operator()( + const BucketRanges* const a, + const BucketRanges* const b) const { + return a->Equals(b); } 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(); + 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_) +void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() { + lock_.Get().AssertAcquired(); + if (top_) return; - // Ensure that an instance of the StatisticsRecorder object is created. - g_statistics_recorder_.Get(); -} - -// static -bool StatisticsRecorder::IsActive() { - base::AutoLock auto_lock(lock_.Get()); - return histograms_ != nullptr; + 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) { - providers_->push_back(provider); + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); + top_->providers_.push_back(provider); } // static HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate( HistogramBase* histogram) { - HistogramBase* histogram_to_delete = nullptr; - HistogramBase* histogram_to_return = nullptr; - { - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) { - histogram_to_return = 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 (!histograms_)|. - ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 - } 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; - } + // 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; } - delete histogram_to_delete; - return histogram_to_return; + + 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(); - base::AutoLock auto_lock(lock_.Get()); - if (!ranges_) { + const BucketRanges* const registered = *top_->ranges_.insert(ranges).first; + if (registered == ranges) { 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; + ranges_deleter.reset(ranges); } - 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; + return registered; } // 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) { + for (const HistogramBase* const histogram : + Sort(WithName(GetHistograms(), query))) { histogram->WriteHTMLGraph(output); - output->append("<br><hr><br>"); + *output += "<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) { + for (const HistogramBase* const histogram : + Sort(WithName(GetHistograms(), query))) { 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 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); + histogram->WriteJSON(&json, verbosity_level); output += json; } output += "]}"; @@ -272,28 +175,13 @@ std::string StatisticsRecorder::ToJSON(const std::string& query) { } // static -void StatisticsRecorder::GetHistograms(Histograms* output) { - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return; - - for (const auto& entry : *histograms_) { - output->push_back(entry.second); - } -} - -// static -void StatisticsRecorder::GetBucketRanges( - std::vector<const BucketRanges*>* output) { - base::AutoLock auto_lock(lock_.Get()); - if (!ranges_) - return; - - for (const auto& entry : *ranges_) { - for (auto* range_entry : *entry.second) { - output->push_back(range_entry); - } - } +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 @@ -303,23 +191,25 @@ HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) { // will acquire the lock at that time. ImportGlobalPersistentHistograms(); - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return nullptr; + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); - HistogramMap::iterator it = histograms_->find(name); - if (histograms_->end() == it) - return nullptr; - return it->second; + 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()); + EnsureGlobalRecorderWhileLocked(); + 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) @@ -328,51 +218,22 @@ void StatisticsRecorder::ImportProvidedHistograms() { } // static -StatisticsRecorder::HistogramIterator StatisticsRecorder::begin( - bool include_persistent) { - DCHECK(histograms_); - ImportGlobalPersistentHistograms(); - - HistogramMap::iterator iter_begin; - { - base::AutoLock auto_lock(lock_.Get()); - iter_begin = histograms_->begin(); - } - return HistogramIterator(iter_begin, include_persistent); -} - -// static -StatisticsRecorder::HistogramIterator StatisticsRecorder::end() { - HistogramMap::iterator iter_end; - { - base::AutoLock auto_lock(lock_.Get()); - iter_end = histograms_->end(); - } - return HistogramIterator(iter_end, true); +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() { - if (!histograms_) - return; - - base::AutoLock auto_lock(lock_.Get()); - g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock(); -} - -// static -void StatisticsRecorder::GetSnapshot(const std::string& query, - Histograms* snapshot) { - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return; - - ImportGlobalPersistentHistograms(); - - for (const auto& entry : *histograms_) { - if (entry.second->histogram_name().find(query) != std::string::npos) - snapshot->push_back(entry.second); - } + const AutoLock auto_lock(lock_.Get()); + InitLogOnShutdownWhileLocked(); } // static @@ -380,16 +241,14 @@ bool StatisticsRecorder::SetCallback( const std::string& name, const StatisticsRecorder::OnSampleCallback& cb) { DCHECK(!cb.is_null()); - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return false; + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); - if (ContainsKey(*callbacks_, name)) + if (!top_->callbacks_.insert({name, cb}).second) return false; - callbacks_->insert(std::make_pair(name, cb)); - auto 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; @@ -397,146 +256,161 @@ bool StatisticsRecorder::SetCallback( // static void StatisticsRecorder::ClearCallback(const std::string& name) { - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return; + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); - callbacks_->erase(name); + top_->callbacks_.erase(name); // We also clear the flag from the histogram (if it exists). - auto 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_) - return OnSampleCallback(); - - auto callback_iterator = callbacks_->find(name); - return callback_iterator != callbacks_->end() ? callback_iterator->second - : OnSampleCallback(); + 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() { - base::AutoLock auto_lock(lock_.Get()); - if (!histograms_) - return 0; - return histograms_->size(); + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); + return top_->histograms_.size(); } // static void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) { - if (histograms_) - histograms_->erase(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::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(); +void StatisticsRecorder::SetRecordChecker( + std::unique_ptr<RecordHistogramChecker> record_checker) { + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); + top_->record_checker_ = std::move(record_checker); +} - // 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 +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 -void StatisticsRecorder::ImportGlobalPersistentHistograms() { - if (!histograms_) - return; +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(); - // 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(); -} + Histograms out; -// 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() { - base::AutoLock auto_lock(lock_.Get()); + const AutoLock auto_lock(lock_.Get()); + EnsureGlobalRecorderWhileLocked(); - existing_histograms_.reset(histograms_); - existing_callbacks_.reset(callbacks_); - existing_ranges_.reset(ranges_); - existing_providers_.reset(providers_); + out.reserve(top_->histograms_.size()); + for (const auto& entry : top_->histograms_) + out.push_back(entry.second); - histograms_ = new HistogramMap; - callbacks_ = new CallbackMap; - ranges_ = new RangesMap; - providers_ = new HistogramProviders; + return out; +} - InitLogOnShutdownWithoutLock(); +// static +StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) { + std::sort(histograms.begin(), histograms.end(), &HistogramNameLesser); + return histograms; } -void StatisticsRecorder::InitLogOnShutdownWithoutLock() { - if (!vlog_initialized_ && VLOG_IS_ON(1)) { - vlog_initialized_ = true; - AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this); - } +// 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 -void StatisticsRecorder::Reset() { - - std::unique_ptr<HistogramMap> histograms_deleter; - std::unique_ptr<CallbackMap> callbacks_deleter; - std::unique_ptr<RangesMap> ranges_deleter; - std::unique_ptr<HistogramProviders> providers_deleter; - { - base::AutoLock auto_lock(lock_.Get()); - histograms_deleter.reset(histograms_); - callbacks_deleter.reset(callbacks_); - ranges_deleter.reset(ranges_); - providers_deleter.reset(providers_); - histograms_ = nullptr; - callbacks_ = nullptr; - ranges_ = nullptr; - providers_ = nullptr; - } - // We are going to leak the histograms and the ranges. +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::DumpHistogramsToVlog(void* instance) { - std::string output; - StatisticsRecorder::WriteGraph(std::string(), &output); - VLOG(1) << output; +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 -StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = nullptr; -// static -StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = nullptr; -// static -StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = nullptr; -// static -StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_; -// static -base::LazyInstance<base::Lock>::Leaky StatisticsRecorder::lock_ = - LAZY_INSTANCE_INITIALIZER; +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 |