Add support for pinning EventLoops

This can be used to help tune realtime performance.

Change-Id: I88031fe65d298b769742bb638a716d29fc965ffd
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index bc5a5ae..7634479 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -1,6 +1,8 @@
 #ifndef AOS_EVENTS_EVENT_LOOP_H_
 #define AOS_EVENTS_EVENT_LOOP_H_
 
+#include <sched.h>
+
 #include <atomic>
 #include <string>
 #include <string_view>
@@ -404,6 +406,15 @@
   internal::TimerTiming timing_;
 };
 
+inline cpu_set_t MakeCpusetFromCpus(std::initializer_list<int> cpus) {
+  cpu_set_t result;
+  CPU_ZERO(&result);
+  for (int cpu : cpus) {
+    CPU_SET(cpu, &result);
+  }
+  return result;
+}
+
 class EventLoop {
  public:
   EventLoop(const Configuration *configuration);
@@ -524,6 +535,10 @@
   virtual void SetRuntimeRealtimePriority(int priority) = 0;
   virtual int priority() const = 0;
 
+  // Sets the scheduler affinity to run the event loop with. This may only be
+  // called before Run().
+  virtual void SetRuntimeAffinity(const cpu_set_t &cpuset) = 0;
+
   // Fetches new messages from the provided channel (path, type).
   //
   // Note: this channel must be a member of the exact configuration object this
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index f0fb3d3..fbcb62e 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -585,6 +585,17 @@
   EXPECT_DEATH(Run(), "realtime");
 }
 
+// Verify that SetRuntimeAffinity fails while running.
+TEST_P(AbstractEventLoopDeathTest, SetRuntimeAffinity) {
+  auto loop = MakePrimary();
+  // Confirm that runtime priority calls work when not running.
+  loop->SetRuntimeAffinity(MakeCpusetFromCpus({0}));
+
+  loop->OnRun([&]() { loop->SetRuntimeAffinity(MakeCpusetFromCpus({1})); });
+
+  EXPECT_DEATH(Run(), "Cannot set affinity while running");
+}
+
 // Verify that registering a watcher and a sender for "/test" fails.
 TEST_P(AbstractEventLoopDeathTest, WatcherAndSender) {
   auto loop = Make();
diff --git a/aos/events/shm_event_loop.cc b/aos/events/shm_event_loop.cc
index b2ccca6..ce34ad1 100644
--- a/aos/events/shm_event_loop.cc
+++ b/aos/events/shm_event_loop.cc
@@ -811,6 +811,10 @@
     }
 
     aos::SetCurrentThreadName(name_.substr(0, 16));
+    const cpu_set_t default_affinity = DefaultAffinity();
+    if (!CPU_EQUAL(&affinity_, &default_affinity)) {
+      ::aos::SetCurrentThreadAffinity(affinity_);
+    }
     // Now, all the callbacks are setup.  Lock everything into memory and go RT.
     if (priority_ != 0) {
       ::aos::InitRT();
@@ -880,6 +884,13 @@
   priority_ = priority;
 }
 
