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/debug/leak_annotations.h"
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/metrics/histogram.h"
12#include "base/strings/stringprintf.h"
13#include "base/synchronization/lock.h"
14
15using std::list;
16using std::string;
17
18namespace {
19// Initialize histogram statistics gathering system.
20base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
21    LAZY_INSTANCE_INITIALIZER;
22}  // namespace
23
24namespace base {
25
26// static
27void StatisticsRecorder::Initialize() {
28  // Ensure that an instance of the StatisticsRecorder object is created.
29  g_statistics_recorder_.Get();
30}
31
32
33// static
34bool StatisticsRecorder::IsActive() {
35  if (lock_ == NULL)
36    return false;
37  base::AutoLock auto_lock(*lock_);
38  return NULL != histograms_;
39}
40
41// static
42HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
43    HistogramBase* histogram) {
44  // As per crbug.com/79322 the histograms are intentionally leaked, so we need
45  // to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used only once
46  // for an object, the duplicates should not be annotated.
47  // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
48  // twice if (lock_ == NULL) || (!histograms_).
49  if (lock_ == NULL) {
50    ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
51    return histogram;
52  }
53
54  HistogramBase* histogram_to_delete = NULL;
55  HistogramBase* histogram_to_return = NULL;
56  {
57    base::AutoLock auto_lock(*lock_);
58    if (histograms_ == NULL) {
59      histogram_to_return = histogram;
60    } else {
61      const string& name = histogram->histogram_name();
62      HistogramMap::iterator it = histograms_->find(name);
63      if (histograms_->end() == it) {
64        (*histograms_)[name] = histogram;
65        ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
66        histogram_to_return = histogram;
67      } else if (histogram == it->second) {
68        // The histogram was registered before.
69        histogram_to_return = histogram;
70      } else {
71        // We already have one histogram with this name.
72        histogram_to_return = it->second;
73        histogram_to_delete = histogram;
74      }
75    }
76  }
77  delete histogram_to_delete;
78  return histogram_to_return;
79}
80
81// static
82const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
83    const BucketRanges* ranges) {
84  DCHECK(ranges->HasValidChecksum());
85  scoped_ptr<const BucketRanges> ranges_deleter;
86
87  if (lock_ == NULL) {
88    ANNOTATE_LEAKING_OBJECT_PTR(ranges);
89    return ranges;
90  }
91
92  base::AutoLock auto_lock(*lock_);
93  if (ranges_ == NULL) {
94    ANNOTATE_LEAKING_OBJECT_PTR(ranges);
95    return ranges;
96  }
97
98  list<const BucketRanges*>* checksum_matching_list;
99  RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
100  if (ranges_->end() == ranges_it) {
101    // Add a new matching list to map.
102    checksum_matching_list = new list<const BucketRanges*>();
103    ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list);
104    (*ranges_)[ranges->checksum()] = checksum_matching_list;
105  } else {
106    checksum_matching_list = ranges_it->second;
107  }
108
109  list<const BucketRanges*>::iterator checksum_matching_list_it;
110  for (checksum_matching_list_it = checksum_matching_list->begin();
111       checksum_matching_list_it != checksum_matching_list->end();
112       ++checksum_matching_list_it) {
113    const BucketRanges* existing_ranges = *checksum_matching_list_it;
114    if (existing_ranges->Equals(ranges)) {
115      if (existing_ranges == ranges) {
116        return ranges;
117      } else {
118        ranges_deleter.reset(ranges);
119        return existing_ranges;
120      }
121    }
122  }
123  // We haven't found a BucketRanges which has the same ranges. Register the
124  // new BucketRanges.
125  checksum_matching_list->push_front(ranges);
126  return ranges;
127}
128
129// static
130void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
131                                        std::string* output) {
132  if (!IsActive())
133    return;
134
135  Histograms snapshot;
136  GetSnapshot(query, &snapshot);
137  for (Histograms::iterator it = snapshot.begin();
138       it != snapshot.end();
139       ++it) {
140    (*it)->WriteHTMLGraph(output);
141    output->append("<br><hr><br>");
142  }
143}
144
145// static
146void StatisticsRecorder::WriteGraph(const std::string& query,
147                                    std::string* output) {
148  if (!IsActive())
149    return;
150  if (query.length())
151    StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
152  else
153    output->append("Collections of all histograms\n");
154
155  Histograms snapshot;
156  GetSnapshot(query, &snapshot);
157  for (Histograms::iterator it = snapshot.begin();
158       it != snapshot.end();
159       ++it) {
160    (*it)->WriteAscii(output);
161    output->append("\n");
162  }
163}
164
165// static
166void StatisticsRecorder::GetHistograms(Histograms* output) {
167  if (lock_ == NULL)
168    return;
169  base::AutoLock auto_lock(*lock_);
170  if (histograms_ == NULL)
171    return;
172
173  for (HistogramMap::iterator it = histograms_->begin();
174       histograms_->end() != it;
175       ++it) {
176    DCHECK_EQ(it->first, it->second->histogram_name());
177    output->push_back(it->second);
178  }
179}
180
181// static
182void StatisticsRecorder::GetBucketRanges(
183    std::vector<const BucketRanges*>* output) {
184  if (lock_ == NULL)
185    return;
186  base::AutoLock auto_lock(*lock_);
187  if (ranges_ == NULL)
188    return;
189
190  for (RangesMap::iterator it = ranges_->begin();
191       ranges_->end() != it;
192       ++it) {
193    list<const BucketRanges*>* ranges_list = it->second;
194    list<const BucketRanges*>::iterator ranges_list_it;
195    for (ranges_list_it = ranges_list->begin();
196         ranges_list_it != ranges_list->end();
197         ++ranges_list_it) {
198      output->push_back(*ranges_list_it);
199    }
200  }
201}
202
203// static
204HistogramBase* StatisticsRecorder::FindHistogram(const std::string& name) {
205  if (lock_ == NULL)
206    return NULL;
207  base::AutoLock auto_lock(*lock_);
208  if (histograms_ == NULL)
209    return NULL;
210
211  HistogramMap::iterator it = histograms_->find(name);
212  if (histograms_->end() == it)
213    return NULL;
214  return it->second;
215}
216
217// private static
218void StatisticsRecorder::GetSnapshot(const std::string& query,
219                                     Histograms* snapshot) {
220  if (lock_ == NULL)
221    return;
222  base::AutoLock auto_lock(*lock_);
223  if (histograms_ == NULL)
224    return;
225
226  for (HistogramMap::iterator it = histograms_->begin();
227       histograms_->end() != it;
228       ++it) {
229    if (it->first.find(query) != std::string::npos)
230      snapshot->push_back(it->second);
231  }
232}
233
234// This singleton instance should be started during the single threaded portion
235// of main(), and hence it is not thread safe.  It initializes globals to
236// provide support for all future calls.
237StatisticsRecorder::StatisticsRecorder() {
238  DCHECK(!histograms_);
239  if (lock_ == NULL) {
240    // This will leak on purpose. It's the only way to make sure we won't race
241    // against the static uninitialization of the module while one of our
242    // static methods relying on the lock get called at an inappropriate time
243    // during the termination phase. Since it's a static data member, we will
244    // leak one per process, which would be similar to the instance allocated
245    // during static initialization and released only on  process termination.
246    lock_ = new base::Lock;
247  }
248  base::AutoLock auto_lock(*lock_);
249  histograms_ = new HistogramMap;
250  ranges_ = new RangesMap;
251
252  if (VLOG_IS_ON(1))
253    AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
254}
255
256// static
257void StatisticsRecorder::DumpHistogramsToVlog(void* instance) {
258  DCHECK(VLOG_IS_ON(1));
259
260  StatisticsRecorder* me = reinterpret_cast<StatisticsRecorder*>(instance);
261  string output;
262  me->WriteGraph(std::string(), &output);
263  VLOG(1) << output;
264}
265
266StatisticsRecorder::~StatisticsRecorder() {
267  DCHECK(histograms_ && ranges_ && lock_);
268
269  // Clean up.
270  scoped_ptr<HistogramMap> histograms_deleter;
271  scoped_ptr<RangesMap> ranges_deleter;
272  // We don't delete lock_ on purpose to avoid having to properly protect
273  // against it going away after we checked for NULL in the static methods.
274  {
275    base::AutoLock auto_lock(*lock_);
276    histograms_deleter.reset(histograms_);
277    ranges_deleter.reset(ranges_);
278    histograms_ = NULL;
279    ranges_ = NULL;
280  }
281  // We are going to leak the histograms and the ranges.
282}
283
284
285// static
286StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL;
287// static
288StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL;
289// static
290base::Lock* StatisticsRecorder::lock_ = NULL;
291
292}  // namespace base
293