Add remote_boot_uuid to Context

This lets us track which boot a message came from and finally fix the
logger relying on ServerStatistics having all the required information
needed to build up the logfile header.

Change-Id: I17fc4c5718d5d69c7a1e154afdd83b1ccb388a8f
diff --git a/aos/BUILD b/aos/BUILD
index 535c0b2..0159623 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -603,6 +603,7 @@
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
     deps = [
+        "@com_github_gflags_gflags//:gflags",
         "@com_github_google_flatbuffers//:flatbuffers",
         "@com_github_google_glog//:glog",
         "@com_google_absl//absl/types:span",
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 22f7c6f..cc5a356 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -75,6 +75,10 @@
   // the caller has access to this context, which makes this pretty useless.
   int buffer_index;
 
+  // UUID of the remote node which sent this message, or this node in the case
+  // of events which are local to this node.
+  UUID remote_boot_uuid = UUID::Zero();
+
   // Efficiently copies the flatbuffer into a FlatbufferVector, allocating
   // memory in the process.  It is vital that T matches the type of the
   // underlying flatbuffer.
@@ -150,7 +154,7 @@
   bool Send(size_t size);
   bool Send(size_t size, monotonic_clock::time_point monotonic_remote_time,
             realtime_clock::time_point realtime_remote_time,
-            uint32_t remote_queue_index);
+            uint32_t remote_queue_index, const UUID &remote_boot_uuid);
 
   // Sends a single block of data by copying it.
   // The remote arguments have the same meaning as in Send above.
@@ -158,7 +162,7 @@
   bool Send(const void *data, size_t size,
             monotonic_clock::time_point monotonic_remote_time,
             realtime_clock::time_point realtime_remote_time,
-            uint32_t remote_queue_index);
+            uint32_t remote_queue_index, const UUID &remote_boot_uuid);
 
   const Channel *channel() const { return channel_; }
 
@@ -195,13 +199,15 @@
   friend class EventLoop;
 
   virtual bool DoSend(const void *data, size_t size,
-                      aos::monotonic_clock::time_point monotonic_remote_time,
-                      aos::realtime_clock::time_point realtime_remote_time,
-                      uint32_t remote_queue_index) = 0;
+                      monotonic_clock::time_point monotonic_remote_time,
+                      realtime_clock::time_point realtime_remote_time,
+                      uint32_t remote_queue_index,
+                      const UUID &remote_boot_uuid) = 0;
   virtual bool DoSend(size_t size,
-                      aos::monotonic_clock::time_point monotonic_remote_time,
-                      aos::realtime_clock::time_point realtime_remote_time,
-                      uint32_t remote_queue_index) = 0;
+                      monotonic_clock::time_point monotonic_remote_time,
+                      realtime_clock::time_point realtime_remote_time,
+                      uint32_t remote_queue_index,
+                      const UUID &remote_boot_uuid) = 0;
 
   EventLoop *const event_loop_;
   const Channel *const channel_;
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index e0c6024..ee77b8a 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -274,6 +274,7 @@
   EXPECT_EQ(fetcher.context().monotonic_remote_time, monotonic_clock::min_time);
   EXPECT_EQ(fetcher.context().realtime_event_time, realtime_clock::min_time);
   EXPECT_EQ(fetcher.context().realtime_remote_time, realtime_clock::min_time);
+  EXPECT_EQ(fetcher.context().remote_boot_uuid, UUID::Zero());
   EXPECT_EQ(fetcher.context().queue_index, 0xffffffffu);
   EXPECT_EQ(fetcher.context().size, 0u);
   EXPECT_EQ(fetcher.context().data, nullptr);
@@ -301,6 +302,7 @@
   EXPECT_LE(fetcher.context().monotonic_event_time, monotonic_now + kEpsilon);
   EXPECT_GE(fetcher.context().realtime_event_time, realtime_now - kEpsilon);
   EXPECT_LE(fetcher.context().realtime_event_time, realtime_now + kEpsilon);
+  EXPECT_EQ(fetcher.context().remote_boot_uuid, loop2->boot_uuid());
   EXPECT_EQ(fetcher.context().queue_index, 0x0u);
   EXPECT_EQ(fetcher.context().size, 20u);
   EXPECT_NE(fetcher.context().data, nullptr);
@@ -955,6 +957,7 @@
     EXPECT_EQ(loop->context().monotonic_remote_time, monotonic_clock::min_time);
     EXPECT_EQ(loop->context().realtime_event_time, realtime_clock::min_time);
     EXPECT_EQ(loop->context().realtime_remote_time, realtime_clock::min_time);
+    EXPECT_EQ(loop->context().remote_boot_uuid, loop->boot_uuid());
     EXPECT_EQ(loop->context().queue_index, 0xffffffffu);
     EXPECT_EQ(loop->context().size, 0u);
     EXPECT_EQ(loop->context().data, nullptr);
@@ -1249,6 +1252,7 @@
               loop1->context().monotonic_event_time);
     EXPECT_EQ(loop1->context().realtime_remote_time,
               loop1->context().realtime_event_time);
+    EXPECT_EQ(loop1->context().remote_boot_uuid, loop1->boot_uuid());
 
     const aos::monotonic_clock::time_point monotonic_now =
         loop1->monotonic_now();
@@ -1296,6 +1300,7 @@
             fetcher.context().realtime_remote_time);
   EXPECT_EQ(fetcher.context().monotonic_event_time,
             fetcher.context().monotonic_remote_time);
