Merge "Detect log files without a header earlier"
diff --git a/aos/BUILD b/aos/BUILD
index 04c0548..04d77f3 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -274,6 +274,7 @@
     ],
     visibility = ["//visibility:public"],
     deps = [
+        ":thread_local",
         "@com_github_google_glog//:glog",
     ],
 )
@@ -543,3 +544,15 @@
     ],
     visibility = ["//visibility:public"],
 )
+
+cc_test(
+    name = "realtime_test",
+    srcs = [
+        "realtime_test.cc",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":realtime",
+        "//aos/testing:googletest",
+    ],
+)
diff --git a/aos/configuration.fbs b/aos/configuration.fbs
index e8a8050..cdf21b8 100644
--- a/aos/configuration.fbs
+++ b/aos/configuration.fbs
@@ -153,6 +153,10 @@
   //
   // Don't specify a hostname in multiple nodes in the same configuration.
   hostnames:[string] (id: 3);
+
+  // An arbitrary list of strings representing properties of each node.  These
+  // can be used to store information about roles.
+  tags:[string] (id: 4);
 }
 
 // Overall configuration datastructure for the pubsub.
diff --git a/aos/configuration.h b/aos/configuration.h
index f1bcece..5579334 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -8,7 +8,7 @@
 
 #include <string_view>
 
-#include "aos/configuration_generated.h"
+#include "aos/configuration_generated.h"  // IWYU pragma: export
 #include "aos/flatbuffers.h"
 
 namespace aos {
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 59968c3..7c1abd0 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -260,6 +260,7 @@
     deps = [
         ":event_loop",
         ":test_message_fbs",
+        "//aos:realtime",
         "//aos/testing:googletest",
     ],
 )
@@ -308,6 +309,7 @@
         ":aos_logging",
         ":event_loop",
         ":simple_channel",
+        "//aos:realtime",
         "//aos/events/logging:logger_fbs",
         "//aos/ipc_lib:index",
         "//aos/network:message_bridge_client_status",
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 6ffb0fe..dc458e7 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -753,6 +753,6 @@
 
 }  // namespace aos
 
-#include "aos/events/event_loop_tmpl.h"
+#include "aos/events/event_loop_tmpl.h"  // IWYU pragma: export
 
 #endif  // AOS_EVENTS_EVENT_LOOP_H
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index 248dcd3..d92927b 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -5,6 +5,7 @@
 #include <unordered_set>
 
 #include "aos/flatbuffer_merge.h"
+#include "aos/realtime.h"
 #include "glog/logging.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -1949,6 +1950,120 @@
   aos::Sender<TestMessage> sender = loop1->MakeSender<TestMessage>("/test");
 }
 
