root/chrome/browser/jankometer_win.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. alarm_count_
  2. Alarm
  3. SetDefaultMessagesToSkip
  4. total_time_watchdog_
  5. StartProcessingTimers
  6. EndProcessingTimers
  7. MessageWillBeMeasured
  8. AttachToCurrentThread
  9. DetachFromCurrentThread
  10. WillProcessIOEvent
  11. DidProcessIOEvent
  12. WillProcessTask
  13. DidProcessTask
  14. AttachToCurrentThread
  15. DetachFromCurrentThread
  16. WillProcessTask
  17. DidProcessTask
  18. WillProcessEvent
  19. DidProcessEvent
  20. InstallJankometer
  21. UninstallJankometer

// Copyright (c) 2012 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 "chrome/browser/jankometer.h"

#include <limits>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/metrics/stats_counters.h"
#include "base/pending_task.h"
#include "base/strings/string_util.h"
#include "base/threading/thread.h"
#include "base/threading/watchdog.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_thread.h"

using base::TimeDelta;
using base::TimeTicks;
using content::BrowserThread;

namespace {

// The maximum threshold of delay of the message  before considering it a delay.
// For a debug build, you may want to set IO delay around 500ms.
// For a release build, setting it around 350ms is sensible.
// Visit about:histograms to see what the distribution is on your system, with
// your build. Be sure to do some work to get interesting stats.
// The numbers below came from a warm start (you'll get about 5-10 alarms with
// a cold start), and running the page-cycler for 5 rounds.
#ifdef NDEBUG
const int kMaxUIMessageDelayMs = 350;
const int kMaxIOMessageDelayMs = 200;
#else
const int kMaxUIMessageDelayMs = 500;
const int kMaxIOMessageDelayMs = 400;
#endif

// Maximum processing time (excluding queueing delay) for a message before
// considering it delayed.
const int kMaxMessageProcessingMs = 100;

// TODO(brettw) Consider making this a pref.
const bool kPlaySounds = false;

//------------------------------------------------------------------------------
// Provide a special watchdog to make it easy to set the breakpoint on this
// class only.
class JankWatchdog : public base::Watchdog {
 public:
  JankWatchdog(const TimeDelta& duration,
               const std::string& thread_watched_name,
               bool enabled)
      : Watchdog(duration, thread_watched_name, enabled),
        thread_name_watched_(thread_watched_name),
        alarm_count_(0) {
  }

  virtual ~JankWatchdog() {}

  virtual void Alarm() OVERRIDE {
    // Put break point here if you want to stop threads and look at what caused
    // the jankiness.
    alarm_count_++;
    Watchdog::Alarm();
  }

 private:
  std::string thread_name_watched_;
  int alarm_count_;

  DISALLOW_COPY_AND_ASSIGN(JankWatchdog);
};

class JankObserverHelper {
 public:
  JankObserverHelper(const std::string& thread_name,
                     const TimeDelta& excessive_duration,
                     bool watchdog_enable);
  ~JankObserverHelper();

  void StartProcessingTimers(const TimeDelta& queueing_time);
  void EndProcessingTimers();

  // Indicate if we will bother to measuer this message.
  bool MessageWillBeMeasured();

  static void SetDefaultMessagesToSkip(int count) { discard_count_ = count; }

 private:
  const TimeDelta max_message_delay_;

  // Indicate if we'll bother measuring this message.
  bool measure_current_message_;

  // Down counter which will periodically hit 0, and only then bother to measure
  // the corresponding message.
  int events_till_measurement_;

  // The value to re-initialize events_till_measurement_ after it reaches 0.
  static int discard_count_;

  // Time at which the current message processing began.
  TimeTicks begin_process_message_;

  // Time the current message spent in the queue -- delta between message
  // construction time and message processing time.
  TimeDelta queueing_time_;

  // Counters for the two types of jank we measure.
  base::StatsCounter slow_processing_counter_;  // Msgs w/ long proc time.
  base::StatsCounter queueing_delay_counter_;   // Msgs w/ long queueing delay.
  base::HistogramBase* const process_times_;  // Time spent proc. task.
  base::HistogramBase* const total_times_;  // Total queueing plus proc.
  JankWatchdog total_time_watchdog_;  // Watching for excessive total_time.

