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