+// Tests that a non-realtime event loop timer is marked non-realtime.
+TEST_P(AbstractEventLoopTest, NonRealtimeEventLoopTimer) {
+  auto loop1 = MakePrimary();
+
+  // Add a timer to actually quit.
+  auto test_timer = loop1->AddTimer([this]() {
+    CheckNotRealtime();
+    this->Exit();
+  });
+
+  loop1->OnRun([&test_timer, &loop1]() {
+    CheckNotRealtime();
+    test_timer->Setup(loop1->monotonic_now(), ::std::chrono::milliseconds(100));
+  });
+
+  Run();
+}
+
+// Tests that a realtime event loop timer is marked realtime.
+TEST_P(AbstractEventLoopTest, RealtimeEventLoopTimer) {
+  auto loop1 = MakePrimary();
+
+  loop1->SetRuntimeRealtimePriority(1);
+
+  // Add a timer to actually quit.
+  auto test_timer = loop1->AddTimer([this]() {
+    CheckRealtime();
+    this->Exit();
+  });
+
+  loop1->OnRun([&test_timer, &loop1]() {
+    CheckRealtime();
+    test_timer->Setup(loop1->monotonic_now(), ::std::chrono::milliseconds(100));
+  });
+
+  Run();
+}
+
+// Tests that a non-realtime event loop phased loop is marked non-realtime.
+TEST_P(AbstractEventLoopTest, NonRealtimeEventLoopPhasedLoop) {
+  auto loop1 = MakePrimary();
+
+  // Add a timer to actually quit.
+  loop1->AddPhasedLoop(
+      [this](int) {
+        CheckNotRealtime();
+        this->Exit();
+      },
+      chrono::seconds(1), chrono::seconds(0));
+
+  Run();
+}
+
+// Tests that a realtime event loop phased loop is marked realtime.
+TEST_P(AbstractEventLoopTest, RealtimeEventLoopPhasedLoop) {
+  auto loop1 = MakePrimary();
+
+  loop1->SetRuntimeRealtimePriority(1);
+
+  // Add a timer to actually quit.
+  loop1->AddPhasedLoop(
+      [this](int) {
+        CheckRealtime();
+        this->Exit();
+      },
+      chrono::seconds(1), chrono::seconds(0));
+
+  Run();
+}
+
+// Tests that a non-realtime event loop watcher is marked non-realtime.
+TEST_P(AbstractEventLoopTest, NonRealtimeEventLoopWatcher) {
+  auto loop1 = MakePrimary();
+  auto loop2 = Make();
+
+  aos::Sender<TestMessage> sender = loop2->MakeSender<TestMessage>("/test");
+
+  loop1->OnRun([&]() {
+    aos::Sender<TestMessage>::Builder msg = sender.MakeBuilder();
+    TestMessage::Builder builder = msg.MakeBuilder<TestMessage>();
+    ASSERT_TRUE(msg.Send(builder.Finish()));
+  });
+
+  loop1->MakeWatcher("/test", [&](const TestMessage &) {
+    CheckNotRealtime();
+    this->Exit();
+  });
+
+  Run();
+}
+
+// Tests that a realtime event loop watcher is marked realtime.
+TEST_P(AbstractEventLoopTest, RealtimeEventLoopWatcher) {
+  auto loop1 = MakePrimary();
+  auto loop2 = Make();
+
+  loop1->SetRuntimeRealtimePriority(1);
+
+  aos::Sender<TestMessage> sender = loop2->MakeSender<TestMessage>("/test");
+
+  loop1->OnRun([&]() {
+    aos::Sender<TestMessage>::Builder msg = sender.MakeBuilder();
+    TestMessage::Builder builder = msg.MakeBuilder<TestMessage>();
+    ASSERT_TRUE(msg.Send(builder.Finish()));
+  });
+
+  loop1->MakeWatcher("/test", [&](const TestMessage &) {
+    CheckRealtime();
+    this->Exit();
+  });
+
+  Run();
+}
+
 // Tests that watchers fail when created on the wrong node.
 TEST_P(AbstractEventLoopDeathTest, NodeWatcher) {
   EnableNodes("them");
diff --git a/aos/events/logging/log_namer.cc b/aos/events/logging/log_namer.cc
index 474386e..411e666 100644
--- a/aos/events/logging/log_namer.cc
+++ b/aos/events/logging/log_namer.cc
@@ -36,7 +36,8 @@
 }
 
 DetachedBufferWriter *LocalLogNamer::MakeWriter(const Channel *channel) {
-  CHECK(configuration::ChannelIsSendableOnNode(channel, node()));
+  CHECK(configuration::ChannelIsSendableOnNode(channel, node()))
+      << ": " << configuration::CleanedChannelToString(channel);
   return data_writer_.get();
 }
 
diff --git a/aos/events/logging/logfile_sorting.cc b/aos/events/logging/logfile_sorting.cc
index f8f08ba..d3f7f38 100644
--- a/aos/events/logging/logfile_sorting.cc
+++ b/aos/events/logging/logfile_sorting.cc
@@ -30,9 +30,20 @@
     std::vector<std::pair<std::string, int>> parts;
   };
 
+  // Struct to hold both the node, and the parts associated with it.
+  struct UnsortedLogPartsMap {
+    std::string logger_node;
+    aos::monotonic_clock::time_point monotonic_start_time =
+        aos::monotonic_clock::min_time;
+    aos::realtime_clock::time_point realtime_start_time =
+        aos::realtime_clock::min_time;
+
+    std::map<std::string, UnsortedLogParts> unsorted_parts;
+  };
+
   // Map holding the log_event_uuid -> second map.  The second map holds the
   // parts_uuid -> list of parts for sorting.
