// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/histogram_synchronizer.h"
#include "base/metrics/histogram.h"
#include "base/logging.h"
#include "base/threading/thread.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/render_messages.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/render_process_host.h"
using base::Time;
using base::TimeDelta;
using base::TimeTicks;
// Negative numbers are never used as sequence numbers. We explicitly pick a
// negative number that is "so negative" that even when we add one (as is done
// when we generated the next sequence number) that it will still be negative.
// We have code that handles wrapping around on an overflow into negative
// territory.
static const int kNeverUsableSequenceNumber = -2;
HistogramSynchronizer::HistogramSynchronizer()
: lock_(),
received_all_renderer_histograms_(&lock_),
callback_task_(NULL),
callback_thread_(NULL),
last_used_sequence_number_(kNeverUsableSequenceNumber),
async_sequence_number_(kNeverUsableSequenceNumber),
async_renderers_pending_(0),
synchronous_sequence_number_(kNeverUsableSequenceNumber),
synchronous_renderers_pending_(0) {
DCHECK(histogram_synchronizer_ == NULL);
histogram_synchronizer_ = this;
}
HistogramSynchronizer::~HistogramSynchronizer() {
// Just in case we have any pending tasks, clear them out.
SetCallbackTaskAndThread(NULL, NULL);
histogram_synchronizer_ = NULL;
}
// static
HistogramSynchronizer* HistogramSynchronizer::CurrentSynchronizer() {
DCHECK(histogram_synchronizer_ != NULL);
return histogram_synchronizer_;
}
void HistogramSynchronizer::FetchRendererHistogramsSynchronously(
TimeDelta wait_time) {
NotifyAllRenderers(SYNCHRONOUS_HISTOGRAMS);
TimeTicks start = TimeTicks::Now();
TimeTicks end_time = start + wait_time;
int unresponsive_renderer_count;
{
base::AutoLock auto_lock(lock_);
while (synchronous_renderers_pending_ > 0 && TimeTicks::Now() < end_time) {
wait_time = end_time - TimeTicks::Now();
received_all_renderer_histograms_.TimedWait(wait_time);
}
unresponsive_renderer_count = synchronous_renderers_pending_;
synchronous_renderers_pending_ = 0;
synchronous_sequence_number_ = kNeverUsableSequenceNumber;
}
UMA_HISTOGRAM_COUNTS("Histogram.RendersNotRespondingSynchronous",
unresponsive_renderer_count);
if (!unresponsive_renderer_count)
UMA_HISTOGRAM_TIMES("Histogram.FetchRendererHistogramsSynchronously",
TimeTicks::Now() - start);
}
// static
void HistogramSynchronizer::FetchRendererHistogramsAsynchronously(
MessageLoop* callback_thread,
Task* callback_task,
int wait_time) {
DCHECK(callback_thread != NULL);
DCHECK(callback_task != NULL);
HistogramSynchronizer* current_synchronizer = CurrentSynchronizer();
if (current_synchronizer == NULL) {
// System teardown is happening.
callback_thread->PostTask(FROM_HERE, callback_task);
return;
}
current_synchronizer->SetCallbackTaskAndThread(callback_thread,
callback_task);
int sequence_number =
current_synchronizer->NotifyAllRenderers(ASYNC_HISTOGRAMS);
// Post a task that would be called after waiting for wait_time. This acts
// as a watchdog, to ensure that a non-responsive renderer won't block us from
// making the callback.
BrowserThread::PostDelayedTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(
current_synchronizer,
&HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback,
sequence_number),
wait_time);
}
// static
void HistogramSynchronizer::DeserializeHistogramList(
int sequence_number,
const std::vector<std::string>& histograms) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (std::vector<std::string>::const_iterator it = histograms.begin();
it < histograms.end();
++it) {
base::Histogram::DeserializeHistogramInfo(*it);
}
HistogramSynchronizer* current_synchronizer = CurrentSynchronizer();
if (current_synchronizer == NULL)
return;
// Record that we have received a histogram from renderer process.
current_synchronizer->DecrementPendingRenderers(sequence_number);
}
int HistogramSynchronizer::NotifyAllRenderers(
RendererHistogramRequester requester) {
// To iterate over RenderProcessHosts, or to send messages to the hosts, we
// need to be on the UI thread.
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
int notification_count = 0;
for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
!it.IsAtEnd(); it.Advance())
++notification_count;
int sequence_number = GetNextAvailableSequenceNumber(requester,
notification_count);
for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
!it.IsAtEnd(); it.Advance()) {
if (!it.GetCurrentValue()->Send(
new ViewMsg_GetRendererHistograms(sequence_number)))
DecrementPendingRenderers(sequence_number);
}
return sequence_number;
}
void HistogramSynchronizer::DecrementPendingRenderers(int sequence_number) {
bool synchronous_completed = false;
bool asynchronous_completed = false;
{
base::AutoLock auto_lock(lock_);
if (sequence_number == async_sequence_number_) {
if (--async_renderers_pending_ <= 0)
asynchronous_completed = true;
} else if (sequence_number == synchronous_sequence_number_) {
if (--synchronous_renderers_pending_ <= 0)
synchronous_completed = true;
}
}
if (asynchronous_completed)
ForceHistogramSynchronizationDoneCallback(sequence_number);
else if (synchronous_completed)
received_all_renderer_histograms_.Signal();
}
void HistogramSynchronizer::SetCallbackTaskAndThread(
MessageLoop* callback_thread,
Task* callback_task) {
Task* old_task = NULL;
MessageLoop* old_thread = NULL;
TimeTicks old_start_time;
int unresponsive_renderers;
const TimeTicks now = TimeTicks::Now();
{
base::AutoLock auto_lock(lock_);
old_task = callback_task_;
callback_task_ = callback_task;
old_thread = callback_thread_;
callback_thread_ = callback_thread;
unresponsive_renderers = async_renderers_pending_;
old_start_time = async_callback_start_time_;
async_callback_start_time_ = now;
// Prevent premature calling of our new callbacks.
async_sequence_number_ = kNeverUsableSequenceNumber;
}
// Just in case there was a task pending....
InternalPostTask(old_thread, old_task, unresponsive_renderers,
old_start_time);
}
void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback(
int sequence_number) {
Task* task = NULL;
MessageLoop* thread = NULL;
TimeTicks started;
int unresponsive_renderers;
{
base::AutoLock lock(lock_);
if (sequence_number != async_sequence_number_)
return;
task = callback_task_;
thread = callback_thread_;
callback_task_ = NULL;
callback_thread_ = NULL;
started = async_callback_start_time_;
unresponsive_renderers = async_renderers_pending_;
}
InternalPostTask(thread, task, unresponsive_renderers, started);
}
void HistogramSynchronizer::InternalPostTask(MessageLoop* thread, Task* task,
int unresponsive_renderers,
const base::TimeTicks& started) {
if (!task || !thread)
return;
UMA_HISTOGRAM_COUNTS("Histogram.RendersNotRespondingAsynchronous",
unresponsive_renderers);
if (!unresponsive_renderers) {
UMA_HISTOGRAM_TIMES("Histogram.FetchRendererHistogramsAsynchronously",
TimeTicks::Now() - started);
}
thread->PostTask(FROM_HERE, task);
}
int HistogramSynchronizer::GetNextAvailableSequenceNumber(
RendererHistogramRequester requester,
int renderer_count) {
base::AutoLock auto_lock(lock_);
++last_used_sequence_number_;
// Watch out for wrapping to a negative number.
if (last_used_sequence_number_ < 0) {
// Bypass the reserved number, which is used when a renderer spontaneously
// decides to send some histogram data.
last_used_sequence_number_ =
chrome::kHistogramSynchronizerReservedSequenceNumber + 1;
}
DCHECK_NE(last_used_sequence_number_,
chrome::kHistogramSynchronizerReservedSequenceNumber);
if (requester == ASYNC_HISTOGRAMS) {
async_sequence_number_ = last_used_sequence_number_;
async_renderers_pending_ = renderer_count;
} else if (requester == SYNCHRONOUS_HISTOGRAMS) {
synchronous_sequence_number_ = last_used_sequence_number_;
synchronous_renderers_pending_ = renderer_count;
}
return last_used_sequence_number_;
}
// static
HistogramSynchronizer* HistogramSynchronizer::histogram_synchronizer_ = NULL;