xref: /aosp_15_r20/external/cronet/ipc/ipc_cpu_perftest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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