  DISALLOW_COPY_AND_ASSIGN(JankObserverHelper);
};

JankObserverHelper::JankObserverHelper(
    const std::string& thread_name,
    const TimeDelta& excessive_duration,
    bool watchdog_enable)
    : max_message_delay_(excessive_duration),
      measure_current_message_(true),
      events_till_measurement_(0),
      slow_processing_counter_(std::string("Chrome.SlowMsg") + thread_name),
      queueing_delay_counter_(std::string("Chrome.DelayMsg") + thread_name),
      process_times_(base::Histogram::FactoryGet(
          std::string("Chrome.ProcMsgL ") + thread_name,
          1, 3600000, 50, base::Histogram::kUmaTargetedHistogramFlag)),
      total_times_(base::Histogram::FactoryGet(
          std::string("Chrome.TotalMsgL ") + thread_name,
          1, 3600000, 50, base::Histogram::kUmaTargetedHistogramFlag)),
      total_time_watchdog_(excessive_duration, thread_name, watchdog_enable) {
  if (discard_count_ > 0) {
    // Select a vaguely random sample-start-point.
    events_till_measurement_ = static_cast<int>(
        (TimeTicks::Now() - TimeTicks()).InSeconds() % (discard_count_ + 1));
  }
}

JankObserverHelper::~JankObserverHelper() {}

// Called when a message has just begun processing, initializes
// per-message variables and timers.
void JankObserverHelper::StartProcessingTimers(const TimeDelta& queueing_time) {
  DCHECK(measure_current_message_);
  begin_process_message_ = TimeTicks::Now();
  queueing_time_ = queueing_time;

  // Simulate arming when the message entered the queue.
  total_time_watchdog_.ArmSomeTimeDeltaAgo(queueing_time_);
  if (queueing_time_ > max_message_delay_) {
    // Message is too delayed.
    queueing_delay_counter_.Increment();
    if (kPlaySounds)
      MessageBeep(MB_ICONASTERISK);
  }
}

// Called when a message has just finished processing, finalizes
// per-message variables and timers.
void JankObserverHelper::EndProcessingTimers() {
  if (!measure_current_message_)
    return;
  total_time_watchdog_.Disarm();
  TimeTicks now = TimeTicks::Now();
  if (begin_process_message_ != TimeTicks()) {
    TimeDelta processing_time = now - begin_process_message_;
    process_times_->AddTime(processing_time);
    total_times_->AddTime(queueing_time_ + processing_time);
  }
  if (now - begin_process_message_ >
      TimeDelta::FromMilliseconds(kMaxMessageProcessingMs)) {
    // Message took too long to process.
    slow_processing_counter_.Increment();
    if (kPlaySounds)
      MessageBeep(MB_ICONHAND);
  }

  // Reset message specific times.
  begin_process_message_ = base::TimeTicks();
  queueing_time_ = base::TimeDelta();
}

bool JankObserverHelper::MessageWillBeMeasured() {
  measure_current_message_ = events_till_measurement_ <= 0;
  if (!measure_current_message_)
    --events_till_measurement_;
  else
    events_till_measurement_ = discard_count_;
  return measure_current_message_;
}

// static
int JankObserverHelper::discard_count_ = 99;  // Measure only 1 in 100.

//------------------------------------------------------------------------------
class IOJankObserver : public base::RefCountedThreadSafe<IOJankObserver>,
                       public base::MessageLoopForIO::IOObserver,
                       public base::MessageLoop::TaskObserver {
 public:
  IOJankObserver(const char* thread_name,
                 TimeDelta excessive_duration,
                 bool watchdog_enable)
      : helper_(thread_name, excessive_duration, watchdog_enable) {}

  // Attaches the observer to the current thread's message loop. You can only
  // attach to the current thread, so this function can be invoked on another
  // thread to attach it.
  void AttachToCurrentThread() {
    base::MessageLoop::current()->AddTaskObserver(this);
    base::MessageLoopForIO::current()->AddIOObserver(this);
  }

  // Detaches the observer to the current thread's message loop.
  void DetachFromCurrentThread() {
    base::MessageLoopForIO::current()->RemoveIOObserver(this);
    base::MessageLoop::current()->RemoveTaskObserver(this);
  }

  virtual void WillProcessIOEvent() OVERRIDE {
    if (!helper_.MessageWillBeMeasured())
      return;
    helper_.StartProcessingTimers(base::TimeDelta());
  }

  virtual void DidProcessIOEvent() OVERRIDE {
    helper_.EndProcessingTimers();
  }

  virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE {
    if (!helper_.MessageWillBeMeasured())
      return;
    base::TimeTicks now = base::TimeTicks::Now();
    const base::TimeDelta queueing_time = now - pending_task.time_posted;
    helper_.StartProcessingTimers(queueing_time);
  }

  virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {
    helper_.EndProcessingTimers();
  }

 private:
  friend class base::RefCountedThreadSafe<IOJankObserver>;

  virtual ~IOJankObserver() {}

  JankObserverHelper helper_;

  DISALLOW_COPY_AND_ASSIGN(IOJankObserver);
};

//------------------------------------------------------------------------------
class UIJankObserver : public base::RefCountedThreadSafe<UIJankObserver>,
                       public base::MessageLoop::TaskObserver,
                       public base::MessageLoopForUI::Observer {
 public:
  UIJankObserver(const char* thread_name,
                 TimeDelta excessive_duration,
                 bool watchdog_enable)
      : helper_(thread_name, excessive_duration, watchdog_enable) {}

  // Attaches the observer to the current thread's message loop. You can only
  // attach to the current thread, so this function can be invoked on another
  // thread to attach it.
  void AttachToCurrentThread() {
    DCHECK(base::MessageLoopForUI::IsCurrent());
    base::MessageLoopForUI::current()->AddObserver(this);
    base::MessageLoop::current()->AddTaskObserver(this);
  }

  // Detaches the observer to the current thread's message loop.
  void DetachFromCurrentThread() {
    DCHECK(base::MessageLoopForUI::IsCurrent());
    base::MessageLoop::current()->RemoveTaskObserver(this);
    base::MessageLoopForUI::current()->RemoveObserver(this);
  }

  virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE {
    if (!helper_.MessageWillBeMeasured())
      return;
    base::TimeTicks now = base::TimeTicks::Now();
    const base::TimeDelta queueing_time = now - pending_task.time_posted;
    helper_.StartProcessingTimers(queueing_time);
  }

  virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {
    helper_.EndProcessingTimers();
  }

  virtual base::EventStatus WillProcessEvent(
      const base::NativeEvent& event) OVERRIDE {
    if (!helper_.MessageWillBeMeasured())
      return base::EVENT_CONTINUE;
    // GetMessageTime returns a LONG (signed 32-bit) and GetTickCount returns
    // a DWORD (unsigned 32-bit). They both wrap around when the time is longer
    // than they can hold. I'm not sure if GetMessageTime wraps around to 0,
    // or if the original time comes from GetTickCount, it might wrap around
    // to -1.
    //
    // Therefore, I cast to DWORD so if it wraps to -1 we will correct it. If
    // it doesn't, then our time delta will be negative if a message happens
    // to straddle the wraparound point, it will still be OK.
    DWORD cur_message_issue_time = static_cast<DWORD>(event.time);
    DWORD cur_time = GetTickCount();
    base::TimeDelta queueing_time =
        base::TimeDelta::FromMilliseconds(cur_time - cur_message_issue_time);

    helper_.StartProcessingTimers(queueing_time);
    return base::EVENT_CONTINUE;
  }

  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE {
    helper_.EndProcessingTimers();
  }

 private:
  friend class base::RefCountedThreadSafe<UIJankObserver>;

  virtual ~UIJankObserver() {}

  JankObserverHelper helper_;

  DISALLOW_COPY_AND_ASSIGN(UIJankObserver);
};

// These objects are created by InstallJankometer and leaked.
const scoped_refptr<UIJankObserver>* ui_observer = NULL;
const scoped_refptr<IOJankObserver>* io_observer = NULL;

}  // namespace