+  EXPECT_EQ(fetcher.context().remote_boot_uuid, loop1->boot_uuid());
 
   EXPECT_TRUE(monotonic_time_offset > ::std::chrono::milliseconds(-500))
       << ": Got "
@@ -1348,6 +1353,7 @@
               loop1->context().monotonic_event_time);
     EXPECT_EQ(loop1->context().realtime_remote_time,
               loop1->context().realtime_event_time);
+    EXPECT_EQ(loop1->context().remote_boot_uuid, loop1->boot_uuid());
 
     const aos::monotonic_clock::time_point monotonic_now =
         loop1->monotonic_now();
@@ -1381,6 +1387,7 @@
             fetcher.context().realtime_remote_time);
   EXPECT_EQ(fetcher.context().monotonic_event_time,
             fetcher.context().monotonic_remote_time);
+  EXPECT_EQ(fetcher.context().remote_boot_uuid, loop1->boot_uuid());
 
   EXPECT_TRUE(monotonic_time_offset > ::std::chrono::milliseconds(-500))
       << ": Got "
@@ -1438,6 +1445,7 @@
 
             EXPECT_EQ(loop1->context().monotonic_remote_time,
                       monotonic_clock::min_time);
+            EXPECT_EQ(loop1->context().remote_boot_uuid, loop1->boot_uuid());
             EXPECT_EQ(loop1->context().realtime_event_time,
                       realtime_clock::min_time);
             EXPECT_EQ(loop1->context().realtime_remote_time,
@@ -1582,13 +1590,13 @@
     FlatbufferDetachedBuffer<timing::Report> primary_report =
         FlatbufferDetachedBuffer<timing::Report>::Empty();
     while (report_fetcher.FetchNext()) {
-      LOG(INFO) << "Report " << FlatbufferToJson(report_fetcher.get());
+      VLOG(1) << "Report " << FlatbufferToJson(report_fetcher.get());
       if (report_fetcher->name()->string_view() == "primary") {
         primary_report = CopyFlatBuffer(report_fetcher.get());
       }
     }
 
-    LOG(INFO) << FlatbufferToJson(primary_report, {.multi_line = true});
+    VLOG(1) << FlatbufferToJson(primary_report, {.multi_line = true});
 
     EXPECT_EQ(primary_report.message().name()->string_view(), "primary");
 
@@ -1849,6 +1857,7 @@
   const aos::realtime_clock::time_point realtime_remote_time =
       aos::realtime_clock::time_point(chrono::seconds(3132));
   const uint32_t remote_queue_index = 0x254971;
+  const UUID remote_boot_uuid = UUID::Random();
 
   std::unique_ptr<aos::RawSender> sender =
       loop1->MakeRawSender(configuration::GetChannel(
@@ -1860,19 +1869,21 @@
 
   loop2->OnRun([&]() {
     EXPECT_TRUE(sender->Send(kData.data(), kData.size(), monotonic_remote_time,
-                             realtime_remote_time, remote_queue_index));
+                             realtime_remote_time, remote_queue_index,
+                             remote_boot_uuid));
   });
 
   bool happened = false;
   loop2->MakeRawWatcher(
       configuration::GetChannel(loop2->configuration(), "/test",
                                 "aos.TestMessage", "", nullptr),
-      [this, monotonic_remote_time, realtime_remote_time,
+      [this, monotonic_remote_time, realtime_remote_time, remote_boot_uuid,
        remote_queue_index, &fetcher,
        &happened](const Context &context, const void * /*message*/) {
         happened = true;
         EXPECT_EQ(monotonic_remote_time, context.monotonic_remote_time);
         EXPECT_EQ(realtime_remote_time, context.realtime_remote_time);
+        EXPECT_EQ(remote_boot_uuid, context.remote_boot_uuid);
         EXPECT_EQ(remote_queue_index, context.remote_queue_index);
 
         ASSERT_TRUE(fetcher->Fetch());
diff --git a/aos/events/event_loop_tmpl.h b/aos/events/event_loop_tmpl.h
index 74d3493..d6922de 100644
--- a/aos/events/event_loop_tmpl.h
+++ b/aos/events/event_loop_tmpl.h
@@ -131,15 +131,15 @@
 
 inline bool RawSender::Send(size_t size) {
   return Send(size, monotonic_clock::min_time, realtime_clock::min_time,
-              0xffffffffu);
+              0xffffffffu, event_loop_->boot_uuid());
 }
 
 inline bool RawSender::Send(
     size_t size, aos::monotonic_clock::time_point monotonic_remote_time,
     aos::realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index) {
+    uint32_t remote_queue_index, const UUID &uuid) {
   if (DoSend(size, monotonic_remote_time, realtime_remote_time,
-             remote_queue_index)) {
+             remote_queue_index, uuid)) {
     timing_.size.Add(size);
     timing_.sender->mutate_count(timing_.sender->count() + 1);
     ftrace_.FormatMessage(
@@ -154,16 +154,16 @@
 
 inline bool RawSender::Send(const void *data, size_t size) {
   return Send(data, size, monotonic_clock::min_time, realtime_clock::min_time,
-              0xffffffffu);
+              0xffffffffu, event_loop_->boot_uuid());
 }
 
 inline bool RawSender::Send(
     const void *data, size_t size,
     aos::monotonic_clock::time_point monotonic_remote_time,
     aos::realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index) {
+    uint32_t remote_queue_index, const UUID &uuid) {
   if (DoSend(data, size, monotonic_remote_time, realtime_remote_time,
-             remote_queue_index)) {
+             remote_queue_index, uuid)) {
     timing_.size.Add(size);
     timing_.sender->mutate_count(timing_.sender->count() + 1);
     ftrace_.FormatMessage(
@@ -190,6 +190,7 @@
   event_loop_->context_.size = 0;
   event_loop_->context_.data = nullptr;
   event_loop_->context_.buffer_index = -1;
+  event_loop_->context_.remote_boot_uuid = event_loop_->boot_uuid();
 
   ftrace_.FormatMessage(
       "timer: %.*s: start now=%" PRId64 " event=%" PRId64,
@@ -235,6 +236,7 @@
   event_loop_->context_.size = 0;
   event_loop_->context_.data = nullptr;
   event_loop_->context_.buffer_index = -1;
+  event_loop_->context_.remote_boot_uuid = event_loop_->boot_uuid();
 
   // Compute how many cycles elapsed and schedule the next wakeup.
   Reschedule(schedule, monotonic_start_time);
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 07b423f..098b78c 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -315,7 +315,8 @@
       for (size_t i = 1; i < filtered_parts.size(); ++i) {
         CHECK_EQ(filtered_parts[i].source_boot_uuid,
                  filtered_parts[0].source_boot_uuid)
-            << ": Found parts from different boots "
+            << ": Found parts from different boots for node "
+            << node->name()->string_view() << " "
             << LogFileVectorToString(log_files_);
       }
       if (!filtered_parts[0].source_boot_uuid.empty()) {
@@ -1125,7 +1126,12 @@
       timestamped_message.data.message().data()->Data(),
       timestamped_message.data.message().data()->size(),
       timestamped_message.monotonic_remote_time,
-      timestamped_message.realtime_remote_time, remote_queue_index);
+      timestamped_message.realtime_remote_time, remote_queue_index,
+      (channel_source_state_[timestamped_message.channel_index] != nullptr
+           ? CHECK_NOTNULL(
+                 channel_source_state_[timestamped_message.channel_index])
+                 ->event_loop_->boot_uuid()
+           : event_loop_->boot_uuid()));
   if (!sent) return false;
 
   if (queue_index_map_[timestamped_message.channel_index]) {
diff --git a/aos/events/shm_event_loop.cc b/aos/events/shm_event_loop.cc
index 61a4149..63e1cb9 100644
--- a/aos/events/shm_event_loop.cc
+++ b/aos/events/shm_event_loop.cc
@@ -63,7 +63,7 @@
 }
 std::string ShmPath(std::string_view shm_base, const Channel *channel) {
   CHECK(channel->has_type());
-  return ShmFolder(shm_base, channel) + channel->type()->str() + ".v3";
+  return ShmFolder(shm_base, channel) + channel->type()->str() + ".v4";
 }
 
 void PageFaultDataWrite(char *data, size_t size) {
@@ -372,7 +372,7 @@
         queue_index.index(), &context_.monotonic_event_time,
         &context_.realtime_event_time, &context_.monotonic_remote_time,
         &context_.realtime_remote_time, &context_.remote_queue_index,
-        &context_.size, copy_buffer);
+        &context_.remote_boot_uuid, &context_.size, copy_buffer);
 
     if (read_result == ipc_lib::LocklessQueueReader::Result::GOOD) {
       if (pin_data()) {
@@ -535,13 +535,15 @@
   bool DoSend(size_t length,
               aos::monotonic_clock::time_point monotonic_remote_time,
               aos::realtime_clock::time_point realtime_remote_time,
-              uint32_t remote_queue_index) override {
+              uint32_t remote_queue_index,
+              const UUID &remote_boot_uuid) override {
     CHECK_LE(length, static_cast<size_t>(channel()->max_size()))
         << ": Sent too big a message on "
         << configuration::CleanedChannelToString(channel());
-    CHECK(lockless_queue_sender_.Send(
-        length, monotonic_remote_time, realtime_remote_time, remote_queue_index,
-        &monotonic_sent_time_, &realtime_sent_time_, &sent_queue_index_))
+    CHECK(lockless_queue_sender_.Send(length, monotonic_remote_time,
+                                      realtime_remote_time, remote_queue_index,
+                                      remote_boot_uuid, &monotonic_sent_time_,
+                                      &realtime_sent_time_, &sent_queue_index_))
         << ": Somebody wrote outside the buffer of their message on channel "
         << configuration::CleanedChannelToString(channel());
 
@@ -552,14 +554,15 @@
   bool DoSend(const void *msg, size_t length,
               aos::monotonic_clock::time_point monotonic_remote_time,
               aos::realtime_clock::time_point realtime_remote_time,
-              uint32_t remote_queue_index) override {
+              uint32_t remote_queue_index,
+              const UUID &remote_boot_uuid) override {
     CHECK_LE(length, static_cast<size_t>(channel()->max_size()))
         << ": Sent too big a message on "
         << configuration::CleanedChannelToString(channel());
     CHECK(lockless_queue_sender_.Send(
         reinterpret_cast<const char *>(msg), length, monotonic_remote_time,
-        realtime_remote_time, remote_queue_index, &monotonic_sent_time_,
-        &realtime_sent_time_, &sent_queue_index_))
+        realtime_remote_time, remote_queue_index, remote_boot_uuid,
+        &monotonic_sent_time_, &realtime_sent_time_, &sent_queue_index_))
         << ": Somebody wrote outside the buffer of their message on channel "
         << configuration::CleanedChannelToString(channel());
     wake_upper_.Wakeup(event_loop()->priority());
diff --git a/aos/events/shm_event_loop_test.cc b/aos/events/shm_event_loop_test.cc
index cd91ab3..2a2b706 100644
--- a/aos/events/shm_event_loop_test.cc
+++ b/aos/events/shm_event_loop_test.cc
@@ -26,12 +26,12 @@
     }
 
     // Clean up anything left there before.
-    unlink((FLAGS_shm_base + "/test/aos.TestMessage.v3").c_str());
-    unlink((FLAGS_shm_base + "/test1/aos.TestMessage.v3").c_str());
-    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v3").c_str());
-    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v3").c_str());
-    unlink((FLAGS_shm_base + "/aos/aos.timing.Report.v3").c_str());
-    unlink((FLAGS_shm_base + "/aos/aos.logging.LogMessageFbs.v3").c_str());
+    unlink((FLAGS_shm_base + "/test/aos.TestMessage.v4").c_str());
+    unlink((FLAGS_shm_base + "/test1/aos.TestMessage.v4").c_str());
+    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v4").c_str());
+    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v4").c_str());
+    unlink((FLAGS_shm_base + "/aos/aos.timing.Report.v4").c_str());
+    unlink((FLAGS_shm_base + "/aos/aos.logging.LogMessageFbs.v4").c_str());
   }
 
   ~ShmEventLoopTestFactory() { FLAGS_override_hostname = ""; }
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index b38829e..9d431b7 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -294,12 +294,14 @@
   bool DoSend(size_t length,
               aos::monotonic_clock::time_point monotonic_remote_time,
               aos::realtime_clock::time_point realtime_remote_time,
-              uint32_t remote_queue_index) override;
+              uint32_t remote_queue_index,
+              const UUID &remote_boot_uuid) override;
 
   bool DoSend(const void *msg, size_t size,
               aos::monotonic_clock::time_point monotonic_remote_time,
               aos::realtime_clock::time_point realtime_remote_time,
-              uint32_t remote_queue_index) override;
+              uint32_t remote_queue_index,
+              const UUID &remote_boot_uuid) override;
 
   int buffer_index() override {
     // First, ensure message_ is allocated.
@@ -834,10 +836,11 @@
   simulated_channel_->CountSenderDestroyed();
 }
 
-bool SimulatedSender::DoSend(
-    size_t length, aos::monotonic_clock::time_point monotonic_remote_time,
-    aos::realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index) {
+bool SimulatedSender::DoSend(size_t length,
+                             monotonic_clock::time_point monotonic_remote_time,
+                             realtime_clock::time_point realtime_remote_time,
+                             uint32_t remote_queue_index,
+                             const UUID &remote_boot_uuid) {
   // The allocations in here are due to infrastructure and don't count in the
   // no mallocs in RT code.
   ScopedNotRealtime nrt;
@@ -847,6 +850,7 @@
   message_->context.remote_queue_index = remote_queue_index;
   message_->context.realtime_event_time = event_loop_->realtime_now();
   message_->context.realtime_remote_time = realtime_remote_time;
+  message_->context.remote_boot_uuid = remote_boot_uuid;
   CHECK_LE(length, message_->context.size);
   message_->context.size = length;
 
@@ -862,11 +866,11 @@
   return true;
 }
 
-bool SimulatedSender::DoSend(
-    const void *msg, size_t size,
-    aos::monotonic_clock::time_point monotonic_remote_time,
-    aos::realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index) {
+bool SimulatedSender::DoSend(const void *msg, size_t size,
+                             monotonic_clock::time_point monotonic_remote_time,
+                             realtime_clock::time_point realtime_remote_time,
+                             uint32_t remote_queue_index,
+                             const UUID &remote_boot_uuid) {
   CHECK_LE(size, this->size())
       << ": Attempting to send too big a message on "
       << configuration::CleanedChannelToString(simulated_channel_->channel());
@@ -883,7 +887,7 @@
          msg, size);
 
   return DoSend(size, monotonic_remote_time, realtime_remote_time,
-                remote_queue_index);
+                remote_queue_index, remote_boot_uuid);
 }
 
 SimulatedTimerHandler::SimulatedTimerHandler(
diff --git a/aos/events/simulated_event_loop_test.cc b/aos/events/simulated_event_loop_test.cc
index 60731c1..22d4028 100644
--- a/aos/events/simulated_event_loop_test.cc
+++ b/aos/events/simulated_event_loop_test.cc
@@ -1485,18 +1485,28 @@
 
   std::unique_ptr<EventLoop> pi1_remote_timestamp =
       simulated_event_loop_factory.MakeEventLoop("pi1_remote_timestamp", pi1);
-  std::string expected_boot_uuid(
-      simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)
-          ->boot_uuid()
-          .ToString());
+  UUID expected_boot_uuid =
+      simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)->boot_uuid();
 
   int timestamp_count = 0;
   pi1_remote_timestamp->MakeWatcher(
+      "/pi2/aos", [&expected_boot_uuid,
+                   &pi1_remote_timestamp](const message_bridge::Timestamp &) {
+        EXPECT_EQ(pi1_remote_timestamp->context().remote_boot_uuid,
+                  expected_boot_uuid);
+      });
+  pi1_remote_timestamp->MakeWatcher(
+      "/test",
+      [&expected_boot_uuid, &pi1_remote_timestamp](const examples::Pong &) {
+        EXPECT_EQ(pi1_remote_timestamp->context().remote_boot_uuid,
+                  expected_boot_uuid);
+      });
+  pi1_remote_timestamp->MakeWatcher(
       shared() ? "/pi1/aos/remote_timestamps/pi2"
                : "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
       [&timestamp_count, &expected_boot_uuid](const RemoteMessage &header) {
         EXPECT_TRUE(header.has_boot_uuid());
-        EXPECT_EQ(header.boot_uuid()->string_view(), expected_boot_uuid);
+        EXPECT_EQ(UUID::FromString(header.boot_uuid()), expected_boot_uuid);
         VLOG(1) << aos::FlatbufferToJson(&header);
         ++timestamp_count;
       });
@@ -1511,7 +1521,7 @@
           EXPECT_TRUE(connection->has_boot_uuid());
           if (connection->node()->name()->string_view() == "pi2") {
             EXPECT_EQ(expected_boot_uuid,
-                      connection->boot_uuid()->string_view())
+                      UUID::FromString(connection->boot_uuid()))
                 << " : Got " << aos::FlatbufferToJson(&stats);
             ++pi1_server_statistics_count;
           }
@@ -1527,15 +1537,12 @@
   // Confirm that reboot changes the UUID.
   simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)->Reboot();
 
