|  | // Copyright 2014 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 <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/format_macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/synchronization/condition_variable.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/time/time.h" | 
|  | #include "build_config.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "testing/perf/perf_test.h" | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | #include "base/android/java_handler_thread.h" | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | class ScheduleWorkTest : public testing::Test { | 
|  | public: | 
|  | ScheduleWorkTest() : counter_(0) {} | 
|  |  | 
|  | void SetUp() override { | 
|  | if (base::ThreadTicks::IsSupported()) | 
|  | base::ThreadTicks::WaitUntilInitialized(); | 
|  | } | 
|  |  | 
|  | void Increment(uint64_t amount) { counter_ += amount; } | 
|  |  | 
|  | void Schedule(int index) { | 
|  | base::TimeTicks start = base::TimeTicks::Now(); | 
|  | base::ThreadTicks thread_start; | 
|  | if (ThreadTicks::IsSupported()) | 
|  | thread_start = base::ThreadTicks::Now(); | 
|  | base::TimeDelta minimum = base::TimeDelta::Max(); | 
|  | base::TimeDelta maximum = base::TimeDelta(); | 
|  | base::TimeTicks now, lastnow = start; | 
|  | uint64_t schedule_calls = 0u; | 
|  | do { | 
|  | for (size_t i = 0; i < kBatchSize; ++i) { | 
|  | target_message_loop()->ScheduleWork(); | 
|  | schedule_calls++; | 
|  | } | 
|  | now = base::TimeTicks::Now(); | 
|  | base::TimeDelta laptime = now - lastnow; | 
|  | lastnow = now; | 
|  | minimum = std::min(minimum, laptime); | 
|  | maximum = std::max(maximum, laptime); | 
|  | } while (now - start < base::TimeDelta::FromSeconds(kTargetTimeSec)); | 
|  |  | 
|  | scheduling_times_[index] = now - start; | 
|  | if (ThreadTicks::IsSupported()) | 
|  | scheduling_thread_times_[index] = | 
|  | base::ThreadTicks::Now() - thread_start; | 
|  | min_batch_times_[index] = minimum; | 
|  | max_batch_times_[index] = maximum; | 
|  | target_message_loop()->task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&ScheduleWorkTest::Increment, | 
|  | base::Unretained(this), schedule_calls)); | 
|  | } | 
|  |  | 
|  | void ScheduleWork(MessageLoop::Type target_type, int num_scheduling_threads) { | 
|  | #if defined(OS_ANDROID) | 
|  | if (target_type == MessageLoop::TYPE_JAVA) { | 
|  | java_thread_.reset(new android::JavaHandlerThread("target")); | 
|  | java_thread_->Start(); | 
|  | } else | 
|  | #endif | 
|  | { | 
|  | target_.reset(new Thread("target")); | 
|  | target_->StartWithOptions(Thread::Options(target_type, 0u)); | 
|  |  | 
|  | // Without this, it's possible for the scheduling threads to start and run | 
|  | // before the target thread. In this case, the scheduling threads will | 
|  | // call target_message_loop()->ScheduleWork(), which dereferences the | 
|  | // loop's message pump, which is only created after the target thread has | 
|  | // finished starting. | 
|  | target_->WaitUntilThreadStarted(); | 
|  | } | 
|  |  | 
|  | std::vector<std::unique_ptr<Thread>> scheduling_threads; | 
|  | scheduling_times_.reset(new base::TimeDelta[num_scheduling_threads]); | 
|  | scheduling_thread_times_.reset(new base::TimeDelta[num_scheduling_threads]); | 
|  | min_batch_times_.reset(new base::TimeDelta[num_scheduling_threads]); | 
|  | max_batch_times_.reset(new base::TimeDelta[num_scheduling_threads]); | 
|  |  | 
|  | for (int i = 0; i < num_scheduling_threads; ++i) { | 
|  | scheduling_threads.push_back(std::make_unique<Thread>("posting thread")); | 
|  | scheduling_threads[i]->Start(); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < num_scheduling_threads; ++i) { | 
|  | scheduling_threads[i]->task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&ScheduleWorkTest::Schedule, | 
|  | base::Unretained(this), i)); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < num_scheduling_threads; ++i) { | 
|  | scheduling_threads[i]->Stop(); | 
|  | } | 
|  | #if defined(OS_ANDROID) | 
|  | if (target_type == MessageLoop::TYPE_JAVA) { | 
|  | java_thread_->Stop(); | 
|  | java_thread_.reset(); | 
|  | } else | 
|  | #endif | 
|  | { | 
|  | target_->Stop(); | 
|  | target_.reset(); | 
|  | } | 
|  | base::TimeDelta total_time; | 
|  | base::TimeDelta total_thread_time; | 
|  | base::TimeDelta min_batch_time = base::TimeDelta::Max(); | 
|  | base::TimeDelta max_batch_time = base::TimeDelta(); | 
|  | for (int i = 0; i < num_scheduling_threads; ++i) { | 
|  | total_time += scheduling_times_[i]; | 
|  | total_thread_time += scheduling_thread_times_[i]; | 
|  | min_batch_time = std::min(min_batch_time, min_batch_times_[i]); | 
|  | max_batch_time = std::max(max_batch_time, max_batch_times_[i]); | 
|  | } | 
|  | std::string trace = StringPrintf( | 
|  | "%d_threads_scheduling_to_%s_pump", | 
|  | num_scheduling_threads, | 
|  | target_type == MessageLoop::TYPE_IO | 
|  | ? "io" | 
|  | : (target_type == MessageLoop::TYPE_UI ? "ui" : "default")); | 
|  | perf_test::PrintResult( | 
|  | "task", | 
|  | "", | 
|  | trace, | 
|  | total_time.InMicroseconds() / static_cast<double>(counter_), | 
|  | "us/task", | 
|  | true); | 
|  | perf_test::PrintResult( | 
|  | "task", | 
|  | "_min_batch_time", | 
|  | trace, | 
|  | min_batch_time.InMicroseconds() / static_cast<double>(kBatchSize), | 
|  | "us/task", | 
|  | false); | 
|  | perf_test::PrintResult( | 
|  | "task", | 
|  | "_max_batch_time", | 
|  | trace, | 
|  | max_batch_time.InMicroseconds() / static_cast<double>(kBatchSize), | 
|  | "us/task", | 
|  | false); | 
|  | if (ThreadTicks::IsSupported()) { | 
|  | perf_test::PrintResult( | 
|  | "task", | 
|  | "_thread_time", | 
|  | trace, | 
|  | total_thread_time.InMicroseconds() / static_cast<double>(counter_), | 
|  | "us/task", | 
|  | true); | 
|  | } | 
|  | } | 
|  |  | 
|  | MessageLoop* target_message_loop() { | 
|  | #if defined(OS_ANDROID) | 
|  | if (java_thread_) | 
|  | return java_thread_->message_loop(); | 
|  | #endif | 
|  | return target_->message_loop(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<Thread> target_; | 
|  | #if defined(OS_ANDROID) | 
|  | std::unique_ptr<android::JavaHandlerThread> java_thread_; | 
|  | #endif | 
|  | std::unique_ptr<base::TimeDelta[]> scheduling_times_; | 
|  | std::unique_ptr<base::TimeDelta[]> scheduling_thread_times_; | 
|  | std::unique_ptr<base::TimeDelta[]> min_batch_times_; | 
|  | std::unique_ptr<base::TimeDelta[]> max_batch_times_; | 
|  | uint64_t counter_; | 
|  |  | 
|  | static const size_t kTargetTimeSec = 5; | 
|  | static const size_t kBatchSize = 1000; | 
|  | }; | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToIOFromOneThread) { | 
|  | ScheduleWork(MessageLoop::TYPE_IO, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToIOFromTwoThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_IO, 2); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToIOFromFourThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_IO, 4); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToUIFromOneThread) { | 
|  | ScheduleWork(MessageLoop::TYPE_UI, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToUIFromTwoThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_UI, 2); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToUIFromFourThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_UI, 4); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromOneThread) { | 
|  | ScheduleWork(MessageLoop::TYPE_DEFAULT, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromTwoThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_DEFAULT, 2); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromFourThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_DEFAULT, 4); | 
|  | } | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromOneThread) { | 
|  | ScheduleWork(MessageLoop::TYPE_JAVA, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromTwoThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_JAVA, 2); | 
|  | } | 
|  |  | 
|  | TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromFourThreads) { | 
|  | ScheduleWork(MessageLoop::TYPE_JAVA, 4); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | class FakeMessagePump : public MessagePump { | 
|  | public: | 
|  | FakeMessagePump() = default; | 
|  | ~FakeMessagePump() override = default; | 
|  |  | 
|  | void Run(Delegate* delegate) override {} | 
|  |  | 
|  | void Quit() override {} | 
|  | void ScheduleWork() override {} | 
|  | void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override {} | 
|  | }; | 
|  |  | 
|  | class PostTaskTest : public testing::Test { | 
|  | public: | 
|  | void Run(int batch_size, int tasks_per_reload) { | 
|  | base::TimeTicks start = base::TimeTicks::Now(); | 
|  | base::TimeTicks now; | 
|  | MessageLoop loop(std::unique_ptr<MessagePump>(new FakeMessagePump)); | 
|  | scoped_refptr<internal::IncomingTaskQueue> queue( | 
|  | new internal::IncomingTaskQueue(&loop)); | 
|  | uint32_t num_posted = 0; | 
|  | do { | 
|  | for (int i = 0; i < batch_size; ++i) { | 
|  | for (int j = 0; j < tasks_per_reload; ++j) { | 
|  | queue->AddToIncomingQueue(FROM_HERE, DoNothing(), base::TimeDelta(), | 
|  | Nestable::kNonNestable); | 
|  | num_posted++; | 
|  | } | 
|  | TaskQueue loop_local_queue; | 
|  | queue->ReloadWorkQueue(&loop_local_queue); | 
|  | while (!loop_local_queue.empty()) { | 
|  | PendingTask t = std::move(loop_local_queue.front()); | 
|  | loop_local_queue.pop(); | 
|  | loop.RunTask(&t); | 
|  | } | 
|  | } | 
|  |  | 
|  | now = base::TimeTicks::Now(); | 
|  | } while (now - start < base::TimeDelta::FromSeconds(5)); | 
|  | std::string trace = StringPrintf("%d_tasks_per_reload", tasks_per_reload); | 
|  | perf_test::PrintResult( | 
|  | "task", | 
|  | "", | 
|  | trace, | 
|  | (now - start).InMicroseconds() / static_cast<double>(num_posted), | 
|  | "us/task", | 
|  | true); | 
|  | queue->WillDestroyCurrentMessageLoop(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(PostTaskTest, OneTaskPerReload) { | 
|  | Run(10000, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(PostTaskTest, TenTasksPerReload) { | 
|  | Run(10000, 10); | 
|  | } | 
|  |  | 
|  | TEST_F(PostTaskTest, OneHundredTasksPerReload) { | 
|  | Run(1000, 100); | 
|  | } | 
|  |  | 
|  | }  // namespace base |