Home | History | Annotate | Download | only in metrics
      1 // Copyright (c) 2011 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 "base/threading/thread_restrictions.h"
      6 #include "build/build_config.h"
      7 #include "chrome/browser/metrics/metrics_service.h"
      8 #include "chrome/browser/metrics/thread_watcher.h"
      9 #include "content/common/notification_service.h"
     10 
     11 #if defined(OS_WIN)
     12 #include <Objbase.h>
     13 #endif
     14 
     15 // static
     16 const int ThreadWatcher::kPingCount = 3;
     17 
     18 // ThreadWatcher methods and members.
     19 ThreadWatcher::ThreadWatcher(const BrowserThread::ID& thread_id,
     20                              const std::string& thread_name,
     21                              const base::TimeDelta& sleep_time,
     22                              const base::TimeDelta& unresponsive_time)
     23     : thread_id_(thread_id),
     24       thread_name_(thread_name),
     25       sleep_time_(sleep_time),
     26       unresponsive_time_(unresponsive_time),
     27       ping_time_(base::TimeTicks::Now()),
     28       ping_sequence_number_(0),
     29       active_(false),
     30       ping_count_(kPingCount),
     31       histogram_(NULL),
     32       ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
     33   Initialize();
     34 }
     35 
     36 ThreadWatcher::~ThreadWatcher() {}
     37 
     38 // static
     39 void ThreadWatcher::StartWatching(const BrowserThread::ID& thread_id,
     40                                   const std::string& thread_name,
     41                                   const base::TimeDelta& sleep_time,
     42                                   const base::TimeDelta& unresponsive_time) {
     43   DCHECK_GE(sleep_time.InMilliseconds(), 0);
     44   DCHECK_GE(unresponsive_time.InMilliseconds(), sleep_time.InMilliseconds());
     45 
     46   // If we are not on WatchDogThread, then post a task to call StartWatching on
     47   // WatchDogThread.
     48   if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
     49     WatchDogThread::PostTask(
     50         FROM_HERE,
     51         NewRunnableFunction(
     52             &ThreadWatcher::StartWatching,
     53             thread_id, thread_name, sleep_time, unresponsive_time));
     54     return;
     55   }
     56 
     57   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
     58 
     59   // Create a new thread watcher object for the given thread and activate it.
     60   ThreadWatcher* watcher =
     61       new ThreadWatcher(thread_id, thread_name, sleep_time, unresponsive_time);
     62   DCHECK(watcher);
     63   // If we couldn't register the thread watcher object, we are shutting down,
     64   // then don't activate thread watching.
     65   if (!ThreadWatcherList::IsRegistered(thread_id))
     66     return;
     67   watcher->ActivateThreadWatching();
     68 }
     69 
     70 void ThreadWatcher::ActivateThreadWatching() {
     71   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
     72   if (active_) return;
     73   active_ = true;
     74   ping_count_ = kPingCount;
     75   MessageLoop::current()->PostTask(
     76       FROM_HERE,
     77       method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage));
     78 }
     79 
     80 void ThreadWatcher::DeActivateThreadWatching() {
     81   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
     82   active_ = false;
     83   ping_count_ = 0;
     84   method_factory_.RevokeAll();
     85 }
     86 
     87 void ThreadWatcher::WakeUp() {
     88   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
     89   // There is some user activity, PostPingMessage task of thread watcher if
     90   // needed.
     91   if (!active_) return;
     92 
     93   if (ping_count_ <= 0) {
     94     ping_count_ = kPingCount;
     95     PostPingMessage();
     96   } else {
     97     ping_count_ = kPingCount;
     98   }
     99 }
    100 
    101 void ThreadWatcher::PostPingMessage() {
    102   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    103   // If we have stopped watching or if the user is idle, then stop sending
    104   // ping messages.
    105   if (!active_ || ping_count_ <= 0)
    106     return;
    107 
    108   // Save the current time when we have sent ping message.
    109   ping_time_ = base::TimeTicks::Now();
    110 
    111   // Send a ping message to the watched thread.
    112   Task* callback_task = method_factory_.NewRunnableMethod(
    113       &ThreadWatcher::OnPongMessage, ping_sequence_number_);
    114   if (BrowserThread::PostTask(
    115           thread_id(),
    116           FROM_HERE,
    117           NewRunnableFunction(
    118               &ThreadWatcher::OnPingMessage, thread_id_, callback_task))) {
    119       // Post a task to check the responsiveness of watched thread.
    120       MessageLoop::current()->PostDelayedTask(
    121           FROM_HERE,
    122           method_factory_.NewRunnableMethod(
    123               &ThreadWatcher::OnCheckResponsiveness, ping_sequence_number_),
    124           unresponsive_time_.InMilliseconds());
    125   } else {
    126     // Watched thread might have gone away, stop watching it.
    127     delete callback_task;
    128     DeActivateThreadWatching();
    129   }
    130 }
    131 
    132 void ThreadWatcher::OnPongMessage(uint64 ping_sequence_number) {
    133   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    134   // Record watched thread's response time.
    135   base::TimeDelta response_time = base::TimeTicks::Now() - ping_time_;
    136   histogram_->AddTime(response_time);
    137 
    138   // Check if there are any extra pings in flight.
    139   DCHECK_EQ(ping_sequence_number_, ping_sequence_number);
    140   if (ping_sequence_number_ != ping_sequence_number)
    141     return;
    142 
    143   // Increment sequence number for the next ping message to indicate watched
    144   // thread is responsive.
    145   ++ping_sequence_number_;
    146 
    147   // If we have stopped watching or if the user is idle, then stop sending
    148   // ping messages.
    149   if (!active_ || --ping_count_ <= 0)
    150     return;
    151 
    152   MessageLoop::current()->PostDelayedTask(
    153       FROM_HERE,
    154       method_factory_.NewRunnableMethod(&ThreadWatcher::PostPingMessage),
    155       sleep_time_.InMilliseconds());
    156 }
    157 
    158 bool ThreadWatcher::OnCheckResponsiveness(uint64 ping_sequence_number) {
    159   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    160   // If we have stopped watching then consider thread as responding.
    161   if (!active_)
    162     return true;
    163   // If the latest ping_sequence_number_ is not same as the ping_sequence_number
    164   // that is passed in, then we can assume OnPongMessage was called.
    165   // OnPongMessage increments ping_sequence_number_.
    166   return ping_sequence_number_ != ping_sequence_number;
    167 }
    168 
    169 void ThreadWatcher::Initialize() {
    170   ThreadWatcherList::Register(this);
    171   const std::string histogram_name =
    172       "ThreadWatcher.ResponseTime." + thread_name_;
    173   histogram_ = base::Histogram::FactoryTimeGet(
    174       histogram_name,
    175       base::TimeDelta::FromMilliseconds(1),
    176       base::TimeDelta::FromSeconds(100), 50,
    177       base::Histogram::kUmaTargetedHistogramFlag);
    178 }
    179 
    180 // static
    181 void ThreadWatcher::OnPingMessage(const BrowserThread::ID& thread_id,
    182                                   Task* callback_task) {
    183   // This method is called on watched thread.
    184   DCHECK(BrowserThread::CurrentlyOn(thread_id));
    185   WatchDogThread::PostTask(FROM_HERE, callback_task);
    186 }
    187 
    188 // ThreadWatcherList methods and members.
    189 //
    190 // static
    191 ThreadWatcherList* ThreadWatcherList::global_ = NULL;
    192 
    193 ThreadWatcherList::ThreadWatcherList()
    194     : last_wakeup_time_(base::TimeTicks::Now()) {
    195   // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
    196   // are on UI thread, but Unit tests are not running on UI thread.
    197   DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
    198   CHECK(!global_);
    199   global_ = this;
    200   // Register Notifications observer.
    201   MetricsService::SetUpNotifications(&registrar_, this);
    202 }
    203 
    204 ThreadWatcherList::~ThreadWatcherList() {
    205   base::AutoLock auto_lock(lock_);
    206   DCHECK(this == global_);
    207   global_ = NULL;
    208 }
    209 
    210 // static
    211 void ThreadWatcherList::Register(ThreadWatcher* watcher) {
    212   if (!global_)
    213     return;
    214   base::AutoLock auto_lock(global_->lock_);
    215   DCHECK(!global_->PreLockedFind(watcher->thread_id()));
    216   global_->registered_[watcher->thread_id()] = watcher;
    217 }
    218 
    219 // static
    220 bool ThreadWatcherList::IsRegistered(const BrowserThread::ID thread_id) {
    221   return NULL != ThreadWatcherList::Find(thread_id);
    222 }
    223 
    224 // static
    225 void ThreadWatcherList::StartWatchingAll() {
    226   if (!WatchDogThread::CurrentlyOnWatchDogThread()) {
    227     WatchDogThread::PostDelayedTask(
    228         FROM_HERE,
    229         NewRunnableFunction(&ThreadWatcherList::StartWatchingAll),
    230         base::TimeDelta::FromSeconds(5).InMilliseconds());
    231     return;
    232   }
    233   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    234   const base::TimeDelta kSleepTime = base::TimeDelta::FromSeconds(5);
    235   const base::TimeDelta kUnresponsiveTime = base::TimeDelta::FromSeconds(10);
    236   if (BrowserThread::IsMessageLoopValid(BrowserThread::UI)) {
    237     ThreadWatcher::StartWatching(BrowserThread::UI, "UI", kSleepTime,
    238                                  kUnresponsiveTime);
    239   }
    240   if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
    241     ThreadWatcher::StartWatching(BrowserThread::IO, "IO", kSleepTime,
    242                                  kUnresponsiveTime);
    243   }
    244   if (BrowserThread::IsMessageLoopValid(BrowserThread::DB)) {
    245     ThreadWatcher::StartWatching(BrowserThread::DB, "DB", kSleepTime,
    246                                  kUnresponsiveTime);
    247   }
    248   if (BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
    249     ThreadWatcher::StartWatching(BrowserThread::FILE, "FILE", kSleepTime,
    250                                  kUnresponsiveTime);
    251   }
    252   if (BrowserThread::IsMessageLoopValid(BrowserThread::CACHE)) {
    253     ThreadWatcher::StartWatching(BrowserThread::CACHE, "CACHE", kSleepTime,
    254                                  kUnresponsiveTime);
    255   }
    256 }
    257 
    258 // static
    259 void ThreadWatcherList::StopWatchingAll() {
    260   // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
    261   // are on UI thread, but Unit tests are not running on UI thread.
    262   DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
    263   if (!global_)
    264     return;
    265 
    266   // Remove all notifications for all watched threads.
    267   RemoveNotifications();
    268 
    269   // Delete all thread watcher objects on WatchDogThread.
    270   WatchDogThread::PostTask(
    271       FROM_HERE,
    272       NewRunnableMethod(global_, &ThreadWatcherList::DeleteAll));
    273 }
    274 
    275 // static
    276 void ThreadWatcherList::RemoveNotifications() {
    277   // Assert we are not running on WATCHDOG thread. Would be ideal to assert we
    278   // are on UI thread, but Unit tests are not running on UI thread.
    279   DCHECK(!WatchDogThread::CurrentlyOnWatchDogThread());
    280   if (!global_)
    281     return;
    282   base::AutoLock auto_lock(global_->lock_);
    283   global_->registrar_.RemoveAll();
    284 }
    285 
    286 void ThreadWatcherList::DeleteAll() {
    287   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    288   base::AutoLock auto_lock(lock_);
    289   while (!registered_.empty()) {
    290     RegistrationList::iterator it = registered_.begin();
    291     delete it->second;
    292     registered_.erase(it->first);
    293   }
    294 }
    295 
    296 void ThreadWatcherList::Observe(NotificationType type,
    297                                 const NotificationSource& source,
    298                                 const NotificationDetails& details) {
    299   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    300   // There is some user activity, see if thread watchers are to be awakened.
    301   bool need_to_awaken = false;
    302   base::TimeTicks now = base::TimeTicks::Now();
    303   {
    304     base::AutoLock lock(lock_);
    305     if (now - last_wakeup_time_ > base::TimeDelta::FromSeconds(2)) {
    306       need_to_awaken = true;
    307       last_wakeup_time_ = now;
    308     }
    309   }
    310   if (need_to_awaken) {
    311     WatchDogThread::PostTask(
    312         FROM_HERE,
    313         NewRunnableMethod(this, &ThreadWatcherList::WakeUpAll));
    314   }
    315 }
    316 
    317 void ThreadWatcherList::WakeUpAll() {
    318   DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
    319   if (!global_)
    320     return;
    321   base::AutoLock auto_lock(lock_);
    322   for (RegistrationList::iterator it = global_->registered_.begin();
    323        global_->registered_.end() != it;
    324        ++it)
    325     it->second->WakeUp();
    326 }
    327 
    328 // static
    329 ThreadWatcher* ThreadWatcherList::Find(const BrowserThread::ID& thread_id) {
    330   if (!global_)
    331     return NULL;
    332   base::AutoLock auto_lock(global_->lock_);
    333   return global_->PreLockedFind(thread_id);
    334 }
    335 
    336 ThreadWatcher* ThreadWatcherList::PreLockedFind(
    337     const BrowserThread::ID& thread_id) {
    338   RegistrationList::iterator it = registered_.find(thread_id);
    339   if (registered_.end() == it)
    340     return NULL;
    341   return it->second;
    342 }
    343 
    344 // WatchDogThread methods and members.
    345 //
    346 // static
    347 base::Lock WatchDogThread::lock_;
    348 // static
    349 WatchDogThread* WatchDogThread::watchdog_thread_ = NULL;
    350 
    351 // The WatchDogThread object must outlive any tasks posted to the IO thread
    352 // before the Quit task.
    353 DISABLE_RUNNABLE_METHOD_REFCOUNT(WatchDogThread);
    354 
    355 WatchDogThread::WatchDogThread() : Thread("WATCHDOG") {
    356 }
    357 
    358 WatchDogThread::~WatchDogThread() {
    359   // We cannot rely on our base class to stop the thread since we want our
    360   // CleanUp function to run.
    361   Stop();
    362 }
    363 
    364 // static
    365 bool WatchDogThread::CurrentlyOnWatchDogThread() {
    366   base::AutoLock lock(lock_);
    367   return watchdog_thread_ &&
    368     watchdog_thread_->message_loop() == MessageLoop::current();
    369 }
    370 
    371 // static
    372 bool WatchDogThread::PostTask(const tracked_objects::Location& from_here,
    373                               Task* task) {
    374   return PostTaskHelper(from_here, task, 0);
    375 }
    376 
    377 // static
    378 bool WatchDogThread::PostDelayedTask(const tracked_objects::Location& from_here,
    379                                      Task* task,
    380                                      int64 delay_ms) {
    381   return PostTaskHelper(from_here, task, delay_ms);
    382 }
    383 
    384 // static
    385 bool WatchDogThread::PostTaskHelper(
    386     const tracked_objects::Location& from_here,
    387     Task* task,
    388     int64 delay_ms) {
    389   {
    390     base::AutoLock lock(lock_);
    391 
    392     MessageLoop* message_loop = watchdog_thread_ ?
    393         watchdog_thread_->message_loop() : NULL;
    394     if (message_loop) {
    395       message_loop->PostDelayedTask(from_here, task, delay_ms);
    396       return true;
    397     }
    398   }
    399   delete task;
    400 
    401   return false;
    402 }
    403 
    404 void WatchDogThread::Init() {
    405   // This thread shouldn't be allowed to perform any blocking disk I/O.
    406   base::ThreadRestrictions::SetIOAllowed(false);
    407 
    408 #if defined(OS_WIN)
    409   // Initializes the COM library on the current thread.
    410   HRESULT result = CoInitialize(NULL);
    411   CHECK(result == S_OK);
    412 #endif
    413 
    414   base::AutoLock lock(lock_);
    415   CHECK(!watchdog_thread_);
    416   watchdog_thread_ = this;
    417 }
    418 
    419 void WatchDogThread::CleanUp() {
    420   base::AutoLock lock(lock_);
    421   watchdog_thread_ = NULL;
    422 }
    423 
    424 void WatchDogThread::CleanUpAfterMessageLoopDestruction() {
    425 #if defined(OS_WIN)
    426   // Closes the COM library on the current thread. CoInitialize must
    427   // be balanced by a corresponding call to CoUninitialize.
    428   CoUninitialize();
    429 #endif
    430 }
    431