// Copyright 2014 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/memory/ptr_util.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_time_logger.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "build/build_config.h" #include "ipc/ipc_channel_mojo.h" #include "ipc/ipc_perftest_messages.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/embedder/embedder.h" #include "mojo/core/test/mojo_test_base.h" #include "mojo/core/test/multiprocess_test_helper.h" #include "mojo/public/cpp/bindings/associated_receiver_set.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/system/message_pipe.h" namespace IPC { namespace { constexpr base::TimeDelta kLongTestTimeout = base::Seconds(80); class PingPongTestParams { public: PingPongTestParams(size_t size, int count) : message_size_(size), message_count_(count) {} size_t message_size() const { return message_size_; } int message_count() const { return message_count_; } private: size_t message_size_; int message_count_; }; class InterfacePassingTestParams { public: InterfacePassingTestParams(size_t rounds, size_t num_interfaces) : rounds_(rounds), num_interfaces_(num_interfaces) {} size_t rounds() const { return rounds_; } size_t num_interfaces() const { return num_interfaces_; } private: size_t rounds_; size_t num_interfaces_; }; #ifdef NDEBUG const int kMultiplier = 100; #else // Debug builds on Windows run these tests orders of magnitude more slowly. const int kMultiplier = 1; #endif std::vector GetDefaultTestParams() { // Test several sizes. We use 12^N for message size, and limit the message // count to keep the test duration reasonable. std::vector list; list.push_back(PingPongTestParams(12, 500 * kMultiplier)); list.push_back(PingPongTestParams(144, 500 * kMultiplier)); list.push_back(PingPongTestParams(1728, 500 * kMultiplier)); list.push_back(PingPongTestParams(20736, 120 * kMultiplier)); list.push_back(PingPongTestParams(248832, 10 * kMultiplier)); return list; } std::vector GetDefaultInterfacePassingTestParams() { std::vector list; list.push_back({500 * kMultiplier, 0}); list.push_back({500 * kMultiplier, 1}); list.push_back({500 * kMultiplier, 2}); list.push_back({500 * kMultiplier, 4}); list.push_back({500 * kMultiplier, 8}); return list; } class MojoInterfacePerfTest : public mojo::core::test::MojoTestBase { public: MojoInterfacePerfTest() : message_count_(0), count_down_(0) {} MojoInterfacePerfTest(const MojoInterfacePerfTest&) = delete; MojoInterfacePerfTest& operator=(const MojoInterfacePerfTest&) = delete; protected: void RunPingPongServer(MojoHandle mp, const std::string& label) { label_ = label; mojo::MessagePipeHandle mp_handle(mp); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); ping_receiver_.Bind( mojo::PendingRemote(std::move(scoped_mp), 0u)); LockThreadAffinity thread_locker(kSharedCore); std::vector params = GetDefaultTestParams(); for (size_t i = 0; i < params.size(); i++) { base::RunLoop loop; ping_receiver_->Ping( "hello", base::BindOnce(&MojoInterfacePerfTest::OnPong, base::Unretained(this), loop.QuitWhenIdleClosure())); message_count_ = count_down_ = params[i].message_count(); payload_ = std::string(params[i].message_size(), 'a'); loop.Run(); } ping_receiver_->Quit(); std::ignore = ping_receiver_.Unbind().PassPipe().release(); } void OnPong(base::OnceClosure quit_closure, const std::string& value) { if (value == "hello") { DCHECK(!perf_logger_.get()); std::string test_name = base::StringPrintf("IPC_%s_Perf_%dx_%zu", label_.c_str(), message_count_, payload_.size()); perf_logger_ = std::make_unique(test_name.c_str()); } else { DCHECK_EQ(payload_.size(), value.size()); CHECK(count_down_ > 0); count_down_--; if (count_down_ == 0) { perf_logger_.reset(); if (!quit_closure.is_null()) { std::move(quit_closure).Run(); } return; } } if (sync_) { for (int i = 0; i < count_down_; ++i) { std::string response; ping_receiver_->SyncPing(payload_, &response); DCHECK_EQ(response, payload_); } perf_logger_.reset(); if (!quit_closure.is_null()) { std::move(quit_closure).Run(); } } else { ping_receiver_->Ping( payload_, base::BindOnce(&MojoInterfacePerfTest::OnPong, base::Unretained(this), std::move(quit_closure))); } } static int RunPingPongClient(MojoHandle mp) { mojo::MessagePipeHandle mp_handle(mp); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); LockThreadAffinity thread_locker(kSharedCore); // In single process mode, this is running in a task and by default other // tasks (in particular, the binding) won't run. To keep the single process // and multi-process code paths the same, enable nestable tasks. base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure()); run_loop.Run(); return 0; } bool sync_ = false; private: int message_count_; int count_down_; std::string label_; std::string payload_; mojo::Remote ping_receiver_; std::unique_ptr perf_logger_; }; class InterfacePassingTestDriverImpl : public mojom::InterfacePassingTestDriver, public mojom::PingReceiver { public: InterfacePassingTestDriverImpl(mojo::ScopedMessagePipeHandle handle, base::OnceClosure quit_closure) : receiver_(this, mojo::PendingReceiver( std::move(handle))), quit_closure_(std::move(quit_closure)) {} ~InterfacePassingTestDriverImpl() override { std::ignore = receiver_.Unbind().PassPipe().release(); } private: // mojom::InterfacePassingTestDriver implementation: void Init(InitCallback callback) override { std::move(callback).Run(); } void GetPingReceiver( std::vector> receivers, GetPingReceiverCallback callback) override { for (auto& receiver : receivers) ping_receiver_receivers_.Add(this, std::move(receiver)); ping_receiver_receivers_.Clear(); std::move(callback).Run(); } void GetAssociatedPingReceiver( std::vector> receivers, GetAssociatedPingReceiverCallback callback) override { for (auto& receiver : receivers) ping_receiver_associated_receivers_.Add(this, std::move(receiver)); ping_receiver_associated_receivers_.Clear(); std::move(callback).Run(); } void Quit() override { if (!quit_closure_.is_null()) { std::move(quit_closure_).Run(); } } // mojom::PingReceiver implementation: void Ping(PingCallback callback) override { std::move(callback).Run(); } mojo::ReceiverSet ping_receiver_receivers_; mojo::AssociatedReceiverSet ping_receiver_associated_receivers_; mojo::Receiver receiver_; base::OnceClosure quit_closure_; }; class MojoInterfacePassingPerfTest : public mojo::core::test::MojoTestBase { public: MojoInterfacePassingPerfTest() = default; MojoInterfacePassingPerfTest(const MojoInterfacePassingPerfTest&) = delete; MojoInterfacePassingPerfTest& operator=(const MojoInterfacePassingPerfTest&) = delete; protected: void RunInterfacePassingServer(MojoHandle mp, const std::string& label, bool associated) { label_ = label; associated_ = associated; mojo::MessagePipeHandle mp_handle(mp); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); driver_remote_.Bind(mojo::PendingRemote( std::move(scoped_mp), 0u)); auto params = GetDefaultInterfacePassingTestParams(); LockThreadAffinity thread_locker(kSharedCore); for (size_t i = 0; i < params.size(); ++i) { driver_remote_->Init( base::BindOnce(&MojoInterfacePassingPerfTest::OnInitCallback, base::Unretained(this))); rounds_ = count_down_ = params[i].rounds(); num_interfaces_ = params[i].num_interfaces(); base::RunLoop run_loop; quit_closure_ = run_loop.QuitWhenIdleClosure(); run_loop.Run(); } driver_remote_->Quit(); std::ignore = driver_remote_.Unbind().PassPipe().release(); } void OnInitCallback() { DCHECK(!perf_logger_.get()); std::string test_name = base::StringPrintf( "IPC_%s_Perf_%zux_%zu", label_.c_str(), rounds_, num_interfaces_); perf_logger_ = std::make_unique(test_name.c_str()); DoNextRound(); } void DoNextRound() { if (associated_) { std::vector> associated_remotes(num_interfaces_); std::vector> receivers(num_interfaces_); for (size_t i = 0; i < num_interfaces_; ++i) { receivers[i] = associated_remotes[i].BindNewEndpointAndPassReceiver(); // Force the interface pointer to do full initialization. associated_remotes[i].get(); } driver_remote_->GetAssociatedPingReceiver( std::move(receivers), base::BindOnce(&MojoInterfacePassingPerfTest::OnGetReceiverCallback, base::Unretained(this))); } else { std::vector> remotes(num_interfaces_); std::vector> receivers( num_interfaces_); for (size_t i = 0; i < num_interfaces_; ++i) { receivers[i] = remotes[i].BindNewPipeAndPassReceiver(); // Force the interface pointer to do full initialization. remotes[i].get(); } driver_remote_->GetPingReceiver( std::move(receivers), base::BindOnce(&MojoInterfacePassingPerfTest::OnGetReceiverCallback, base::Unretained(this))); } } void OnGetReceiverCallback() { CHECK_GT(count_down_, 0u); count_down_--; if (count_down_ == 0) { perf_logger_.reset(); if (!quit_closure_.is_null()) { std::move(quit_closure_).Run(); } return; } DoNextRound(); } static int RunInterfacePassingClient(MojoHandle mp) { mojo::MessagePipeHandle mp_handle(mp); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); LockThreadAffinity thread_locker(kSharedCore); // In single process mode, this is running in a task and by default other // tasks (in particular, the binding) won't run. To keep the single process // and multi-process code paths the same, enable nestable tasks. base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); InterfacePassingTestDriverImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure()); run_loop.Run(); return 0; } private: size_t rounds_ = 0; size_t count_down_ = 0; size_t num_interfaces_ = 0; std::string label_; bool associated_ = false; std::unique_ptr perf_logger_; mojo::Remote driver_remote_; base::OnceClosure quit_closure_; }; DEFINE_TEST_CLIENT_WITH_PIPE(InterfacePassingClient, MojoInterfacePassingPerfTest, h) { base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout(FROM_HERE, kLongTestTimeout); return RunInterfacePassingClient(h); } enum class InProcessMessageMode { kSerialized, kUnserialized, }; template class InProcessPerfTest : public TestBase, public testing::WithParamInterface { public: InProcessPerfTest() { switch (GetParam()) { case InProcessMessageMode::kSerialized: mojo::Connector::OverrideDefaultSerializationBehaviorForTesting( mojo::Connector::OutgoingSerializationMode::kEager, mojo::Connector::IncomingSerializationMode::kDispatchAsIs); break; case InProcessMessageMode::kUnserialized: mojo::Connector::OverrideDefaultSerializationBehaviorForTesting( mojo::Connector::OutgoingSerializationMode::kLazy, mojo::Connector::IncomingSerializationMode::kDispatchAsIs); break; } } }; using MojoInProcessInterfacePerfTest = InProcessPerfTest; using MojoInProcessInterfacePassingPerfTest = InProcessPerfTest; DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoInterfacePerfTest, h) { base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); return RunPingPongClient(h); } // Similar to MojoChannelPerfTest above, but uses a Mojo interface instead of // raw IPC::Messages. TEST_F(MojoInterfacePerfTest, MultiprocessPingPong) { RunTestClient("PingPongClient", [&](MojoHandle h) { base::test::SingleThreadTaskEnvironment task_environment; RunPingPongServer(h, "Multiprocess"); }); } TEST_F(MojoInterfacePerfTest, MultiprocessSyncPing) { sync_ = true; RunTestClient("PingPongClient", [&](MojoHandle h) { base::test::SingleThreadTaskEnvironment task_environment; RunPingPongServer(h, "MultiprocessSync"); }); } TEST_F(MojoInterfacePassingPerfTest, MultiprocessInterfacePassing) { RunTestClient("InterfacePassingClient", [&](MojoHandle h) { base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout(FROM_HERE, kLongTestTimeout); RunInterfacePassingServer(h, "InterfacePassing", false /* associated */); }); } TEST_F(MojoInterfacePassingPerfTest, MultiprocessAssociatedInterfacePassing) { RunTestClient("InterfacePassingClient", [&](MojoHandle h) { base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); RunInterfacePassingServer(h, "AssociatedInterfacePassing", true /* associated*/); }); } // A single process version of the above test. TEST_P(MojoInProcessInterfacePerfTest, MultiThreadPingPong) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::Thread client_thread("PingPongClient"); client_thread.Start(); client_thread.task_runner()->PostTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&RunPingPongClient), client_handle)); base::test::SingleThreadTaskEnvironment task_environment; RunPingPongServer(server_handle, "SingleProcess"); } TEST_P(MojoInProcessInterfacePerfTest, SingleThreadPingPong) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::test::SingleThreadTaskEnvironment task_environment; mojo::MessagePipeHandle mp_handle(client_handle); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); LockThreadAffinity thread_locker(kSharedCore); ReflectorImpl impl(std::move(scoped_mp), base::OnceClosure()); RunPingPongServer(server_handle, "SingleProcess"); } INSTANTIATE_TEST_SUITE_P(All, MojoInProcessInterfacePerfTest, testing::Values(InProcessMessageMode::kSerialized, InProcessMessageMode::kUnserialized)); TEST_P(MojoInProcessInterfacePassingPerfTest, MultiThreadInterfacePassing) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::Thread client_thread("InterfacePassingClient"); client_thread.Start(); client_thread.task_runner()->PostTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&RunInterfacePassingClient), client_handle)); base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); RunInterfacePassingServer(server_handle, "SingleProcess", false /* associated */); } TEST_P(MojoInProcessInterfacePassingPerfTest, MultiThreadAssociatedInterfacePassing) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::Thread client_thread("InterfacePassingClient"); client_thread.Start(); client_thread.task_runner()->PostTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&RunInterfacePassingClient), client_handle)); base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); RunInterfacePassingServer(server_handle, "SingleProcess", true /* associated */); } TEST_P(MojoInProcessInterfacePassingPerfTest, SingleThreadInterfacePassing) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); mojo::MessagePipeHandle mp_handle(client_handle); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); LockThreadAffinity thread_locker(kSharedCore); InterfacePassingTestDriverImpl impl(std::move(scoped_mp), base::OnceClosure()); RunInterfacePassingServer(server_handle, "SingleProcess", false /* associated */); } TEST_P(MojoInProcessInterfacePassingPerfTest, SingleThreadAssociatedInterfacePassing) { MojoHandle server_handle, client_handle; CreateMessagePipe(&server_handle, &client_handle); base::test::SingleThreadTaskEnvironment task_environment; base::test::ScopedRunLoopTimeout increased_timeout( FROM_HERE, TestTimeouts::action_max_timeout()); mojo::MessagePipeHandle mp_handle(client_handle); mojo::ScopedMessagePipeHandle scoped_mp(mp_handle); LockThreadAffinity thread_locker(kSharedCore); InterfacePassingTestDriverImpl impl(std::move(scoped_mp), base::OnceClosure()); RunInterfacePassingServer(server_handle, "SingleProcess", true /* associated */); } INSTANTIATE_TEST_SUITE_P(All, MojoInProcessInterfacePassingPerfTest, testing::Values(InProcessMessageMode::kSerialized, InProcessMessageMode::kUnserialized)); class CallbackPerfTest : public testing::Test { public: CallbackPerfTest() : client_thread_("PingPongClient"), message_count_(0), count_down_(0) {} CallbackPerfTest(const CallbackPerfTest&) = delete; CallbackPerfTest& operator=(const CallbackPerfTest&) = delete; protected: void RunMultiThreadPingPongServer() { client_thread_.Start(); LockThreadAffinity thread_locker(kSharedCore); std::vector params = GetDefaultTestParams(); for (size_t i = 0; i < params.size(); i++) { std::string hello("hello"); base::RunLoop loop; client_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::Ping, base::Unretained(this), hello, loop.QuitWhenIdleClosure())); message_count_ = count_down_ = params[i].message_count(); payload_ = std::string(params[i].message_size(), 'a'); loop.Run(); } } void Ping(const std::string& value, base::OnceClosure quit_closure) { task_environment_.GetMainThreadTaskRunner()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::OnPong, base::Unretained(this), value, std::move(quit_closure))); } void OnPong(const std::string& value, base::OnceClosure quit_closure) { if (value == "hello") { DCHECK(!perf_logger_.get()); std::string test_name = base::StringPrintf("Callback_MultiProcess_Perf_%dx_%zu", message_count_, payload_.size()); perf_logger_ = std::make_unique(test_name.c_str()); } else { DCHECK_EQ(payload_.size(), value.size()); CHECK(count_down_ > 0); count_down_--; if (count_down_ == 0) { perf_logger_.reset(); if (!quit_closure.is_null()) { std::move(quit_closure).Run(); } return; } } client_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::Ping, base::Unretained(this), payload_, std::move(quit_closure))); } void RunSingleThreadNoPostTaskPingPongServer() { LockThreadAffinity thread_locker(kSharedCore); std::vector params = GetDefaultTestParams(); base::RepeatingCallback)> ping = base::BindRepeating(&CallbackPerfTest::SingleThreadPingNoPostTask, base::Unretained(this)); for (size_t i = 0; i < params.size(); i++) { payload_ = std::string(params[i].message_size(), 'a'); std::string test_name = base::StringPrintf("Callback_SingleThreadNoPostTask_Perf_%dx_%zu", params[i].message_count(), payload_.size()); perf_logger_ = std::make_unique(test_name.c_str()); for (int j = 0; j < params[i].message_count(); ++j) { ping.Run(payload_, j, base::BindOnce(&CallbackPerfTest::SingleThreadPongNoPostTask, base::Unretained(this))); } perf_logger_.reset(); } } void SingleThreadPingNoPostTask( const std::string& value, int i, base::OnceCallback pong) { std::move(pong).Run(value, i); } void SingleThreadPongNoPostTask(const std::string& value, int i) {} void RunSingleThreadPostTaskPingPongServer() { LockThreadAffinity thread_locker(kSharedCore); std::vector params = GetDefaultTestParams(); for (size_t i = 0; i < params.size(); i++) { std::string hello("hello"); base::RunLoop loop; base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::SingleThreadPingPostTask, base::Unretained(this), hello, loop.QuitWhenIdleClosure())); message_count_ = count_down_ = params[i].message_count(); payload_ = std::string(params[i].message_size(), 'a'); loop.Run(); } } void SingleThreadPingPostTask(const std::string& value, base::OnceClosure quit_closure) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::SingleThreadPongPostTask, base::Unretained(this), value, std::move(quit_closure))); } void SingleThreadPongPostTask(const std::string& value, base::OnceClosure quit_closure) { if (value == "hello") { DCHECK(!perf_logger_.get()); std::string test_name = base::StringPrintf("Callback_SingleThreadPostTask_Perf_%dx_%zu", message_count_, payload_.size()); perf_logger_ = std::make_unique(test_name.c_str()); } else { DCHECK_EQ(payload_.size(), value.size()); CHECK(count_down_ > 0); count_down_--; if (count_down_ == 0) { perf_logger_.reset(); if (!quit_closure.is_null()) { std::move(quit_closure).Run(); } return; } } base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&CallbackPerfTest::SingleThreadPingPostTask, base::Unretained(this), payload_, std::move(quit_closure))); } private: base::Thread client_thread_; base::test::SingleThreadTaskEnvironment task_environment_; int message_count_; int count_down_; std::string payload_; std::unique_ptr perf_logger_; }; // Sends the same data as above using PostTask to a different thread instead of // IPCs for comparison. TEST_F(CallbackPerfTest, MultiThreadPingPong) { RunMultiThreadPingPongServer(); } // Sends the same data as above using PostTask to the same thread. TEST_F(CallbackPerfTest, SingleThreadPostTaskPingPong) { RunSingleThreadPostTaskPingPongServer(); } // Sends the same data as above without using PostTask to the same thread. TEST_F(CallbackPerfTest, SingleThreadNoPostTaskPingPong) { RunSingleThreadNoPostTaskPingPongServer(); } } // namespace } // namespace IPC