1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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/json/string_escape.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/metrics_hashes.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/synchronization/lock.h"
16 #include "base/values.h"
17
18 namespace {
19 // Initialize histogram statistics gathering system.
20 base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
21 LAZY_INSTANCE_INITIALIZER;
22 } // namespace
23
24 namespace base {
25
26 // static
Initialize()27 void StatisticsRecorder::Initialize() {
28 // Ensure that an instance of the StatisticsRecorder object is created.
29 g_statistics_recorder_.Get();
30 }
31
32 // static
IsActive()33 bool StatisticsRecorder::IsActive() {
34 if (lock_ == NULL)
35 return false;
36 base::AutoLock auto_lock(*lock_);
37 return NULL != histograms_;
38 }
39
40 // static
RegisterOrDeleteDuplicate(HistogramBase * histogram)41 HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
42 HistogramBase* histogram) {
43 // As per crbug.com/79322 the histograms are intentionally leaked.
44 if (lock_ == NULL) {
45 return histogram;
46 }
47
48 HistogramBase* histogram_to_delete = NULL;
49 HistogramBase* histogram_to_return = NULL;
50 {
51 base::AutoLock auto_lock(*lock_);
52 if (histograms_ == NULL) {
53 histogram_to_return = histogram;
54 } else {
55 const std::string& name = histogram->histogram_name();
56 uint64_t name_hash = histogram->name_hash();
57 HistogramMap::iterator it = histograms_->find(name_hash);
58 if (histograms_->end() == it) {
59 (*histograms_)[name_hash] = histogram;
60 // If there are callbacks for this histogram, we set the kCallbackExists
61 // flag.
62 auto callback_iterator = callbacks_->find(name);
63 if (callback_iterator != callbacks_->end()) {
64 if (!callback_iterator->second.is_null())
65 histogram->SetFlags(HistogramBase::kCallbackExists);
66 else
67 histogram->ClearFlags(HistogramBase::kCallbackExists);
68 }
69 histogram_to_return = histogram;
70 } else if (histogram == it->second) {
71 // The histogram was registered before.
72 histogram_to_return = histogram;
73 } else {
74 // We already have one histogram with this name.
75 DCHECK_EQ(histogram->histogram_name(),
76 it->second->histogram_name()) << "hash collision";
77 histogram_to_return = it->second;
78 histogram_to_delete = histogram;
79 }
80 }
81 }
82 delete histogram_to_delete;
83 return histogram_to_return;
84 }
85
86 // static
RegisterOrDeleteDuplicateRanges(const BucketRanges * ranges)87 const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
88 const BucketRanges* ranges) {
89 DCHECK(ranges->HasValidChecksum());
90 scoped_ptr<const BucketRanges> ranges_deleter;
91
92 if (lock_ == NULL) {
93 return ranges;
94 }
95
96 base::AutoLock auto_lock(*lock_);
97 if (ranges_ == NULL) {
98 return ranges;
99 }
100
101 std::list<const BucketRanges*>* checksum_matching_list;
102 RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
103 if (ranges_->end() == ranges_it) {
104 // Add a new matching list to map.
105 checksum_matching_list = new std::list<const BucketRanges*>();
106 (*ranges_)[ranges->checksum()] = checksum_matching_list;
107 } else {
108 checksum_matching_list = ranges_it->second;
109 }
110
111 for (const BucketRanges* existing_ranges : *checksum_matching_list) {
112 if (existing_ranges->Equals(ranges)) {
113 if (existing_ranges == ranges) {
114 return ranges;
115 } else {
116 ranges_deleter.reset(ranges);
117 return existing_ranges;
118 }
119 }
120 }
121 // We haven't found a BucketRanges which has the same ranges. Register the
122 // new BucketRanges.
123 checksum_matching_list->push_front(ranges);
124 return ranges;
125 }
126
127 // static
WriteHTMLGraph(const std::string & query,std::string * output)128 void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
129 std::string* output) {
130 if (!IsActive())
131 return;
132
133 Histograms snapshot;
134 GetSnapshot(query, &snapshot);
135 for (const HistogramBase* histogram : snapshot) {
136 histogram->WriteHTMLGraph(output);
137 output->append("<br><hr><br>");
138 }
139 }
140
141 // static
WriteGraph(const std::string & query,std::string * output)142 void StatisticsRecorder::WriteGraph(const std::string& query,
143 std::string* output) {
144 if (!IsActive())
145 return;
146 if (query.length())
147 StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
148 else
149 output->append("Collections of all histograms\n");
150
151 Histograms snapshot;
152 GetSnapshot(query, &snapshot);
153 for (const HistogramBase* histogram : snapshot) {
154 histogram->WriteAscii(output);
155 output->append("\n");
156 }
157 }
158
159 // static
ToJSON(const std::string & query)160 std::string StatisticsRecorder::ToJSON(const std::string& query) {
161 if (!IsActive())
162 return std::string();
163
164 std::string output("{");
165 if (!query.empty()) {
166 output += "\"query\":";
167 EscapeJSONString(query, true, &output);
168 output += ",";
169 }
170
171 Histograms snapshot;
172 GetSnapshot(query, &snapshot);
173 output += "\"histograms\":[";
174 bool first_histogram = true;
175 for (const HistogramBase* histogram : snapshot) {
176 if (first_histogram)
177 first_histogram = false;
178 else
179 output += ",";
180 std::string json;
181 histogram->WriteJSON(&json);
182 output += json;
183 }
184 output += "]}";
185 return output;
186 }
187
188 // static
GetHistograms(Histograms * output)189 void StatisticsRecorder::GetHistograms(Histograms* output) {
190 if (lock_ == NULL)
191 return;
192 base::AutoLock auto_lock(*lock_);
193 if (histograms_ == NULL)
194 return;
195
196 for (const auto& entry : *histograms_) {
197 DCHECK_EQ(entry.first, entry.second->name_hash());
198 output->push_back(entry.second);
199 }
200 }
201
202 // static
GetBucketRanges(std::vector<const BucketRanges * > * output)203 void StatisticsRecorder::GetBucketRanges(
204 std::vector<const BucketRanges*>* output) {
205 if (lock_ == NULL)
206 return;
207 base::AutoLock auto_lock(*lock_);
208 if (ranges_ == NULL)
209 return;
210
211 for (const auto& entry : *ranges_) {
212 for (const auto& range_entry : *entry.second) {
213 output->push_back(range_entry);
214 }
215 }
216 }
217
218 // static
FindHistogram(const std::string & name)219 HistogramBase* StatisticsRecorder::FindHistogram(const std::string& name) {
220 if (lock_ == NULL)
221 return NULL;
222 base::AutoLock auto_lock(*lock_);
223 if (histograms_ == NULL)
224 return NULL;
225
226 HistogramMap::iterator it = histograms_->find(HashMetricName(name));
227 if (histograms_->end() == it)
228 return NULL;
229 DCHECK_EQ(name, it->second->histogram_name()) << "hash collision";
230 return it->second;
231 }
232
233 // static
SetCallback(const std::string & name,const StatisticsRecorder::OnSampleCallback & cb)234 bool StatisticsRecorder::SetCallback(
235 const std::string& name,
236 const StatisticsRecorder::OnSampleCallback& cb) {
237 DCHECK(!cb.is_null());
238 if (lock_ == NULL)
239 return false;
240 base::AutoLock auto_lock(*lock_);
241 if (histograms_ == NULL)
242 return false;
243
244 if (ContainsKey(*callbacks_, name))
245 return false;
246 callbacks_->insert(std::make_pair(name, cb));
247
248 HistogramMap::iterator it = histograms_->find(HashMetricName(name));
249 if (it != histograms_->end()) {
250 DCHECK_EQ(name, it->second->histogram_name()) << "hash collision";
251 it->second->SetFlags(HistogramBase::kCallbackExists);
252 }
253
254 return true;
255 }
256
257 // static
ClearCallback(const std::string & name)258 void StatisticsRecorder::ClearCallback(const std::string& name) {
259 if (lock_ == NULL)
260 return;
261 base::AutoLock auto_lock(*lock_);
262 if (histograms_ == NULL)
263 return;
264
265 callbacks_->erase(name);
266
267 // We also clear the flag from the histogram (if it exists).
268 HistogramMap::iterator it = histograms_->find(HashMetricName(name));
269 if (it != histograms_->end()) {
270 DCHECK_EQ(name, it->second->histogram_name()) << "hash collision";
271 it->second->ClearFlags(HistogramBase::kCallbackExists);
272 }
273 }
274
275 // static
FindCallback(const std::string & name)276 StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
277 const std::string& name) {
278 if (lock_ == NULL)
279 return OnSampleCallback();
280 base::AutoLock auto_lock(*lock_);
281 if (histograms_ == NULL)
282 return OnSampleCallback();
283
284 auto callback_iterator = callbacks_->find(name);
285 return callback_iterator != callbacks_->end() ? callback_iterator->second
286 : OnSampleCallback();
287 }
288
289 // private static
GetSnapshot(const std::string & query,Histograms * snapshot)290 void StatisticsRecorder::GetSnapshot(const std::string& query,
291 Histograms* snapshot) {
292 if (lock_ == NULL)
293 return;
294 base::AutoLock auto_lock(*lock_);
295 if (histograms_ == NULL)
296 return;
297
298 for (const auto& entry : *histograms_) {
299 if (entry.second->histogram_name().find(query) != std::string::npos)
300 snapshot->push_back(entry.second);
301 }
302 }
303
304 // This singleton instance should be started during the single threaded portion
305 // of main(), and hence it is not thread safe. It initializes globals to
306 // provide support for all future calls.
StatisticsRecorder()307 StatisticsRecorder::StatisticsRecorder() {
308 DCHECK(!histograms_);
309 if (lock_ == NULL) {
310 // This will leak on purpose. It's the only way to make sure we won't race
311 // against the static uninitialization of the module while one of our
312 // static methods relying on the lock get called at an inappropriate time
313 // during the termination phase. Since it's a static data member, we will
314 // leak one per process, which would be similar to the instance allocated
315 // during static initialization and released only on process termination.
316 lock_ = new base::Lock;
317 }
318 base::AutoLock auto_lock(*lock_);
319 histograms_ = new HistogramMap;
320 callbacks_ = new CallbackMap;
321 ranges_ = new RangesMap;
322
323 if (VLOG_IS_ON(1))
324 AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
325 }
326
327 // static
DumpHistogramsToVlog(void *)328 void StatisticsRecorder::DumpHistogramsToVlog(void* /* instance */) {
329 std::string output;
330 StatisticsRecorder::WriteGraph(std::string(), &output);
331 VLOG(1) << output;
332 }
333
~StatisticsRecorder()334 StatisticsRecorder::~StatisticsRecorder() {
335 DCHECK(histograms_ && ranges_ && lock_);
336
337 // Clean up.
338 scoped_ptr<HistogramMap> histograms_deleter;
339 scoped_ptr<CallbackMap> callbacks_deleter;
340 scoped_ptr<RangesMap> ranges_deleter;
341 // We don't delete lock_ on purpose to avoid having to properly protect
342 // against it going away after we checked for NULL in the static methods.
343 {
344 base::AutoLock auto_lock(*lock_);
345 histograms_deleter.reset(histograms_);
346 callbacks_deleter.reset(callbacks_);
347 ranges_deleter.reset(ranges_);
348 histograms_ = NULL;
349 callbacks_ = NULL;
350 ranges_ = NULL;
351 }
352 // We are going to leak the histograms and the ranges.
353 }
354
355
356 // static
357 StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL;
358 // static
359 StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = NULL;
360 // static
361 StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL;
362 // static
363 base::Lock* StatisticsRecorder::lock_ = NULL;
364
365 } // namespace base
366