-  EXPECT_NE(expected_boot_uuid,
-            simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)
-                ->boot_uuid()
-                .ToString());
+  EXPECT_NE(
+      expected_boot_uuid,
+      simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)->boot_uuid());
 
   expected_boot_uuid =
-      simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)
-          ->boot_uuid()
-          .ToString();
+      simulated_event_loop_factory.GetNodeEventLoopFactory(pi2)->boot_uuid();
   timestamp_count = 0;
   pi1_server_statistics_count = 0;
 
diff --git a/aos/events/simulated_network_bridge.cc b/aos/events/simulated_network_bridge.cc
index e5eb4cb..eb7f243 100644
--- a/aos/events/simulated_network_bridge.cc
+++ b/aos/events/simulated_network_bridge.cc
@@ -136,7 +136,8 @@
     sender_->Send(fetcher_->context().data, fetcher_->context().size,
                   fetcher_->context().monotonic_event_time,
                   fetcher_->context().realtime_event_time,
-                  fetcher_->context().queue_index);
+                  fetcher_->context().queue_index,
+                  fetcher_->context().remote_boot_uuid);
 
     // And simulate message_bridge's offset recovery.
     client_status_->SampleFilter(client_index_,
diff --git a/aos/ipc_lib/BUILD b/aos/ipc_lib/BUILD
index 026f866..c24fcee 100644
--- a/aos/ipc_lib/BUILD
+++ b/aos/ipc_lib/BUILD
@@ -175,6 +175,7 @@
         ":data_alignment",
         ":index",
         "//aos:realtime",
+        "//aos:uuid",
         "//aos/time",
         "//aos/util:compiler_memory_barrier",
         "@com_github_google_glog//:glog",
diff --git a/aos/ipc_lib/lockless_queue.cc b/aos/ipc_lib/lockless_queue.cc
index a0c68cb..98701b4 100644
--- a/aos/ipc_lib/lockless_queue.cc
+++ b/aos/ipc_lib/lockless_queue.cc
@@ -908,7 +908,7 @@
     const char *data, size_t length,
     monotonic_clock::time_point monotonic_remote_time,
     realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index,
+    uint32_t remote_queue_index, const UUID &remote_boot_uuid,
     monotonic_clock::time_point *monotonic_sent_time,
     realtime_clock::time_point *realtime_sent_time, uint32_t *queue_index) {
   CHECK_LE(length, size());
@@ -917,14 +917,14 @@
   // adhere to this convention and place it at the end.
   memcpy((reinterpret_cast<char *>(Data()) + size() - length), data, length);
   return Send(length, monotonic_remote_time, realtime_remote_time,
-              remote_queue_index, monotonic_sent_time,
+              remote_queue_index, remote_boot_uuid, monotonic_sent_time,
               realtime_sent_time, queue_index);
 }
 
 bool LocklessQueueSender::Send(
     size_t length, monotonic_clock::time_point monotonic_remote_time,
     realtime_clock::time_point realtime_remote_time,
-    uint32_t remote_queue_index,
+    uint32_t remote_queue_index, const UUID &remote_boot_uuid,
     monotonic_clock::time_point *monotonic_sent_time,
     realtime_clock::time_point *realtime_sent_time, uint32_t *queue_index) {
   const size_t queue_size = memory_->queue_size();
@@ -949,6 +949,7 @@
   // Pass these through.  Any alternative behavior can be implemented out a
   // layer.
   message->header.remote_queue_index = remote_queue_index;
+  message->header.remote_boot_uuid = remote_boot_uuid;
   message->header.monotonic_remote_time = monotonic_remote_time;
   message->header.realtime_remote_time = realtime_remote_time;
 
@@ -1209,7 +1210,7 @@
     realtime_clock::time_point *realtime_sent_time,
     monotonic_clock::time_point *monotonic_remote_time,
     realtime_clock::time_point *realtime_remote_time,
-    uint32_t *remote_queue_index, size_t *length,
+    uint32_t *remote_queue_index, UUID *remote_boot_uuid, size_t *length,
     char *data) const {
   const size_t queue_size = memory_->queue_size();
 
@@ -1293,6 +1294,7 @@
   }
   *monotonic_remote_time = m->header.monotonic_remote_time;
   *realtime_remote_time = m->header.realtime_remote_time;
