| // 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 <limits> |
| |
| #include "chrome/browser/jankometer.h" |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/stats_counters.h" |
| #include "base/string_util.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/watchdog.h" |
| #include "base/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "content/browser/browser_thread.h" |
| |
| #if defined(TOOLKIT_USES_GTK) |
| #include "chrome/browser/ui/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 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() { |
| // 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::Histogram* const process_times_; // Time spent proc. task. |
| base::Histogram* 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 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() { |
| 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 defined(OS_WIN) |
| if (kPlaySounds) |
| MessageBeep(MB_ICONHAND); |
| #endif |
| } |
| |
| // 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 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() { |
| if (!helper_.MessageWillBeMeasured()) |
| return; |
| helper_.StartProcessingTimers(base::TimeDelta()); |
| } |
| |
| virtual void DidProcessIOEvent() { |
| helper_.EndProcessingTimers(); |
| } |
| |
| virtual void WillProcessTask(const Task* task) { |
| if (!helper_.MessageWillBeMeasured()) |
| return; |
| base::TimeTicks now = base::TimeTicks::Now(); |
| const base::TimeDelta queueing_time = now - task->tracked_birth_time(); |
| helper_.StartProcessingTimers(queueing_time); |
| } |
| |
| virtual void DidProcessTask(const Task* task) { |
| 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(const Task* task) { |
| if (!helper_.MessageWillBeMeasured()) |
| return; |
| base::TimeTicks now = base::TimeTicks::Now(); |
| const base::TimeDelta queueing_time = now - task->tracked_birth_time(); |
| helper_.StartProcessingTimers(queueing_time); |
| } |
| |
| virtual void DidProcessTask(const Task* task) { |
| helper_.EndProcessingTimers(); |
| } |
| |
| #if defined(OS_WIN) |
| virtual void WillProcessMessage(const MSG& msg) { |
| if (!helper_.MessageWillBeMeasured()) |
| return; |
| // 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) { |
| if (!helper_.MessageWillBeMeasured()) |
| return; |
| // 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; |
| } |
| |
| 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, |
| 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; |
| } |
| } |