Chromium Code Reviews| 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..8b01531f6d980ac6391dcd8919c2709eb4da95b2 100644 |
| --- a/base/message_loop/message_pump_mac.mm |
| +++ b/base/message_loop/message_pump_mac.mm |
| @@ -7,9 +7,14 @@ |
| #import <Foundation/Foundation.h> |
| #include <limits> |
| +#include <stack> |
| +#include "base/format_macros.h" |
| #include "base/logging.h" |
| +#include "base/mac/scoped_cftyperef.h" |
| +#include "base/metrics/histogram.h" |
| #include "base/run_loop.h" |
| +#include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #if !defined(OS_IOS) |
| @@ -51,6 +56,228 @@ 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_) |
| + return; |
| + |
| + sampling_start_time_ = generation_start_time_ = TimeTicks::Now(); |
| + |
| + CFRunLoopTimerContext timer_context = { .info = this }; |
| + timer_.reset(CFRunLoopTimerCreate( |
| + NULL, // allocator |
| + (Time::Now() + sampling_interval_).ToCFAbsoluteTime(), |
| + sampling_interval_.InSecondsF(), |
| + 0, // flags |
| + 0, // order |
| + &MessagePumpInstrumentation::TimerFired, |
| + &timer_context)); |
| + CFRunLoopAddTimer(CFRunLoopGetCurrent(), |
| + timer_, |
| + kCFRunLoopCommonModes); |
| + } |
| + |
| + // 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: |
| + // NOTE: Do not add value-based histograms before this event, only after. |
| + 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_generation_(0) { |
| + // Create all the histogram objects that will be used for sampling. |
| + const char kHistogramName[] = "MessagePumpMac.%s.SampleMs.%" PRId64; |
| + for (TimeDelta i; i < sampling_duration_; i += sampling_interval_) { |
|
jar (doing other things)
2013/08/22 23:03:39
I'm not clear at all why you want these different
|
| + int64 sample = i.InMilliseconds(); |
| + |
| + // Generate the time-based histograms. |
| + for (HistogramEvent j = LOOP_CYCLE; j < QUEUE_SIZE; ++j) { |
| + 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)); |
| + } |
| + } |
| + } |
| + |
| + ~MessagePumpInstrumentation() { |
| + if (timer_) |
| + CFRunLoopTimerInvalidate(timer_); |
| + } |
| + |
| + 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"; |
| + default: |
| + NOTREACHED(); |
| + return NULL; |
| + } |
| + } |
| + |
| + static void TimerFired(CFRunLoopTimerRef timer, void* context) { |
| + static_cast<MessagePumpInstrumentation*>(context)->TimerFired(); |
| + } |
| + |
| + // Called by the run loop when the sampling_interval_ has elapsed. Advances |
| + // the sample_generation_, which controls into which histogram data is |
| + // recorded, while recording and accounting for timer skew. Will delete this |
| + // object after |sampling_duration_| has elapsed. |
| + void TimerFired() { |
| + TimeTicks now = TimeTicks::Now(); |
| + TimeDelta delta = now - generation_start_time_; |
| + |
| + UMA_HISTOGRAM_TIMES("MessagePumpMac.TimerSkew", sampling_interval_ - delta); |
|
jar (doing other things)
2013/08/22 23:03:39
I think you're trying to measure how the actual in
Robert Sesek
2013/08/23 16:58:24
I guess measuring it here is not really necessary
|
| + |
| + // The timer fired, so advance the generation by at least one. |
| + ++sample_generation_; |
| + |
| + // To account for large timer skew/drift, advance the generation by any |
| + // more completed intervals. |
| + for (TimeDelta skew_advance = delta - sampling_interval_; |
| + skew_advance >= sampling_interval_; |
| + skew_advance -= sampling_interval_) { |
| + ++sample_generation_; |
| + } |
| + |
| + generation_start_time_ = now; |
| + if (now >= sampling_start_time_ + sampling_duration_) |
| + delete this; |
| + } |
| + |
| + HistogramBase* GetHistogram(HistogramEvent event) { |
| + DCHECK_LT(sample_generation_, histograms_[event].size()); |
| + return histograms_[event][sample_generation_]; |
| + } |
| + |
| + // Vends the pointer to the Create()or. |
| + WeakPtrFactory<MessagePumpInstrumentation> weak_ptr_factory_; |
| + |
| + // The interval and duration of the sampling. |
| + TimeDelta sampling_interval_; |
| + TimeDelta sampling_duration_; |
| + |
| + // The time at which sampling started. |
| + TimeTicks sampling_start_time_; |
| + |
| + // The timer that advances the sample_generation_ and sets the |
| + // generation_start_time_ for the current sample interval. |
| + base::ScopedCFTypeRef<CFRunLoopTimerRef> timer_; |
| + |
| + // 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]; |
| + |
| + // The index in the second dimension of histograms_, which controls in which |
| + // sampled histogram events are recorded. |
| + size_t sample_generation_; |
| + |
| + // The last time at which the timer fired. This is used to track timer skew |
| + // (i.e. it did not fire on time) and properly account for it when advancing |
| + // samle_generation_. |
| + TimeTicks generation_start_time_; |
| + |
| + // 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,10 +328,11 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() |
| CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); |
| observer_context.info = this; |
| pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator |
| - kCFRunLoopBeforeWaiting, |
| + kCFRunLoopBeforeWaiting | |
| + kCFRunLoopAfterWaiting, |
| true, // repeat |
| 0, // priority |
| - PreWaitObserver, |
| + PrePostWaitObserver, |
| &observer_context); |
| CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes); |
| @@ -193,6 +421,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 +473,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 +515,9 @@ bool MessagePumpCFRunLoopBase::RunWork() { |
| CFRunLoopSourceSignal(work_source_); |
| } |
| + if (instrumentation_) |
| + instrumentation_->WorkSourceExited(); |
| + |
| return resignal_work_source; |
| } |
| @@ -363,11 +602,18 @@ void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { |
| // Called from the run loop. |
| // static |
| -void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, |
| - CFRunLoopActivity activity, |
| - void* info) { |
| +void MessagePumpCFRunLoopBase::PrePostWaitObserver( |
| + CFRunLoopObserverRef observer, |
| + CFRunLoopActivity activity, |
| + void* info) { |
| MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
| + if (activity == kCFRunLoopAfterWaiting) { |
| + if (self->instrumentation_) |
| + self->instrumentation_->WaitingFinished(); |
| + return; |
|
jar (doing other things)
2013/08/22 23:03:39
This maybe suggests it could be called before or a
Robert Sesek
2013/08/23 16:58:24
Each observer actually has an associated cost (a l
|
| + } |
| + |
| // Attempt to do some idle work before going to sleep. |
| self->RunIdleWork(); |
| @@ -376,6 +622,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 +651,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_; |
| @@ -415,9 +667,9 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, |
| // handling sources to exiting without any sleep. This most commonly |
| // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it |
| // to make a single pass through the loop and exit without sleep. Some |
| - // native loops use CFRunLoop in this way. Because PreWaitObserver will |
| - // not be called in these case, MaybeScheduleNestingDeferredWork needs |
| - // to be called here, as the run loop exits. |
| + // native loops use CFRunLoop in this way. Because PrePostWaitObserver |
| + // will not be called in these case, MaybeScheduleNestingDeferredWork |
| + // needs to be called here, as the run loop exits. |
| // |
| // MaybeScheduleNestingDeferredWork consults self->nesting_level_ |
| // to determine whether to schedule nesting-deferred work. It expects |
| @@ -427,6 +679,9 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, |
| // loop. |
| self->MaybeScheduleNestingDeferredWork(); |
| --self->nesting_level_; |
| + |
| + if (self->instrumentation_) |
| + self->instrumentation_->LoopExited(); |
| break; |
| default: |
| @@ -556,11 +811,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: |