+  *remote_boot_uuid = m->header.remote_boot_uuid;
   if (data) {
     memcpy(data, m->data(memory_->message_data_size()),
            memory_->message_data_size());
diff --git a/aos/ipc_lib/lockless_queue.h b/aos/ipc_lib/lockless_queue.h
index 69207f4..41aa0fb 100644
--- a/aos/ipc_lib/lockless_queue.h
+++ b/aos/ipc_lib/lockless_queue.h
@@ -13,6 +13,7 @@
 #include "aos/ipc_lib/data_alignment.h"
 #include "aos/ipc_lib/index.h"
 #include "aos/time/time.h"
+#include "aos/uuid.h"
 
 namespace aos {
 namespace ipc_lib {
@@ -92,6 +93,9 @@
     // Queue index from the remote node.
     uint32_t remote_queue_index;
 
+    // Remote boot UUID for this message.
+    UUID remote_boot_uuid;
+
     size_t length;
   } header;
 
@@ -305,7 +309,7 @@
   void *Data();
   bool Send(size_t length, monotonic_clock::time_point monotonic_remote_time,
             realtime_clock::time_point realtime_remote_time,
-            uint32_t remote_queue_index,
+            uint32_t remote_queue_index, const UUID &remote_boot_uuid,
             monotonic_clock::time_point *monotonic_sent_time = nullptr,
             realtime_clock::time_point *realtime_sent_time = nullptr,
             uint32_t *queue_index = nullptr);
@@ -314,10 +318,10 @@
   bool Send(const char *data, size_t length,
             monotonic_clock::time_point monotonic_remote_time,
             realtime_clock::time_point realtime_remote_time,
-            uint32_t remote_queue_index,
-            monotonic_clock::time_point *monotonic_sent_time,
-            realtime_clock::time_point *realtime_sent_time,
-            uint32_t *queue_index);
+            uint32_t remote_queue_index, const UUID &remote_boot_uuid,
+            monotonic_clock::time_point *monotonic_sent_time = nullptr,
+            realtime_clock::time_point *realtime_sent_time = nullptr,
+            uint32_t *queue_index = nullptr);
 
   int buffer_index() const;
 
@@ -400,7 +404,7 @@
               realtime_clock::time_point *realtime_sent_time,
               monotonic_clock::time_point *monotonic_remote_time,
               realtime_clock::time_point *realtime_remote_time,
-              uint32_t *remote_queue_index,
+              uint32_t *remote_queue_index, UUID *remote_boot_uuid,
               size_t *length, char *data) const;
 
   // Returns the index to the latest queue message.  Returns empty_queue_index()
