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