xref: /aosp_15_r20/external/cronet/base/metrics/statistics_recorder.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/metrics/statistics_recorder.h"
6 
7 #include <string_view>
8 
9 #include "base/at_exit.h"
10 #include "base/barrier_closure.h"
11 #include "base/containers/contains.h"
12 #include "base/debug/leak_annotations.h"
13 #include "base/json/string_escape.h"
14 #include "base/logging.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/metrics/histogram.h"
17 #include "base/metrics/histogram_snapshot_manager.h"
18 #include "base/metrics/metrics_hashes.h"
19 #include "base/metrics/persistent_histogram_allocator.h"
20 #include "base/metrics/record_histogram_checker.h"
21 #include "base/ranges/algorithm.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/values.h"
25 #include "build/build_config.h"
26 
27 namespace base {
28 namespace {
29 
HistogramNameLesser(const base::HistogramBase * a,const base::HistogramBase * b)30 bool HistogramNameLesser(const base::HistogramBase* a,
31                          const base::HistogramBase* b) {
32   return strcmp(a->histogram_name(), b->histogram_name()) < 0;
33 }
34 
35 }  // namespace
36 
37 // static
38 LazyInstance<Lock>::Leaky StatisticsRecorder::lock_ = LAZY_INSTANCE_INITIALIZER;
39 
40 // static
41 LazyInstance<Lock>::Leaky StatisticsRecorder::snapshot_lock_ =
42     LAZY_INSTANCE_INITIALIZER;
43 
44 // static
45 StatisticsRecorder::SnapshotTransactionId
46     StatisticsRecorder::last_snapshot_transaction_id_ = 0;
47 
48 // static
49 StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
50 
51 // static
52 bool StatisticsRecorder::is_vlog_initialized_ = false;
53 
54 // static
55 std::atomic<bool> StatisticsRecorder::have_active_callbacks_{false};
56 
57 // static
58 std::atomic<StatisticsRecorder::GlobalSampleCallback>
59     StatisticsRecorder::global_sample_callback_{nullptr};
60 
61 StatisticsRecorder::ScopedHistogramSampleObserver::
ScopedHistogramSampleObserver(const std::string & name,OnSampleCallback callback)62     ScopedHistogramSampleObserver(const std::string& name,
63                                   OnSampleCallback callback)
64     : histogram_name_(name), callback_(callback) {
65   StatisticsRecorder::AddHistogramSampleObserver(histogram_name_, this);
66 }
67 
68 StatisticsRecorder::ScopedHistogramSampleObserver::
~ScopedHistogramSampleObserver()69     ~ScopedHistogramSampleObserver() {
70   StatisticsRecorder::RemoveHistogramSampleObserver(histogram_name_, this);
71 }
72 
RunCallback(const char * histogram_name,uint64_t name_hash,HistogramBase::Sample sample)73 void StatisticsRecorder::ScopedHistogramSampleObserver::RunCallback(
74     const char* histogram_name,
75     uint64_t name_hash,
76     HistogramBase::Sample sample) {
77   callback_.Run(histogram_name, name_hash, sample);
78 }
79 
~StatisticsRecorder()80 StatisticsRecorder::~StatisticsRecorder() {
81   const AutoLock auto_lock(GetLock());
82   DCHECK_EQ(this, top_);
83   top_ = previous_;
84 }
85 
86 // static
EnsureGlobalRecorderWhileLocked()87 void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
88   AssertLockHeld();
89   if (top_) {
90     return;
91   }
92 
93   const StatisticsRecorder* const p = new StatisticsRecorder;
94   // The global recorder is never deleted.
95   ANNOTATE_LEAKING_OBJECT_PTR(p);
96   DCHECK_EQ(p, top_);
97 }
98 
99 // static
RegisterHistogramProvider(const WeakPtr<HistogramProvider> & provider)100 void StatisticsRecorder::RegisterHistogramProvider(
101     const WeakPtr<HistogramProvider>& provider) {
102   const AutoLock auto_lock(GetLock());
103   EnsureGlobalRecorderWhileLocked();
104   top_->providers_.push_back(provider);
105 }
106 
107 // static
RegisterOrDeleteDuplicate(HistogramBase * histogram)108 HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
109     HistogramBase* histogram) {
110   CHECK(histogram);
111 
112   uint64_t hash = histogram->name_hash();
113 
114   // Ensure that histograms use HashMetricName() to compute their hash, since
115   // that function is used to look up histograms. Intentionally a DCHECK since
116   // this is expensive.
117   DCHECK_EQ(hash, HashMetricName(histogram->histogram_name()));
118 
119   // Declared before |auto_lock| so that the histogram is deleted after the lock
120   // is released (no point in holding the lock longer than needed).
121   std::unique_ptr<HistogramBase> histogram_deleter;
122   const AutoLock auto_lock(GetLock());
123   EnsureGlobalRecorderWhileLocked();
124 
125   HistogramBase*& registered = top_->histograms_[hash];
126 
127   if (!registered) {
128     registered = histogram;
129     ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
130     // If there are callbacks for this histogram, we set the kCallbackExists
131     // flag.
132     if (base::Contains(top_->observers_, hash)) {
133       // Note: SetFlags() does not write to persistent memory, it only writes to
134       // an in-memory version of the flags.
135       histogram->SetFlags(HistogramBase::kCallbackExists);
136     }
137 
138     return histogram;
139   }
140 
141   // Assert that there was no collision. Note that this is intentionally a
142   // DCHECK because 1) this is expensive to call repeatedly, and 2) this
143   // comparison may cause a read in persistent memory, which can cause I/O (this
144   // is bad because |lock_| is currently being held).
145   //
146   // If you are a developer adding a new histogram and this DCHECK is being hit,
147   // you are unluckily a victim of a hash collision. For now, the best solution
148   // is to rename the histogram. Reach out to [email protected] if
149   // you are unsure!
150   DCHECK_EQ(strcmp(histogram->histogram_name(), registered->histogram_name()),
151             0)
152       << "Histogram name hash collision between " << histogram->histogram_name()
153       << " and " << registered->histogram_name() << " (hash = " << hash << ")";
154 
155   if (histogram == registered) {
156     // The histogram was registered before.
157     return histogram;
158   }
159 
160   // We already have a histogram with this name.
161   histogram_deleter.reset(histogram);
162   return registered;
163 }
164 
165 // static
RegisterOrDeleteDuplicateRanges(const BucketRanges * ranges)166 const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
167     const BucketRanges* ranges) {
168   const BucketRanges* registered;
169   {
170     const AutoLock auto_lock(GetLock());
171     EnsureGlobalRecorderWhileLocked();
172 
173     registered = top_->ranges_manager_.GetOrRegisterCanonicalRanges(ranges);
174   }
175 
176   // Delete the duplicate ranges outside the lock to reduce contention.
177   if (registered != ranges) {
178     delete ranges;
179   } else {
180     ANNOTATE_LEAKING_OBJECT_PTR(ranges);
181   }
182 
183   return registered;
184 }
185 
186 // static
WriteGraph(const std::string & query,std::string * output)187 void StatisticsRecorder::WriteGraph(const std::string& query,
188                                     std::string* output) {
189   if (query.length())
190     StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
191   else
192     output->append("Collections of all histograms\n");
193 
194   for (const HistogramBase* const histogram :
195        Sort(WithName(GetHistograms(), query))) {
196     histogram->WriteAscii(output);
197     output->append("\n");
198   }
199 }
200 
201 // static
ToJSON(JSONVerbosityLevel verbosity_level)202 std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
203   std::string output = "{\"histograms\":[";
204   const char* sep = "";
205   for (const HistogramBase* const histogram : Sort(GetHistograms())) {
206     output += sep;
207     sep = ",";
208     std::string json;
209     histogram->WriteJSON(&json, verbosity_level);
210     output += json;
211   }
212   output += "]}";
213   return output;
214 }
215 
216 // static
GetBucketRanges()217 std::vector<const BucketRanges*> StatisticsRecorder::GetBucketRanges() {
218   const AutoLock auto_lock(GetLock());
219 
220   // Manipulate |top_| through a const variable to ensure it is not mutated.
221   const auto* const_top = top_;
222   if (!const_top) {
223     return std::vector<const BucketRanges*>();
224   }
225 
226   return const_top->ranges_manager_.GetBucketRanges();
227 }
228 
229 // static
FindHistogram(std::string_view name)230 HistogramBase* StatisticsRecorder::FindHistogram(std::string_view name) {
231   uint64_t hash = HashMetricName(name);
232 
233   // This must be called *before* the lock is acquired below because it may call
234   // back into StatisticsRecorder to register histograms. Those called methods
235   // will acquire the lock at that time.
236   ImportGlobalPersistentHistograms();
237 
238   const AutoLock auto_lock(GetLock());
239 
240   // Manipulate |top_| through a const variable to ensure it is not mutated.
241   const auto* const_top = top_;
242   if (!const_top) {
243     return nullptr;
244   }
245 
246   return const_top->FindHistogramByHashInternal(hash, name);
247 }
248 
249 // static
250 StatisticsRecorder::HistogramProviders
GetHistogramProviders()251 StatisticsRecorder::GetHistogramProviders() {
252   const AutoLock auto_lock(GetLock());
253 
254   // Manipulate |top_| through a const variable to ensure it is not mutated.
255   const auto* const_top = top_;
256   if (!const_top) {
257     return StatisticsRecorder::HistogramProviders();
258   }
259   return const_top->providers_;
260 }
261 
262 // static
ImportProvidedHistograms(bool async,OnceClosure done_callback)263 void StatisticsRecorder::ImportProvidedHistograms(bool async,
264                                                   OnceClosure done_callback) {
265   // Merge histogram data from each provider in turn.
266   HistogramProviders providers = GetHistogramProviders();
267   auto barrier_callback =
268       BarrierClosure(providers.size(), std::move(done_callback));
269   for (const WeakPtr<HistogramProvider>& provider : providers) {
270     // Weak-pointer may be invalid if the provider was destructed, though they
271     // generally never are.
272     if (!provider) {
273       barrier_callback.Run();
274       continue;
275     }
276     provider->MergeHistogramDeltas(async, barrier_callback);
277   }
278 }
279 
280 // static
ImportProvidedHistogramsSync()281 void StatisticsRecorder::ImportProvidedHistogramsSync() {
282   ImportProvidedHistograms(/*async=*/false, /*done_callback=*/DoNothing());
283 }
284 
285 // static
PrepareDeltas(bool include_persistent,HistogramBase::Flags flags_to_set,HistogramBase::Flags required_flags,HistogramSnapshotManager * snapshot_manager)286 StatisticsRecorder::SnapshotTransactionId StatisticsRecorder::PrepareDeltas(
287     bool include_persistent,
288     HistogramBase::Flags flags_to_set,
289     HistogramBase::Flags required_flags,
290     HistogramSnapshotManager* snapshot_manager) {
291   Histograms histograms = Sort(GetHistograms(include_persistent));
292   AutoLock lock(snapshot_lock_.Get());
293   snapshot_manager->PrepareDeltas(std::move(histograms), flags_to_set,
294                                   required_flags);
295   return ++last_snapshot_transaction_id_;
296 }
297 
298 // static
299 StatisticsRecorder::SnapshotTransactionId
SnapshotUnloggedSamples(HistogramBase::Flags required_flags,HistogramSnapshotManager * snapshot_manager)300 StatisticsRecorder::SnapshotUnloggedSamples(
301     HistogramBase::Flags required_flags,
302     HistogramSnapshotManager* snapshot_manager) {
303   Histograms histograms = Sort(GetHistograms());
304   AutoLock lock(snapshot_lock_.Get());
305   snapshot_manager->SnapshotUnloggedSamples(std::move(histograms),
306                                             required_flags);
307   return ++last_snapshot_transaction_id_;
308 }
309 
310 // static
311 StatisticsRecorder::SnapshotTransactionId
GetLastSnapshotTransactionId()312 StatisticsRecorder::GetLastSnapshotTransactionId() {
313   AutoLock lock(snapshot_lock_.Get());
314   return last_snapshot_transaction_id_;
315 }
316 
317 // static
InitLogOnShutdown()318 void StatisticsRecorder::InitLogOnShutdown() {
319   const AutoLock auto_lock(GetLock());
320   InitLogOnShutdownWhileLocked();
321 }
322 
FindHistogramByHashInternal(uint64_t hash,std::string_view name) const323 HistogramBase* StatisticsRecorder::FindHistogramByHashInternal(
324     uint64_t hash,
325     std::string_view name) const {
326   AssertLockHeld();
327   const HistogramMap::const_iterator it = histograms_.find(hash);
328   if (it == histograms_.end()) {
329     return nullptr;
330   }
331   // Assert that there was no collision. Note that this is intentionally a
332   // DCHECK because 1) this is expensive to call repeatedly, and 2) this
333   // comparison may cause a read in persistent memory, which can cause I/O (this
334   // is bad because |lock_| is currently being held).
335   //
336   // If you are a developer adding a new histogram and this DCHECK is being hit,
337   // you are unluckily a victim of a hash collision. For now, the best solution
338   // is to rename the histogram. Reach out to [email protected] if
339   // you are unsure!
340   DCHECK_EQ(name, it->second->histogram_name())
341       << "Histogram name hash collision between " << name << " and "
342       << it->second->histogram_name() << " (hash = " << hash << ")";
343   return it->second;
344 }
345 
346 // static
AddHistogramSampleObserver(const std::string & name,StatisticsRecorder::ScopedHistogramSampleObserver * observer)347 void StatisticsRecorder::AddHistogramSampleObserver(
348     const std::string& name,
349     StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
350   DCHECK(observer);
351   uint64_t hash = HashMetricName(name);
352 
353   const AutoLock auto_lock(GetLock());
354   EnsureGlobalRecorderWhileLocked();
355 
356   auto iter = top_->observers_.find(hash);
357   if (iter == top_->observers_.end()) {
358     top_->observers_.insert(
359         {hash, base::MakeRefCounted<HistogramSampleObserverList>(
360                    ObserverListPolicy::EXISTING_ONLY)});
361   }
362 
363   top_->observers_[hash]->AddObserver(observer);
364 
365   HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
366   if (histogram) {
367     // Note: SetFlags() does not write to persistent memory, it only writes to
368     // an in-memory version of the flags.
369     histogram->SetFlags(HistogramBase::kCallbackExists);
370   }
371 
372   have_active_callbacks_.store(
373       global_sample_callback() || !top_->observers_.empty(),
374       std::memory_order_relaxed);
375 }
376 
377 // static
RemoveHistogramSampleObserver(const std::string & name,StatisticsRecorder::ScopedHistogramSampleObserver * observer)378 void StatisticsRecorder::RemoveHistogramSampleObserver(
379     const std::string& name,
380     StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
381   uint64_t hash = HashMetricName(name);
382 
383   const AutoLock auto_lock(GetLock());
384   EnsureGlobalRecorderWhileLocked();
385 
386   auto iter = top_->observers_.find(hash);
387   CHECK(iter != top_->observers_.end(), base::NotFatalUntil::M125);
388 
389   auto result = iter->second->RemoveObserver(observer);
390   if (result ==
391       HistogramSampleObserverList::RemoveObserverResult::kWasOrBecameEmpty) {
392     top_->observers_.erase(hash);
393 
394     // We also clear the flag from the histogram (if it exists).
395     HistogramBase* histogram = top_->FindHistogramByHashInternal(hash, name);
396     if (histogram) {
397       // Note: ClearFlags() does not write to persistent memory, it only writes
398       // to an in-memory version of the flags.
399       histogram->ClearFlags(HistogramBase::kCallbackExists);
400     }
401   }
402 
403   have_active_callbacks_.store(
404       global_sample_callback() || !top_->observers_.empty(),
405       std::memory_order_relaxed);
406 }
407 
408 // static
FindAndRunHistogramCallbacks(base::PassKey<HistogramBase>,const char * histogram_name,uint64_t name_hash,HistogramBase::Sample sample)409 void StatisticsRecorder::FindAndRunHistogramCallbacks(
410     base::PassKey<HistogramBase>,
411     const char* histogram_name,
412     uint64_t name_hash,
413     HistogramBase::Sample sample) {
414   DCHECK_EQ(name_hash, HashMetricName(histogram_name));
415 
416   const AutoLock auto_lock(GetLock());
417 
418   // Manipulate |top_| through a const variable to ensure it is not mutated.
419   const auto* const_top = top_;
420   if (!const_top) {
421     return;
422   }
423 
424   auto it = const_top->observers_.find(name_hash);
425 
426   // Ensure that this observer is still registered, as it might have been
427   // unregistered before we acquired the lock.
428   if (it == const_top->observers_.end()) {
429     return;
430   }
431 
432   it->second->Notify(FROM_HERE, &ScopedHistogramSampleObserver::RunCallback,
433                      histogram_name, name_hash, sample);
434 }
435 
436 // static
SetGlobalSampleCallback(const GlobalSampleCallback & new_global_sample_callback)437 void StatisticsRecorder::SetGlobalSampleCallback(
438     const GlobalSampleCallback& new_global_sample_callback) {
439   const AutoLock auto_lock(GetLock());
440   EnsureGlobalRecorderWhileLocked();
441 
442   DCHECK(!global_sample_callback() || !new_global_sample_callback);
443   global_sample_callback_.store(new_global_sample_callback);
444 
445   have_active_callbacks_.store(
446       new_global_sample_callback || !top_->observers_.empty(),
447       std::memory_order_relaxed);
448 }
449 
450 // static
GetHistogramCount()451 size_t StatisticsRecorder::GetHistogramCount() {
452   const AutoLock auto_lock(GetLock());
453 
454   // Manipulate |top_| through a const variable to ensure it is not mutated.
455   const auto* const_top = top_;
456   if (!const_top) {
457     return 0;
458   }
459   return const_top->histograms_.size();
460 }
461 
462 // static
ForgetHistogramForTesting(std::string_view name)463 void StatisticsRecorder::ForgetHistogramForTesting(std::string_view name) {
464   const AutoLock auto_lock(GetLock());
465   EnsureGlobalRecorderWhileLocked();
466 
467   uint64_t hash = HashMetricName(name);
468   HistogramBase* base = top_->FindHistogramByHashInternal(hash, name);
469   if (!base) {
470     return;
471   }
472 
473   if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
474     // When forgetting a histogram, it's likely that other information is also
475     // becoming invalid. Clear the persistent reference that may no longer be
476     // valid. There's no danger in this as, at worst, duplicates will be created
477     // in persistent memory.
478     static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
479   }
480 
481   // This performs another lookup in the map, but this is fine since this is
482   // only used in tests.
483   top_->histograms_.erase(hash);
484 }
485 
486 // static
487 std::unique_ptr<StatisticsRecorder>
CreateTemporaryForTesting()488 StatisticsRecorder::CreateTemporaryForTesting() {
489   const AutoLock auto_lock(GetLock());
490   std::unique_ptr<StatisticsRecorder> temporary_recorder =
491       WrapUnique(new StatisticsRecorder());
492   temporary_recorder->ranges_manager_
493       .DoNotReleaseRangesOnDestroyForTesting();  // IN-TEST
494   return temporary_recorder;
495 }
496 
497 // static
SetRecordChecker(std::unique_ptr<RecordHistogramChecker> record_checker)498 void StatisticsRecorder::SetRecordChecker(
499     std::unique_ptr<RecordHistogramChecker> record_checker) {
500   const AutoLock auto_lock(GetLock());
501   EnsureGlobalRecorderWhileLocked();
502   top_->record_checker_ = std::move(record_checker);
503 }
504 
505 // static
ShouldRecordHistogram(uint32_t histogram_hash)506 bool StatisticsRecorder::ShouldRecordHistogram(uint32_t histogram_hash) {
507   const AutoLock auto_lock(GetLock());
508 
509   // Manipulate |top_| through a const variable to ensure it is not mutated.
510   const auto* const_top = top_;
511   return !const_top || !const_top->record_checker_ ||
512          const_top->record_checker_->ShouldRecord(histogram_hash);
513 }
514 
515 // static
GetHistograms(bool include_persistent)516 StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms(
517     bool include_persistent) {
518   // This must be called *before* the lock is acquired below because it will
519   // call back into this object to register histograms. Those called methods
520   // will acquire the lock at that time.
521   ImportGlobalPersistentHistograms();
522 
523   Histograms out;
524 
525   const AutoLock auto_lock(GetLock());
526 
527   // Manipulate |top_| through a const variable to ensure it is not mutated.
528   const auto* const_top = top_;
529   if (!const_top) {
530     return out;
531   }
532 
533   out.reserve(const_top->histograms_.size());
534   for (const auto& entry : const_top->histograms_) {
535     // Note: HasFlags() does not read to persistent memory, it only reads an
536     // in-memory version of the flags.
537     bool is_persistent = entry.second->HasFlags(HistogramBase::kIsPersistent);
538     if (!include_persistent && is_persistent) {
539       continue;
540     }
541     out.push_back(entry.second);
542   }
543 
544   return out;
545 }
546 
547 // static
Sort(Histograms histograms)548 StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
549   ranges::sort(histograms, &HistogramNameLesser);
550   return histograms;
551 }
552 
553 // static
WithName(Histograms histograms,const std::string & query,bool case_sensitive)554 StatisticsRecorder::Histograms StatisticsRecorder::WithName(
555     Histograms histograms,
556     const std::string& query,
557     bool case_sensitive) {
558   // Need a C-string query for comparisons against C-string histogram name.
559   std::string lowercase_query;
560   const char* query_string;
561   if (case_sensitive) {
562     query_string = query.c_str();
563   } else {
564     lowercase_query = base::ToLowerASCII(query);
565     query_string = lowercase_query.c_str();
566   }
567 
568   histograms.erase(
569       ranges::remove_if(
570           histograms,
571           [query_string, case_sensitive](const HistogramBase* const h) {
572             return !strstr(
573                 case_sensitive
574                     ? h->histogram_name()
575                     : base::ToLowerASCII(h->histogram_name()).c_str(),
576                 query_string);
577           }),
578       histograms.end());
579   return histograms;
580 }
581 
582 // static
ImportGlobalPersistentHistograms()583 void StatisticsRecorder::ImportGlobalPersistentHistograms() {
584   // Import histograms from known persistent storage. Histograms could have been
585   // added by other processes and they must be fetched and recognized locally.
586   // If the persistent memory segment is not shared between processes, this call
587   // does nothing.
588   if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get())
589     allocator->ImportHistogramsToStatisticsRecorder();
590 }
591 
StatisticsRecorder()592 StatisticsRecorder::StatisticsRecorder() {
593   AssertLockHeld();
594   previous_ = top_;
595   top_ = this;
596   InitLogOnShutdownWhileLocked();
597 }
598 
599 // static
InitLogOnShutdownWhileLocked()600 void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
601   AssertLockHeld();
602   if (!is_vlog_initialized_ && VLOG_IS_ON(1)) {
603     is_vlog_initialized_ = true;
604     const auto dump_to_vlog = [](void*) {
605       std::string output;
606       WriteGraph("", &output);
607       VLOG(1) << output;
608     };
609     AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
610   }
611 }
612 
613 }  // namespace base
614