// 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 #include "base/message_loop/message_loop.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/test/perf_log.h" #include "base/timer/timer.h" #include "ipc/ipc_channel_proxy.h" #include "ipc/ipc_perftest_messages.h" #include "ipc/ipc_perftest_util.h" #include "ipc/ipc_sync_channel.h" #include "ipc/ipc_test.mojom.h" #include "ipc/ipc_test_base.h" #include "mojo/edk/test/mojo_test_base.h" #include "mojo/edk/test/multiprocess_test_helper.h" #include "mojo/public/cpp/bindings/binding.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 GetDefaultTestParams() { std::vector 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::TimeDelta::FromSecondsD(1.0 / frames_per_second); } class PerfCpuLogger { public: explicit PerfCpuLogger(base::StringPiece test_name) : test_name_(test_name), process_metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()) { process_metrics_->GetPlatformIndependentCPUUsage(); } ~PerfCpuLogger() { double result = process_metrics_->GetPlatformIndependentCPUUsage(); base::LogPerfResult(test_name_.c_str(), result, "%"); } private: std::string test_name_; std::unique_ptr process_metrics_; DISALLOW_COPY_AND_ASSIGN(PerfCpuLogger); }; MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) { MojoPerfTestClient client; int rv = mojo::edk::test::MultiprocessTestHelper::RunClientMain( base::Bind(&MojoPerfTestClient::Run, base::Unretained(&client)), true /* pass_pipe_ownership_to_main */); base::RunLoop run_loop; run_loop.RunUntilIdle(); return rv; } class ChannelSteadyPingPongListener : public Listener { public: ChannelSteadyPingPongListener() = default; ~ChannelSteadyPingPongListener() override = default; void Init(Sender* sender) { DCHECK(!sender_); sender_ = sender; } void SetTestParams(const TestParams& params, const std::string& label, bool sync, const base::Closure& quit_closure) { params_ = params; label_ = label; sync_ = sync; quit_closure_ = quit_closure; payload_ = std::string(params.message_size, 'a'); } bool OnMessageReceived(const Message& message) override { CHECK(sender_); bool handled = true; IPC_BEGIN_MESSAGE_MAP(ChannelSteadyPingPongListener, message) IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello) IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void OnHello() { cpu_logger_ = std::make_unique(GetLogTitle(label_, params_)); frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds; timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this, &ChannelSteadyPingPongListener::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; sender_->Send(new TestMsg_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 { SendPong(); } count_down_ = params_.messages_per_frame; } } void StopPingPong() { cpu_logger_.reset(); timer_.AbandonAndStop(); quit_closure_.Run(); } void OnPing(const std::string& payload) { // Include message deserialization in latency. DCHECK_EQ(payload_.size(), payload.size()); CHECK_GT(count_down_, 0); count_down_--; if (count_down_ > 0) { SendPong(); } else { CHECK_GT(frame_count_down_, 0); frame_count_down_--; if (frame_count_down_ == 0) StopPingPong(); } } void SendPong() { sender_->Send(new TestMsg_Ping(payload_)); } private: Sender* sender_ = nullptr; TestParams params_; std::string payload_; std::string label_; bool sync_ = false; int count_down_ = 0; int frame_count_down_ = 0; base::RepeatingTimer timer_; std::unique_ptr cpu_logger_; base::Closure quit_closure_; }; class ChannelSteadyPingPongTest : public IPCChannelMojoTestBase { public: ChannelSteadyPingPongTest() = default; ~ChannelSteadyPingPongTest() override = default; void RunPingPongServer(const std::string& label, bool sync) { Init("MojoPerfTestClient"); // Set up IPC channel and start client. ChannelSteadyPingPongListener listener; std::unique_ptr channel_proxy; std::unique_ptr shutdown_event; if (sync) { shutdown_event = std::make_unique( base::WaitableEvent::ResetPolicy::MANUAL, base::WaitableEvent::InitialState::NOT_SIGNALED); channel_proxy = IPC::SyncChannel::Create( TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener, GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get(), false, shutdown_event.get()); } else { channel_proxy = IPC::ChannelProxy::Create( TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener, GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get()); } listener.Init(channel_proxy.get()); LockThreadAffinity thread_locker(kSharedCore); std::vector params_list = GetDefaultTestParams(); for (const auto& params : params_list) { base::RunLoop run_loop; listener.SetTestParams(params, label, sync, run_loop.QuitWhenIdleClosure()); // This initial message will kick-start the ping-pong of messages. channel_proxy->Send(new TestMsg_Hello); run_loop.Run(); } // Send quit message. channel_proxy->Send(new TestMsg_Quit); EXPECT_TRUE(WaitForClientShutdown()); channel_proxy.reset(); } }; TEST_F(ChannelSteadyPingPongTest, AsyncPingPong) { RunPingPongServer("IPC_CPU_Async", false); } TEST_F(ChannelSteadyPingPongTest, SyncPingPong) { RunPingPongServer("IPC_CPU_Sync", true); } class MojoSteadyPingPongTest : public mojo::edk::test::MojoTestBase { public: MojoSteadyPingPongTest() = default; 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(IPC::mojom::ReflectorPtrInfo(std::move(scoped_mp), 0u)); LockThreadAffinity thread_locker(kSharedCore); std::vector params_list = GetDefaultTestParams(); for (const auto& params : params_list) { params_ = params; payload_ = std::string(params.message_size, 'a'); ping_receiver_->Ping("hello", base::Bind(&MojoSteadyPingPongTest::OnHello, base::Unretained(this))); base::RunLoop run_loop; quit_closure_ = run_loop.QuitWhenIdleClosure(); run_loop.Run(); } ping_receiver_->Quit(); ignore_result(ping_receiver_.PassInterface().PassHandle().release()); } void OnHello(const std::string& value) { cpu_logger_ = std::make_unique(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_.AbandonAndStop(); 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::Bind(&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; IPC::mojom::ReflectorPtr ping_receiver_; int count_down_ = 0; int frame_count_down_ = 0; base::RepeatingTimer timer_; std::unique_ptr cpu_logger_; base::Closure quit_closure_; DISALLOW_COPY_AND_ASSIGN(MojoSteadyPingPongTest); }; DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoSteadyPingPongTest, h) { base::MessageLoop main_message_loop; return RunPingPongClient(h); } // Similar to ChannelSteadyPingPongTest above, but uses a Mojo interface // instead of raw IPC::Messages. TEST_F(MojoSteadyPingPongTest, AsyncPingPong) { RunTestClient("PingPongClient", [&](MojoHandle h) { base::MessageLoop main_message_loop; RunPingPongServer(h, "Mojo_CPU_Async", false); }); } TEST_F(MojoSteadyPingPongTest, SyncPingPong) { RunTestClient("PingPongClient", [&](MojoHandle h) { base::MessageLoop main_message_loop; RunPingPongServer(h, "Mojo_CPU_Sync", true); }); } } // namespace } // namespace IPC