void InstallJankometer(const CommandLine& parsed_command_line) {
  if (ui_observer || io_observer) {
    NOTREACHED() << "Initializing jank-o-meter twice";
    return;
  }

  bool ui_watchdog_enabled = false;
  bool io_watchdog_enabled = false;
  if (parsed_command_line.HasSwitch(switches::kEnableWatchdog)) {
    std::string list =
        parsed_command_line.GetSwitchValueASCII(switches::kEnableWatchdog);
    if (list.npos != list.find("ui"))
      ui_watchdog_enabled = true;
    if (list.npos != list.find("io"))
      io_watchdog_enabled = true;
  }

  if (ui_watchdog_enabled || io_watchdog_enabled)
    JankObserverHelper::SetDefaultMessagesToSkip(0);  // Watch everything.

  // Install on the UI thread.
  ui_observer = new scoped_refptr<UIJankObserver>(
      new UIJankObserver(
          "UI",
          TimeDelta::FromMilliseconds(kMaxUIMessageDelayMs),
          ui_watchdog_enabled));
  (*ui_observer)->AttachToCurrentThread();

  // Now install on the I/O thread. Hiccups on that thread will block
  // interaction with web pages. We must proxy to that thread before we can
  // add our observer.
  io_observer = new scoped_refptr<IOJankObserver>(
      new IOJankObserver(
          "IO",
          TimeDelta::FromMilliseconds(kMaxIOMessageDelayMs),
          io_watchdog_enabled));
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&IOJankObserver::AttachToCurrentThread, io_observer->get()));
}

void UninstallJankometer() {
  if (ui_observer) {
    (*ui_observer)->DetachFromCurrentThread();
    delete ui_observer;
    ui_observer = NULL;
  }
  if (io_observer) {
    // IO thread can't be running when we remove observers.
    DCHECK((!g_browser_process) || !(g_browser_process->io_thread()));
    delete io_observer;
    io_observer = NULL;
  }
}

/* [<][>][^][v][top][bottom][index][help] */