| // 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/memory/raw_ptr.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 |  | 
 | #include <string> | 
 |  | 
 | #include "base/threading/simple_thread.h" | 
 | #include "base/time/time.h" | 
 | #include "base/timer/elapsed_timer.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "testing/perf/perf_result_reporter.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kMetricPrefixWaitableEvent[] = "WaitableEvent."; | 
 | constexpr char kMetricWaitTime[] = "wait_time_per_sample"; | 
 | constexpr char kMetricSignalTime[] = "signal_time_per_sample"; | 
 | constexpr char kMetricElapsedCycles[] = "elapsed_cycles"; | 
 | constexpr char kStorySingleThread[] = "single_thread_1000_samples"; | 
 | constexpr char kStoryMultiThreadWaiter[] = "multi_thread_1000_samples_waiter"; | 
 | constexpr char kStoryMultiThreadSignaler[] = | 
 |     "multi_thread_1000_samples_signaler"; | 
 | constexpr char kStoryTimedThroughput[] = "timed_throughput"; | 
 |  | 
 | perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { | 
 |   perf_test::PerfResultReporter reporter(kMetricPrefixWaitableEvent, | 
 |                                          story_name); | 
 |   reporter.RegisterImportantMetric(kMetricWaitTime, "ns"); | 
 |   reporter.RegisterImportantMetric(kMetricSignalTime, "ns"); | 
 |   reporter.RegisterImportantMetric(kMetricElapsedCycles, "count"); | 
 |   return reporter; | 
 | } | 
 |  | 
 | class TraceWaitableEvent { | 
 |  public: | 
 |   TraceWaitableEvent() = default; | 
 |  | 
 |   TraceWaitableEvent(const TraceWaitableEvent&) = delete; | 
 |   TraceWaitableEvent& operator=(const TraceWaitableEvent&) = delete; | 
 |  | 
 |   ~TraceWaitableEvent() = default; | 
 |  | 
 |   void Signal() { | 
 |     ElapsedTimer timer; | 
 |     event_.Signal(); | 
 |     total_signal_time_ += timer.Elapsed(); | 
 |     ++signal_samples_; | 
 |   } | 
 |  | 
 |   void Wait() { | 
 |     ElapsedTimer timer; | 
 |     event_.Wait(); | 
 |     total_wait_time_ += timer.Elapsed(); | 
 |     ++wait_samples_; | 
 |   } | 
 |  | 
 |   bool TimedWaitUntil(const TimeTicks& end_time) { | 
 |     ElapsedTimer timer; | 
 |     const bool signaled = event_.TimedWait(end_time - timer.Begin()); | 
 |     total_wait_time_ += timer.Elapsed(); | 
 |     ++wait_samples_; | 
 |     return signaled; | 
 |   } | 
 |  | 
 |   bool IsSignaled() { return event_.IsSignaled(); } | 
 |  | 
 |   TimeDelta total_signal_time() const { return total_signal_time_; } | 
 |   TimeDelta total_wait_time() const { return total_wait_time_; } | 
 |   size_t signal_samples() const { return signal_samples_; } | 
 |   size_t wait_samples() const { return wait_samples_; } | 
 |  | 
 |  private: | 
 |   WaitableEvent event_{WaitableEvent::ResetPolicy::AUTOMATIC}; | 
 |  | 
 |   TimeDelta total_signal_time_; | 
 |   TimeDelta total_wait_time_; | 
 |  | 
 |   size_t signal_samples_ = 0U; | 
 |   size_t wait_samples_ = 0U; | 
 | }; | 
 |  | 
 | class SignalerThread : public SimpleThread { | 
 |  public: | 
 |   SignalerThread(TraceWaitableEvent* waiter, TraceWaitableEvent* signaler) | 
 |       : SimpleThread("WaitableEventPerfTest signaler"), | 
 |         waiter_(waiter), | 
 |         signaler_(signaler) {} | 
 |  | 
 |   SignalerThread(const SignalerThread&) = delete; | 
 |   SignalerThread& operator=(const SignalerThread&) = delete; | 
 |  | 
 |   ~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_; | 
 |   raw_ptr<TraceWaitableEvent> waiter_; | 
 |   raw_ptr<TraceWaitableEvent> signaler_; | 
 | }; | 
 |  | 
 | void PrintPerfWaitableEvent(const TraceWaitableEvent* event, | 
 |                             const std::string& story_name, | 
 |                             size_t* elapsed_cycles = nullptr) { | 
 |   auto reporter = SetUpReporter(story_name); | 
 |   reporter.AddResult( | 
 |       kMetricSignalTime, | 
 |       static_cast<size_t>(event->total_signal_time().InNanoseconds()) / | 
 |           event->signal_samples()); | 
 |   reporter.AddResult( | 
 |       kMetricWaitTime, | 
 |       static_cast<size_t>(event->total_wait_time().InNanoseconds()) / | 
 |           event->wait_samples()); | 
 |   if (elapsed_cycles) { | 
 |     reporter.AddResult(kMetricElapsedCycles, *elapsed_cycles); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(WaitableEventPerfTest, SingleThread) { | 
 |   const size_t kSamples = 1000; | 
 |  | 
 |   TraceWaitableEvent event; | 
 |  | 
 |   for (size_t i = 0; i < kSamples; ++i) { | 
 |     event.Signal(); | 
 |     event.Wait(); | 
 |   } | 
 |  | 
 |   PrintPerfWaitableEvent(&event, kStorySingleThread); | 
 | } | 
 |  | 
 | TEST(WaitableEventPerfTest, MultipleThreads) { | 
 |   const size_t kSamples = 1000; | 
 |  | 
 |   TraceWaitableEvent waiter; | 
 |   TraceWaitableEvent signaler; | 
 |  | 
 |   // 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, kStoryMultiThreadWaiter); | 
 |   PrintPerfWaitableEvent(&signaler, kStoryMultiThreadSignaler); | 
 | } | 
 |  | 
 | TEST(WaitableEventPerfTest, Throughput) { | 
 |   TraceWaitableEvent event; | 
 |  | 
 |   SignalerThread thread(nullptr, &event); | 
 |   thread.Start(); | 
 |  | 
 |   const TimeTicks end_time = TimeTicks::Now() + Seconds(1); | 
 |   size_t count = 0; | 
 |   while (event.TimedWaitUntil(end_time)) { | 
 |     ++count; | 
 |   } | 
 |  | 
 |   thread.RequestStop(); | 
 |   thread.Join(); | 
 |  | 
 |   PrintPerfWaitableEvent(&event, kStoryTimedThroughput, &count); | 
 | } | 
 |  | 
 | }  // namespace base |