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