aos: Make SetCurrentThreadAffinity errors a bit nicer

When the SetCurrentThreadAffinity call fails, it prints this error
message:

    Check failed: sched_setaffinity(0, sizeof(cpuset), &cpuset) == 0: Invalid argument [22]

It doesn't actually tell you what CPUs you tried to set. That can make
debugging harder. This patch fixes that by enhancing the error message
to print the set of CPUs that were pass in.

Change-Id: I3ae58f8a6c38b564b3706517a73e9ad43d85ef53
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/realtime.cc b/aos/realtime.cc
index 72daa6b..1e10457 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -161,8 +161,24 @@
   MarkRealtime(false);
 }
 
+std::ostream &operator<<(std::ostream &stream, const cpu_set_t &cpuset) {
+  stream << "{CPUs ";
+  bool first_found = false;
+  for (int i = 0; i < CPU_SETSIZE; ++i) {
+    if (CPU_ISSET(i, &cpuset)) {
+      if (first_found) {
+        stream << ", ";
+      }
+      stream << i;
+      first_found = true;
+    }
+  }
+  stream << "}";
+  return stream;
+}
+
 void SetCurrentThreadAffinity(const cpu_set_t &cpuset) {
-  PCHECK(sched_setaffinity(0, sizeof(cpuset), &cpuset) == 0);
+  PCHECK(sched_setaffinity(0, sizeof(cpuset), &cpuset) == 0) << cpuset;
 }
 
 void SetCurrentThreadName(const std::string_view name) {
diff --git a/aos/realtime.h b/aos/realtime.h
index caf0daa..261eb26 100644
--- a/aos/realtime.h
+++ b/aos/realtime.h
@@ -3,6 +3,7 @@
 
 #include <sched.h>
 
+#include <ostream>
 #include <string_view>
 
 #include "glog/logging.h"
@@ -28,6 +29,9 @@
 // name can have a maximum of 16 characters.
 void SetCurrentThreadName(const std::string_view name);
 
+// Stringifies the cpu_set_t for streams.
+std::ostream &operator<<(std::ostream &stream, const cpu_set_t &cpuset);
+
 // Creates a cpu_set_t from a list of CPUs.
 inline cpu_set_t MakeCpusetFromCpus(std::initializer_list<int> cpus) {
   cpu_set_t result;
diff --git a/aos/realtime_test.cc b/aos/realtime_test.cc
index 348489b..9a0c94e 100644
--- a/aos/realtime_test.cc
+++ b/aos/realtime_test.cc
@@ -132,6 +132,20 @@
 
 #endif
 
+// Tests that we see which CPUs we tried to set when it fails. This can be
+// useful for debugging.
+TEST(RealtimeDeathTest, SetAffinityErrorMessage) {
+  EXPECT_DEATH({ SetCurrentThreadAffinity(MakeCpusetFromCpus({1000})); },
+               "sched_setaffinity\\(0, sizeof\\(cpuset\\), &cpuset\\) == 0 "
+               "\\{CPUs 1000\\}: Invalid argument");
+  EXPECT_DEATH(
+      {
+        SetCurrentThreadAffinity(MakeCpusetFromCpus({1000, 1001}));
+      },
+      "sched_setaffinity\\(0, sizeof\\(cpuset\\), &cpuset\\) == 0 "
+      "\\{CPUs 1000, 1001\\}: Invalid argument");
+}
+
 }  // namespace aos::testing
 
 // We need a special gtest main to force die_on_malloc support on.  Otherwise