diff --git a/aos/ipc_lib/lockless_queue_death_test.cc b/aos/ipc_lib/lockless_queue_death_test.cc
index 346f88e..b521d9e 100644
--- a/aos/ipc_lib/lockless_queue_death_test.cc
+++ b/aos/ipc_lib/lockless_queue_death_test.cc
@@ -534,7 +534,7 @@
           char data[100];
           size_t s = snprintf(data, sizeof(data), "foobar%d", i + 1);
           sender.Send(data, s + 1, monotonic_clock::min_time,
-                      realtime_clock::min_time, 0xffffffffl,
+                      realtime_clock::min_time, 0xffffffffl, UUID::Zero(),
                       nullptr, nullptr, nullptr);
           // Pin a message, so when we keep writing we will exercise the pinning
           // logic.
@@ -608,7 +608,7 @@
           char data[100];
           size_t s = snprintf(data, sizeof(data), "foobar%d", 971);
           sender.Send(data, s + 1, monotonic_clock::min_time,
-                      realtime_clock::min_time, 0xffffffffl,
+                      realtime_clock::min_time, 0xffffffffl, UUID::Zero(),
                       nullptr, nullptr, nullptr);
         }
 
@@ -622,13 +622,14 @@
           monotonic_clock::time_point monotonic_remote_time;
           realtime_clock::time_point realtime_remote_time;
           uint32_t remote_queue_index;
