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
16const int ThreadWatcher::kPingCount = 3;
17
18// ThreadWatcher methods and members.
19ThreadWatcher::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
36ThreadWatcher::~ThreadWatcher() {}
37
38// static
39void 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
70void 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
80void ThreadWatcher::DeActivateThreadWatching() {
81  DCHECK(WatchDogThread::CurrentlyOnWatchDogThread());
82  active_ = false;
83  ping_count_ = 0;
84  method_factory_.RevokeAll();
85}
86
87void 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
101void 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
132void 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
158bool 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
169void 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
181void 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
191ThreadWatcherList* ThreadWatcherList::global_ = NULL;
192
193ThreadWatcherList::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
204ThreadWatcherList::~ThreadWatcherList() {
205  base::AutoLock auto_lock(lock_);
206  DCHECK(this == global_);
207  global_ = NULL;
208}
209
210// static
211void 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
220bool ThreadWatcherList::IsRegistered(const BrowserThread::ID thread_id) {
221  return NULL != ThreadWatcherList::Find(thread_id);
222}
223
224// static
225void 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
259void 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
276void 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
286void 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
296void 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
317void 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
329ThreadWatcher* 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
336ThreadWatcher* 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
347base::Lock WatchDogThread::lock_;
348// static
349WatchDogThread* WatchDogThread::watchdog_thread_ = NULL;
350
351// The WatchDogThread object must outlive any tasks posted to the IO thread
352// before the Quit task.
353DISABLE_RUNNABLE_METHOD_REFCOUNT(WatchDogThread);
354
355WatchDogThread::WatchDogThread() : Thread("WATCHDOG") {
356}
357
358WatchDogThread::~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
365bool WatchDogThread::CurrentlyOnWatchDogThread() {
366  base::AutoLock lock(lock_);
367  return watchdog_thread_ &&
368    watchdog_thread_->message_loop() == MessageLoop::current();
369}
370
371// static
372bool WatchDogThread::PostTask(const tracked_objects::Location& from_here,
373                              Task* task) {
374  return PostTaskHelper(from_here, task, 0);
375}
376
377// static
378bool 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
385bool 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
404void 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
419void WatchDogThread::CleanUp() {
420  base::AutoLock lock(lock_);
421  watchdog_thread_ = NULL;
422}
423
424void 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