// 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(&registrar_, 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
}