+          UUID remote_boot_uuid;
           char read_data[1024];
           size_t length;
 
-          LocklessQueueReader::Result read_result =
-              reader.Read(i, &monotonic_sent_time, &realtime_sent_time,
-                          &monotonic_remote_time, &realtime_remote_time,
-                          &remote_queue_index, &length, &(read_data[0]));
+          LocklessQueueReader::Result read_result = reader.Read(
+              i, &monotonic_sent_time, &realtime_sent_time,
+              &monotonic_remote_time, &realtime_remote_time,
+              &remote_queue_index, &remote_boot_uuid, &length, &(read_data[0]));
 
           if (read_result != LocklessQueueReader::Result::GOOD) {
             if (read_result == LocklessQueueReader::Result::TOO_OLD) {
diff --git a/aos/ipc_lib/lockless_queue_test.cc b/aos/ipc_lib/lockless_queue_test.cc
index c8f3d23..91f995b 100644
--- a/aos/ipc_lib/lockless_queue_test.cc
+++ b/aos/ipc_lib/lockless_queue_test.cc
@@ -239,7 +239,7 @@
     char data[100];
     size_t s = snprintf(data, sizeof(data), "foobar%d", i);
     sender.Send(data, s, monotonic_clock::min_time, realtime_clock::min_time,
-                0xffffffffu, nullptr, nullptr, nullptr);
+                0xffffffffu, UUID::Zero(), nullptr, nullptr, nullptr);
 
     // Confirm that the queue index still makes sense.  This is easier since the
     // empty case has been handled.
@@ -251,6 +251,7 @@
     monotonic_clock::time_point monotonic_remote_time;
     realtime_clock::time_point realtime_remote_time;
     uint32_t remote_queue_index;
+    UUID remote_boot_uuid;
     char read_data[1024];
     size_t length;
 
@@ -260,10 +261,10 @@
     } else {
       index = index.IncrementBy(i - 5);
     }
