|  | // 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 <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/atomicops.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/synchronization/atomic_flag.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/threading/sequenced_task_runner_handle.h" | 
|  | #include "base/time/time.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "testing/perf/perf_test.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // A thread that waits for the caller to signal an event before proceeding to | 
|  | // call Action::Run(). | 
|  | class PostingThread { | 
|  | public: | 
|  | class Action { | 
|  | public: | 
|  | virtual ~Action() = default; | 
|  |  | 
|  | // Called after the thread is started and |start_event_| is signalled. | 
|  | virtual void Run() = 0; | 
|  |  | 
|  | protected: | 
|  | Action() = default; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(Action); | 
|  | }; | 
|  |  | 
|  | // Creates a PostingThread where the thread waits on |start_event| before | 
|  | // calling action->Run(). If a thread is returned, the thread is guaranteed to | 
|  | // be allocated and running and the caller must call Join() before destroying | 
|  | // the PostingThread. | 
|  | static std::unique_ptr<PostingThread> Create(WaitableEvent* start_event, | 
|  | std::unique_ptr<Action> action) { | 
|  | auto posting_thread = | 
|  | WrapUnique(new PostingThread(start_event, std::move(action))); | 
|  |  | 
|  | if (!posting_thread->Start()) | 
|  | return nullptr; | 
|  |  | 
|  | return posting_thread; | 
|  | } | 
|  |  | 
|  | ~PostingThread() { DCHECK_EQ(!thread_handle_.is_null(), join_called_); } | 
|  |  | 
|  | void Join() { | 
|  | PlatformThread::Join(thread_handle_); | 
|  | join_called_ = true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | class Delegate final : public PlatformThread::Delegate { | 
|  | public: | 
|  | Delegate(PostingThread* outer, std::unique_ptr<Action> action) | 
|  | : outer_(outer), action_(std::move(action)) { | 
|  | DCHECK(outer_); | 
|  | DCHECK(action_); | 
|  | } | 
|  |  | 
|  | ~Delegate() override = default; | 
|  |  | 
|  | private: | 
|  | void ThreadMain() override { | 
|  | outer_->thread_started_.Signal(); | 
|  | outer_->start_event_->Wait(); | 
|  | action_->Run(); | 
|  | } | 
|  |  | 
|  | PostingThread* const outer_; | 
|  | const std::unique_ptr<Action> action_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(Delegate); | 
|  | }; | 
|  |  | 
|  | PostingThread(WaitableEvent* start_event, std::unique_ptr<Action> delegate) | 
|  | : start_event_(start_event), | 
|  | thread_started_(WaitableEvent::ResetPolicy::MANUAL, | 
|  | WaitableEvent::InitialState::NOT_SIGNALED), | 
|  | delegate_(this, std::move(delegate)) { | 
|  | DCHECK(start_event_); | 
|  | } | 
|  |  | 
|  | bool Start() { | 
|  | bool thread_created = | 
|  | PlatformThread::Create(0, &delegate_, &thread_handle_); | 
|  | if (thread_created) | 
|  | thread_started_.Wait(); | 
|  |  | 
|  | return thread_created; | 
|  | } | 
|  |  | 
|  | bool join_called_ = false; | 
|  | WaitableEvent* const start_event_; | 
|  | WaitableEvent thread_started_; | 
|  | Delegate delegate_; | 
|  |  | 
|  | PlatformThreadHandle thread_handle_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(PostingThread); | 
|  | }; | 
|  |  | 
|  | class MessageLoopPerfTest : public ::testing::TestWithParam<int> { | 
|  | public: | 
|  | MessageLoopPerfTest() | 
|  | : message_loop_task_runner_(SequencedTaskRunnerHandle::Get()), | 
|  | run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL, | 
|  | WaitableEvent::InitialState::NOT_SIGNALED) {} | 
|  |  | 
|  | static std::string ParamInfoToString( | 
|  | ::testing::TestParamInfo<int> param_info) { | 
|  | return PostingThreadCountToString(param_info.param); | 
|  | } | 
|  |  | 
|  | static std::string PostingThreadCountToString(int posting_threads) { | 
|  | // Special case 1 thread for thread vs threads. | 
|  | if (posting_threads == 1) | 
|  | return "1_Posting_Thread"; | 
|  |  | 
|  | return StringPrintf("%d_Posting_Threads", posting_threads); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | class ContinuouslyPostTasks final : public PostingThread::Action { | 
|  | public: | 
|  | ContinuouslyPostTasks(MessageLoopPerfTest* outer) : outer_(outer) { | 
|  | DCHECK(outer_); | 
|  | } | 
|  | ~ContinuouslyPostTasks() override = default; | 
|  |  | 
|  | private: | 
|  | void Run() override { | 
|  | RepeatingClosure task_to_run = | 
|  | BindRepeating([](size_t* num_tasks_run) { ++*num_tasks_run; }, | 
|  | &outer_->num_tasks_run_); | 
|  | while (!outer_->stop_posting_threads_.IsSet()) { | 
|  | outer_->message_loop_task_runner_->PostTask(FROM_HERE, task_to_run); | 
|  | subtle::NoBarrier_AtomicIncrement(&outer_->num_tasks_posted_, 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | MessageLoopPerfTest* const outer_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(ContinuouslyPostTasks); | 
|  | }; | 
|  |  | 
|  | void SetUp() override { | 
|  | // This check is here because we can't ASSERT_TRUE in the constructor. | 
|  | ASSERT_TRUE(message_loop_task_runner_); | 
|  | } | 
|  |  | 
|  | // Runs ActionType::Run() on |num_posting_threads| and requests test | 
|  | // termination around |duration|. | 
|  | template <typename ActionType> | 
|  | void RunTest(const int num_posting_threads, TimeDelta duration) { | 
|  | std::vector<std::unique_ptr<PostingThread>> threads; | 
|  | for (int i = 0; i < num_posting_threads; ++i) { | 
|  | threads.emplace_back(PostingThread::Create( | 
|  | &run_posting_threads_, std::make_unique<ActionType>(this))); | 
|  | // Don't assert here to simplify the code that requires a Join() call for | 
|  | // every created PostingThread. | 
|  | EXPECT_TRUE(threads[i]); | 
|  | } | 
|  |  | 
|  | RunLoop run_loop; | 
|  | message_loop_task_runner_->PostDelayedTask( | 
|  | FROM_HERE, | 
|  | BindOnce( | 
|  | [](RunLoop* run_loop, AtomicFlag* stop_posting_threads) { | 
|  | stop_posting_threads->Set(); | 
|  | run_loop->Quit(); | 
|  | }, | 
|  | &run_loop, &stop_posting_threads_), | 
|  | duration); | 
|  |  | 
|  | TimeTicks post_task_start = TimeTicks::Now(); | 
|  | run_posting_threads_.Signal(); | 
|  |  | 
|  | TimeTicks run_loop_start = TimeTicks::Now(); | 
|  | run_loop.Run(); | 
|  | tasks_run_duration_ = TimeTicks::Now() - run_loop_start; | 
|  |  | 
|  | for (auto& thread : threads) | 
|  | thread->Join(); | 
|  |  | 
|  | tasks_posted_duration_ = TimeTicks::Now() - post_task_start; | 
|  | } | 
|  |  | 
|  | size_t num_tasks_posted() const { | 
|  | return subtle::NoBarrier_Load(&num_tasks_posted_); | 
|  | } | 
|  |  | 
|  | TimeDelta tasks_posted_duration() const { return tasks_posted_duration_; } | 
|  |  | 
|  | size_t num_tasks_run() const { return num_tasks_run_; } | 
|  |  | 
|  | TimeDelta tasks_run_duration() const { return tasks_run_duration_; } | 
|  |  | 
|  | private: | 
|  | MessageLoop message_loop_; | 
|  |  | 
|  | // Accessed on multiple threads, thread-safe or constant: | 
|  | const scoped_refptr<SequencedTaskRunner> message_loop_task_runner_; | 
|  | WaitableEvent run_posting_threads_; | 
|  | AtomicFlag stop_posting_threads_; | 
|  | subtle::AtomicWord num_tasks_posted_ = 0; | 
|  |  | 
|  | // Accessed only on the test case thread: | 
|  | TimeDelta tasks_posted_duration_; | 
|  | TimeDelta tasks_run_duration_; | 
|  | size_t num_tasks_run_ = 0; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MessageLoopPerfTest); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_P(MessageLoopPerfTest, PostTaskRate) { | 
|  | // Measures the average rate of posting tasks from different threads and the | 
|  | // average rate that the message loop is running those tasks. | 
|  | RunTest<ContinuouslyPostTasks>(GetParam(), TimeDelta::FromSeconds(3)); | 
|  | perf_test::PrintResult("task_posting", "", | 
|  | PostingThreadCountToString(GetParam()), | 
|  | tasks_posted_duration().InMicroseconds() / | 
|  | static_cast<double>(num_tasks_posted()), | 
|  | "us/task", true); | 
|  | perf_test::PrintResult("task_running", "", | 
|  | PostingThreadCountToString(GetParam()), | 
|  | tasks_run_duration().InMicroseconds() / | 
|  | static_cast<double>(num_tasks_run()), | 
|  | "us/task", true); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | MessageLoopPerfTest, | 
|  | ::testing::Values(1, 5, 10), | 
|  | MessageLoopPerfTest::ParamInfoToString); | 
|  | }  // namespace base |