blob: 39cf7bc2b20e8af39c0d3e2dc7514b18f3e8e660 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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