Merge "Improve log_cats raw and fetching capabilities"
diff --git a/aos/configuration.cc b/aos/configuration.cc
index cef3876..43e44f2 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -12,6 +12,7 @@
 #include <string_view>
 
 #include "absl/container/btree_set.h"
+#include "absl/strings/str_cat.h"
 #include "aos/configuration_generated.h"
 #include "aos/flatbuffer_merge.h"
 #include "aos/json_to_flatbuffer.h"
@@ -539,6 +540,12 @@
   return FlatbufferToJson(cleaned_channel);
 }
 
+std::string StrippedChannelToString(const Channel *channel) {
+  return absl::StrCat("{ \"name\": \"", channel->name()->string_view(),
+                      "\", \"type\": \"", channel->type()->string_view(),
+                      "\" }");
+}
+
 FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
     const Flatbuffer<Configuration> &config,
     const std::vector<aos::FlatbufferString<reflection::Schema>> &schemas) {
diff --git a/aos/configuration.h b/aos/configuration.h
index 9193fd5..ccc6246 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -113,6 +113,8 @@
 
 // Prints a channel to json, but without the schema.
 std::string CleanedChannelToString(const Channel *channel);
+// Prints out a channel to json, but only with the name and type.
+std::string StrippedChannelToString(const Channel *channel);
 
 // Returns the node names that this node should be forwarding to.
 std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index a938367..d1481d5 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -13,11 +13,48 @@
 DEFINE_string(type, "",
               "Channel type to match for printing out channels. Empty means no "
               "type filter.");
+DEFINE_bool(fetch, false,
+            "If true, also print out the messages from before the start of the "
+            "log file");
 DEFINE_bool(raw, false,
             "If true, just print the data out unsorted and unparsed");
+DEFINE_bool(format_raw, true,
+            "If true and --raw is specified, print out raw data, but use the "
+            "schema to format the data.");
 DEFINE_int32(max_vector_size, 100,
              "If positive, vectors longer than this will not be printed");
 
+void LogContext(const aos::Channel *channel, std::string node_name,
+                const aos::Context &context) {
+  // Print the flatbuffer out to stdout, both to remove the
+  // unnecessary cruft from glog and to allow the user to readily
+  // redirect just the logged output independent of any debugging
+  // information on stderr.
+  if (context.monotonic_remote_time != context.monotonic_event_time) {
+    std::cout << node_name << context.realtime_event_time << " ("
+              << context.monotonic_event_time << ") sent "
+              << context.realtime_remote_time << " ("
+              << context.monotonic_remote_time << ") "
+              << channel->name()->c_str() << ' ' << channel->type()->c_str()
+              << ": "
+              << aos::FlatbufferToJson(
+                     channel->schema(),
+                     static_cast<const uint8_t *>(context.data),
+                     FLAGS_max_vector_size)
+              << std::endl;
+  } else {
+    std::cout << node_name << context.realtime_event_time << " ("
+              << context.monotonic_event_time << ") "
+              << channel->name()->c_str() << ' ' << channel->type()->c_str()
+              << ": "
+              << aos::FlatbufferToJson(
+                     channel->schema(),
+                     static_cast<const uint8_t *>(context.data),
+                     FLAGS_max_vector_size)
+              << std::endl;
+  }
+}
+
 int main(int argc, char **argv) {
   gflags::SetUsageMessage(
       "Usage:\n"
@@ -45,9 +82,24 @@
       if (!message) {
         break;
       }
+      const aos::Channel *channel =
+          reader.log_file_header()->configuration()->channels()->Get(
+              message.value().message().channel_index());
 
-      std::cout << aos::FlatbufferToJson(message.value(), FLAGS_max_vector_size)
-                << std::endl;
+      if (FLAGS_format_raw && message.value().message().data() != nullptr) {
+        std::cout << aos::configuration::StrippedChannelToString(channel) << " "
+                  << aos::FlatbufferToJson(message.value(), false, 4) << ": "
+                  << aos::FlatbufferToJson(
+                         channel->schema(),
+                         message.value().message().data()->data(),
+                         FLAGS_max_vector_size)
+                  << std::endl;
+      } else {
+        std::cout << aos::configuration::StrippedChannelToString(channel) << " "
+                  << aos::FlatbufferToJson(message.value(), false,
+                                           FLAGS_max_vector_size)
+                  << std::endl;
+      }
     }
     return 0;
   }
@@ -63,16 +115,22 @@
   }
 
   aos::logger::LogReader reader(logfiles);