-    LocklessQueueReader::Result read_result =
-        reader.Read(index.index(), &monotonic_sent_time, &realtime_sent_time,
-                    &monotonic_remote_time, &realtime_remote_time,
-                    &remote_queue_index, &length, &(read_data[0]));
+    LocklessQueueReader::Result read_result = reader.Read(
+        index.index(), &monotonic_sent_time, &realtime_sent_time,
+        &monotonic_remote_time, &realtime_remote_time, &remote_queue_index,
+        &remote_boot_uuid, &length, &(read_data[0]));
 
     // This should either return GOOD, or TOO_OLD if it is before the start of
     // the queue.
diff --git a/aos/ipc_lib/queue_racer.cc b/aos/ipc_lib/queue_racer.cc
index afd4135..cf46807 100644
--- a/aos/ipc_lib/queue_racer.cc
+++ b/aos/ipc_lib/queue_racer.cc
@@ -178,7 +178,7 @@
 
         ++started_writes_;
         sender.Send(sizeof(ThreadPlusCount), aos::monotonic_clock::min_time,
-                    aos::realtime_clock::min_time, 0xffffffff,
+                    aos::realtime_clock::min_time, 0xffffffff, UUID::Zero(),
                     nullptr, nullptr, nullptr);
         // Blank out the new scratch buffer, to catch other people using it.
         {
@@ -267,6 +267,7 @@
     realtime_clock::time_point realtime_sent_time;
     monotonic_clock::time_point monotonic_remote_time;
     realtime_clock::time_point realtime_remote_time;
+    UUID remote_boot_uuid;
     uint32_t remote_queue_index;
     size_t length;
     char read_data[1024];
@@ -275,10 +276,10 @@
     const uint32_t wrapped_i =
         i % static_cast<size_t>(QueueIndex::MaxIndex(
                 0xffffffffu, LocklessQueueSize(queue_.memory())));
-    LocklessQueueReader::Result read_result =
-        reader.Read(wrapped_i, &monotonic_sent_time, &realtime_sent_time,
-                    &monotonic_remote_time, &realtime_remote_time,
-                    &remote_queue_index, &length, &(read_data[0]));
+    LocklessQueueReader::Result read_result = reader.Read(
+        wrapped_i, &monotonic_sent_time, &realtime_sent_time,
+        &monotonic_remote_time, &realtime_remote_time, &remote_queue_index,
+        &remote_boot_uuid, &length, &(read_data[0]));
 
     if (race_reads) {
       if (read_result == LocklessQueueReader::Result::NOTHING_NEW) {
@@ -301,6 +302,7 @@
 
     EXPECT_EQ(monotonic_remote_time, aos::monotonic_clock::min_time);
     EXPECT_EQ(realtime_remote_time, aos::realtime_clock::min_time);
+    EXPECT_EQ(remote_boot_uuid, UUID::Zero());
 
     ThreadPlusCount tpc;
     ASSERT_EQ(length, sizeof(ThreadPlusCount));
diff --git a/aos/network/message_bridge_client_lib.cc b/aos/network/message_bridge_client_lib.cc
index bde8480..a5113a0 100644
--- a/aos/network/message_bridge_client_lib.cc
+++ b/aos/network/message_bridge_client_lib.cc
@@ -250,7 +250,8 @@
                      chrono::nanoseconds(remote_data->monotonic_sent_time())),
                  realtime_clock::time_point(
                      chrono::nanoseconds(remote_data->realtime_sent_time())),
-                 remote_data->queue_index());
+                 remote_data->queue_index(),
+                 UUID::FromVector(remote_data->boot_uuid()));
 
     client_status_->SampleFilter(
         client_index_,
diff --git a/aos/network/message_bridge_server_lib.cc b/aos/network/message_bridge_server_lib.cc
index 7347607..18df3fa 100644
--- a/aos/network/message_bridge_server_lib.cc
+++ b/aos/network/message_bridge_server_lib.cc
@@ -39,6 +39,9 @@
       fbb.CreateVector(static_cast<const uint8_t *>(context.data),
                        context.size);
 
+  flatbuffers::Offset<flatbuffers::Vector<uint8_t>> boot_uuid_offset =
+      context.remote_boot_uuid.PackVector(&fbb);
+
   RemoteData::Builder remote_data_builder(fbb);
   remote_data_builder.add_channel_index(channel_index_);
   remote_data_builder.add_queue_index(context.queue_index);
@@ -47,6 +50,7 @@
   remote_data_builder.add_realtime_sent_time(
       context.realtime_event_time.time_since_epoch().count());
   remote_data_builder.add_data(data_offset);
+  remote_data_builder.add_boot_uuid(boot_uuid_offset);
 
   // TODO(austin): Use an iovec to build it up in 3 parts to avoid the copy?
   // Only useful when not logging.
diff --git a/aos/network/message_bridge_server_status.cc b/aos/network/message_bridge_server_status.cc
index 7788f4a..06f88ed 100644
--- a/aos/network/message_bridge_server_status.cc
+++ b/aos/network/message_bridge_server_status.cc
@@ -355,6 +355,7 @@
   context.realtime_event_time = timestamp_sender_.realtime_sent_time();
   context.queue_index = timestamp_sender_.sent_queue_index();
   context.size = timestamp_copy.span().size();
+  context.remote_boot_uuid = event_loop_->boot_uuid();
   context.data = timestamp_copy.span().data();
 
   // Since we are building up the timestamp to send here, we need to trigger the
diff --git a/aos/network/message_bridge_test.cc b/aos/network/message_bridge_test.cc
index e2dddc4..1b6f594 100644
--- a/aos/network/message_bridge_test.cc
+++ b/aos/network/message_bridge_test.cc
@@ -11,6 +11,8 @@
 #include "aos/util/file.h"
 #include "gtest/gtest.h"
 
+DECLARE_string(boot_uuid);
+
 namespace aos {
 void SetShmBase(const std::string_view base);
 
@@ -47,7 +49,9 @@
  public:
   MessageBridgeParameterizedTest()
       : config(aos::configuration::ReadConfig(
-            absl::StrCat("aos/network/", GetParam().config))) {
+            absl::StrCat("aos/network/", GetParam().config))),
+        pi1_boot_uuid_(UUID::Random()),
+        pi2_boot_uuid_(UUID::Random()) {
     util::UnlinkRecursive(ShmBase("pi1"));
     util::UnlinkRecursive(ShmBase("pi2"));
   }
@@ -57,11 +61,13 @@
   void OnPi1() {
     DoSetShmBase("pi1");
     FLAGS_override_hostname = "raspberrypi";
+    FLAGS_boot_uuid = pi1_boot_uuid_.ToString();
   }
 
   void OnPi2() {
     DoSetShmBase("pi2");
     FLAGS_override_hostname = "raspberrypi2";
+    FLAGS_boot_uuid = pi2_boot_uuid_.ToString();
   }
 
   void MakePi1Server() {
@@ -148,6 +154,12 @@
         "/pi1/aos", [](const Timestamp &timestamp) {
           VLOG(1) << "/pi1/aos Timestamp " << FlatbufferToJson(&timestamp);
         });
+    pi1_test_event_loop->MakeWatcher(
+        "/pi2/aos", [this](const Timestamp &timestamp) {
+          VLOG(1) << "/pi2/aos Timestamp " << FlatbufferToJson(&timestamp);
+          EXPECT_EQ(pi1_test_event_loop->context().remote_boot_uuid,
+                    pi2_boot_uuid_);
+        });
   }
 
   void StartPi1Test() {
@@ -258,6 +270,12 @@
         });
 
     pi2_test_event_loop->MakeWatcher(
+        "/pi1/aos", [this](const Timestamp &timestamp) {
+          VLOG(1) << "/pi1/aos Timestamp " << FlatbufferToJson(&timestamp);
+          EXPECT_EQ(pi2_test_event_loop->context().remote_boot_uuid,
+                    pi1_boot_uuid_);
+        });
+    pi2_test_event_loop->MakeWatcher(
         "/pi2/aos", [](const Timestamp &timestamp) {
           VLOG(1) << "/pi2/aos Timestamp " << FlatbufferToJson(&timestamp);
         });
