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 <memory>
8
9 #include "base/at_exit.h"
10 #include "base/containers/contains.h"
11 #include "base/debug/leak_annotations.h"
12 #include "base/json/string_escape.h"
13 #include "base/logging.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/metrics/histogram.h"
16 #include "base/metrics/histogram_snapshot_manager.h"
17 #include "base/metrics/metrics_hashes.h"
18 #include "base/metrics/persistent_histogram_allocator.h"
19 #include "base/metrics/record_histogram_checker.h"
20 #include "base/ranges/algorithm.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/values.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<base::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(lock_.Get());
80 DCHECK_EQ(this, top_);
81 top_ = previous_;
82 }
83
84 // static
EnsureGlobalRecorderWhileLocked()85 void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
86 lock_.Get().AssertAcquired();
87 if (top_)
88 return;
89
90 const StatisticsRecorder* const p = new StatisticsRecorder;
91 // The global recorder is never deleted.
92 ANNOTATE_LEAKING_OBJECT_PTR(p);
93 DCHECK_EQ(p, top_);
94 }
95
96 // static
RegisterHistogramProvider(const WeakPtr<HistogramProvider> & provider)97 void StatisticsRecorder::RegisterHistogramProvider(
98 const WeakPtr<HistogramProvider>& provider) {
99 const AutoLock auto_lock(lock_.Get());
100 EnsureGlobalRecorderWhileLocked();
101 top_->providers_.push_back(provider);
102 }
103
104 // static
RegisterOrDeleteDuplicate(HistogramBase * histogram)105 HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
106 HistogramBase* histogram) {
107 // Declared before |auto_lock| to ensure correct destruction order.
108 std::unique_ptr<HistogramBase> histogram_deleter;
109 const AutoLock auto_lock(lock_.Get());
110 EnsureGlobalRecorderWhileLocked();
111
112 const char* const name = histogram->histogram_name();
113 HistogramBase*& registered = top_->histograms_[name];
114
115 if (!registered) {
116 // |name| is guaranteed to never change or be deallocated so long
117 // as the histogram is alive (which is forever).
118 registered = histogram;
119 ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
120 // If there are callbacks for this histogram, we set the kCallbackExists
121 // flag.
122 if (base::Contains(top_->observers_, name))
123 histogram->SetFlags(HistogramBase::kCallbackExists);
124
125 return histogram;
126 }
127
128 if (histogram == registered) {
129 // The histogram was registered before.
130 return histogram;
131 }
132
133 // We already have one histogram with this name.
134 histogram_deleter.reset(histogram);
135 return registered;
136 }
137
138 // static
RegisterOrDeleteDuplicateRanges(const BucketRanges * ranges)139 const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
140 const BucketRanges* ranges) {
141 const AutoLock auto_lock(lock_.Get());
142 EnsureGlobalRecorderWhileLocked();
143
144 const BucketRanges* const registered =
145 top_->ranges_manager_.RegisterOrDeleteDuplicateRanges(ranges);
146
147 if (registered == ranges)
148 ANNOTATE_LEAKING_OBJECT_PTR(ranges);
149
150 return registered;
151 }
152
153 // static
WriteGraph(const std::string & query,std::string * output)154 void StatisticsRecorder::WriteGraph(const std::string& query,
155 std::string* output) {
156 if (query.length())
157 StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
158 else
159 output->append("Collections of all histograms\n");
160
161 for (const HistogramBase* const histogram :
162 Sort(WithName(GetHistograms(), query))) {
163 histogram->WriteAscii(output);
164 output->append("\n");
165 }
166 }
167
168 // static
ToJSON(JSONVerbosityLevel verbosity_level)169 std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
170 std::string output = "{\"histograms\":[";
171 const char* sep = "";
172 for (const HistogramBase* const histogram : Sort(GetHistograms())) {
173 output += sep;
174 sep = ",";
175 std::string json;
176 histogram->WriteJSON(&json, verbosity_level);
177 output += json;
178 }
179 output += "]}";
180 return output;
181 }
182
183 // static
GetBucketRanges()184 std::vector<const BucketRanges*> StatisticsRecorder::GetBucketRanges() {
185 const AutoLock auto_lock(lock_.Get());
186 EnsureGlobalRecorderWhileLocked();
187
188 return top_->ranges_manager_.GetBucketRanges();
189 }
190
191 // static
FindHistogram(base::StringPiece name)192 HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
193 // This must be called *before* the lock is acquired below because it will
194 // call back into this object to register histograms. Those called methods
195 // will acquire the lock at that time.
196 ImportGlobalPersistentHistograms();
197
198 const AutoLock auto_lock(lock_.Get());
199 EnsureGlobalRecorderWhileLocked();
200
201 const HistogramMap::const_iterator it = top_->histograms_.find(name);
202 return it != top_->histograms_.end() ? it->second : nullptr;
203 }
204
205 // static
206 StatisticsRecorder::HistogramProviders
GetHistogramProviders()207 StatisticsRecorder::GetHistogramProviders() {
208 const AutoLock auto_lock(lock_.Get());
209 EnsureGlobalRecorderWhileLocked();
210 return top_->providers_;
211 }
212
213 // static
ImportProvidedHistograms()214 void StatisticsRecorder::ImportProvidedHistograms() {
215 // Merge histogram data from each provider in turn.
216 for (const WeakPtr<HistogramProvider>& provider : GetHistogramProviders()) {
217 // Weak-pointer may be invalid if the provider was destructed, though they
218 // generally never are.
219 if (provider)
220 provider->MergeHistogramDeltas();
221 }
222 }
223
224 // static
PrepareDeltas(bool include_persistent,HistogramBase::Flags flags_to_set,HistogramBase::Flags required_flags,HistogramSnapshotManager * snapshot_manager)225 StatisticsRecorder::SnapshotTransactionId StatisticsRecorder::PrepareDeltas(
226 bool include_persistent,
227 HistogramBase::Flags flags_to_set,
228 HistogramBase::Flags required_flags,
229 HistogramSnapshotManager* snapshot_manager) {
230 Histograms histograms = Sort(GetHistograms(include_persistent));
231 base::AutoLock lock(snapshot_lock_.Get());
232 snapshot_manager->PrepareDeltas(std::move(histograms), flags_to_set,
233 required_flags);
234 return ++last_snapshot_transaction_id_;
235 }
236
237 // static
238 StatisticsRecorder::SnapshotTransactionId
SnapshotUnloggedSamples(HistogramBase::Flags required_flags,HistogramSnapshotManager * snapshot_manager)239 StatisticsRecorder::SnapshotUnloggedSamples(
240 HistogramBase::Flags required_flags,
241 HistogramSnapshotManager* snapshot_manager) {
242 Histograms histograms = Sort(GetHistograms());
243 base::AutoLock lock(snapshot_lock_.Get());
244 snapshot_manager->SnapshotUnloggedSamples(std::move(histograms),
245 required_flags);
246 return ++last_snapshot_transaction_id_;
247 }
248
249 // static
250 StatisticsRecorder::SnapshotTransactionId
GetLastSnapshotTransactionId()251 StatisticsRecorder::GetLastSnapshotTransactionId() {
252 base::AutoLock lock(snapshot_lock_.Get());
253 return last_snapshot_transaction_id_;
254 }
255
256 // static
InitLogOnShutdown()257 void StatisticsRecorder::InitLogOnShutdown() {
258 const AutoLock auto_lock(lock_.Get());
259 InitLogOnShutdownWhileLocked();
260 }
261
262 // static
AddHistogramSampleObserver(const std::string & name,StatisticsRecorder::ScopedHistogramSampleObserver * observer)263 void StatisticsRecorder::AddHistogramSampleObserver(
264 const std::string& name,
265 StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
266 DCHECK(observer);
267 const AutoLock auto_lock(lock_.Get());
268 EnsureGlobalRecorderWhileLocked();
269
270 auto iter = top_->observers_.find(name);
271 if (iter == top_->observers_.end()) {
272 top_->observers_.insert(
273 {name, base::MakeRefCounted<HistogramSampleObserverList>()});
274 }
275
276 top_->observers_[name]->AddObserver(observer);
277
278 const HistogramMap::const_iterator it = top_->histograms_.find(name);
279 if (it != top_->histograms_.end())
280 it->second->SetFlags(HistogramBase::kCallbackExists);
281
282 have_active_callbacks_.store(
283 global_sample_callback() || !top_->observers_.empty(),
284 std::memory_order_relaxed);
285 }
286
287 // static
RemoveHistogramSampleObserver(const std::string & name,StatisticsRecorder::ScopedHistogramSampleObserver * observer)288 void StatisticsRecorder::RemoveHistogramSampleObserver(
289 const std::string& name,
290 StatisticsRecorder::ScopedHistogramSampleObserver* observer) {
291 const AutoLock auto_lock(lock_.Get());
292 EnsureGlobalRecorderWhileLocked();
293
294 auto iter = top_->observers_.find(name);
295 DCHECK(iter != top_->observers_.end());
296
297 auto result = iter->second->RemoveObserver(observer);
298 if (result ==
299 HistogramSampleObserverList::RemoveObserverResult::kWasOrBecameEmpty) {
300 top_->observers_.erase(name);
301
302 // We also clear the flag from the histogram (if it exists).
303 const HistogramMap::const_iterator it = top_->histograms_.find(name);
304 if (it != top_->histograms_.end())
305 it->second->ClearFlags(HistogramBase::kCallbackExists);
306 }
307
308 have_active_callbacks_.store(
309 global_sample_callback() || !top_->observers_.empty(),
310 std::memory_order_relaxed);
311 }
312
313 // static
FindAndRunHistogramCallbacks(base::PassKey<HistogramBase>,const char * histogram_name,uint64_t name_hash,HistogramBase::Sample sample)314 void StatisticsRecorder::FindAndRunHistogramCallbacks(
315 base::PassKey<HistogramBase>,
316 const char* histogram_name,
317 uint64_t name_hash,
318 HistogramBase::Sample sample) {
319 const AutoLock auto_lock(lock_.Get());
320 EnsureGlobalRecorderWhileLocked();
321
322 auto it = top_->observers_.find(histogram_name);
323
324 // Ensure that this observer is still registered, as it might have been
325 // unregistered before we acquired the lock.
326 if (it == top_->observers_.end())
327 return;
328
329 it->second->Notify(FROM_HERE, &ScopedHistogramSampleObserver::RunCallback,
330 histogram_name, name_hash, sample);
331 }
332
333 // static
SetGlobalSampleCallback(const GlobalSampleCallback & new_global_sample_callback)334 void StatisticsRecorder::SetGlobalSampleCallback(
335 const GlobalSampleCallback& new_global_sample_callback) {
336 const AutoLock auto_lock(lock_.Get());
337 EnsureGlobalRecorderWhileLocked();
338
339 DCHECK(!global_sample_callback() || !new_global_sample_callback);
340 global_sample_callback_.store(new_global_sample_callback);
341
342 have_active_callbacks_.store(
343 new_global_sample_callback || !top_->observers_.empty(),
344 std::memory_order_relaxed);
345 }
346
347 // static
GetHistogramCount()348 size_t StatisticsRecorder::GetHistogramCount() {
349 const AutoLock auto_lock(lock_.Get());
350 EnsureGlobalRecorderWhileLocked();
351 return top_->histograms_.size();
352 }
353
354 // static
ForgetHistogramForTesting(base::StringPiece name)355 void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
356 const AutoLock auto_lock(lock_.Get());
357 EnsureGlobalRecorderWhileLocked();
358
359 const HistogramMap::iterator found = top_->histograms_.find(name);
360 if (found == top_->histograms_.end())
361 return;
362
363 HistogramBase* const base = found->second;
364 if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
365 // When forgetting a histogram, it's likely that other information is
366 // also becoming invalid. Clear the persistent reference that may no
367 // longer be valid. There's no danger in this as, at worst, duplicates
368 // will be created in persistent memory.
369 static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
370 }
371
372 top_->histograms_.erase(found);
373 }
374
375 // static
376 std::unique_ptr<StatisticsRecorder>
CreateTemporaryForTesting()377 StatisticsRecorder::CreateTemporaryForTesting() {
378 const AutoLock auto_lock(lock_.Get());
379 std::unique_ptr<StatisticsRecorder> temporary_recorder =
380 WrapUnique(new StatisticsRecorder());
381 temporary_recorder->ranges_manager_
382 .DoNotReleaseRangesOnDestroyForTesting(); // IN-TEST
383 return temporary_recorder;
384 }
385
386 // static
SetRecordChecker(std::unique_ptr<RecordHistogramChecker> record_checker)387 void StatisticsRecorder::SetRecordChecker(
388 std::unique_ptr<RecordHistogramChecker> record_checker) {
389 const AutoLock auto_lock(lock_.Get());
390 EnsureGlobalRecorderWhileLocked();
391 top_->record_checker_ = std::move(record_checker);
392 }
393
394 // static
ShouldRecordHistogram(uint32_t histogram_hash)395 bool StatisticsRecorder::ShouldRecordHistogram(uint32_t histogram_hash) {
396 const AutoLock auto_lock(lock_.Get());
397 EnsureGlobalRecorderWhileLocked();
398 return !top_->record_checker_ ||
399 top_->record_checker_->ShouldRecord(histogram_hash);
400 }
401
402 // static
GetHistograms(bool include_persistent)403 StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms(
404 bool include_persistent) {
405 // This must be called *before* the lock is acquired below because it will
406 // call back into this object to register histograms. Those called methods
407 // will acquire the lock at that time.
408 ImportGlobalPersistentHistograms();
409
410 Histograms out;
411
412 const AutoLock auto_lock(lock_.Get());
413 EnsureGlobalRecorderWhileLocked();
414
415 out.reserve(top_->histograms_.size());
416 for (const auto& entry : top_->histograms_) {
417 bool is_persistent = entry.second->HasFlags(HistogramBase::kIsPersistent);
418 if (!include_persistent && is_persistent)
419 continue;
420 out.push_back(entry.second);
421 }
422
423 return out;
424 }
425
426 // static
Sort(Histograms histograms)427 StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
428 ranges::sort(histograms, &HistogramNameLesser);
429 return histograms;
430 }
431
432 // static
WithName(Histograms histograms,const std::string & query,bool case_sensitive)433 StatisticsRecorder::Histograms StatisticsRecorder::WithName(
434 Histograms histograms,
435 const std::string& query,
436 bool case_sensitive) {
437 // Need a C-string query for comparisons against C-string histogram name.
438 std::string lowercase_query;
439 const char* query_string;
440 if (case_sensitive) {
441 query_string = query.c_str();
442 } else {
443 lowercase_query = base::ToLowerASCII(query);
444 query_string = lowercase_query.c_str();
445 }
446
447 histograms.erase(
448 ranges::remove_if(
449 histograms,
450 [query_string, case_sensitive](const HistogramBase* const h) {
451 return !strstr(
452 case_sensitive
453 ? h->histogram_name()
454 : base::ToLowerASCII(h->histogram_name()).c_str(),
455 query_string);
456 }),
457 histograms.end());
458 return histograms;
459 }
460
461 // static
ImportGlobalPersistentHistograms()462 void StatisticsRecorder::ImportGlobalPersistentHistograms() {
463 // Import histograms from known persistent storage. Histograms could have been
464 // added by other processes and they must be fetched and recognized locally.
465 // If the persistent memory segment is not shared between processes, this call
466 // does nothing.
467 if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get())
468 allocator->ImportHistogramsToStatisticsRecorder();
469 }
470
StatisticsRecorder()471 StatisticsRecorder::StatisticsRecorder() {
472 lock_.Get().AssertAcquired();
473 previous_ = top_;
474 top_ = this;
475 InitLogOnShutdownWhileLocked();
476 }
477
478 // static
InitLogOnShutdownWhileLocked()479 void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
480 lock_.Get().AssertAcquired();
481 if (!is_vlog_initialized_ && VLOG_IS_ON(1)) {
482 is_vlog_initialized_ = true;
483 const auto dump_to_vlog = [](void*) {
484 std::string output;
485 WriteGraph("", &output);
486 VLOG(1) << output;
487 };
488 AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
489 }
490 }
491
492 } // namespace base
493