diff options
Diffstat (limited to 'chrome/browser/jankometer.cc')
-rw-r--r-- | chrome/browser/jankometer.cc | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/chrome/browser/jankometer.cc b/chrome/browser/jankometer.cc new file mode 100644 index 0000000..d4a70a2 --- /dev/null +++ b/chrome/browser/jankometer.cc @@ -0,0 +1,367 @@ +// Copyright (c) 2010 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 <limits> + +#include "chrome/browser/jankometer.h" + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/histogram.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "base/time.h" +#include "base/watchdog.h" +#include "build/build_config.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/common/chrome_switches.h" + +#if defined(TOOLKIT_USES_GTK) +#include "chrome/browser/gtk/gtk_util.h" +#endif + +using base::TimeDelta; +using base::TimeTicks; + +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 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() { + // 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(); + + private: + const TimeDelta max_message_delay_; + + // 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. + StatsCounter slow_processing_counter_; // Messages with long processing time. + StatsCounter queueing_delay_counter_; // Messages with long queueing delay. + scoped_refptr<Histogram> process_times_; // Time spent processing task. + scoped_refptr<Histogram> total_times_; // Total queueing plus processing. + 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), + slow_processing_counter_(std::string("Chrome.SlowMsg") + thread_name), + queueing_delay_counter_(std::string("Chrome.DelayMsg") + thread_name), + total_time_watchdog_(excessive_duration, thread_name, watchdog_enable) { + process_times_ = Histogram::FactoryGet( + std::string("Chrome.ProcMsgL ") + thread_name, + 1, 3600000, 50, Histogram::kUmaTargetedHistogramFlag); + total_times_ = Histogram::FactoryGet( + std::string("Chrome.TotalMsgL ") + thread_name, + 1, 3600000, 50, Histogram::kUmaTargetedHistogramFlag); +} + +JankObserverHelper::~JankObserverHelper() {} + +// Called when a message has just begun processing, initializes +// per-message variables and timers. +void JankObserverHelper::StartProcessingTimers(const TimeDelta& queueing_time) { + 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 defined(OS_WIN) + if (kPlaySounds) + MessageBeep(MB_ICONASTERISK); +#endif + } +} + +// Called when a message has just finished processing, finalizes +// per-message variables and timers. +void JankObserverHelper::EndProcessingTimers() { + 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 defined(OS_WIN) + if (kPlaySounds) + MessageBeep(MB_ICONHAND); +#endif + } + + // Reset message specific times. + begin_process_message_ = base::TimeTicks(); + queueing_time_ = base::TimeDelta(); +} + +//------------------------------------------------------------------------------ +class IOJankObserver : public base::RefCountedThreadSafe<IOJankObserver>, + public MessageLoopForIO::IOObserver, + public MessageLoop::TaskObserver { + public: + IOJankObserver(const char* thread_name, + TimeDelta excessive_duration, + bool watchdog_enable) + : helper_(thread_name, excessive_duration, watchdog_enable) {} + + ~IOJankObserver() {} + + // 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() { + MessageLoop::current()->AddTaskObserver(this); + MessageLoopForIO::current()->AddIOObserver(this); + } + + // Detaches the observer to the current thread's message loop. + void DetachFromCurrentThread() { + MessageLoopForIO::current()->RemoveIOObserver(this); + MessageLoop::current()->RemoveTaskObserver(this); + } + + virtual void WillProcessIOEvent() { + helper_.StartProcessingTimers(base::TimeDelta()); + } + + virtual void DidProcessIOEvent() { + helper_.EndProcessingTimers(); + } + + virtual void WillProcessTask(base::TimeTicks birth_time) { + base::TimeTicks now = base::TimeTicks::Now(); + const base::TimeDelta queueing_time = now - birth_time; + helper_.StartProcessingTimers(queueing_time); + } + + virtual void DidProcessTask() { + helper_.EndProcessingTimers(); + } + + private: + friend class base::RefCountedThreadSafe<IOJankObserver>; + + JankObserverHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(IOJankObserver); +}; + +//------------------------------------------------------------------------------ +class UIJankObserver : public base::RefCountedThreadSafe<UIJankObserver>, + public MessageLoop::TaskObserver, + public 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_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + MessageLoopForUI::current()->AddObserver(this); + MessageLoop::current()->AddTaskObserver(this); + } + + // Detaches the observer to the current thread's message loop. + void DetachFromCurrentThread() { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + MessageLoop::current()->RemoveTaskObserver(this); + MessageLoopForUI::current()->RemoveObserver(this); + } + + virtual void WillProcessTask(base::TimeTicks birth_time) { + base::TimeTicks now = base::TimeTicks::Now(); + const base::TimeDelta queueing_time = now - birth_time; + helper_.StartProcessingTimers(queueing_time); + } + + virtual void DidProcessTask() { + helper_.EndProcessingTimers(); + } + +#if defined(OS_WIN) + virtual void WillProcessMessage(const MSG& msg) { + // 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>(msg.time); + DWORD cur_time = GetTickCount(); + base::TimeDelta queueing_time = + base::TimeDelta::FromMilliseconds(cur_time - cur_message_issue_time); + + helper_.StartProcessingTimers(queueing_time); + } + + virtual void DidProcessMessage(const MSG& msg) { + helper_.EndProcessingTimers(); + } +#elif defined(TOOLKIT_USES_GTK) + virtual void WillProcessEvent(GdkEvent* event) { + // TODO(evanm): we want to set queueing_time_ using + // gdk_event_get_time, but how do you convert that info + // into a delta? + // guint event_time = gdk_event_get_time(event); + base::TimeDelta queueing_time = base::TimeDelta::FromMilliseconds(0); + helper_.StartProcessingTimers(queueing_time); + } + + virtual void DidProcessEvent(GdkEvent* event) { + helper_.EndProcessingTimers(); + } +#endif + + private: + friend class base::RefCountedThreadSafe<UIJankObserver>; + + ~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; + } + + // 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)); + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(io_observer->get(), + &IOJankObserver::AttachToCurrentThread)); +} + +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; + } +} |