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