// 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 }