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