histogram_synchronizer.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "content/browser/histogram_synchronizer.h"
6
7#include "base/bind.h"
8#include "base/lazy_instance.h"
9#include "base/logging.h"
10#include "base/metrics/histogram.h"
11#include "base/pickle.h"
12#include "base/threading/thread.h"
13#include "base/threading/thread_restrictions.h"
14#include "content/browser/histogram_controller.h"
15#include "content/public/browser/browser_thread.h"
16#include "content/public/browser/histogram_fetcher.h"
17#include "content/public/common/content_constants.h"
18
19using base::Time;
20using base::TimeDelta;
21using base::TimeTicks;
22
23namespace {
24
25// Negative numbers are never used as sequence numbers.  We explicitly pick a
26// negative number that is "so negative" that even when we add one (as is done
27// when we generated the next sequence number) that it will still be negative.
28// We have code that handles wrapping around on an overflow into negative
29// territory.
30static const int kNeverUsableSequenceNumber = -2;
31
32}  // anonymous namespace
33
34namespace content {
35
36// The "RequestContext" structure describes an individual request received from
37// the UI. All methods are accessible on UI thread.
38class HistogramSynchronizer::RequestContext {
39 public:
40  // A map from sequence_number_ to the actual RequestContexts.
41  typedef std::map<int, RequestContext*> RequestContextMap;
42
43  RequestContext(const base::Closure& callback, int sequence_number)
44      : callback_(callback),
45        sequence_number_(sequence_number),
46        received_process_group_count_(0),
47        processes_pending_(0) {
48  }
49  ~RequestContext() {}
50
51  void SetReceivedProcessGroupCount(bool done) {
52    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
53    received_process_group_count_ = done;
54  }
55
56  // Methods for book keeping of processes_pending_.
57  void AddProcessesPending(int processes_pending) {
58    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
59    processes_pending_ += processes_pending;
60  }
61
62  void DecrementProcessesPending() {
63    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
64    --processes_pending_;
65  }
66
67  // Records that we are waiting for one less histogram data from a process for
68  // the given sequence number. If |received_process_group_count_| and
69  // |processes_pending_| are zero, then delete the current object by calling
70  // Unregister.
71  void DeleteIfAllDone() {
72    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
73
74    if (processes_pending_ <= 0 && received_process_group_count_)
75      RequestContext::Unregister(sequence_number_);
76  }
77
78  // Register |callback| in |outstanding_requests_| map for the given
79  // |sequence_number|.
80  static void Register(const base::Closure& callback, int sequence_number) {
81    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
82
83    RequestContext* request = new RequestContext(callback, sequence_number);
84    outstanding_requests_.Get()[sequence_number] = request;
85  }
86
87  // Find the |RequestContext| in |outstanding_requests_| map for the given
88  // |sequence_number|.
89  static RequestContext* GetRequestContext(int sequence_number) {
90    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
91
92    RequestContextMap::iterator it =
93        outstanding_requests_.Get().find(sequence_number);
94    if (it == outstanding_requests_.Get().end())
95      return NULL;
96
97    RequestContext* request = it->second;
98    DCHECK_EQ(sequence_number, request->sequence_number_);
99    return request;
100  }
101
102  // Delete the entry for the given |sequence_number| from
103  // |outstanding_requests_| map. This method is called when all changes have
104  // been acquired, or when the wait time expires (whichever is sooner).
105  static void Unregister(int sequence_number) {
106    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
107
108    RequestContextMap::iterator it =
109        outstanding_requests_.Get().find(sequence_number);
110    if (it == outstanding_requests_.Get().end())
111      return;
112
113    RequestContext* request = it->second;
114    DCHECK_EQ(sequence_number, request->sequence_number_);
115    bool received_process_group_count = request->received_process_group_count_;
116    int unresponsive_processes = request->processes_pending_;
117
118    request->callback_.Run();
119
120    delete request;
121    outstanding_requests_.Get().erase(it);
122
123    UMA_HISTOGRAM_BOOLEAN("Histogram.ReceivedProcessGroupCount",
124                          received_process_group_count);
125    UMA_HISTOGRAM_COUNTS("Histogram.PendingProcessNotResponding",
126                         unresponsive_processes);
127  }
128
129  // Delete all the entries in |outstanding_requests_| map.
130  static void OnShutdown() {
131    // Just in case we have any pending tasks, clear them out.
132    while (!outstanding_requests_.Get().empty()) {
133      RequestContextMap::iterator it = outstanding_requests_.Get().begin();
134      delete it->second;
135      outstanding_requests_.Get().erase(it);
136    }
137  }
138
139  // Requests are made to asynchronously send data to the |callback_|.
140  base::Closure callback_;
141
142  // The sequence number used by the most recent update request to contact all
143  // processes.
144  int sequence_number_;
145
146  // Indicates if we have received all pending processes count.
147  bool received_process_group_count_;
148
149  // The number of pending processes (all renderer processes and browser child
150  // processes) that have not yet responded to requests.
151  int processes_pending_;
152
153  // Map of all outstanding RequestContexts, from sequence_number_ to
154  // RequestContext.
155  static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_;
156};
157
158// static
159base::LazyInstance
160    <HistogramSynchronizer::RequestContext::RequestContextMap>::Leaky
161        HistogramSynchronizer::RequestContext::outstanding_requests_ =
162            LAZY_INSTANCE_INITIALIZER;
163
164HistogramSynchronizer::HistogramSynchronizer()
165    : lock_(),
166      callback_thread_(NULL),
167      last_used_sequence_number_(kNeverUsableSequenceNumber),
168      async_sequence_number_(kNeverUsableSequenceNumber) {
169  HistogramController::GetInstance()->Register(this);
170}
171
172HistogramSynchronizer::~HistogramSynchronizer() {
173  RequestContext::OnShutdown();
174
175  // Just in case we have any pending tasks, clear them out.
176  SetCallbackTaskAndThread(NULL, base::Closure());
177}
178
179HistogramSynchronizer* HistogramSynchronizer::GetInstance() {
180  return Singleton<HistogramSynchronizer,
181      LeakySingletonTraits<HistogramSynchronizer> >::get();
182}
183
184// static
185void HistogramSynchronizer::FetchHistograms() {
186  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
187    BrowserThread::PostTask(
188        BrowserThread::UI, FROM_HERE,
189        base::Bind(&HistogramSynchronizer::FetchHistograms));
190    return;
191  }
192  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
193
194  HistogramSynchronizer* current_synchronizer =
195      HistogramSynchronizer::GetInstance();
196  if (current_synchronizer == NULL)
197    return;
198
199  current_synchronizer->RegisterAndNotifyAllProcesses(
200      HistogramSynchronizer::UNKNOWN,
201      base::TimeDelta::FromMinutes(1));
202}
203
204void FetchHistogramsAsynchronously(MessageLoop* callback_thread,
205                                   const base::Closure& callback,
206                                   base::TimeDelta wait_time) {
207  HistogramSynchronizer::FetchHistogramsAsynchronously(
208      callback_thread, callback, wait_time);
209}
210
211// static
212void HistogramSynchronizer::FetchHistogramsAsynchronously(
213    MessageLoop* callback_thread,
214    const base::Closure& callback,
215    base::TimeDelta wait_time) {
216  DCHECK(callback_thread != NULL);
217  DCHECK(!callback.is_null());
218
219  HistogramSynchronizer* current_synchronizer =
220      HistogramSynchronizer::GetInstance();
221  current_synchronizer->SetCallbackTaskAndThread(
222      callback_thread, callback);
223
224  current_synchronizer->RegisterAndNotifyAllProcesses(
225      HistogramSynchronizer::ASYNC_HISTOGRAMS, wait_time);
226}
227
228void HistogramSynchronizer::RegisterAndNotifyAllProcesses(
229    ProcessHistogramRequester requester,
230    base::TimeDelta wait_time) {
231  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
232
233  int sequence_number = GetNextAvailableSequenceNumber(requester);
234
235  base::Closure callback = base::Bind(
236      &HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback,
237      base::Unretained(this),
238      sequence_number);
239
240  RequestContext::Register(callback, sequence_number);
241
242  // Get histogram data from renderer and browser child processes.
243  HistogramController::GetInstance()->GetHistogramData(sequence_number);
244
245  // Post a task that would be called after waiting for wait_time.  This acts
246  // as a watchdog, to cancel the requests for non-responsive processes.
247  BrowserThread::PostDelayedTask(
248      BrowserThread::UI, FROM_HERE,
249      base::Bind(&RequestContext::Unregister, sequence_number),
250      wait_time);
251}
252
253void HistogramSynchronizer::OnPendingProcesses(int sequence_number,
254                                               int pending_processes,
255                                               bool end) {
256  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257
258  RequestContext* request = RequestContext::GetRequestContext(sequence_number);
259  if (!request)
260    return;
261  request->AddProcessesPending(pending_processes);
262  request->SetReceivedProcessGroupCount(end);
263  request->DeleteIfAllDone();
264}
265
266void HistogramSynchronizer::OnHistogramDataCollected(
267    int sequence_number,
268    const std::vector<std::string>& pickled_histograms) {
269  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270
271  RequestContext* request = RequestContext::GetRequestContext(sequence_number);
272
273  for (std::vector<std::string>::const_iterator it = pickled_histograms.begin();
274       it < pickled_histograms.end();
275       ++it) {
276    Pickle pickle(it->data(), it->size());
277    PickleIterator iter(pickle);
278    base::DeserializeHistogramAndAddSamples(&iter);
279  }
280
281  if (!request)
282    return;
283
284  // Delete request if we have heard back from all child processes.
285  request->DecrementProcessesPending();
286  request->DeleteIfAllDone();
287}
288
289void HistogramSynchronizer::SetCallbackTaskAndThread(
290    MessageLoop* callback_thread,
291    const base::Closure& callback) {
292  base::Closure old_callback;
293  MessageLoop* old_thread = NULL;
294  {
295    base::AutoLock auto_lock(lock_);
296    old_callback = callback_;
297    callback_ = callback;
298    old_thread = callback_thread_;
299    callback_thread_ = callback_thread;
300    // Prevent premature calling of our new callbacks.
301    async_sequence_number_ = kNeverUsableSequenceNumber;
302  }
303  // Just in case there was a task pending....
304  InternalPostTask(old_thread, old_callback);
305}
306
307void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback(
308    int sequence_number) {
309  base::Closure callback;
310  MessageLoop* thread = NULL;
311  {
312    base::AutoLock lock(lock_);
313    if (sequence_number != async_sequence_number_)
314      return;
315    callback = callback_;
316    thread = callback_thread_;
317    callback_.Reset();
318    callback_thread_ = NULL;
319  }
320  InternalPostTask(thread, callback);
321}
322
323void HistogramSynchronizer::InternalPostTask(MessageLoop* thread,
324                                             const base::Closure& callback) {
325  if (callback.is_null() || !thread)
326    return;
327  thread->PostTask(FROM_HERE, callback);
328}
329
330int HistogramSynchronizer::GetNextAvailableSequenceNumber(
331    ProcessHistogramRequester requester) {
332  base::AutoLock auto_lock(lock_);
333  ++last_used_sequence_number_;
334  // Watch out for wrapping to a negative number.
335  if (last_used_sequence_number_ < 0) {
336    // Bypass the reserved number, which is used when a renderer spontaneously
337    // decides to send some histogram data.
338    last_used_sequence_number_ =
339        kHistogramSynchronizerReservedSequenceNumber + 1;
340  }
341  DCHECK_NE(last_used_sequence_number_,
342            kHistogramSynchronizerReservedSequenceNumber);
343  if (requester == ASYNC_HISTOGRAMS)
344    async_sequence_number_ = last_used_sequence_number_;
345  return last_used_sequence_number_;
346}
347
348}  // namespace content
349