-  std::map<std::string, std::map<std::string, UnsortedLogParts>> parts_list;
+  std::map<std::string, UnsortedLogPartsMap> parts_list;
 
   // Sort part files without UUIDs and part indexes as well.  Extract everything
   // useful from the log in the first pass, then sort later.
@@ -64,6 +75,11 @@
             ? log_header.message().node()->name()->string_view()
             : "";
 
+    const std::string_view logger_node =
+        log_header.message().has_logger_node()
+            ? log_header.message().logger_node()->name()->string_view()
+            : "";
+
     // Looks like an old log.  No UUID, index, and also single node.  We have
     // little to no multi-node log files in the wild without part UUIDs and
     // indexes which we care much about.
@@ -99,6 +115,9 @@
     CHECK(log_header.message().has_parts_uuid());
     CHECK(log_header.message().has_parts_index());
 
+    CHECK_EQ(log_header.message().has_logger_node(),
+             log_header.message().has_node());
+
     const std::string log_event_uuid =
         log_header.message().log_event_uuid()->str();
     const std::string parts_uuid = log_header.message().parts_uuid()->str();
@@ -108,14 +127,28 @@
     if (log_it == parts_list.end()) {
       log_it =
           parts_list
-              .insert(std::make_pair(log_event_uuid,
-                                     std::map<std::string, UnsortedLogParts>()))
+              .insert(std::make_pair(log_event_uuid, UnsortedLogPartsMap()))
               .first;
+      log_it->second.logger_node = logger_node;
+    } else {
+      CHECK_EQ(log_it->second.logger_node, logger_node);
     }
 
-    auto it = log_it->second.find(parts_uuid);
-    if (it == log_it->second.end()) {
-      it = log_it->second.insert(std::make_pair(parts_uuid, UnsortedLogParts()))
+    if (node == log_it->second.logger_node) {
+      if (log_it->second.monotonic_start_time ==
+          aos::monotonic_clock::min_time) {
+        log_it->second.monotonic_start_time = monotonic_start_time;
+        log_it->second.realtime_start_time = realtime_start_time;
+      } else {
+        CHECK_EQ(log_it->second.monotonic_start_time, monotonic_start_time);
+        CHECK_EQ(log_it->second.realtime_start_time, realtime_start_time);
+      }
+    }
+
+    auto it = log_it->second.unsorted_parts.find(parts_uuid);
+    if (it == log_it->second.unsorted_parts.end()) {
+      it = log_it->second.unsorted_parts
+               .insert(std::make_pair(parts_uuid, UnsortedLogParts()))
                .first;
       it->second.monotonic_start_time = monotonic_start_time;
       it->second.realtime_start_time = realtime_start_time;
@@ -157,6 +190,8 @@
         p.parts.parts.emplace_back(std::move(f.second));
       }
       log_file.parts.emplace_back(std::move(p.parts));
+      log_file.monotonic_start_time = log_file.parts[0].monotonic_start_time;
+      log_file.realtime_start_time = log_file.parts[0].realtime_start_time;
       result.emplace_back(std::move(log_file));
     }
 
@@ -166,11 +201,14 @@
   // Now, sort them and produce the final vector form.
   std::vector<LogFile> result;
   result.reserve(parts_list.size());
