Add CPU usage perf tests for legacy IPC and Mojo.

These tests do async/sync ping-pongs between two processes for legacy IPC/Mojo.
They do it in a somewhat fixed-rate manner: split one second into a fixed
amount of "frames", and do a fixed amount of ping-pongs for each frame.

Current numbers:
Linux; release component build; Z620
Test name description:
 * Async / Sync: Whether ping-pong is done using async messages or sync messages.
 * MsgSize: Size of each message, in bytes.
 * FrmPerSec: Frames per second.
 * MsgPerFrm: How many messages sent and received (i.e., round trips) per frame.

Test result: The percentage of a CPU core used during the test, on average.
(It could be the sum of cost on multiple cores.)

[ RUN      ] ChannelSteadyPingPongTest.AsyncPingPong
IPC_CPU_Async_MsgSize_144_FrmPerSec_20_MsgPerFrm_10	5.68031	%
IPC_CPU_Async_MsgSize_144_FrmPerSec_60_MsgPerFrm_10	15.7017	%

[ RUN      ] ChannelSteadyPingPongTest.SyncPingPong
IPC_CPU_Sync_MsgSize_144_FrmPerSec_20_MsgPerFrm_10	5.68387	%
IPC_CPU_Sync_MsgSize_144_FrmPerSec_60_MsgPerFrm_10	15.4848	%

[ RUN      ] MojoSteadyPingPongTest.AsyncPingPong
Mojo_CPU_Async_MsgSize_144_FrmPerSec_20_MsgPerFrm_10	3.68818	%
Mojo_CPU_Async_MsgSize_144_FrmPerSec_60_MsgPerFrm_10	10.2393	%

[ RUN      ] MojoSteadyPingPongTest.SyncPingPong
Mojo_CPU_Sync_MsgSize_144_FrmPerSec_20_MsgPerFrm_10	3.69054	%
Mojo_CPU_Sync_MsgSize_144_FrmPerSec_60_MsgPerFrm_10	10.7317	%


