• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
JankWatchdog(const TimeDelta & duration,const std::string & thread_watched_name,bool enabled)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 
~JankWatchdog()68   virtual ~JankWatchdog() {}
69 
Alarm()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 
SetDefaultMessagesToSkip(int count)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 
JankObserverHelper(const std::string & thread_name,const TimeDelta & excessive_duration,bool watchdog_enable)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 
~JankObserverHelper()152 JankObserverHelper::~JankObserverHelper() {}
153 
154 // Called when a message has just begun processing, initializes
155 // per-message variables and timers.
StartProcessingTimers(const TimeDelta & queueing_time)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.
EndProcessingTimers()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 
MessageWillBeMeasured()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:
IOJankObserver(const char * thread_name,TimeDelta excessive_duration,bool watchdog_enable)217   IOJankObserver(const char* thread_name,
218                  TimeDelta excessive_duration,
219                  bool watchdog_enable)
220       : helper_(thread_name, excessive_duration, watchdog_enable) {}
221 
~IOJankObserver()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.
AttachToCurrentThread()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.
DetachFromCurrentThread()233   void DetachFromCurrentThread() {
234     MessageLoopForIO::current()->RemoveIOObserver(this);
235     MessageLoop::current()->RemoveTaskObserver(this);
236   }
237 
WillProcessIOEvent()238   virtual void WillProcessIOEvent() {
239     if (!helper_.MessageWillBeMeasured())
240       return;
241     helper_.StartProcessingTimers(base::TimeDelta());
242   }
243 
DidProcessIOEvent()244   virtual void DidProcessIOEvent() {
245     helper_.EndProcessingTimers();
246   }
247 
WillProcessTask(const Task * task)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 
DidProcessTask(const Task * task)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:
UIJankObserver(const char * thread_name,TimeDelta excessive_duration,bool watchdog_enable)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.
AttachToCurrentThread()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.
DetachFromCurrentThread()288   void DetachFromCurrentThread() {
289     DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI);
290     MessageLoop::current()->RemoveTaskObserver(this);
291     MessageLoopForUI::current()->RemoveObserver(this);
292   }
293 
WillProcessTask(const Task * task)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 
DidProcessTask(const Task * task)302   virtual void DidProcessTask(const Task* task) {
303     helper_.EndProcessingTimers();
304   }
305 
306 #if defined(OS_WIN)
WillProcessMessage(const MSG & msg)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 
DidProcessMessage(const MSG & msg)327   virtual void DidProcessMessage(const MSG& msg) {
328     helper_.EndProcessingTimers();
329   }
330 #elif defined(TOOLKIT_USES_GTK)
WillProcessEvent(GdkEvent * event)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 
DidProcessEvent(GdkEvent * event)342   virtual void DidProcessEvent(GdkEvent* event) {
343     helper_.EndProcessingTimers();
344   }
345 #endif
346 
347  private:
348   friend class base::RefCountedThreadSafe<UIJankObserver>;
349 
~UIJankObserver()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 
InstallJankometer(const CommandLine & parsed_command_line)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 
UninstallJankometer()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