-  for (std::pair<const std::string, std::map<std::string, UnsortedLogParts>>
-           &logs : parts_list) {
+  for (std::pair<const std::string, UnsortedLogPartsMap> &logs : parts_list) {
     LogFile new_file;
     new_file.log_event_uuid = logs.first;
-    for (std::pair<const std::string, UnsortedLogParts> &parts : logs.second) {
+    new_file.logger_node = logs.second.logger_node;
+    new_file.monotonic_start_time = logs.second.monotonic_start_time;
+    new_file.realtime_start_time = logs.second.realtime_start_time;
+    for (std::pair<const std::string, UnsortedLogParts> &parts :
+         logs.second.unsorted_parts) {
       LogParts new_parts;
       new_parts.monotonic_start_time = parts.second.monotonic_start_time;
       new_parts.realtime_start_time = parts.second.realtime_start_time;
@@ -199,6 +237,11 @@
   if (!file.log_event_uuid.empty()) {
     stream << "\"log_event_uuid\": \"" << file.log_event_uuid << "\", ";
   }
+  if (!file.logger_node.empty()) {
+    stream << "\"logger_node\": \"" << file.logger_node << "\", ";
+  }
+  stream << "\"monotonic_start_time\": " << file.monotonic_start_time
+         << ", \"realtime_start_time\": " << file.realtime_start_time << ", [";
   stream << "\"parts\": [";
   for (size_t i = 0; i < file.parts.size(); ++i) {
     if (i != 0u) {
diff --git a/aos/events/logging/logfile_sorting.h b/aos/events/logging/logfile_sorting.h
index 8b59dff..50bfbd7 100644
--- a/aos/events/logging/logfile_sorting.h
+++ b/aos/events/logging/logfile_sorting.h
@@ -36,6 +36,13 @@
   // The UUID tying them all together (if available)
   std::string log_event_uuid;
 
+  // The node the logger was running on (if available)
+  std::string logger_node;
+
+  // The start time on the logger node.
+  aos::monotonic_clock::time_point monotonic_start_time;
+  aos::realtime_clock::time_point realtime_start_time;
+
   // All the parts, unsorted.
   std::vector<LogParts> parts;
 };
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index 1affce9..57d274b 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -1127,9 +1127,11 @@
 
   size_t missing_rt_count = 0;
 
+  std::vector<std::string> logger_nodes;
   for (const LogFile &log_file : sorted_parts) {
     EXPECT_FALSE(log_file.log_event_uuid.empty());
     log_event_uuids.insert(log_file.log_event_uuid);
+    logger_nodes.emplace_back(log_file.logger_node);
     both_uuids.insert(log_file.log_event_uuid);
 
     for (const LogParts &part : log_file.parts) {
@@ -1160,6 +1162,16 @@
   // (inner vectors all need to be in order, but outer one doesn't matter).
   EXPECT_THAT(ToLogReaderVector(sorted_parts),
               ::testing::UnorderedElementsAreArray(structured_logfiles_));
+
+  EXPECT_THAT(logger_nodes, ::testing::UnorderedElementsAre("pi1", "pi2"));
+
+  EXPECT_NE(sorted_parts[0].realtime_start_time, aos::realtime_clock::min_time);
+  EXPECT_NE(sorted_parts[1].realtime_start_time, aos::realtime_clock::min_time);
+
+  EXPECT_NE(sorted_parts[0].monotonic_start_time,
+            aos::monotonic_clock::min_time);
+  EXPECT_NE(sorted_parts[1].monotonic_start_time,
+            aos::monotonic_clock::min_time);
 }
 
 // Tests that if we remap a remapped channel, it shows up correctly.
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index c429eef..3daabb2 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -9,6 +9,7 @@
 #include "aos/events/aos_logging.h"
 #include "aos/events/simulated_network_bridge.h"
 #include "aos/json_to_flatbuffer.h"
+#include "aos/realtime.h"
 #include "aos/util/phased_loop.h"
 
 namespace aos {
@@ -19,6 +20,16 @@
 
 namespace {
 
+class ScopedMarkRealtimeRestorer {
+ public:
+  ScopedMarkRealtimeRestorer(bool rt) : rt_(rt), prior_(MarkRealtime(rt)) {}
+  ~ScopedMarkRealtimeRestorer() { CHECK_EQ(rt_, MarkRealtime(prior_)); }
+
+ private:
+  const bool rt_;
+  const bool prior_;
+};
+
 // Container for both a message, and the context for it for simulation.  This
 // makes tracking the timestamps associated with the data easy.
 struct SimulatedMessage final {
@@ -544,7 +555,10 @@
   }
 
   void OnRun(::std::function<void()> on_run) override {
-    scheduler_->ScheduleOnRun(on_run);
+    scheduler_->ScheduleOnRun([this, on_run = std::move(on_run)]() {
+      ScopedMarkRealtimeRestorer rt(priority() > 0);
+      on_run();
+    });
   }
 
   const Node *node() const override { return node_; }
@@ -737,7 +751,10 @@
     context.realtime_remote_time = context.realtime_event_time;
   }
 
-  DoCallCallback([monotonic_now]() { return monotonic_now; }, context);
+  {
+    ScopedMarkRealtimeRestorer rt(simulated_event_loop_->priority() > 0);
+    DoCallCallback([monotonic_now]() { return monotonic_now; }, context);
+  }
 
   msgs_.pop_front();
   if (token_ != scheduler_->InvalidToken()) {
@@ -855,7 +872,10 @@
     simulated_event_loop_->AddEvent(&event_);
   }
 
-  Call([monotonic_now]() { return monotonic_now; }, monotonic_now);
+  {
+    ScopedMarkRealtimeRestorer rt(simulated_event_loop_->priority() > 0);
+    Call([monotonic_now]() { return monotonic_now; }, monotonic_now);
+  }
 }
 
 void SimulatedTimerHandler::Disable() {
@@ -891,9 +911,14 @@
   if (simulated_event_loop_->log_impl_) {
     prev_logger.Swap(simulated_event_loop_->log_impl_);
   }
-  Call(
-      [monotonic_now]() { return monotonic_now; },
-      [this](monotonic_clock::time_point sleep_time) { Schedule(sleep_time); });
+
+  {
+    ScopedMarkRealtimeRestorer rt(simulated_event_loop_->priority() > 0);
+    Call([monotonic_now]() { return monotonic_now; },
+         [this](monotonic_clock::time_point sleep_time) {
+           Schedule(sleep_time);
+         });
+  }
 }
 
 void SimulatedPhasedLoopHandler::Schedule(
diff --git a/aos/flatbuffers.h b/aos/flatbuffers.h
index 5da7466..b619452 100644
--- a/aos/flatbuffers.h
+++ b/aos/flatbuffers.h
@@ -7,7 +7,7 @@
 #include "absl/types/span.h"
 #include "aos/containers/resizeable_buffer.h"
 #include "aos/macros.h"
-#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/flatbuffers.h"  // IWYU pragma: export
 #include "glog/logging.h"
 
 namespace aos {
@@ -264,9 +264,12 @@
   }
 
   void Reset() {
-    CHECK(!allocator_.is_allocated()) << ": May not reset while building";
+    CHECK(!allocator_.is_allocated() || data_ != nullptr)
+        << ": May not reset while building";
     fbb_ = flatbuffers::FlatBufferBuilder(Size, &allocator_);
     fbb_.ForceDefaults(true);
+    data_ = nullptr;
+    size_ = 0;
   }
 
   flatbuffers::FlatBufferBuilder *fbb() {
diff --git a/aos/network/message_bridge_server_lib.cc b/aos/network/message_bridge_server_lib.cc
index b272c29..b80547d 100644
--- a/aos/network/message_bridge_server_lib.cc
+++ b/aos/network/message_bridge_server_lib.cc
@@ -248,6 +248,12 @@
   const Channel *const timestamp_channel = configuration::GetChannel(
       event_loop_->configuration(), "/aos", Timestamp::GetFullyQualifiedName(),
       event_loop_->name(), event_loop_->node());
+  CHECK(timestamp_channel != nullptr)
+      << ": Failed to find timestamp channel {\"name\": \"/aos\", \"type\": \""
+      << Timestamp::GetFullyQualifiedName() << "\"}";
+  CHECK(configuration::ChannelIsSendableOnNode(timestamp_channel,
+                                               event_loop_->node()))
+      << ": Timestamp channel is not sendable on this node.";
 
   for (const Channel *channel : *event_loop_->configuration()->channels()) {
     CHECK(channel->has_source_node());
@@ -302,11 +308,17 @@
         timestamp_state_ = state.get();
       }
       channels_.emplace_back(std::move(state));
+    } else if (channel == timestamp_channel) {
+      std::unique_ptr<ChannelState> state(
+          new ChannelState{channel, channel_index});
+      timestamp_state_ = state.get();
+      channels_.emplace_back(std::move(state));
     } else {
       channels_.emplace_back(nullptr);
     }
     ++channel_index;
   }
+  CHECK(timestamp_state_ != nullptr);
 
   // Buffer up the max size a bit so everything fits nicely.
   LOG(INFO) << "Max message size for all clients is " << max_size;
diff --git a/aos/realtime.cc b/aos/realtime.cc
index a41eb0d..9df7aca 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -1,18 +1,19 @@
 #include "aos/realtime.h"
 
+#include <errno.h>
+#include <malloc.h>
+#include <sched.h>
+#include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
-#include <errno.h>
-#include <sched.h>
+#include <sys/prctl.h>
 #include <sys/resource.h>
 #include <sys/types.h>
 #include <unistd.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <sys/prctl.h>
-#include <malloc.h>
 
+#include "aos/thread_local.h"
 #include "glog/logging.h"
 
 namespace FLAG__namespace_do_not_use_directly_use_DECLARE_double_instead {
@@ -68,6 +69,7 @@
 }  // namespace
 
 void LockAllMemory() {
+  CheckNotRealtime();
   // Allow locking as much as we want into RAM.
   SetSoftRLimit(RLIMIT_MEMLOCK, RLIM_INFINITY, SetLimitForRoot::kNo);
 
@@ -101,6 +103,7 @@
 }
 
 void InitRT() {
+  CheckNotRealtime();
   LockAllMemory();
 
   // Only let rt processes run for 3 seconds straight.
@@ -114,6 +117,7 @@
   struct sched_param param;
   param.sched_priority = 0;
   PCHECK(sched_setscheduler(0, SCHED_OTHER, &param) == 0);
+  MarkRealtime(false);
 }
 
 void SetCurrentThreadAffinity(const cpu_set_t &cpuset) {
@@ -141,6 +145,7 @@
 
   struct sched_param param;
   param.sched_priority = priority;
+  MarkRealtime(true);
   PCHECK(sched_setscheduler(0, SCHED_FIFO, &param) == 0)
       << ": changing to SCHED_FIFO with " << priority;
 }
@@ -155,4 +160,20 @@
                 AllowSoftLimitDecrease::kNo);
 }
 
+namespace {
+AOS_THREAD_LOCAL bool is_realtime = false;
+}
+
+bool MarkRealtime(bool realtime) {
+  const bool prior = is_realtime;
+  is_realtime = realtime;
+  return prior;
+}
+
+void CheckRealtime() { CHECK(is_realtime); }
+
+void CheckNotRealtime() { CHECK(!is_realtime); }
+
+ScopedRealtimeRestorer::ScopedRealtimeRestorer() : prior_(is_realtime) {}
+
 }  // namespace aos
diff --git a/aos/realtime.h b/aos/realtime.h
index 6e0a472..52db4a8 100644
--- a/aos/realtime.h
+++ b/aos/realtime.h
@@ -4,6 +4,8 @@
 #include <sched.h>
 #include <string_view>
 
+#include "glog/logging.h"
+
 namespace aos {
 
 // Locks everything into memory and sets the limits.  This plus InitNRT are
@@ -34,6 +36,55 @@
 
 void ExpandStackSize();
 
+// CHECKs that we are (or are not) running on the RT scheduler.  Useful for
+// enforcing that operations which are or are not bounded shouldn't be run. This
+// works both in simulation and when running against the real target.
+void CheckRealtime();
+void CheckNotRealtime();
+
+// Marks that we are or are not running on the realtime scheduler.  Returns the
+// previous state.
+//
+// Note: this shouldn't be used directly.  The event loop primitives should be
+// used instead.
+bool MarkRealtime(bool realtime);
+
+// Class which restores the current RT state when destructed.
+class ScopedRealtimeRestorer {
+ public:
+  ScopedRealtimeRestorer();
+  ~ScopedRealtimeRestorer() { MarkRealtime(prior_); }
+
+ private:
+  const bool prior_;
+};
+
+// Class which marks us as on the RT scheduler until it goes out of scope.
+// Note: this shouldn't be needed for most applications.
+class ScopedRealtime {
+ public:
+  ScopedRealtime() : prior_(MarkRealtime(true)) {}
+  ~ScopedRealtime() {
+    CHECK(MarkRealtime(prior_)) << ": Priority was modified";
+  }
+
+ private:
+  const bool prior_;
+};
+
+// Class which marks us as not on the RT scheduler until it goes out of scope.
+// Note: this shouldn't be needed for most applications.
+class ScopedNotRealtime {
+ public:
+  ScopedNotRealtime() : prior_(MarkRealtime(false)) {}
+  ~ScopedNotRealtime() {
+    CHECK(!MarkRealtime(prior_)) << ": Priority was modified";
+  }
+
+ private:
+  const bool prior_;
+};
+
 }  // namespace aos
 
 #endif  // AOS_REALTIME_H_
diff --git a/aos/realtime_test.cc b/aos/realtime_test.cc
new file mode 100644
index 0000000..e77f140
--- /dev/null
+++ b/aos/realtime_test.cc
@@ -0,0 +1,76 @@
+#include "aos/realtime.h"
+
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+// Tests that ScopedRealtime handles the simple case.
+TEST(RealtimeTest, ScopedRealtime) {
+  CheckNotRealtime();
+  {
+    ScopedRealtime rt;
+    CheckRealtime();
+  }
+  CheckNotRealtime();
+}
+
+// Tests that ScopedRealtime handles nesting.
+TEST(RealtimeTest, DoubleScopedRealtime) {
+  CheckNotRealtime();
+  {
+    ScopedRealtime rt;
+    CheckRealtime();
+    {
+      ScopedRealtime rt2;
+      CheckRealtime();
+    }
+    CheckRealtime();
+  }
+  CheckNotRealtime();
+}
+
+// Tests that ScopedRealtime handles nesting with ScopedNotRealtime.
+TEST(RealtimeTest, ScopedNotRealtime) {
+  CheckNotRealtime();
+  {
+    ScopedRealtime rt;
+    CheckRealtime();
+    {
+      ScopedNotRealtime nrt;
+      CheckNotRealtime();
+    }
+    CheckRealtime();
+  }
+  CheckNotRealtime();
+}
+
+// Tests that ScopedRealtimeRestorer works both when starting RT and nonrt.
+TEST(RealtimeTest, ScopedRealtimeRestorer) {
+  CheckNotRealtime();
+  {
+    ScopedRealtime rt;
+    CheckRealtime();
+    {
+      ScopedRealtimeRestorer restore;
+      CheckRealtime();
+
+      MarkRealtime(false);
+      CheckNotRealtime();
+    }
+    CheckRealtime();
+  }
+  CheckNotRealtime();
+
+  {
+    ScopedRealtimeRestorer restore;
+    CheckNotRealtime();
+
+    MarkRealtime(true);
+    CheckRealtime();
+  }
+  CheckNotRealtime();
+}
+
+}  // namespace testing
+}  // namespace aos
diff --git a/third_party/gmp/BUILD b/third_party/gmp/BUILD
index b60c349..0ac8edc 100644
--- a/third_party/gmp/BUILD
+++ b/third_party/gmp/BUILD
@@ -47,6 +47,9 @@
     ],
 }
 
+# gmp's tools leak memory on purpose. Just skip asan for them.
+tool_features = ["-asan"]
+
 genrule(
     name = "gmp_h_copy",
     srcs = file_from_architecture(architecture_paths, "gmp.h"),
@@ -112,6 +115,7 @@
     name = "gen-fac",
     srcs = ["gen-fac.c"],
     copts = copts,
+    features = tool_features,
     deps = [":bootstrap"],
 )
 
@@ -133,6 +137,7 @@
     name = "gen-fib",
     srcs = ["gen-fib.c"],
     copts = copts,
+    features = tool_features,
     deps = [":bootstrap"],
 )
 
@@ -154,6 +159,7 @@
     name = "gen-bases",
     srcs = ["gen-bases.c"],
     copts = copts,
+    features = tool_features,
     deps = [":bootstrap"],
 )
 
@@ -175,6 +181,7 @@
     name = "gen-trialdivtab",
     srcs = ["gen-trialdivtab.c"],
     copts = copts,
+    features = tool_features,
     deps = [":bootstrap"],
 )