Bug: 759845
Change-Id: Idc8139a90c3175193aab68a8fc91d1aeac1aa340
Reviewed-on: https://chromium-review.googlesource.com/639340
Reviewed-by: Tom Sepez <[email protected]>
Reviewed-by: John Abd-El-Malek <[email protected]>
Commit-Queue: Yuzhu Shen <[email protected]>
Cr-Commit-Position: refs/heads/master@{#500074}
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index 42621399..5753bd5 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -250,7 +250,12 @@
 
   test("ipc_perftests") {
     sources = [
+      "ipc_cpu_perftest.cc",
       "ipc_mojo_perftest.cc",
+      "ipc_perftest_messages.cc",
+      "ipc_perftest_messages.h",
+      "ipc_perftest_util.cc",
+      "ipc_perftest_util.h",
       "run_all_perftests.cc",
     ]
 
diff --git a/ipc/DEPS b/ipc/DEPS
index f8d30bb..662e341 100644
--- a/ipc/DEPS
+++ b/ipc/DEPS
@@ -14,7 +14,8 @@
     "+mojo/edk/embedder",
     "+mojo/edk/test",
   ],
-  "ipc_perftest_support\.cc": [
+  "ipc_.*perftest.*\.cc": [
+    "+mojo/edk/embedder",
     "+mojo/edk/test",
   ],
   "run_all_(unit|perf)tests\.cc": [
diff --git a/ipc/OWNERS b/ipc/OWNERS
index 0617e7bf..22545c9 100644
--- a/ipc/OWNERS
+++ b/ipc/OWNERS
@@ -8,6 +8,8 @@
 # new sandbox escapes.
 per-file ipc_message_start.h=set noparent
 per-file ipc_message_start.h=file://ipc/SECURITY_OWNERS
+per-file *_messages.cc=set noparent
+per-file *_messages.cc=file://ipc/SECURITY_OWNERS
 per-file *_messages*.h=set noparent
 per-file *_messages*.h=file://ipc/SECURITY_OWNERS
 per-file *.mojom=set noparent
diff --git a/ipc/ipc_cpu_perftest.cc b/ipc/ipc_cpu_perftest.cc
new file mode 100644
index 0000000..976ca1f
--- /dev/null
+++ b/ipc/ipc_cpu_perftest.cc
@@ -0,0 +1,416 @@
+// 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 <memory>
+
+#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() {}
+  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::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<base::ProcessMetrics> 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<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,
+                 &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<PerfCpuLogger> 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<ChannelProxy> channel_proxy;
+    std::unique_ptr<base::WaitableEvent> shutdown_event;
+
+    if (sync) {
+      shutdown_event = std::make_unique<base::WaitableEvent>(
+          base::WaitableEvent::ResetPolicy::MANUAL,
+          base::WaitableEvent::InitialState::NOT_SIGNALED);
+      channel_proxy = IPC::SyncChannel::Create(
+          TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+          GetIOThreadTaskRunner(), false, shutdown_event.get());
+    } else {
+      channel_proxy = IPC::ChannelProxy::Create(
+          TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+          GetIOThreadTaskRunner());
+    }
+    listener.Init(channel_proxy.get());
+
+    LockThreadAffinity thread_locker(kSharedCore);
+    std::vector<TestParams> 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<TestParams> 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<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_.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<PerfCpuLogger> 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
diff --git a/ipc/ipc_mojo_perftest.cc b/ipc/ipc_mojo_perftest.cc
index 45f5203..b7cea2a9 100644
--- a/ipc/ipc_mojo_perftest.cc
+++ b/ipc/ipc_mojo_perftest.cc
@@ -15,6 +15,8 @@
 #include "base/threading/thread.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.mojom.h"
 #include "ipc/ipc_test_base.h"
@@ -27,25 +29,9 @@
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
-#define IPC_MESSAGE_IMPL
-#include "ipc/ipc_message_macros.h"
-
-#define IPC_MESSAGE_START TestMsgStart
-
-IPC_MESSAGE_CONTROL0(TestMsg_Hello)
-IPC_MESSAGE_CONTROL0(TestMsg_Quit)
-IPC_MESSAGE_CONTROL1(TestMsg_Ping, std::string)
-IPC_SYNC_MESSAGE_CONTROL1_1(TestMsg_SyncPing, std::string, std::string)
-
 namespace IPC {
 namespace {
 
-scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() {
-  scoped_refptr<base::TaskRunner> runner = mojo::edk::GetIOTaskRunner();
-  return scoped_refptr<base::SingleThreadTaskRunner>(
-      static_cast<base::SingleThreadTaskRunner*>(runner.get()));
-}
-
 class PerformanceChannelListener : public Listener {
  public:
   explicit PerformanceChannelListener(const std::string& label)
@@ -136,102 +122,6 @@
   std::unique_ptr<base::PerfTimeLogger> perf_logger_;
 };
 
-// This channel listener just replies to all messages with the exact same
-// message. It assumes each message has one string parameter. When the string
-// "quit" is sent, it will exit.
-class ChannelReflectorListener : public Listener {
- public:
-  ChannelReflectorListener() : channel_(NULL) {
-    VLOG(1) << "Client listener up";
-  }
-
-  ~ChannelReflectorListener() override { VLOG(1) << "Client listener down"; }
-
-  void Init(Sender* channel) {
-    DCHECK(!channel_);
-    channel_ = channel;
-  }
-
-  bool OnMessageReceived(const Message& message) override {
-    CHECK(channel_);
-    bool handled = true;
-    IPC_BEGIN_MESSAGE_MAP(ChannelReflectorListener, message)
-      IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
-      IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
-      IPC_MESSAGE_HANDLER(TestMsg_SyncPing, OnSyncPing)
-      IPC_MESSAGE_HANDLER(TestMsg_Quit, OnQuit)
-      IPC_MESSAGE_UNHANDLED(handled = false)
-    IPC_END_MESSAGE_MAP()
-    return handled;
-  }
-
-  void OnHello() { channel_->Send(new TestMsg_Hello); }
-
-  void OnPing(const std::string& payload) {
-    channel_->Send(new TestMsg_Ping(payload));
-  }
-
-  void OnSyncPing(const std::string& payload, std::string* response) {
-    *response = payload;
-  }
-
-  void OnQuit() { base::RunLoop::QuitCurrentWhenIdleDeprecated(); }
-
-  void Send(IPC::Message* message) { channel_->Send(message); }
-
- private:
-  Sender* channel_;
-};
-
-// This class locks the current thread to a particular CPU core. This is
-// important because otherwise the different threads and processes of these
-// tests end up on different CPU cores which means that all of the cores are
-// lightly loaded so the OS (Windows and Linux) fails to ramp up the CPU
-// frequency, leading to unpredictable and often poor performance.
-class LockThreadAffinity {
- public:
-  explicit LockThreadAffinity(int cpu_number) : affinity_set_ok_(false) {
-#if defined(OS_WIN)
-    const DWORD_PTR thread_mask = static_cast<DWORD_PTR>(1) << cpu_number;
-    old_affinity_ = SetThreadAffinityMask(GetCurrentThread(), thread_mask);
-    affinity_set_ok_ = old_affinity_ != 0;
-#elif defined(OS_LINUX)
-    cpu_set_t cpuset;
-    CPU_ZERO(&cpuset);
-    CPU_SET(cpu_number, &cpuset);
-    auto get_result = sched_getaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
-    DCHECK_EQ(0, get_result);
-    auto set_result = sched_setaffinity(0, sizeof(cpuset), &cpuset);
-    // Check for get_result failure, even though it should always succeed.
-    affinity_set_ok_ = (set_result == 0) && (get_result == 0);
-#endif
-    if (!affinity_set_ok_)
-      LOG(WARNING) << "Failed to set thread affinity to CPU " << cpu_number;
-  }
-
-  ~LockThreadAffinity() {
-    if (!affinity_set_ok_)
-      return;
-#if defined(OS_WIN)
-    auto set_result = SetThreadAffinityMask(GetCurrentThread(), old_affinity_);
-    DCHECK_NE(0u, set_result);
-#elif defined(OS_LINUX)
-    auto set_result = sched_setaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
-    DCHECK_EQ(0, set_result);
-#endif
-  }
-
- private:
-  bool affinity_set_ok_;
-#if defined(OS_WIN)
-  DWORD_PTR old_affinity_;
-#elif defined(OS_LINUX)
-  cpu_set_t old_cpuset_;
-#endif
-
-  DISALLOW_COPY_AND_ASSIGN(LockThreadAffinity);
-};
-
 class PingPongTestParams {
  public:
   PingPongTestParams(size_t size, int count)
@@ -287,10 +177,6 @@
   return list;
 }
 
-// Avoid core 0 due to conflicts with Intel's Power Gadget.
-// Setting thread affinity will fail harmlessly on single/dual core machines.
-const int kSharedCore = 2;
-
 class MojoChannelPerfTest : public IPCChannelMojoTestBase {
  public:
   MojoChannelPerfTest() = default;
@@ -374,34 +260,6 @@
   run_loop.RunUntilIdle();
 }
 
-class MojoPerfTestClient {
- public:
-  MojoPerfTestClient() : listener_(new ChannelReflectorListener()) {
-    mojo::edk::test::MultiprocessTestHelper::ChildSetup();
-  }
-
-  ~MojoPerfTestClient() = default;
-
-  int Run(MojoHandle handle) {
-    handle_ = mojo::MakeScopedHandle(mojo::MessagePipeHandle(handle));
-    LockThreadAffinity thread_locker(kSharedCore);
-
-    std::unique_ptr<ChannelProxy> channel =
-        IPC::ChannelProxy::Create(handle_.release(), Channel::MODE_CLIENT,
-                                  listener_.get(), GetIOThreadTaskRunner());
-    listener_->Init(channel.get());
-
-    base::RunLoop().Run();
-    return 0;
-  }
-
- private:
-  base::MessageLoop main_message_loop_;
-  std::unique_ptr<ChannelReflectorListener> listener_;
-  std::unique_ptr<Channel> channel_;
-  mojo::ScopedMessagePipeHandle handle_;
-};
-
 MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) {
   MojoPerfTestClient client;
   int rv = mojo::edk::test::MultiprocessTestHelper::RunClientMain(
@@ -414,29 +272,6 @@
   return rv;
 }
 
-class ReflectorImpl : public IPC::mojom::Reflector {
- public:
-  explicit ReflectorImpl(mojo::ScopedMessagePipeHandle handle)
-      : binding_(this, IPC::mojom::ReflectorRequest(std::move(handle))) {}
-  ~ReflectorImpl() override {
-    ignore_result(binding_.Unbind().PassMessagePipe().release());
-  }
-
- private:
-  // IPC::mojom::Reflector:
-  void Ping(const std::string& value, PingCallback callback) override {
-    std::move(callback).Run(value);
-  }
-
-  void SyncPing(const std::string& value, PingCallback callback) override {
-    std::move(callback).Run(value);
-  }
-
-  void Quit() override { base::RunLoop::QuitCurrentWhenIdleDeprecated(); }
-
-  mojo::Binding<IPC::mojom::Reflector> binding_;
-};
-
 class MojoInterfacePerfTest : public mojo::edk::test::MojoTestBase {
  public:
   MojoInterfacePerfTest() : message_count_(0), count_down_(0) {}
@@ -509,8 +344,9 @@
         base::MessageLoop::current());
 
     LockThreadAffinity thread_locker(kSharedCore);
-    ReflectorImpl impl(std::move(scoped_mp));
-    base::RunLoop().Run();
+    base::RunLoop run_loop;
+    ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure());
+    run_loop.Run();
     return 0;
   }
 
@@ -798,7 +634,7 @@
   mojo::MessagePipeHandle mp_handle(client_handle);
   mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
   LockThreadAffinity thread_locker(kSharedCore);
-  ReflectorImpl impl(std::move(scoped_mp));
+  ReflectorImpl impl(std::move(scoped_mp), base::Closure());
 
   RunPingPongServer(server_handle, "SingleProcess");
 }
diff --git a/ipc/ipc_perftest_messages.cc b/ipc/ipc_perftest_messages.cc
new file mode 100644
index 0000000..4a84dae
--- /dev/null
+++ b/ipc/ipc_perftest_messages.cc
@@ -0,0 +1,7 @@
+// 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_perftest_messages.h"
diff --git a/ipc/ipc_perftest_messages.h b/ipc/ipc_perftest_messages.h
new file mode 100644
index 0000000..1dc0890
--- /dev/null
+++ b/ipc/ipc_perftest_messages.h
@@ -0,0 +1,16 @@
+// 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.
+
+// Multiply-included message header, no traditional include guard.
+
+#include <string>
+
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+IPC_MESSAGE_CONTROL0(TestMsg_Hello)
+IPC_MESSAGE_CONTROL0(TestMsg_Quit)
+IPC_MESSAGE_CONTROL1(TestMsg_Ping, std::string)
+IPC_SYNC_MESSAGE_CONTROL1_1(TestMsg_SyncPing, std::string, std::string)
diff --git a/ipc/ipc_perftest_util.cc b/ipc/ipc_perftest_util.cc
new file mode 100644
index 0000000..57f054d
--- /dev/null
+++ b/ipc/ipc_perftest_util.cc
@@ -0,0 +1,145 @@
+// 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 "ipc/ipc_perftest_util.h"
+
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_perftest_messages.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/test/multiprocess_test_helper.h"
+
+namespace IPC {
+
+scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() {
+  scoped_refptr<base::TaskRunner> runner = mojo::edk::GetIOTaskRunner();
+  return scoped_refptr<base::SingleThreadTaskRunner>(
+      static_cast<base::SingleThreadTaskRunner*>(runner.get()));
+}
+
+ChannelReflectorListener::ChannelReflectorListener() : channel_(NULL) {
+  VLOG(1) << "Client listener up";
+}
+
+ChannelReflectorListener::~ChannelReflectorListener() {
+  VLOG(1) << "Client listener down";
+}
+
+void ChannelReflectorListener::Init(Sender* channel,
+                                    const base::Closure& quit_closure) {
+  DCHECK(!channel_);
+  channel_ = channel;
+  quit_closure_ = quit_closure;
+}
+
+bool ChannelReflectorListener::OnMessageReceived(const Message& message) {
+  CHECK(channel_);
+  bool handled = true;
+  IPC_BEGIN_MESSAGE_MAP(ChannelReflectorListener, message)
+    IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
+    IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
+    IPC_MESSAGE_HANDLER(TestMsg_SyncPing, OnSyncPing)
+    IPC_MESSAGE_HANDLER(TestMsg_Quit, OnQuit)
+    IPC_MESSAGE_UNHANDLED(handled = false)
+  IPC_END_MESSAGE_MAP()
+  return handled;
+}
+
+void ChannelReflectorListener::OnHello() {
+  channel_->Send(new TestMsg_Hello);
+}
+
+void ChannelReflectorListener::OnPing(const std::string& payload) {
+  channel_->Send(new TestMsg_Ping(payload));
+}
+
+void ChannelReflectorListener::OnSyncPing(const std::string& payload,
+                                          std::string* response) {
+  *response = payload;
+}
+
+void ChannelReflectorListener::OnQuit() {
+  quit_closure_.Run();
+}
+
+void ChannelReflectorListener::Send(IPC::Message* message) {
+  channel_->Send(message);
+}
+
+LockThreadAffinity::LockThreadAffinity(int cpu_number)
+    : affinity_set_ok_(false) {
+#if defined(OS_WIN)
+  const DWORD_PTR thread_mask = static_cast<DWORD_PTR>(1) << cpu_number;
+  old_affinity_ = SetThreadAffinityMask(GetCurrentThread(), thread_mask);
+  affinity_set_ok_ = old_affinity_ != 0;
+#elif defined(OS_LINUX)
+  cpu_set_t cpuset;
+  CPU_ZERO(&cpuset);
+  CPU_SET(cpu_number, &cpuset);
+  auto get_result = sched_getaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
+  DCHECK_EQ(0, get_result);
+  auto set_result = sched_setaffinity(0, sizeof(cpuset), &cpuset);
+  // Check for get_result failure, even though it should always succeed.
+  affinity_set_ok_ = (set_result == 0) && (get_result == 0);
+#endif
+  if (!affinity_set_ok_)
+    LOG(WARNING) << "Failed to set thread affinity to CPU " << cpu_number;
+}
+
+LockThreadAffinity::~LockThreadAffinity() {
+  if (!affinity_set_ok_)
+    return;
+#if defined(OS_WIN)
+  auto set_result = SetThreadAffinityMask(GetCurrentThread(), old_affinity_);
+  DCHECK_NE(0u, set_result);
+#elif defined(OS_LINUX)
+  auto set_result = sched_setaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
+  DCHECK_EQ(0, set_result);
+#endif
+}
+
+MojoPerfTestClient::MojoPerfTestClient()
+    : listener_(new ChannelReflectorListener()) {
+  mojo::edk::test::MultiprocessTestHelper::ChildSetup();
+}
+
+MojoPerfTestClient::~MojoPerfTestClient() = default;
+
+int MojoPerfTestClient::Run(MojoHandle handle) {
+  handle_ = mojo::MakeScopedHandle(mojo::MessagePipeHandle(handle));
+  LockThreadAffinity thread_locker(kSharedCore);
+
+  base::RunLoop run_loop;
+  std::unique_ptr<ChannelProxy> channel =
+      IPC::ChannelProxy::Create(handle_.release(), Channel::MODE_CLIENT,
+                                listener_.get(), GetIOThreadTaskRunner());
+  listener_->Init(channel.get(), run_loop.QuitWhenIdleClosure());
+  run_loop.Run();
+  return 0;
+}
+
+ReflectorImpl::ReflectorImpl(mojo::ScopedMessagePipeHandle handle,
+                             const base::Closure& quit_closure)
+    : quit_closure_(quit_closure),
+      binding_(this, IPC::mojom::ReflectorRequest(std::move(handle))) {}
+
+ReflectorImpl::~ReflectorImpl() {
+  ignore_result(binding_.Unbind().PassMessagePipe().release());
+}
+
+void ReflectorImpl::Ping(const std::string& value, PingCallback callback) {
+  std::move(callback).Run(value);
+}
+
+void ReflectorImpl::SyncPing(const std::string& value, PingCallback callback) {
+  std::move(callback).Run(value);
+}
+
+void ReflectorImpl::Quit() {
+  if (quit_closure_)
+    quit_closure_.Run();
+}
+
+}  // namespace IPC
diff --git a/ipc/ipc_perftest_util.h b/ipc/ipc_perftest_util.h
new file mode 100644
index 0000000..3c9ceeb
--- /dev/null
+++ b/ipc/ipc_perftest_util.h
@@ -0,0 +1,119 @@
+// 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.
+
+#ifndef IPC_IPC_PERFTEST_UTIL_H_
+#define IPC_IPC_PERFTEST_UTIL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process_metrics.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_test.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace IPC {
+
+scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner();
+
+// This channel listener just replies to all messages with the exact same
+// message. It assumes each message has one string parameter. When the string
+// "quit" is sent, it will exit.
+class ChannelReflectorListener : public Listener {
+ public:
+  ChannelReflectorListener();
+
+  ~ChannelReflectorListener() override;
+
+  void Init(Sender* channel, const base::Closure& quit_closure);
+
+  bool OnMessageReceived(const Message& message) override;
+
+  void OnHello();
+
+  void OnPing(const std::string& payload);
+
+  void OnSyncPing(const std::string& payload, std::string* response);
+
+  void OnQuit();
+
+  void Send(IPC::Message* message);
+
+ private:
+  Sender* channel_;
+  base::Closure quit_closure_;
+};
+
+// This class locks the current thread to a particular CPU core. This is
+// important because otherwise the different threads and processes of these
+// tests end up on different CPU cores which means that all of the cores are
+// lightly loaded so the OS (Windows and Linux) fails to ramp up the CPU
+// frequency, leading to unpredictable and often poor performance.
+class LockThreadAffinity {
+ public:
+  explicit LockThreadAffinity(int cpu_number);
+
+  ~LockThreadAffinity();
+
+ private:
+  bool affinity_set_ok_;
+#if defined(OS_WIN)
+  DWORD_PTR old_affinity_;
+#elif defined(OS_LINUX)
+  cpu_set_t old_cpuset_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(LockThreadAffinity);
+};
+
+// Avoid core 0 due to conflicts with Intel's Power Gadget.
+// Setting thread affinity will fail harmlessly on single/dual core machines.
+const int kSharedCore = 2;
+
+class MojoPerfTestClient {
+ public:
+  MojoPerfTestClient();
+
+  ~MojoPerfTestClient();
+
+  int Run(MojoHandle handle);
+
+ private:
+  base::MessageLoop main_message_loop_;
+  std::unique_ptr<ChannelReflectorListener> listener_;
+  std::unique_ptr<Channel> channel_;
+  mojo::ScopedMessagePipeHandle handle_;
+};
+
+class ReflectorImpl : public IPC::mojom::Reflector {
+ public:
+  explicit ReflectorImpl(mojo::ScopedMessagePipeHandle handle,
+                         const base::Closure& quit_closure);
+
+  ~ReflectorImpl() override;
+
+ private:
+  // IPC::mojom::Reflector:
+  void Ping(const std::string& value, PingCallback callback) override;
+
+  void SyncPing(const std::string& value, PingCallback callback) override;
+
+  void Quit() override;
+
+  base::Closure quit_closure_;
+  mojo::Binding<IPC::mojom::Reflector> binding_;
+};
+
+}  // namespace IPC
+
+#endif  // IPC_IPC_PERFTEST_UTIL_H_