| // Copyright 2017 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 "base/synchronization/waitable_event.h" | 
 |  | 
 | #include <numeric> | 
 |  | 
 | #include "base/threading/simple_thread.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "testing/perf/perf_test.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | namespace { | 
 |  | 
 | class TraceWaitableEvent { | 
 |  public: | 
 |   TraceWaitableEvent(size_t samples) | 
 |       : event_(WaitableEvent::ResetPolicy::AUTOMATIC, | 
 |                WaitableEvent::InitialState::NOT_SIGNALED), | 
 |         samples_(samples) { | 
 |     signal_times_.reserve(samples); | 
 |     wait_times_.reserve(samples); | 
 |   } | 
 |  | 
 |   ~TraceWaitableEvent() = default; | 
 |  | 
 |   void Signal() { | 
 |     TimeTicks start = TimeTicks::Now(); | 
 |     event_.Signal(); | 
 |     signal_times_.push_back(TimeTicks::Now() - start); | 
 |   } | 
 |  | 
 |   void Wait() { | 
 |     TimeTicks start = TimeTicks::Now(); | 
 |     event_.Wait(); | 
 |     wait_times_.push_back(TimeTicks::Now() - start); | 
 |   } | 
 |  | 
 |   bool TimedWaitUntil(const TimeTicks& end_time) { | 
 |     TimeTicks start = TimeTicks::Now(); | 
 |     bool signaled = event_.TimedWaitUntil(end_time); | 
 |     wait_times_.push_back(TimeTicks::Now() - start); | 
 |     return signaled; | 
 |   } | 
 |  | 
 |   bool IsSignaled() { return event_.IsSignaled(); } | 
 |  | 
 |   const std::vector<TimeDelta>& signal_times() const { return signal_times_; } | 
 |   const std::vector<TimeDelta>& wait_times() const { return wait_times_; } | 
 |   size_t samples() const { return samples_; } | 
 |  | 
 |  private: | 
 |   WaitableEvent event_; | 
 |  | 
 |   std::vector<TimeDelta> signal_times_; | 
 |   std::vector<TimeDelta> wait_times_; | 
 |  | 
 |   const size_t samples_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TraceWaitableEvent); | 
 | }; | 
 |  | 
 | class SignalerThread : public SimpleThread { | 
 |  public: | 
 |   SignalerThread(TraceWaitableEvent* waiter, TraceWaitableEvent* signaler) | 
 |       : SimpleThread("WaitableEventPerfTest signaler"), | 
 |         waiter_(waiter), | 
 |         signaler_(signaler) {} | 
 |  | 
 |   ~SignalerThread() override = default; | 
 |  | 
 |   void Run() override { | 
 |     while (!stop_event_.IsSignaled()) { | 
 |       if (waiter_) | 
 |         waiter_->Wait(); | 
 |       if (signaler_) | 
 |         signaler_->Signal(); | 
 |     } | 
 |   } | 
 |  | 
 |   // Signals the thread to stop on the next iteration of its loop (which | 
 |   // will happen immediately if no |waiter_| is present or is signaled. | 
 |   void RequestStop() { stop_event_.Signal(); } | 
 |  | 
 |  private: | 
 |   WaitableEvent stop_event_{WaitableEvent::ResetPolicy::MANUAL, | 
 |                             WaitableEvent::InitialState::NOT_SIGNALED}; | 
 |   TraceWaitableEvent* waiter_; | 
 |   TraceWaitableEvent* signaler_; | 
 |   DISALLOW_COPY_AND_ASSIGN(SignalerThread); | 
 | }; | 
 |  | 
 | void PrintPerfWaitableEvent(const TraceWaitableEvent* event, | 
 |                             const std::string& modifier, | 
 |                             const std::string& trace) { | 
 |   TimeDelta signal_time = std::accumulate( | 
 |       event->signal_times().begin(), event->signal_times().end(), TimeDelta()); | 
 |   TimeDelta wait_time = std::accumulate(event->wait_times().begin(), | 
 |                                         event->wait_times().end(), TimeDelta()); | 
 |   perf_test::PrintResult( | 
 |       "signal_time", modifier, trace, | 
 |       static_cast<size_t>(signal_time.InNanoseconds()) / event->samples(), | 
 |       "ns/sample", true); | 
 |   perf_test::PrintResult( | 
 |       "wait_time", modifier, trace, | 
 |       static_cast<size_t>(wait_time.InNanoseconds()) / event->samples(), | 
 |       "ns/sample", true); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(WaitableEventPerfTest, SingleThread) { | 
 |   const size_t kSamples = 1000; | 
 |  | 
 |   TraceWaitableEvent event(kSamples); | 
 |  | 
 |   for (size_t i = 0; i < kSamples; ++i) { | 
 |     event.Signal(); | 
 |     event.Wait(); | 
 |   } | 
 |  | 
 |   PrintPerfWaitableEvent(&event, "", "singlethread-1000-samples"); | 
 | } | 
 |  | 
 | TEST(WaitableEventPerfTest, MultipleThreads) { | 
 |   const size_t kSamples = 1000; | 
 |  | 
 |   TraceWaitableEvent waiter(kSamples); | 
 |   TraceWaitableEvent signaler(kSamples); | 
 |  | 
 |   // The other thread will wait and signal on the respective opposite events. | 
 |   SignalerThread thread(&signaler, &waiter); | 
 |   thread.Start(); | 
 |  | 
 |   for (size_t i = 0; i < kSamples; ++i) { | 
 |     signaler.Signal(); | 
 |     waiter.Wait(); | 
 |   } | 
 |  | 
 |   // Signal the stop event and then make sure the signaler event it is | 
 |   // waiting on is also signaled. | 
 |   thread.RequestStop(); | 
 |   signaler.Signal(); | 
 |  | 
 |   thread.Join(); | 
 |  | 
 |   PrintPerfWaitableEvent(&waiter, "_waiter", "multithread-1000-samples"); | 
 |   PrintPerfWaitableEvent(&signaler, "_signaler", "multithread-1000-samples"); | 
 | } | 
 |  | 
 | TEST(WaitableEventPerfTest, Throughput) { | 
 |   // Reserve a lot of sample space. | 
 |   const size_t kCapacity = 500000; | 
 |   TraceWaitableEvent event(kCapacity); | 
 |  | 
 |   SignalerThread thread(nullptr, &event); | 
 |   thread.Start(); | 
 |  | 
 |   TimeTicks end_time = TimeTicks::Now() + TimeDelta::FromSeconds(1); | 
 |   size_t count = 0; | 
 |   while (event.TimedWaitUntil(end_time)) { | 
 |     ++count; | 
 |   } | 
 |  | 
 |   thread.RequestStop(); | 
 |   thread.Join(); | 
 |  | 
 |   perf_test::PrintResult("counts", "", "throughput", count, "signals", true); | 
 |   PrintPerfWaitableEvent(&event, "", "throughput"); | 
 |  | 
 |   // Make sure that allocation didn't happen during the test. | 
 |   EXPECT_LE(event.signal_times().capacity(), kCapacity); | 
 |   EXPECT_LE(event.wait_times().capacity(), kCapacity); | 
 | } | 
 |  | 
 | }  // namespace base |