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 <limits> 6 7 #include "chrome/browser/jankometer.h" 8 9 #include "base/basictypes.h" 10 #include "base/command_line.h" 11 #include "base/memory/ref_counted.h" 12 #include "base/message_loop.h" 13 #include "base/metrics/histogram.h" 14 #include "base/metrics/stats_counters.h" 15 #include "base/string_util.h" 16 #include "base/threading/thread.h" 17 #include "base/threading/watchdog.h" 18 #include "base/time.h" 19 #include "build/build_config.h" 20 #include "chrome/browser/browser_process.h" 21 #include "chrome/common/chrome_switches.h" 22 #include "content/browser/browser_thread.h" 23 24 #if defined(TOOLKIT_USES_GTK) 25 #include "chrome/browser/ui/gtk/gtk_util.h" 26 #endif 27 28 using base::TimeDelta; 29 using base::TimeTicks; 30 31 namespace { 32 33 // The maximum threshold of delay of the message before considering it a delay. 34 // For a debug build, you may want to set IO delay around 500ms. 35 // For a release build, setting it around 350ms is sensible. 36 // Visit about:histograms to see what the distribution is on your system, with 37 // your build. Be sure to do some work to get interesting stats. 38 // The numbers below came from a warm start (you'll get about 5-10 alarms with 39 // a cold start), and running the page-cycler for 5 rounds. 40 #ifdef NDEBUG 41 const int kMaxUIMessageDelayMs = 350; 42 const int kMaxIOMessageDelayMs = 200; 43 #else 44 const int kMaxUIMessageDelayMs = 500; 45 const int kMaxIOMessageDelayMs = 400; 46 #endif 47 48 // Maximum processing time (excluding queueing delay) for a message before 49 // considering it delayed. 50 const int kMaxMessageProcessingMs = 100; 51 52 // TODO(brettw) Consider making this a pref. 53 const bool kPlaySounds = false; 54 55 //------------------------------------------------------------------------------ 56 // Provide a special watchdog to make it easy to set the breakpoint on this 57 // class only. 58 class JankWatchdog : public base::Watchdog { 59 public: 60 JankWatchdog(const TimeDelta& duration, 61 const std::string& thread_watched_name, 62 bool enabled) 63 : Watchdog(duration, thread_watched_name, enabled), 64 thread_name_watched_(thread_watched_name), 65 alarm_count_(0) { 66 } 67 68 virtual ~JankWatchdog() {} 69 70 virtual void Alarm() { 71 // Put break point here if you want to stop threads and look at what caused 72 // the jankiness. 73 alarm_count_++; 74 Watchdog::Alarm(); 75 } 76 77 private: 78 std::string thread_name_watched_; 79 int alarm_count_; 80 81 DISALLOW_COPY_AND_ASSIGN(JankWatchdog); 82 }; 83 84 class JankObserverHelper { 85 public: 86 JankObserverHelper(const std::string& thread_name, 87 const TimeDelta& excessive_duration, 88 bool watchdog_enable); 89 ~JankObserverHelper(); 90 91 void StartProcessingTimers(const TimeDelta& queueing_time); 92 void EndProcessingTimers(); 93 94 // Indicate if we will bother to measuer this message. 95 bool MessageWillBeMeasured(); 96 97 static void SetDefaultMessagesToSkip(int count) { discard_count_ = count; } 98 99 private: 100 const TimeDelta max_message_delay_; 101 102 // Indicate if we'll bother measuring this message. 103 bool measure_current_message_; 104 105 // Down counter which will periodically hit 0, and only then bother to measure 106 // the corresponding message. 107 int events_till_measurement_; 108 109 // The value to re-initialize events_till_measurement_ after it reaches 0. 110 static int discard_count_; 111 112 // Time at which the current message processing began. 113 TimeTicks begin_process_message_; 114 115 // Time the current message spent in the queue -- delta between message 116 // construction time and message processing time. 117 TimeDelta queueing_time_; 118 119 // Counters for the two types of jank we measure. 120 base::StatsCounter slow_processing_counter_; // Msgs w/ long proc time. 121 base::StatsCounter queueing_delay_counter_; // Msgs w/ long queueing delay. 122 base::Histogram* const process_times_; // Time spent proc. task. 123 base::Histogram* const total_times_; // Total queueing plus proc. 124 JankWatchdog total_time_watchdog_; // Watching for excessive total_time. 125 126 DISALLOW_COPY_AND_ASSIGN(JankObserverHelper); 127 }; 128 129 JankObserverHelper::JankObserverHelper( 130 const std::string& thread_name, 131 const TimeDelta& excessive_duration, 132 bool watchdog_enable) 133 : max_message_delay_(excessive_duration), 134 measure_current_message_(true), 135 events_till_measurement_(0), 136 slow_processing_counter_(std::string("Chrome.SlowMsg") + thread_name), 137 queueing_delay_counter_(std::string("Chrome.DelayMsg") + thread_name), 138 process_times_(base::Histogram::FactoryGet( 139 std::string("Chrome.ProcMsgL ") + thread_name, 140 1, 3600000, 50, base::Histogram::kUmaTargetedHistogramFlag)), 141 total_times_(base::Histogram::FactoryGet( 142 std::string("Chrome.TotalMsgL ") + thread_name, 143 1, 3600000, 50, base::Histogram::kUmaTargetedHistogramFlag)), 144 total_time_watchdog_(excessive_duration, thread_name, watchdog_enable) { 145 if (discard_count_ > 0) { 146 // Select a vaguely random sample-start-point. 147 events_till_measurement_ = static_cast<int>( 148 (TimeTicks::Now() - TimeTicks()).InSeconds() % (discard_count_ + 1)); 149 } 150 } 151 152 JankObserverHelper::~JankObserverHelper() {} 153 154 // Called when a message has just begun processing, initializes 155 // per-message variables and timers. 156 void JankObserverHelper::StartProcessingTimers(const TimeDelta& queueing_time) { 157 DCHECK(measure_current_message_); 158 begin_process_message_ = TimeTicks::Now(); 159 queueing_time_ = queueing_time; 160 161 // Simulate arming when the message entered the queue. 162 total_time_watchdog_.ArmSomeTimeDeltaAgo(queueing_time_); 163 if (queueing_time_ > max_message_delay_) { 164 // Message is too delayed. 165 queueing_delay_counter_.Increment(); 166 #if defined(OS_WIN) 167 if (kPlaySounds) 168 MessageBeep(MB_ICONASTERISK); 169 #endif 170 } 171 } 172 173 // Called when a message has just finished processing, finalizes 174 // per-message variables and timers. 175 void JankObserverHelper::EndProcessingTimers() { 176 if (!measure_current_message_) 177 return; 178 total_time_watchdog_.Disarm(); 179 TimeTicks now = TimeTicks::Now(); 180 if (begin_process_message_ != TimeTicks()) { 181 TimeDelta processing_time = now - begin_process_message_; 182 process_times_->AddTime(processing_time); 183 total_times_->AddTime(queueing_time_ + processing_time); 184 } 185 if (now - begin_process_message_ > 186 TimeDelta::FromMilliseconds(kMaxMessageProcessingMs)) { 187 // Message took too long to process. 188 slow_processing_counter_.Increment(); 189 #if defined(OS_WIN) 190 if (kPlaySounds) 191 MessageBeep(MB_ICONHAND); 192 #endif 193 } 194 195 // Reset message specific times. 196 begin_process_message_ = base::TimeTicks(); 197 queueing_time_ = base::TimeDelta(); 198 } 199 200 bool JankObserverHelper::MessageWillBeMeasured() { 201 measure_current_message_ = events_till_measurement_ <= 0; 202 if (!measure_current_message_) 203 --events_till_measurement_; 204 else 205 events_till_measurement_ = discard_count_; 206 return measure_current_message_; 207 } 208 209 // static 210 int JankObserverHelper::discard_count_ = 99; // Measure only 1 in 100. 211 212 //------------------------------------------------------------------------------ 213 class IOJankObserver : public base::RefCountedThreadSafe<IOJankObserver>, 214 public MessageLoopForIO::IOObserver, 215 public MessageLoop::TaskObserver { 216 public: 217 IOJankObserver(const char* thread_name, 218 TimeDelta excessive_duration, 219 bool watchdog_enable) 220 : helper_(thread_name, excessive_duration, watchdog_enable) {} 221 222 ~IOJankObserver() {} 223 224 // Attaches the observer to the current thread's message loop. You can only 225 // attach to the current thread, so this function can be invoked on another 226 // thread to attach it. 227 void AttachToCurrentThread() { 228 MessageLoop::current()->AddTaskObserver(this); 229 MessageLoopForIO::current()->AddIOObserver(this); 230 } 231 232 // Detaches the observer to the current thread's message loop. 233 void DetachFromCurrentThread() { 234 MessageLoopForIO::current()->RemoveIOObserver(this); 235 MessageLoop::current()->RemoveTaskObserver(this); 236 } 237 238 virtual void WillProcessIOEvent() { 239 if (!helper_.MessageWillBeMeasured()) 240 return; 241 helper_.StartProcessingTimers(base::TimeDelta()); 242 } 243 244 virtual void DidProcessIOEvent() { 245 helper_.EndProcessingTimers(); 246 } 247 248 virtual void WillProcessTask(const Task* task) { 249 if (!helper_.MessageWillBeMeasured()) 250 return; 251 base::TimeTicks now = base::TimeTicks::Now(); 252 const base::TimeDelta queueing_time = now - task->tracked_birth_time(); 253 helper_.StartProcessingTimers(queueing_time); 254 } 255 256 virtual void DidProcessTask(const Task* task) { 257 helper_.EndProcessingTimers(); 258 } 259 260 private: 261 friend class base::RefCountedThreadSafe<IOJankObserver>; 262 263 JankObserverHelper helper_; 264 265 DISALLOW_COPY_AND_ASSIGN(IOJankObserver); 266 }; 267 268 //------------------------------------------------------------------------------ 269 class UIJankObserver : public base::RefCountedThreadSafe<UIJankObserver>, 270 public MessageLoop::TaskObserver, 271 public MessageLoopForUI::Observer { 272 public: 273 UIJankObserver(const char* thread_name, 274 TimeDelta excessive_duration, 275 bool watchdog_enable) 276 : helper_(thread_name, excessive_duration, watchdog_enable) {} 277 278 // Attaches the observer to the current thread's message loop. You can only 279 // attach to the current thread, so this function can be invoked on another 280 // thread to attach it. 281 void AttachToCurrentThread() { 282 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); 283 MessageLoopForUI::current()->AddObserver(this); 284 MessageLoop::current()->AddTaskObserver(this); 285 } 286 287 // Detaches the observer to the current thread's message loop. 288 void DetachFromCurrentThread() { 289 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); 290 MessageLoop::current()->RemoveTaskObserver(this); 291 MessageLoopForUI::current()->RemoveObserver(this); 292 } 293 294 virtual void WillProcessTask(const Task* task) { 295 if (!helper_.MessageWillBeMeasured()) 296 return; 297 base::TimeTicks now = base::TimeTicks::Now(); 298 const base::TimeDelta queueing_time = now - task->tracked_birth_time(); 299 helper_.StartProcessingTimers(queueing_time); 300 } 301 302 virtual void DidProcessTask(const Task* task) { 303 helper_.EndProcessingTimers(); 304 } 305 306 #if defined(OS_WIN) 307 virtual void WillProcessMessage(const MSG& msg) { 308 if (!helper_.MessageWillBeMeasured()) 309 return; 310 // GetMessageTime returns a LONG (signed 32-bit) and GetTickCount returns 311 // a DWORD (unsigned 32-bit). They both wrap around when the time is longer 312 // than they can hold. I'm not sure if GetMessageTime wraps around to 0, 313 // or if the original time comes from GetTickCount, it might wrap around 314 // to -1. 315 // 316 // Therefore, I cast to DWORD so if it wraps to -1 we will correct it. If 317 // it doesn't, then our time delta will be negative if a message happens 318 // to straddle the wraparound point, it will still be OK. 319 DWORD cur_message_issue_time = static_cast<DWORD>(msg.time); 320 DWORD cur_time = GetTickCount(); 321 base::TimeDelta queueing_time = 322 base::TimeDelta::FromMilliseconds(cur_time - cur_message_issue_time); 323 324 helper_.StartProcessingTimers(queueing_time); 325 } 326 327 virtual void DidProcessMessage(const MSG& msg) { 328 helper_.EndProcessingTimers(); 329 } 330 #elif defined(TOOLKIT_USES_GTK) 331 virtual void WillProcessEvent(GdkEvent* event) { 332 if (!helper_.MessageWillBeMeasured()) 333 return; 334 // TODO(evanm): we want to set queueing_time_ using 335 // gdk_event_get_time, but how do you convert that info 336 // into a delta? 337 // guint event_time = gdk_event_get_time(event); 338 base::TimeDelta queueing_time = base::TimeDelta::FromMilliseconds(0); 339 helper_.StartProcessingTimers(queueing_time); 340 } 341 342 virtual void DidProcessEvent(GdkEvent* event) { 343 helper_.EndProcessingTimers(); 344 } 345 #endif 346 347 private: 348 friend class base::RefCountedThreadSafe<UIJankObserver>; 349 350 ~UIJankObserver() {} 351 352 JankObserverHelper helper_; 353 354 DISALLOW_COPY_AND_ASSIGN(UIJankObserver); 355 }; 356 357 // These objects are created by InstallJankometer and leaked. 358 const scoped_refptr<UIJankObserver>* ui_observer = NULL; 359 const scoped_refptr<IOJankObserver>* io_observer = NULL; 360 361 } // namespace 362 363 void InstallJankometer(const CommandLine& parsed_command_line) { 364 if (ui_observer || io_observer) { 365 NOTREACHED() << "Initializing jank-o-meter twice"; 366 return; 367 } 368 369 bool ui_watchdog_enabled = false; 370 bool io_watchdog_enabled = false; 371 if (parsed_command_line.HasSwitch(switches::kEnableWatchdog)) { 372 std::string list = 373 parsed_command_line.GetSwitchValueASCII(switches::kEnableWatchdog); 374 if (list.npos != list.find("ui")) 375 ui_watchdog_enabled = true; 376 if (list.npos != list.find("io")) 377 io_watchdog_enabled = true; 378 } 379 380 if (ui_watchdog_enabled || io_watchdog_enabled) 381 JankObserverHelper::SetDefaultMessagesToSkip(0); // Watch everything. 382 383 // Install on the UI thread. 384 ui_observer = new scoped_refptr<UIJankObserver>( 385 new UIJankObserver( 386 "UI", 387 TimeDelta::FromMilliseconds(kMaxUIMessageDelayMs), 388 ui_watchdog_enabled)); 389 (*ui_observer)->AttachToCurrentThread(); 390 391 // Now install on the I/O thread. Hiccups on that thread will block 392 // interaction with web pages. We must proxy to that thread before we can 393 // add our observer. 394 io_observer = new scoped_refptr<IOJankObserver>( 395 new IOJankObserver( 396 "IO", 397 TimeDelta::FromMilliseconds(kMaxIOMessageDelayMs), 398 io_watchdog_enabled)); 399 BrowserThread::PostTask( 400 BrowserThread::IO, FROM_HERE, 401 NewRunnableMethod(io_observer->get(), 402 &IOJankObserver::AttachToCurrentThread)); 403 } 404 405 void UninstallJankometer() { 406 if (ui_observer) { 407 (*ui_observer)->DetachFromCurrentThread(); 408 delete ui_observer; 409 ui_observer = NULL; 410 } 411 if (io_observer) { 412 // IO thread can't be running when we remove observers. 413 DCHECK((!g_browser_process) || !(g_browser_process->io_thread())); 414 delete io_observer; 415 io_observer = NULL; 416 } 417 } 418