Index: base/message_loop/message_pump_mac.mm |
diff --git a/base/message_loop/message_pump_mac.mm b/base/message_loop/message_pump_mac.mm |
index f885fbf5f6bbb9d53fc1d24363c0f427699ace6c..fa959479a2581059fbfcf7720a47d0166823a865 100644 |
--- a/base/message_loop/message_pump_mac.mm |
+++ b/base/message_loop/message_pump_mac.mm |
@@ -7,10 +7,14 @@ |
#import <Foundation/Foundation.h> |
#include <limits> |
+#include <stack> |
#include "base/logging.h" |
+#include "base/metrics/histogram.h" |
#include "base/run_loop.h" |
+#include "base/strings/stringprintf.h" |
#include "base/time/time.h" |
+#include "base/timer/timer.h" |
#if !defined(OS_IOS) |
#import <AppKit/AppKit.h> |
@@ -51,6 +55,181 @@ class MessagePumpScopedAutoreleasePool { |
DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); |
}; |
+// This class is used to instrument the MessagePump to gather various timing |
+// data about when the underlying run loop is entered, when it is waiting, and |
+// when it is servicing its delegate. |
+// |
+// The metrics are gathered as UMA-tracked histograms. To gather the data over |
+// time, sampling is used, such that a new histogram is created for each metric |
+// every |sampling_interval| for |sampling_duration|. After sampling is |
+// complete, this class deletes itself. |
+class MessagePumpInstrumentation { |
+ public: |
+ // Creates an instrument for the MessagePump on the current thread. Every |
+ // |sampling_interval|, a new histogram will be created to track the metrics |
+ // over time. After |sampling_duration|, this will delete itself, causing the |
+ // WeakPtr to go NULL. |
+ static WeakPtr<MessagePumpInstrumentation> Create( |
+ const TimeDelta& sampling_interval, |
+ const TimeDelta& sampling_duration) { |
+ MessagePumpInstrumentation* instrument = |
+ new MessagePumpInstrumentation(sampling_interval, sampling_duration); |
+ return instrument->weak_ptr_factory_.GetWeakPtr(); |
+ } |
+ |
+ // Starts the timer that runs the sampling instrumentation. Can be called |
+ // multiple times as a noop. |
+ void StartIfNeeded() { |
+ if (timer_.IsRunning()) |
+ return; |
+ timer_.Start(FROM_HERE, sampling_interval_, |
+ base::Bind(&MessagePumpInstrumentation::TimerFired, |
+ weak_ptr_factory_.GetWeakPtr())); |
+ } |
+ |
+ // Used to track kCFRunLoopEntry. |
+ void LoopEntered() { |
+ loop_run_times_.push(TimeTicks::Now()); |
+ } |
+ |
+ // Used to track kCFRunLoopExit. |
+ void LoopExited() { |
+ TimeDelta duration = TimeTicks::Now() - loop_run_times_.top(); |
+ loop_run_times_.pop(); |
+ GetHistogram(LOOP_CYCLE)->AddTime(duration); |
+ } |
+ |
+ // Used to track kCFRunLoopBeforeWaiting. |
+ void WaitingStarted() { |
+ loop_wait_times_.push(TimeTicks::Now()); |
+ } |
+ |
+ // Used to track kCFRunLoopAfterWaiting. |
+ void WaitingFinished() { |
+ TimeDelta duration = TimeTicks::Now() - loop_wait_times_.top(); |
+ loop_wait_times_.pop(); |
+ GetHistogram(LOOP_WAIT)->AddTime(duration); |
+ } |
+ |
+ // Used to track when the MessagePump will invoke its |delegate|. |
+ void WorkSourceEntered(MessagePump::Delegate* delegate) { |
+ work_source_times_.push(TimeTicks::Now()); |
+ if (delegate) { |
+ size_t queue_size; |
+ TimeDelta queuing_delay; |
+ delegate->GetQueueingInformation(&queue_size, &queuing_delay); |
+ GetHistogram(QUEUE_SIZE)->Add(queue_size); |
+ GetHistogram(QUEUE_DELAY)->AddTime(queuing_delay); |
+ } |
+ } |
+ |
+ // Used to track the completion of servicing the MessagePump::Delegate. |
+ void WorkSourceExited() { |
+ TimeDelta duration = TimeTicks::Now() - work_source_times_.top(); |
+ work_source_times_.pop(); |
+ GetHistogram(WORK_SOURCE)->AddTime(duration); |
+ } |
+ |
+ private: |
+ enum HistogramEvent { |
+ // Time-based histograms: |
+ LOOP_CYCLE, // LoopEntered/LoopExited |
+ LOOP_WAIT, // WaitingStarted/WaitingEnded |
+ WORK_SOURCE, // WorkSourceExited |
+ QUEUE_DELAY, // WorkSourceEntered |
+ |
+ // Value-based histograms: |
+ QUEUE_SIZE, // WorkSourceEntered |
+ |
+ HISTOGRAM_EVENT_MAX, |
+ }; |
+ |
+ MessagePumpInstrumentation(const TimeDelta& sampling_interval, |
+ const TimeDelta& sampling_duration) |
+ : weak_ptr_factory_(this), |
+ sampling_interval_(sampling_interval), |
+ sampling_duration_(sampling_duration), |
+ sample_count_(0), |
+ timer_(true, true) { |
+ const char kHistogramName[] = "MessagePumpMac.%s.SampleMs.%lld"; |
+ |
+ // Create all the histogram objects that will be used for sampling. |
+ for (TimeDelta i; i < sampling_duration_; i += sampling_interval_) { |
+ int64 sample = i.InMilliseconds(); |
+ |
+ // Generate the time-based histograms. |
+ for (HistogramEvent j = LOOP_CYCLE; j < QUEUE_SIZE; ++j) { |
Mark Mentovai
2013/08/20 22:44:34
There’s no indication in the enum above that QUEUE
Robert Sesek
2013/08/22 17:49:15
"Solved" with a comment. Fixing this with an in-be
|
+ std::string name = StringPrintf(kHistogramName, NameForEnum(j), sample); |
+ histograms_[j].push_back( |
+ Histogram::FactoryTimeGet(name, TimeDelta::FromMilliseconds(1), |
+ sampling_interval_, 50, |
+ HistogramBase::kUmaTargetedHistogramFlag)); |
+ } |
+ |
+ // Generate the value-based histograms. |
+ for (HistogramEvent j = QUEUE_SIZE; j < HISTOGRAM_EVENT_MAX; ++j) { |
+ std::string name = StringPrintf(kHistogramName, NameForEnum(j), sample); |
+ histograms_[j].push_back( |
+ Histogram::FactoryGet(name, 1, 10000, 50, |
+ HistogramBase::kUmaTargetedHistogramFlag)); |
+ } |
+ } |
Mark Mentovai
2013/08/20 22:44:34
That was only 75 histograms.
Robert Sesek
2013/08/22 17:49:15
I'll leave it to jar@ to comment on the implicatio
jar (doing other things)
2013/08/22 23:03:39
I'm not convinced that all these histogtrams are u
|
+ } |
+ |
+ const char* NameForEnum(HistogramEvent event) { |
+ switch (event) { |
+ case LOOP_CYCLE: return "LoopCycle"; |
+ case LOOP_WAIT: return "Waiting"; |
+ case WORK_SOURCE: return "WorkSource"; |
+ case QUEUE_DELAY: return "QueueingDelay"; |
+ case QUEUE_SIZE: return "QueueSize"; |
+ case HISTOGRAM_EVENT_MAX: |
Mark Mentovai
2013/08/20 22:44:34
This line is not needed. (Some compiler with lots
Robert Sesek
2013/08/22 17:49:15
Done.
|
+ default: |
+ NOTREACHED(); |
+ return NULL; |
+ } |
+ } |
+ |
+ // Called when |sampling_interval| has elapsed. |
+ void TimerFired() { |
+ ++sample_count_; |
+ |
+ if (sampling_interval_ * sample_count_ >= sampling_duration_) { |
+ timer_.Stop(); |
+ delete this; |
+ } |
+ } |
+ |
+ HistogramBase* GetHistogram(HistogramEvent event) { |
+ return histograms_[event][sample_count_]; |
+ } |
+ |
+ // Vends the pointer to the Create()er. |
+ WeakPtrFactory<MessagePumpInstrumentation> weak_ptr_factory_; |
+ |
+ // The interval and duration of the sampling, as well as the number of |
+ // times the timer has fired. |
+ TimeDelta sampling_interval_; |
+ TimeDelta sampling_duration_; |
+ size_t sample_count_; |
+ |
+ // The timer that invokes TimerFired() to advance the |sample_count_|. |
+ Timer timer_; |
Mark Mentovai
2013/08/20 22:44:34
…this is running on a Timer. And the timers are di
Robert Sesek
2013/08/22 17:49:15
Sigh, I know.
|
+ |
+ // The two-dimensional array of histograms. The first dimension is the |
+ // HistogramEvent type. The second is for the sampling intervals. |
+ std::vector<HistogramBase*> histograms_[HISTOGRAM_EVENT_MAX]; |
+ |
+ // MessagePump activations can be nested. Use a stack for each of the |
+ // possibly reentrant HistogramEvent types to properly balance and calculate |
+ // the timing information. |
+ std::stack<TimeTicks> loop_run_times_; |
+ std::stack<TimeTicks> loop_wait_times_; |
+ std::stack<TimeTicks> work_source_times_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MessagePumpInstrumentation); |
+}; |
+ |
// Must be called on the run loop thread. |
MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() |
: delegate_(NULL), |
@@ -101,7 +280,8 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() |
CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); |
observer_context.info = this; |
pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator |
- kCFRunLoopBeforeWaiting, |
+ kCFRunLoopBeforeWaiting | |
+ kCFRunLoopAfterWaiting, |
true, // repeat |
0, // priority |
PreWaitObserver, |
Mark Mentovai
2013/08/20 22:44:34
Either your new code is temporary (and you should
Robert Sesek
2013/08/22 17:49:15
Done.
|
@@ -193,6 +373,11 @@ void MessagePumpCFRunLoopBase::SetDelegate(Delegate* delegate) { |
} |
} |
+void MessagePumpCFRunLoopBase::EnableInstrumentation() { |
+ instrumentation_ = MessagePumpInstrumentation::Create( |
+ TimeDelta::FromSeconds(1), TimeDelta::FromSeconds(15)); |
+} |
+ |
// May be called on any thread. |
void MessagePumpCFRunLoopBase::ScheduleWork() { |
CFRunLoopSourceSignal(work_source_); |
@@ -240,6 +425,9 @@ bool MessagePumpCFRunLoopBase::RunWork() { |
return false; |
} |
+ if (instrumentation_) |
+ instrumentation_->WorkSourceEntered(delegate_); |
+ |
// The NSApplication-based run loop only drains the autorelease pool at each |
// UI event (NSEvent). The autorelease pool is not drained for each |
// CFRunLoopSource target that's run. Use a local pool for any autoreleased |
@@ -279,6 +467,9 @@ bool MessagePumpCFRunLoopBase::RunWork() { |
CFRunLoopSourceSignal(work_source_); |
} |
+ if (instrumentation_) |
+ instrumentation_->WorkSourceExited(); |
+ |
return resignal_work_source; |
} |
@@ -368,6 +559,12 @@ void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, |
void* info) { |
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
+ if (activity == kCFRunLoopAfterWaiting) { |
+ if (self->instrumentation_) |
+ self->instrumentation_->WaitingFinished(); |
+ return; |
+ } |
+ |
// Attempt to do some idle work before going to sleep. |
self->RunIdleWork(); |
@@ -376,6 +573,9 @@ void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, |
// nesting-deferred work may have accumulated. Schedule it for processing |
// if appropriate. |
self->MaybeScheduleNestingDeferredWork(); |
+ |
+ if (self->instrumentation_) |
+ self->instrumentation_->WaitingStarted(); |
} |
// Called from the run loop. |
@@ -402,6 +602,9 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, |
switch (activity) { |
case kCFRunLoopEntry: |
+ if (self->instrumentation_) |
+ self->instrumentation_->LoopEntered(); |
+ |
++self->nesting_level_; |
if (self->nesting_level_ > self->deepest_nesting_level_) { |
self->deepest_nesting_level_ = self->nesting_level_; |
@@ -427,6 +630,9 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, |
// loop. |
self->MaybeScheduleNestingDeferredWork(); |
--self->nesting_level_; |
+ |
+ if (self->instrumentation_) |
+ self->instrumentation_->LoopExited(); |
break; |
default: |
@@ -556,11 +762,15 @@ void MessagePumpUIApplication::Attach(Delegate* delegate) { |
MessagePumpNSApplication::MessagePumpNSApplication() |
: keep_running_(true), |
running_own_loop_(false) { |
+ EnableInstrumentation(); |
} |
MessagePumpNSApplication::~MessagePumpNSApplication() {} |
void MessagePumpNSApplication::DoRun(Delegate* delegate) { |
+ if (instrumentation_) |
+ instrumentation_->StartIfNeeded(); |
+ |
bool last_running_own_loop_ = running_own_loop_; |
// NSApp must be initialized by calling: |