| // 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 <memory> | 
 | #include <string_view> | 
 | #include <tuple> | 
 |  | 
 | #include "base/check_op.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/process/process_metrics.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/test/perf_log.h" | 
 | #include "base/test/task_environment.h" | 
 | #include "base/timer/timer.h" | 
 | #include "base/types/expected.h" | 
 | #include "ipc/ipc_channel_proxy.h" | 
 | #include "ipc/ipc_perftest_util.h" | 
 | #include "ipc/ipc_sync_channel.h" | 
 | #include "ipc/ipc_test.test-mojom.h" | 
 | #include "ipc/ipc_test_base.h" | 
 | #include "mojo/core/test/mojo_test_base.h" | 
 | #include "mojo/core/test/multiprocess_test_helper.h" | 
 | #include "mojo/public/cpp/bindings/pending_remote.h" | 
 | #include "mojo/public/cpp/bindings/remote.h" | 
 | #include "mojo/public/cpp/system/message_pipe.h" | 
 |  | 
 | namespace IPC { | 
 | namespace { | 
 |  | 
 | struct TestParams { | 
 |   TestParams() = default; | 
 |   TestParams(size_t in_message_size, | 
 |              size_t in_frames_per_second, | 
 |              size_t in_messages_per_frame, | 
 |              size_t in_duration_in_seconds) | 
 |       : message_size(in_message_size), | 
 |         frames_per_second(in_frames_per_second), | 
 |         messages_per_frame(in_messages_per_frame), | 
 |         duration_in_seconds(in_duration_in_seconds) {} | 
 |  | 
 |   size_t message_size; | 
 |   size_t frames_per_second; | 
 |   size_t messages_per_frame; | 
 |   size_t duration_in_seconds; | 
 | }; | 
 |  | 
 | std::vector<TestParams> GetDefaultTestParams() { | 
 |   std::vector<TestParams> list; | 
 |   list.push_back({144, 20, 10, 10}); | 
 |   list.push_back({144, 60, 10, 10}); | 
 |   return list; | 
 | } | 
 |  | 
 | std::string GetLogTitle(const std::string& label, const TestParams& params) { | 
 |   return base::StringPrintf( | 
 |       "%s_MsgSize_%zu_FrmPerSec_%zu_MsgPerFrm_%zu", label.c_str(), | 
 |       params.message_size, params.frames_per_second, params.messages_per_frame); | 
 | } | 
 |  | 
 | base::TimeDelta GetFrameTime(size_t frames_per_second) { | 
 |   return base::Seconds(1.0 / frames_per_second); | 
 | } | 
 |  | 
 | class PerfCpuLogger { | 
 |  public: | 
 |   explicit PerfCpuLogger(std::string_view test_name) | 
 |       : test_name_(test_name), | 
 |         process_metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()) { | 
 |     // Query the CPU usage once to start the recording interval. | 
 |     const double inital_cpu_usage = | 
 |         process_metrics_->GetPlatformIndependentCPUUsage().value_or(-1.0); | 
 |     // This should have been the first call so the reported cpu usage should be | 
 |     // exactly zero. | 
 |     DCHECK_EQ(inital_cpu_usage, 0.0); | 
 |   } | 
 |  | 
 |   PerfCpuLogger(const PerfCpuLogger&) = delete; | 
 |   PerfCpuLogger& operator=(const PerfCpuLogger&) = delete; | 
 |  | 
 |   ~PerfCpuLogger() { | 
 |     const double result = | 
 |         process_metrics_->GetPlatformIndependentCPUUsage().value_or(-1.0); | 
 |     base::LogPerfResult(test_name_.c_str(), result, "%"); | 
 |   } | 
 |  | 
 |  private: | 
 |   std::string test_name_; | 
 |   std::unique_ptr<base::ProcessMetrics> process_metrics_; | 
 | }; | 
 |  | 
 | MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) { | 
 |   MojoPerfTestClient client; | 
 |   int rv = mojo::core::test::MultiprocessTestHelper::RunClientMain( | 
 |       base::BindOnce(&MojoPerfTestClient::Run, base::Unretained(&client)), | 
 |       true /* pass_pipe_ownership_to_main */); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   run_loop.RunUntilIdle(); | 
 |  | 
 |   return rv; | 
 | } | 
 |  | 
 | class MojoSteadyPingPongTest : public mojo::core::test::MojoTestBase { | 
 |  public: | 
 |   MojoSteadyPingPongTest() = default; | 
 |  | 
 |   MojoSteadyPingPongTest(const MojoSteadyPingPongTest&) = delete; | 
 |   MojoSteadyPingPongTest& operator=(const MojoSteadyPingPongTest&) = delete; | 
 |  | 
 |  protected: | 
 |   void RunPingPongServer(MojoHandle mp, const std::string& label, bool sync) { | 
 |     label_ = label; | 
 |     sync_ = sync; | 
 |  | 
 |     mojo::MessagePipeHandle mp_handle(mp); | 
 |     mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); | 
 |     ping_receiver_.Bind( | 
 |         mojo::PendingRemote<IPC::mojom::Reflector>(std::move(scoped_mp), 0u)); | 
 |  | 
 |     LockThreadAffinity thread_locker(kSharedCore); | 
 |     std::vector<TestParams> params_list = GetDefaultTestParams(); | 
 |     for (const auto& params : params_list) { | 
 |       params_ = params; | 
 |       payload_ = std::string(params.message_size, 'a'); | 
 |  | 
 |       ping_receiver_->Ping("hello", | 
 |                            base::BindOnce(&MojoSteadyPingPongTest::OnHello, | 
 |                                           base::Unretained(this))); | 
 |       base::RunLoop run_loop; | 
 |       quit_closure_ = run_loop.QuitWhenIdleClosure(); | 
 |       run_loop.Run(); | 
 |     } | 
 |  | 
 |     ping_receiver_->Quit(); | 
 |  | 
 |     std::ignore = ping_receiver_.Unbind().PassPipe().release(); | 
 |   } | 
 |  | 
 |   void OnHello(const std::string& value) { | 
 |     cpu_logger_ = std::make_unique<PerfCpuLogger>(GetLogTitle(label_, params_)); | 
 |  | 
 |     frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds; | 
 |  | 
 |     timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this, | 
 |                  &MojoSteadyPingPongTest::StartPingPong); | 
 |   } | 
 |  | 
 |   void StartPingPong() { | 
 |     if (sync_) { | 
 |       base::TimeTicks before = base::TimeTicks::Now(); | 
 |       for (count_down_ = params_.messages_per_frame; count_down_ > 0; | 
 |            --count_down_) { | 
 |         std::string response; | 
 |         ping_receiver_->SyncPing(payload_, &response); | 
 |         DCHECK_EQ(response, payload_); | 
 |       } | 
 |  | 
 |       if (base::TimeTicks::Now() - before > | 
 |           GetFrameTime(params_.frames_per_second)) { | 
 |         LOG(ERROR) << "Frame " << frame_count_down_ | 
 |                    << " wasn't able to complete on time!"; | 
 |       } | 
 |  | 
 |       CHECK_GT(frame_count_down_, 0); | 
 |       frame_count_down_--; | 
 |       if (frame_count_down_ == 0) | 
 |         StopPingPong(); | 
 |     } else { | 
 |       if (count_down_ != 0) { | 
 |         LOG(ERROR) << "Frame " << frame_count_down_ | 
 |                    << " wasn't able to complete on time!"; | 
 |       } else { | 
 |         SendPing(); | 
 |       } | 
 |       count_down_ = params_.messages_per_frame; | 
 |     } | 
 |   } | 
 |  | 
 |   void StopPingPong() { | 
 |     cpu_logger_.reset(); | 
 |     timer_.Stop(); | 
 |     std::move(quit_closure_).Run(); | 
 |   } | 
 |  | 
 |   void OnPong(const std::string& value) { | 
 |     // Include message deserialization in latency. | 
 |     DCHECK_EQ(payload_.size(), value.size()); | 
 |  | 
 |     CHECK_GT(count_down_, 0); | 
 |     count_down_--; | 
 |     if (count_down_ > 0) { | 
 |       SendPing(); | 
 |     } else { | 
 |       CHECK_GT(frame_count_down_, 0); | 
 |       frame_count_down_--; | 
 |       if (frame_count_down_ == 0) | 
 |         StopPingPong(); | 
 |     } | 
 |   } | 
 |  | 
 |   void SendPing() { | 
 |     ping_receiver_->Ping(payload_, | 
 |                          base::BindOnce(&MojoSteadyPingPongTest::OnPong, | 
 |                                         base::Unretained(this))); | 
 |   } | 
 |  | 
 |   static int RunPingPongClient(MojoHandle mp) { | 
 |     mojo::MessagePipeHandle mp_handle(mp); | 
 |     mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); | 
 |  | 
 |     LockThreadAffinity thread_locker(kSharedCore); | 
 |     base::RunLoop run_loop; | 
 |     ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure()); | 
 |     run_loop.Run(); | 
 |     return 0; | 
 |   } | 
 |  | 
 |  private: | 
 |   TestParams params_; | 
 |   std::string payload_; | 
 |   std::string label_; | 
 |   bool sync_ = false; | 
 |  | 
 |   mojo::Remote<IPC::mojom::Reflector> ping_receiver_; | 
 |  | 
 |   int count_down_ = 0; | 
 |   int frame_count_down_ = 0; | 
 |  | 
 |   base::RepeatingTimer timer_; | 
 |   std::unique_ptr<PerfCpuLogger> cpu_logger_; | 
 |  | 
 |   base::OnceClosure quit_closure_; | 
 | }; | 
 |  | 
 | DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoSteadyPingPongTest, h) { | 
 |   base::test::SingleThreadTaskEnvironment task_environment; | 
 |   base::test::ScopedRunLoopTimeout increased_timeout( | 
 |       FROM_HERE, TestTimeouts::action_max_timeout()); | 
 |   return RunPingPongClient(h); | 
 | } | 
 |  | 
 | TEST_F(MojoSteadyPingPongTest, AsyncPingPong) { | 
 |   RunTestClient("PingPongClient", [&](MojoHandle h) { | 
 |     base::test::SingleThreadTaskEnvironment task_environment; | 
 |     base::test::ScopedRunLoopTimeout increased_timeout( | 
 |         FROM_HERE, TestTimeouts::action_max_timeout()); | 
 |     RunPingPongServer(h, "Mojo_CPU_Async", false); | 
 |   }); | 
 | } | 
 |  | 
 | TEST_F(MojoSteadyPingPongTest, SyncPingPong) { | 
 |   RunTestClient("PingPongClient", [&](MojoHandle h) { | 
 |     base::test::SingleThreadTaskEnvironment task_environment; | 
 |     base::test::ScopedRunLoopTimeout increased_timeout( | 
 |         FROM_HERE, TestTimeouts::action_max_timeout()); | 
 |     RunPingPongServer(h, "Mojo_CPU_Sync", true); | 
 |   }); | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace IPC |