// Copyright (c) 2011 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 "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/metrics/metrics_service.h"
#include "chrome/browser/metrics/thread_watcher.h"
#include "content/common/notification_service.h"
#if defined(OS_WIN)
#include <Objbase.h>
#endif
// static
const int ThreadWatcher::kPingCount = 3;
// ThreadWatcher methods and members.
ThreadWatcher::ThreadWatcher(const BrowserThread::ID& thread_id,
const std::string& thread_name,
const base::TimeDelta& sleep_time,
const base::TimeDelta& unresponsive_time)
: thread_id_(thread_id),
thread_name_(thread_name),
sleep_time_(sleep_time),
unresponsive_time_(unresponsive_time),
ping_time_(base::TimeTicks::Now()),
ping_sequence_number_(0),
active_(false),
ping_count_(kPingCount),
histogram_(NULL),
ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
Initialize();
}
ThreadWatcher::~ThreadWatcher() {}
// static
void ThreadWatcher::StartWatching(const BrowserThread::ID& thread_id,
const std::string& thread_name,
const base::TimeDelta& sleep_time,
const base::TimeDelta& unresponsive_time) {
DCHECK_GE(sleep_time.InMilliseconds(), 0);
DCHECK_GE(unresponsive_time.InMilliseconds(), sleep_time.InMilliseconds());
// If we are not on WatchDogThread, then post a task to call StartWatching on
// WatchDogThread.
if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
WatchDogThread::PostTask(
FROM_HERE,
NewRunnableFunction(
&ThreadWatcher::StartWatching,
thread_id, thread_name, sleep_time, unresponsive_time));
return;
}
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// Create a new thread watcher object for the given thread and activate it.
ThreadWatcher* watcher =
new ThreadWatcher(thread_id, thread_name, sleep_time, unresponsive_time);
DCHECK(watcher);
// If we couldn't register the thread watcher object, we are shutting down,
// then don't activate thread watching.
if (!ThreadWatcherList::IsRegistered(thread_id))
return;
watcher->ActivateThreadWatching();
}
void ThreadWatcher::ActivateThreadWatching() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
if (active_) return;
active_ = true;
ping_count_ = kPingCount;
MessageLoop::current()->PostTask(
FROM_HERE,
method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage));
}
void ThreadWatcher::DeActivateThreadWatching() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
active_ = false;
ping_count_ = 0;
method_factory_.RevokeAll();
}
void ThreadWatcher::WakeUp() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// There is some user activity, PostPingMessage task of thread watcher if
// needed.
if (!active_) return;
if (ping_count_ <= 0) {
ping_count_ = kPingCount;
PostPingMessage();
} else {
ping_count_ = kPingCount;
}
}
void ThreadWatcher::PostPingMessage() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// If we have stopped watching or if the user is idle, then stop sending
// ping messages.
if (!active_ || ping_count_ <= 0)
return;
// Save the current time when we have sent ping message.
ping_time_ = base::TimeTicks::Now();
// Send a ping message to the watched thread.
Task* callback_task = method_factory_.NewRunnableMethod(
&ThreadWatcher::OnPongMessage, ping_sequence_number_);
if (BrowserThread::PostTask(
thread_id(),
FROM_HERE,
NewRunnableFunction(
&ThreadWatcher::OnPingMessage, thread_id_, callback_task))) {
// Post a task to check the responsiveness of watched thread.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&ThreadWatcher::OnCheckResponsiveness, ping_sequence_number_),
unresponsive_time_.InMilliseconds());
} else {
// Watched thread might have gone away, stop watching it.
delete callback_task;
DeActivateThreadWatching();
}
}
void ThreadWatcher::OnPongMessage(uint64 ping_sequence_number) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// Record watched thread's response time.
base::TimeDelta response_time = base::TimeTicks::Now() - ping_time_;
histogram_->AddTime(response_time);
// Check if there are any extra pings in flight.
DCHECK_EQ(ping_sequence_number_, ping_sequence_number);
if (ping_sequence_number_ != ping_sequence_number)
return;
// Increment sequence number for the next ping message to indicate watched
// thread is responsive.
++ping_sequence_number_;
// If we have stopped watching or if the user is idle, then stop sending
// ping messages.
if (!active_ || --ping_count_ <= 0)
return;
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage),
sleep_time_.InMilliseconds());
}
bool ThreadWatcher::OnCheckResponsiveness(uint64 ping_sequence_number) {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
// If we have stopped watching then consider thread as responding.
if (!active_)
return true;
// If the latest ping_sequence_number_ is not same as the ping_sequence_number
// that is passed in, then we can assume OnPongMessage was called.
// OnPongMessage increments ping_sequence_number_.
return ping_sequence_number_ != ping_sequence_number;
}
void ThreadWatcher::Initialize() {
ThreadWatcherList::Register(this);
const std::string histogram_name =
"ThreadWatcher.ResponseTime." + thread_name_;
histogram_ = base::Histogram::FactoryTimeGet(
histogram_name,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromSeconds(100), 50,
base::Histogram::kUmaTargetedHistogramFlag);
}
// static
void ThreadWatcher::OnPingMessage(const BrowserThread::ID& thread_id,
Task* callback_task) {
// This method is called on watched thread.
DCHECK(BrowserThread::CurrentlyOn(thread_id));
WatchDogThread::PostTask(FROM_HERE, callback_task);
}
// ThreadWatcherList methods and members.
//
// static
ThreadWatcherList* ThreadWatcherList::global_ = NULL;
ThreadWatcherList::ThreadWatcherList()
: last_wakeup_time_(base::TimeTicks::Now()) {
// Assert we are not running on WATCHDOG thread. Would be ideal to assert we
// are on UI thread, but Unit tests are not running on UI thread.
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
CHECK(!global_);
global_ = this;
// Register Notifications observer.
MetricsService::SetUpNotifications(®istrar_, this);
}
ThreadWatcherList::~ThreadWatcherList() {
base::AutoLock auto_lock(lock_);
DCHECK(this == global_);
global_ = NULL;
}
// static
void ThreadWatcherList::Register(ThreadWatcher* watcher) {
if (!global_)
return;
base::AutoLock auto_lock(global_->lock_);
DCHECK(!global_->PreLockedFind(watcher->thread_id()));
global_->registered_[watcher->thread_id()] = watcher;
}
// static
bool ThreadWatcherList::IsRegistered(const BrowserThread::ID thread_id) {
return NULL != ThreadWatcherList::Find(thread_id);
}
// static
void ThreadWatcherList::StartWatchingAll() {
if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
WatchDogThread::PostDelayedTask(
FROM_HERE,
NewRunnableFunction(&ThreadWatcherList::StartWatchingAll),
base::TimeDelta::FromSeconds(5).InMilliseconds());
return;
}
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
const base::TimeDelta kSleepTime = base::TimeDelta::FromSeconds(5);
const base::TimeDelta kUnresponsiveTime = base::TimeDelta::FromSeconds(10);
if (BrowserThread::IsMessageLoopValid(BrowserThread::UI)) {
ThreadWatcher::StartWatching(BrowserThread::UI, "UI", kSleepTime,
kUnresponsiveTime);
}
if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
ThreadWatcher::StartWatching(BrowserThread::IO, "IO", kSleepTime,
kUnresponsiveTime);
}
if (BrowserThread::IsMessageLoopValid(BrowserThread::DB)) {
ThreadWatcher::StartWatching(BrowserThread::DB, "DB", kSleepTime,
kUnresponsiveTime);
}
if (BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
ThreadWatcher::StartWatching(BrowserThread::FILE, "FILE", kSleepTime,
kUnresponsiveTime);
}
if (BrowserThread::IsMessageLoopValid(BrowserThread::CACHE)) {
ThreadWatcher::StartWatching(BrowserThread::CACHE, "CACHE", kSleepTime,
kUnresponsiveTime);
}
}
// static
void ThreadWatcherList::StopWatchingAll() {
// Assert we are not running on WATCHDOG thread. Would be ideal to assert we
// are on UI thread, but Unit tests are not running on UI thread.
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
if (!global_)
return;
// Remove all notifications for all watched threads.
RemoveNotifications();
// Delete all thread watcher objects on WatchDogThread.
WatchDogThread::PostTask(
FROM_HERE,
NewRunnableMethod(global_, &ThreadWatcherList::DeleteAll));
}
// static
void ThreadWatcherList::RemoveNotifications() {
// Assert we are not running on WATCHDOG thread. Would be ideal to assert we
// are on UI thread, but Unit tests are not running on UI thread.
DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
if (!global_)
return;
base::AutoLock auto_lock(global_->lock_);
global_->registrar_.RemoveAll();
}
void ThreadWatcherList::DeleteAll() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
base::AutoLock auto_lock(lock_);
while (!registered_.empty()) {
RegistrationList::iterator it = registered_.begin();
delete it->second;
registered_.erase(it->first);
}
}
void ThreadWatcherList::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// There is some user activity, see if thread watchers are to be awakened.
bool need_to_awaken = false;
base::TimeTicks now = base::TimeTicks::Now();
{
base::AutoLock lock(lock_);
if (now - last_wakeup_time_ > base::TimeDelta::FromSeconds(2)) {
need_to_awaken = true;
last_wakeup_time_ = now;
}
}
if (need_to_awaken) {
WatchDogThread::PostTask(
FROM_HERE,
NewRunnableMethod(this, &ThreadWatcherList::WakeUpAll));
}
}
void ThreadWatcherList::WakeUpAll() {
DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
if (!global_)
return;
base::AutoLock auto_lock(lock_);
for (RegistrationList::iterator it = global_->registered_.begin();
global_->registered_.end() != it;
++it)
it->second->WakeUp();
}
// static
ThreadWatcher* ThreadWatcherList::Find(const BrowserThread::ID& thread_id) {
if (!global_)
return NULL;
base::AutoLock auto_lock(global_->lock_);
return global_->PreLockedFind(thread_id);
}
ThreadWatcher* ThreadWatcherList::PreLockedFind(
const BrowserThread::ID& thread_id) {
RegistrationList::iterator it = registered_.find(thread_id);
if (registered_.end() == it)
return NULL;
return it->second;
}
// WatchDogThread methods and members.
//
// static
base::Lock WatchDogThread::lock_;
// static
WatchDogThread* WatchDogThread::watchdog_thread_ = NULL;
// The WatchDogThread object must outlive any tasks posted to the IO thread
// before the Quit task.
DISABLE_RUNNABLE_METHOD_REFCOUNT(WatchDogThread);
WatchDogThread::WatchDogThread() : Thread("WATCHDOG") {
}
WatchDogThread::~WatchDogThread() {
// We cannot rely on our base class to stop the thread since we want our
// CleanUp function to run.
Stop();
}
// static
bool WatchDogThread::CurrentlyOnWatchDogThread() {
base::AutoLock lock(lock_);
return watchdog_thread_ &&
watchdog_thread_->message_loop() == MessageLoop::current();
}
// static
bool WatchDogThread::PostTask(const tracked_objects::Location& from_here,
Task* task) {
return PostTaskHelper(from_here, task, 0);
}
// static
bool WatchDogThread::PostDelayedTask(const tracked_objects::Location& from_here,
Task* task,
int64 delay_ms) {
return PostTaskHelper(from_here, task, delay_ms);
}
// static
bool WatchDogThread::PostTaskHelper(
const tracked_objects::Location& from_here,
Task* task,
int64 delay_ms) {
{
base::AutoLock lock(lock_);
MessageLoop* message_loop = watchdog_thread_ ?
watchdog_thread_->message_loop() : NULL;
if (message_loop) {
message_loop->PostDelayedTask(from_here, task, delay_ms);
return true;
}
}
delete task;
return false;
}
void WatchDogThread::Init() {
// This thread shouldn't be allowed to perform any blocking disk I/O.
base::ThreadRestrictions::SetIOAllowed(false);
#if defined(OS_WIN)
// Initializes the COM library on the current thread.
HRESULT result = CoInitialize(NULL);
CHECK(result == S_OK);
#endif
base::AutoLock lock(lock_);
CHECK(!watchdog_thread_);
watchdog_thread_ = this;
}
void WatchDogThread::CleanUp() {
base::AutoLock lock(lock_);
watchdog_thread_ = NULL;
}
void WatchDogThread::CleanUpAfterMessageLoopDestruction() {
#if defined(OS_WIN)
// Closes the COM library on the current thread. CoInitialize must
// be balanced by a corresponding call to CoUninitialize.
CoUninitialize();
#endif
}