1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6 #include <string_view>
7 #include <tuple>
8
9 #include "base/check_op.h"
10 #include "base/functional/bind.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/process/process_metrics.h"
13 #include "base/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/task/single_thread_task_runner.h"
17 #include "base/test/perf_log.h"
18 #include "base/test/task_environment.h"
19 #include "base/timer/timer.h"
20 #include "base/types/expected.h"
21 #include "ipc/ipc_channel_proxy.h"
22 #include "ipc/ipc_perftest_messages.h"
23 #include "ipc/ipc_perftest_util.h"
24 #include "ipc/ipc_sync_channel.h"
25 #include "ipc/ipc_test.mojom.h"
26 #include "ipc/ipc_test_base.h"
27 #include "mojo/core/test/mojo_test_base.h"
28 #include "mojo/core/test/multiprocess_test_helper.h"
29 #include "mojo/public/cpp/bindings/pending_remote.h"
30 #include "mojo/public/cpp/bindings/remote.h"
31 #include "mojo/public/cpp/system/message_pipe.h"
32
33 namespace IPC {
34 namespace {
35
36 struct TestParams {
37 TestParams() = default;
TestParamsIPC::__anon4420150f0111::TestParams38 TestParams(size_t in_message_size,
39 size_t in_frames_per_second,
40 size_t in_messages_per_frame,
41 size_t in_duration_in_seconds)
42 : message_size(in_message_size),
43 frames_per_second(in_frames_per_second),
44 messages_per_frame(in_messages_per_frame),
45 duration_in_seconds(in_duration_in_seconds) {}
46
47 size_t message_size;
48 size_t frames_per_second;
49 size_t messages_per_frame;
50 size_t duration_in_seconds;
51 };
52
GetDefaultTestParams()53 std::vector<TestParams> GetDefaultTestParams() {
54 std::vector<TestParams> list;
55 list.push_back({144, 20, 10, 10});
56 list.push_back({144, 60, 10, 10});
57 return list;
58 }
59
GetLogTitle(const std::string & label,const TestParams & params)60 std::string GetLogTitle(const std::string& label, const TestParams& params) {
61 return base::StringPrintf(
62 "%s_MsgSize_%zu_FrmPerSec_%zu_MsgPerFrm_%zu", label.c_str(),
63 params.message_size, params.frames_per_second, params.messages_per_frame);
64 }
65
GetFrameTime(size_t frames_per_second)66 base::TimeDelta GetFrameTime(size_t frames_per_second) {
67 return base::Seconds(1.0 / frames_per_second);
68 }
69
70 class PerfCpuLogger {
71 public:
PerfCpuLogger(std::string_view test_name)72 explicit PerfCpuLogger(std::string_view test_name)
73 : test_name_(test_name),
74 process_metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()) {
75 // Query the CPU usage once to start the recording interval.
76 const double inital_cpu_usage =
77 process_metrics_->GetPlatformIndependentCPUUsage().value_or(-1.0);
78 // This should have been the first call so the reported cpu usage should be
79 // exactly zero.
80 DCHECK_EQ(inital_cpu_usage, 0.0);
81 }
82
83 PerfCpuLogger(const PerfCpuLogger&) = delete;
84 PerfCpuLogger& operator=(const PerfCpuLogger&) = delete;
85
~PerfCpuLogger()86 ~PerfCpuLogger() {
87 const double result =
88 process_metrics_->GetPlatformIndependentCPUUsage().value_or(-1.0);
89 base::LogPerfResult(test_name_.c_str(), result, "%");
90 }
91
92 private:
93 std::string test_name_;
94 std::unique_ptr<base::ProcessMetrics> process_metrics_;
95 };
96
MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain)97 MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) {
98 MojoPerfTestClient client;
99 int rv = mojo::core::test::MultiprocessTestHelper::RunClientMain(
100 base::BindOnce(&MojoPerfTestClient::Run, base::Unretained(&client)),
101 true /* pass_pipe_ownership_to_main */);
102
103 base::RunLoop run_loop;
104 run_loop.RunUntilIdle();
105
106 return rv;
107 }
108
109 class ChannelSteadyPingPongListener : public Listener {
110 public:
111 ChannelSteadyPingPongListener() = default;
112
113 ~ChannelSteadyPingPongListener() override = default;
114
Init(Sender * sender)115 void Init(Sender* sender) {
116 DCHECK(!sender_);
117 sender_ = sender;
118 }
119
SetTestParams(const TestParams & params,const std::string & label,bool sync,base::OnceClosure quit_closure)120 void SetTestParams(const TestParams& params,
121 const std::string& label,
122 bool sync,
123 base::OnceClosure quit_closure) {
124 params_ = params;
125 label_ = label;
126 sync_ = sync;
127 quit_closure_ = std::move(quit_closure);
128 payload_ = std::string(params.message_size, 'a');
129 }
130
OnMessageReceived(const Message & message)131 bool OnMessageReceived(const Message& message) override {
132 CHECK(sender_);
133
134 bool handled = true;
135 IPC_BEGIN_MESSAGE_MAP(ChannelSteadyPingPongListener, message)
136 IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
137 IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
138 IPC_MESSAGE_UNHANDLED(handled = false)
139 IPC_END_MESSAGE_MAP()
140 return handled;
141 }
142
OnHello()143 void OnHello() {
144 cpu_logger_ = std::make_unique<PerfCpuLogger>(GetLogTitle(label_, params_));
145
146 frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds;
147
148 timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this,
149 &ChannelSteadyPingPongListener::StartPingPong);
150 }
151
StartPingPong()152 void StartPingPong() {
153 if (sync_) {
154 base::TimeTicks before = base::TimeTicks::Now();
155 for (count_down_ = params_.messages_per_frame; count_down_ > 0;
156 --count_down_) {
157 std::string response;
158 sender_->Send(new TestMsg_SyncPing(payload_, &response));
159 DCHECK_EQ(response, payload_);
160 }
161
162 if (base::TimeTicks::Now() - before >
163 GetFrameTime(params_.frames_per_second)) {
164 LOG(ERROR) << "Frame " << frame_count_down_
165 << " wasn't able to complete on time!";
166 }
167
168 CHECK_GT(frame_count_down_, 0);
169 frame_count_down_--;
170 if (frame_count_down_ == 0)
171 StopPingPong();
172 } else {
173 if (count_down_ != 0) {
174 LOG(ERROR) << "Frame " << frame_count_down_
175 << " wasn't able to complete on time!";
176 } else {
177 SendPong();
178 }
179 count_down_ = params_.messages_per_frame;
180 }
181 }
182
StopPingPong()183 void StopPingPong() {
184 cpu_logger_.reset();
185 timer_.AbandonAndStop();
186 std::move(quit_closure_).Run();
187 }
188
OnPing(const std::string & payload)189 void OnPing(const std::string& payload) {
190 // Include message deserialization in latency.
191 DCHECK_EQ(payload_.size(), payload.size());
192
193 CHECK_GT(count_down_, 0);
194 count_down_--;
195 if (count_down_ > 0) {
196 SendPong();
197 } else {
198 CHECK_GT(frame_count_down_, 0);
199 frame_count_down_--;
200 if (frame_count_down_ == 0)
201 StopPingPong();
202 }
203 }
204
SendPong()205 void SendPong() { sender_->Send(new TestMsg_Ping(payload_)); }
206
207 private:
208 raw_ptr<Sender> sender_ = nullptr;
209 TestParams params_;
210 std::string payload_;
211 std::string label_;
212 bool sync_ = false;
213
214 int count_down_ = 0;
215 int frame_count_down_ = 0;
216
217 base::RepeatingTimer timer_;
218 std::unique_ptr<PerfCpuLogger> cpu_logger_;
219
220 base::OnceClosure quit_closure_;
221 };
222
223 class ChannelSteadyPingPongTest : public IPCChannelMojoTestBase {
224 public:
225 ChannelSteadyPingPongTest() = default;
226 ~ChannelSteadyPingPongTest() override = default;
227
RunPingPongServer(const std::string & label,bool sync)228 void RunPingPongServer(const std::string& label, bool sync) {
229 Init("MojoPerfTestClient");
230
231 // Set up IPC channel and start client.
232 ChannelSteadyPingPongListener listener;
233
234 std::unique_ptr<ChannelProxy> channel_proxy;
235 std::unique_ptr<base::WaitableEvent> shutdown_event;
236
237 if (sync) {
238 shutdown_event = std::make_unique<base::WaitableEvent>(
239 base::WaitableEvent::ResetPolicy::MANUAL,
240 base::WaitableEvent::InitialState::NOT_SIGNALED);
241 channel_proxy = IPC::SyncChannel::Create(
242 TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
243 GetIOThreadTaskRunner(),
244 base::SingleThreadTaskRunner::GetCurrentDefault(), false,
245 shutdown_event.get());
246 } else {
247 channel_proxy = IPC::ChannelProxy::Create(
248 TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
249 GetIOThreadTaskRunner(),
250 base::SingleThreadTaskRunner::GetCurrentDefault());
251 }
252 listener.Init(channel_proxy.get());
253
254 LockThreadAffinity thread_locker(kSharedCore);
255 std::vector<TestParams> params_list = GetDefaultTestParams();
256 for (const auto& params : params_list) {
257 base::RunLoop run_loop;
258
259 listener.SetTestParams(params, label, sync,
260 run_loop.QuitWhenIdleClosure());
261
262 // This initial message will kick-start the ping-pong of messages.
263 channel_proxy->Send(new TestMsg_Hello);
264
265 run_loop.Run();
266 }
267
268 // Send quit message.
269 channel_proxy->Send(new TestMsg_Quit);
270
271 EXPECT_TRUE(WaitForClientShutdown());
272 channel_proxy.reset();
273 }
274 };
275
TEST_F(ChannelSteadyPingPongTest,AsyncPingPong)276 TEST_F(ChannelSteadyPingPongTest, AsyncPingPong) {
277 RunPingPongServer("IPC_CPU_Async", false);
278 }
279
TEST_F(ChannelSteadyPingPongTest,SyncPingPong)280 TEST_F(ChannelSteadyPingPongTest, SyncPingPong) {
281 RunPingPongServer("IPC_CPU_Sync", true);
282 }
283
284 class MojoSteadyPingPongTest : public mojo::core::test::MojoTestBase {
285 public:
286 MojoSteadyPingPongTest() = default;
287
288 MojoSteadyPingPongTest(const MojoSteadyPingPongTest&) = delete;
289 MojoSteadyPingPongTest& operator=(const MojoSteadyPingPongTest&) = delete;
290
291 protected:
RunPingPongServer(MojoHandle mp,const std::string & label,bool sync)292 void RunPingPongServer(MojoHandle mp, const std::string& label, bool sync) {
293 label_ = label;
294 sync_ = sync;
295
296 mojo::MessagePipeHandle mp_handle(mp);
297 mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
298 ping_receiver_.Bind(
299 mojo::PendingRemote<IPC::mojom::Reflector>(std::move(scoped_mp), 0u));
300
301 LockThreadAffinity thread_locker(kSharedCore);
302 std::vector<TestParams> params_list = GetDefaultTestParams();
303 for (const auto& params : params_list) {
304 params_ = params;
305 payload_ = std::string(params.message_size, 'a');
306
307 ping_receiver_->Ping("hello",
308 base::BindOnce(&MojoSteadyPingPongTest::OnHello,
309 base::Unretained(this)));
310 base::RunLoop run_loop;
311 quit_closure_ = run_loop.QuitWhenIdleClosure();
312 run_loop.Run();
313 }
314
315 ping_receiver_->Quit();
316
317 std::ignore = ping_receiver_.Unbind().PassPipe().release();
318 }
319
OnHello(const std::string & value)320 void OnHello(const std::string& value) {
321 cpu_logger_ = std::make_unique<PerfCpuLogger>(GetLogTitle(label_, params_));
322
323 frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds;
324
325 timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this,
326 &MojoSteadyPingPongTest::StartPingPong);
327 }
328
StartPingPong()329 void StartPingPong() {
330 if (sync_) {
331 base::TimeTicks before = base::TimeTicks::Now();
332 for (count_down_ = params_.messages_per_frame; count_down_ > 0;
333 --count_down_) {
334 std::string response;
335 ping_receiver_->SyncPing(payload_, &response);
336 DCHECK_EQ(response, payload_);
337 }
338
339 if (base::TimeTicks::Now() - before >
340 GetFrameTime(params_.frames_per_second)) {
341 LOG(ERROR) << "Frame " << frame_count_down_
342 << " wasn't able to complete on time!";
343 }
344
345 CHECK_GT(frame_count_down_, 0);
346 frame_count_down_--;
347 if (frame_count_down_ == 0)
348 StopPingPong();
349 } else {
350 if (count_down_ != 0) {
351 LOG(ERROR) << "Frame " << frame_count_down_
352 << " wasn't able to complete on time!";
353 } else {
354 SendPing();
355 }
356 count_down_ = params_.messages_per_frame;
357 }
358 }
359
StopPingPong()360 void StopPingPong() {
361 cpu_logger_.reset();
362 timer_.AbandonAndStop();
363 std::move(quit_closure_).Run();
364 }
365
OnPong(const std::string & value)366 void OnPong(const std::string& value) {
367 // Include message deserialization in latency.
368 DCHECK_EQ(payload_.size(), value.size());
369
370 CHECK_GT(count_down_, 0);
371 count_down_--;
372 if (count_down_ > 0) {
373 SendPing();
374 } else {
375 CHECK_GT(frame_count_down_, 0);
376 frame_count_down_--;
377 if (frame_count_down_ == 0)
378 StopPingPong();
379 }
380 }
381
SendPing()382 void SendPing() {
383 ping_receiver_->Ping(payload_,
384 base::BindOnce(&MojoSteadyPingPongTest::OnPong,
385 base::Unretained(this)));
386 }
387
RunPingPongClient(MojoHandle mp)388 static int RunPingPongClient(MojoHandle mp) {
389 mojo::MessagePipeHandle mp_handle(mp);
390 mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
391
392 LockThreadAffinity thread_locker(kSharedCore);
393 base::RunLoop run_loop;
394 ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure());
395 run_loop.Run();
396 return 0;
397 }
398
399 private:
400 TestParams params_;
401 std::string payload_;
402 std::string label_;
403 bool sync_ = false;
404
405 mojo::Remote<IPC::mojom::Reflector> ping_receiver_;
406
407 int count_down_ = 0;
408 int frame_count_down_ = 0;
409
410 base::RepeatingTimer timer_;
411 std::unique_ptr<PerfCpuLogger> cpu_logger_;
412
413 base::OnceClosure quit_closure_;
414 };
415
DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient,MojoSteadyPingPongTest,h)416 DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoSteadyPingPongTest, h) {
417 base::test::SingleThreadTaskEnvironment task_environment;
418 return RunPingPongClient(h);
419 }
420
421 // Similar to ChannelSteadyPingPongTest above, but uses a Mojo interface
422 // instead of raw IPC::Messages.
TEST_F(MojoSteadyPingPongTest,AsyncPingPong)423 TEST_F(MojoSteadyPingPongTest, AsyncPingPong) {
424 RunTestClient("PingPongClient", [&](MojoHandle h) {
425 base::test::SingleThreadTaskEnvironment task_environment;
426 RunPingPongServer(h, "Mojo_CPU_Async", false);
427 });
428 }
429
TEST_F(MojoSteadyPingPongTest,SyncPingPong)430 TEST_F(MojoSteadyPingPongTest, SyncPingPong) {
431 RunTestClient("PingPongClient", [&](MojoHandle h) {
432 base::test::SingleThreadTaskEnvironment task_environment;
433 RunPingPongServer(h, "Mojo_CPU_Sync", true);
434 });
435 }
436
437 } // namespace
438 } // namespace IPC
439