@@ -276,6 +294,8 @@
   }
 
   aos::FlatbufferDetachedBuffer<aos::Configuration> config;
+  const UUID pi1_boot_uuid_;
+  const UUID pi2_boot_uuid_;
 
   std::unique_ptr<aos::ShmEventLoop> pi1_server_event_loop;
   std::unique_ptr<MessageBridgeServer> pi1_message_bridge_server;
@@ -380,11 +400,12 @@
 
   // Count the pongs.
   int pong_count = 0;
-  pong_event_loop.MakeWatcher(
-      "/test", [&pong_count](const examples::Ping &ping) {
-        ++pong_count;
-        VLOG(1) << "Got ping back " << FlatbufferToJson(&ping);
-      });
+  pong_event_loop.MakeWatcher("/test", [&pong_count, &pong_event_loop,
+                                        this](const examples::Ping &ping) {
+    EXPECT_EQ(pong_event_loop.context().remote_boot_uuid, pi1_boot_uuid_);
+    ++pong_count;
+    VLOG(1) << "Got ping back " << FlatbufferToJson(&ping);
+  });
 
   FLAGS_override_hostname = "";
 
diff --git a/aos/uuid.cc b/aos/uuid.cc
index 71e688c..2e3290b 100644
--- a/aos/uuid.cc
+++ b/aos/uuid.cc
@@ -7,8 +7,12 @@
 #include <random>
 #include <string_view>
 
+#include "gflags/gflags.h"
 #include "glog/logging.h"
 
+DEFINE_string(boot_uuid, "",
+              "If set, override the boot UUID to have this value instead.");
+
 namespace aos {
 namespace {
 void ToHex(const uint8_t *val, char *result, size_t count) {
@@ -150,6 +154,10 @@
 }
 
 UUID UUID::BootUUID() {
+  if (!FLAGS_boot_uuid.empty()) {
+    return UUID::FromString(FLAGS_boot_uuid);
+  }
+
   int fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY);
   PCHECK(fd != -1);