-  reader.Register();
+
+  aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
+  reader.Register(&event_loop_factory);
 
   std::vector<std::unique_ptr<aos::EventLoop>> printer_event_loops;
 
   for (const aos::Node *node : reader.Nodes()) {
     std::unique_ptr<aos::EventLoop> printer_event_loop =
-        reader.event_loop_factory()->MakeEventLoop("printer", node);
+        event_loop_factory.MakeEventLoop("printer", node);
     printer_event_loop->SkipTimingReport();
     printer_event_loop->SkipAosLog();
 
+    std::vector<std::tuple<aos::monotonic_clock::time_point, std::string,
+                           std::unique_ptr<aos::RawFetcher>>>
+        messages;
+
     bool found_channel = false;
     const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
         reader.configuration()->channels();
@@ -93,37 +151,43 @@
                             : std::string(node->name()->string_view()) + " ";
 
         CHECK_NOTNULL(channel->schema());
+
+        if (FLAGS_fetch) {
+          // Grab the last message on each channel.
+          std::unique_ptr<aos::RawFetcher> fetcher =
+              printer_event_loop->MakeRawFetcher(channel);
+          if (fetcher->Fetch()) {
+            auto message =
+                std::make_tuple(fetcher->context().monotonic_event_time,
+                                node_name, std::move(fetcher));
+
+            // Insert it sorted into the vector so we can print in time order
+            // instead of channel order at the start.
+            auto it = std::lower_bound(
+                messages.begin(), messages.end(), message,
+                [](const std::tuple<aos::monotonic_clock::time_point,
+                                    std::string,
+                                    std::unique_ptr<aos::RawFetcher>> &a,
+                   const std::tuple<aos::monotonic_clock::time_point,
+                                    std::string,
+                                    std::unique_ptr<aos::RawFetcher>> &b) {
+                  if (std::get<0>(a) < std::get<0>(b)) {
+                    return true;
+                  }
+                  if (std::get<0>(a) > std::get<0>(b)) {
+                    return false;
+                  }
+
+                  return std::get<2>(a)->channel() < std::get<2>(b)->channel();
+                });
+            messages.insert(it, std::move(message));
+          }
+        }
+
         printer_event_loop->MakeRawWatcher(
             channel, [channel, node_name](const aos::Context &context,
-                                          const void *message) {
-              // Print the flatbuffer out to stdout, both to remove the
-              // unnecessary cruft from glog and to allow the user to readily
-              // redirect just the logged output independent of any debugging
-              // information on stderr.
-              if (context.monotonic_remote_time !=
-                  context.monotonic_event_time) {
-                std::cout << node_name << context.realtime_event_time << " ("
-                          << context.monotonic_event_time << ") sent "
-                          << context.realtime_remote_time << " ("
-                          << context.monotonic_remote_time << ") "
-                          << channel->name()->c_str() << ' '
-                          << channel->type()->c_str() << ": "
-                          << aos::FlatbufferToJson(
-                                 channel->schema(),
-                                 static_cast<const uint8_t *>(message),
-                                 FLAGS_max_vector_size)
-                          << std::endl;
-              } else {
-                std::cout << node_name << context.realtime_event_time << " ("
-                          << context.monotonic_event_time << ") "
-                          << channel->name()->c_str() << ' '
-                          << channel->type()->c_str() << ": "
-                          << aos::FlatbufferToJson(
-                                 channel->schema(),
-                                 static_cast<const uint8_t *>(message),
-                                 FLAGS_max_vector_size)
-                          << std::endl;
-              }
+                                          const void * /*message*/) {
+              LogContext(channel, node_name, context);
             });
         found_channel = true;
       }
@@ -132,10 +196,22 @@
     if (!found_channel) {
       LOG(FATAL) << "Could not find any channels";
     }
+    // TODO(austin): Sort between nodes too when it becomes annoying enough.
+    for (const std::tuple<aos::monotonic_clock::time_point, std::string,
+                          std::unique_ptr<aos::RawFetcher>> &message :
+         messages) {
+      LogContext(std::get<2>(message)->channel(), std::get<1>(message),
+                 std::get<2>(message)->context());
+    }
     printer_event_loops.emplace_back(std::move(printer_event_loop));
   }
 
-  reader.event_loop_factory()->Run();
+  if (FLAGS_fetch) {
+    // New line to separate fetched messages from non-fetched messages.
+    std::cout << std::endl;
+  }
+
+  event_loop_factory.Run();
 
   aos::Cleanup();
   return 0;