+void ShmEventLoop::SetRuntimeAffinity(const cpu_set_t &cpuset) {
+  if (is_running()) {
+    LOG(FATAL) << "Cannot set affinity while running.";
+  }
+  affinity_ = cpuset;
+}
+
 void ShmEventLoop::set_name(const std::string_view name) {
   name_ = std::string(name);
   UpdateTimingReport();
diff --git a/aos/events/shm_event_loop.h b/aos/events/shm_event_loop.h
index d3f1295..15759f4 100644
--- a/aos/events/shm_event_loop.h
+++ b/aos/events/shm_event_loop.h
@@ -66,6 +66,7 @@
   void OnRun(std::function<void()> on_run) override;
 
   void SetRuntimeRealtimePriority(int priority) override;
+  void SetRuntimeAffinity(const cpu_set_t &cpuset) override;
 
   void set_name(const std::string_view name) override;
   const std::string_view name() const override { return name_; }
@@ -94,6 +95,14 @@
   friend class internal::ShmSender;
   friend class internal::ShmFetcher;
 
+  static cpu_set_t DefaultAffinity() {
+    cpu_set_t result;
+    for (int i = 0; i < CPU_SETSIZE; ++i) {
+      CPU_SET(i, &result);
+    }
+    return result;
+  }
+
   void HandleEvent();
 
   // Returns the TID of the event loop.
@@ -104,6 +113,7 @@
 
   std::vector<std::function<void()>> on_run_;
   int priority_ = 0;
+  cpu_set_t affinity_ = DefaultAffinity();
   std::string name_;
   const Node *const node_;
 
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index cf58b46..1241834 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -414,6 +414,10 @@
 
   int priority() const override { return priority_; }
 
+  void SetRuntimeAffinity(const cpu_set_t & /*cpuset*/) override {
+    CHECK(!is_running()) << ": Cannot set affinity while running.";
+  }
+
   void Setup() {
     MaybeScheduleTimingReports();
     if (!skip_logger_) {
diff --git a/aos/realtime.cc b/aos/realtime.cc
index 6668855..558417f 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -50,8 +50,7 @@
   if (set_for_root == SetLimitForRoot::kYes || !am_root) {
     struct rlimit64 rlim;
     PCHECK(getrlimit64(resource, &rlim) == 0)
-        << ": " << program_invocation_short_name << "-init: getrlimit64("
-        << resource << ") failed";
+        << ": getting limit for " << resource;
 
     if (allow_decrease == AllowSoftLimitDecrease::kYes) {
       rlim.rlim_cur = soft;
@@ -61,9 +60,8 @@
     rlim.rlim_max = ::std::max(rlim.rlim_max, soft);
 
     PCHECK(setrlimit64(resource, &rlim) == 0)
-        << ": " << program_invocation_short_name << "-init: setrlimit64("
-        << resource << ", {cur=" << (uintmax_t)rlim.rlim_cur
-        << ",max=" << (uintmax_t)rlim.rlim_max << "}) failed";
+        << ": changing limit for " << resource << " to " << rlim.rlim_cur
+        << " with max of " << rlim.rlim_max;
   }
 }
 
@@ -74,8 +72,7 @@
   SetSoftRLimit(RLIMIT_MEMLOCK, RLIM_INFINITY, SetLimitForRoot::kNo);
 
   WriteCoreDumps();
-  PCHECK(mlockall(MCL_CURRENT | MCL_FUTURE) == 0)
-      << ": " << program_invocation_short_name << "-init: mlockall failed";
+  PCHECK(mlockall(MCL_CURRENT | MCL_FUTURE) == 0);
 
   // Don't give freed memory back to the OS.
   CHECK_EQ(1, mallopt(M_TRIM_THRESHOLD, -1));
@@ -114,15 +111,19 @@
 void UnsetCurrentThreadRealtimePriority() {
   struct sched_param param;
   param.sched_priority = 0;
-  PCHECK(sched_setscheduler(0, SCHED_OTHER, &param) == 0)
-      << ": sched_setscheduler(0, SCHED_OTHER, 0) failed";
+  PCHECK(sched_setscheduler(0, SCHED_OTHER, &param) == 0);
+}
+
+void SetCurrentThreadAffinity(const cpu_set_t &cpuset) {
+  PCHECK(sched_setaffinity(0, sizeof(cpuset), &cpuset) == 0);
 }
 
 void SetCurrentThreadName(const std::string_view name) {
   CHECK_LE(name.size(), 16u) << ": thread name '" << name << "' too long";
   VLOG(1) << "This thread is changing to '" << name << "'";
   std::string string_name(name);
-  PCHECK(prctl(PR_SET_NAME, string_name.c_str()) == 0);
+  PCHECK(prctl(PR_SET_NAME, string_name.c_str()) == 0)
+      << ": changing name to " << string_name;
   if (&logging::internal::ReloadThreadName != nullptr) {
     logging::internal::ReloadThreadName();
   }
@@ -135,7 +136,7 @@
   struct sched_param param;
   param.sched_priority = priority;
   PCHECK(sched_setscheduler(0, SCHED_FIFO, &param) == 0)
-      << ": sched_setscheduler(0, SCHED_FIFO, " << priority << ") failed";
+      << ": changing to SCHED_FIFO with " << priority;
 }
 
 void WriteCoreDumps() {
diff --git a/aos/realtime.h b/aos/realtime.h
index 20df979..6e0a472 100644
--- a/aos/realtime.h
+++ b/aos/realtime.h
@@ -1,6 +1,7 @@
 #ifndef AOS_REALTIME_H_
 #define AOS_REALTIME_H_
 
+#include <sched.h>
 #include <string_view>
 
 namespace aos {
@@ -21,6 +22,9 @@
 // Sets the current thread's realtime priority.
 void SetCurrentThreadRealtimePriority(int priority);
 
+// Sets the current thread's scheduling affinity.
+void SetCurrentThreadAffinity(const cpu_set_t &cpuset);
+
 // Sets up this process to write core dump files.
 // This is called by Init*, but it's here for other files that want this
 // behavior without calling Init*.