Merge "Add aos_dump autocomplete script"
diff --git a/aos/BUILD b/aos/BUILD
index 0129187..243e7f3 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -483,6 +483,21 @@
     ],
 )
 
+cc_binary(
+    name = "aos_graph_nodes",
+    srcs = [
+        "aos_graph_nodes.cc",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":configuration",
+        ":json_to_flatbuffer",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
 cc_library(
     name = "ftrace",
     srcs = [
diff --git a/aos/aos_graph_nodes.cc b/aos/aos_graph_nodes.cc
new file mode 100644
index 0000000..952c6b5
--- /dev/null
+++ b/aos/aos_graph_nodes.cc
@@ -0,0 +1,108 @@
+#include <iostream>
+#include <map>
+
+#include "aos/configuration.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "gflags/gflags.h"
+
+DEFINE_bool(all, false,
+            "If true, print out the channels for all nodes in the config file, "
+            "not just the channels which are visible on this node.");
+DEFINE_string(config, "./config.json", "File path of aos configuration");
+DEFINE_bool(short_types, true,
+            "Whether to show a shortened version of the type name");
+
+int main(int argc, char **argv) {
+  gflags::SetUsageMessage(
+      "\nCreates graph of nodes and message channels based on the robot config "
+      "file.  \n\n"
+      "To save to file, run as: \n"
+      "\t aos_graph_nodes > /tmp/graph.dot\n\n"
+      "To display graph, run as: \n"
+      "\t aos_graph_nodes | dot -Tx11");
+  aos::InitGoogle(&argc, &argv);
+
+  // Cycle through this list of colors-- here's some defaults.
+  // Can use color names or Hex values of RGB, e.g., red = "#FF0000"
+  std::vector<std::string> color_list = {"red",    "blue",  "orange", "green",
+                                         "violet", "gold3", "magenta"};
+  int color_index = 0;
+
+  std::string channel_name;
+  std::string message_type;
+
+  // Map from source nodes to color for them
+  std::map<std::string, std::string> color_map;
+
+  if (argc > 1) {
+    LOG(ERROR) << "ERROR: Got unexpected arguments\n\n";
+    gflags::ShowUsageWithFlagsRestrict(argv[0], "aos/aos_graph_nodes.cc");
+    return -1;
+  }
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  const aos::Configuration *config_msg = &config.message();
+  aos::ShmEventLoop event_loop(config_msg);
+  event_loop.SkipTimingReport();
+  event_loop.SkipAosLog();
+
+  // Open output file and print header
+  std::stringstream graph_out;
+  graph_out << "digraph g {" << std::endl;
+
+  for (const aos::Channel *channel : *config_msg->channels()) {
+    VLOG(1) << "Found channel " << channel->type()->string_view();
+    if (FLAGS_all || aos::configuration::ChannelIsReadableOnNode(
+                         channel, event_loop.node())) {
+      flatbuffers::string_view type_name = channel->type()->string_view();
+      if (FLAGS_short_types) {
+        // Strip down to just the top level of the message type
+        type_name = channel->type()->string_view().substr(
+            channel->type()->string_view().rfind(".") + 1, std::string::npos);
+      }
+
+      VLOG(1) << "Found: " << channel->name()->string_view() << ' '
+              << channel->type()->string_view();
+
+      CHECK(channel->has_source_node())
+          << ": Could not find source node for channel "
+          << channel->type()->string_view();
+      std::string source_node_name = channel->source_node()->c_str();
+      VLOG(1) << "Source node name:" << channel->source_node()->c_str();
+
+      // If we haven't seen this node yet, add to our list, with new color
+      if (color_map.count(source_node_name) == 0) {
+        color_map[source_node_name] = color_list[color_index];
+        color_index = (color_index + 1) % color_list.size();
+      }
+
+      if (channel->has_destination_nodes()) {
+        for (const aos::Connection *connection :
+             *channel->destination_nodes()) {
+          VLOG(1) << "Destination Node: " << connection->name()->string_view();
+          graph_out << "\t" << source_node_name << " -> "
+                    << connection->name()->c_str() << " [label=\""
+                    << channel->name()->c_str() << "\\n"
+                    << type_name << "\" color=\"" << color_map[source_node_name]
+                    << "\"];" << std::endl;
+        }
+      }
+    }
+  }
+
+  // Write out all the nodes at the end, with their respective colors
+  for (const auto node_color : color_map) {
+    graph_out << "\t" << node_color.first << " [color=\"" << node_color.second
+              << "\"];" << std::endl;
+  }
+
+  // Close out the file
+  graph_out << "}" << std::endl;
+
+  std::cout << graph_out.str();
+  return 0;
+}
diff --git a/aos/dump_rtprio.cc b/aos/dump_rtprio.cc
index 5f4397f..006b348 100644
--- a/aos/dump_rtprio.cc
+++ b/aos/dump_rtprio.cc
@@ -247,8 +247,6 @@
 }  // namespace
 
 int main() {
-  ::aos::logging::Init();
-
   const int pid_max = find_pid_max();
   const cpu_set_t all_cpus = find_all_cpus();
 
diff --git a/aos/events/event_loop.cc b/aos/events/event_loop.cc
index 639e337..5c0b628 100644
--- a/aos/events/event_loop.cc
+++ b/aos/events/event_loop.cc
@@ -81,9 +81,7 @@
 
 EventLoop::EventLoop(const Configuration *configuration)
     : timing_report_(flatbuffers::DetachedBuffer()),
-      configuration_(configuration) {
-  logging::Init();
-}
+      configuration_(configuration) {}
 
 EventLoop::~EventLoop() {
   CHECK_EQ(senders_.size(), 0u) << ": Not all senders destroyed";
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index 0afdc03..79ea1e3 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -5,6 +5,7 @@
 #include <string>
 #include <string_view>
 #include <vector>
+#include "dirent.h"
 
 #include "aos/configuration.h"
 #include "aos/events/logging/logger.h"
@@ -58,6 +59,31 @@
   }
 }
 
+void SearchDirectory(std::vector<std::string> *files, std::string filename) {
+  DIR *directory = opendir(filename.c_str());
+
+  if (directory == nullptr) {
+    // its not a directory
+    // it could be a file
+    // or it could not exist
+    files->emplace_back(filename);
+    return;
+  }
+
+  struct dirent *directory_entry;
+  while ((directory_entry = readdir(directory)) != nullptr) {
+    std::string next_filename = directory_entry->d_name;
+    if (next_filename == "." || next_filename == "..") {
+      continue;
+    }
+
+    std::string path = filename + "/" + next_filename;
+    SearchDirectory(files, path);
+  }
+
+  closedir(directory);
+}
+
 int main(int argc, char **argv) {
   gflags::SetUsageMessage(
       "Usage:\n"
@@ -124,7 +150,7 @@
 
   std::vector<std::string> unsorted_logfiles;
   for (int i = 1; i < argc; ++i) {
-    unsorted_logfiles.emplace_back(std::string(argv[i]));
+    SearchDirectory(&unsorted_logfiles, argv[i]);
   }
 
   const std::vector<aos::logger::LogFile> logfiles =
diff --git a/aos/init.cc b/aos/init.cc
index 163f5dd..51e22cf 100644
--- a/aos/init.cc
+++ b/aos/init.cc
@@ -39,7 +39,6 @@
 // Common stuff that needs to happen at the beginning of both the realtime and
 // non-realtime initialization sequences. May be called twice.
 void InitStart() {
-  ::aos::logging::Init();
   if (FLAGS_coredump) {
     WriteCoreDumps();
   }
diff --git a/aos/ipc_lib/eventfd_latency.cc b/aos/ipc_lib/eventfd_latency.cc
index 9229f1a..d146c13 100644
--- a/aos/ipc_lib/eventfd_latency.cc
+++ b/aos/ipc_lib/eventfd_latency.cc
@@ -160,7 +160,5 @@
 int main(int argc, char **argv) {
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  ::aos::logging::Init();
-
   return ::aos::Main(argc, argv);
 }
diff --git a/aos/ipc_lib/futex_latency.cc b/aos/ipc_lib/futex_latency.cc
index 59fa860..ef27e29 100644
--- a/aos/ipc_lib/futex_latency.cc
+++ b/aos/ipc_lib/futex_latency.cc
@@ -164,7 +164,5 @@
 int main(int argc, char **argv) {
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  ::aos::logging::Init();
-
   return ::aos::Main(argc, argv);
 }
diff --git a/aos/ipc_lib/ipc_comparison.cc b/aos/ipc_lib/ipc_comparison.cc
index 9191eae..6435ee7 100644
--- a/aos/ipc_lib/ipc_comparison.cc
+++ b/aos/ipc_lib/ipc_comparison.cc
@@ -902,7 +902,6 @@
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   ::aos::InitNRT();
-  ::aos::logging::Init();
 
   return ::aos::Main(argc, argv);
 }
diff --git a/aos/ipc_lib/named_pipe_latency.cc b/aos/ipc_lib/named_pipe_latency.cc
index c3f5c5c..2bd24f3 100644
--- a/aos/ipc_lib/named_pipe_latency.cc
+++ b/aos/ipc_lib/named_pipe_latency.cc
@@ -168,7 +168,5 @@
 int main(int argc, char **argv) {
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  ::aos::logging::Init();
-
   return ::aos::Main(argc, argv);
 }
diff --git a/aos/ipc_lib/signal_stress.cc b/aos/ipc_lib/signal_stress.cc
index ea9537f..60243fd 100644
--- a/aos/ipc_lib/signal_stress.cc
+++ b/aos/ipc_lib/signal_stress.cc
@@ -191,7 +191,5 @@
 int main(int argc, char **argv) {
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  ::aos::logging::Init();
-
   return ::aos::Main(argc, argv);
 }
diff --git a/aos/logging/context.cc b/aos/logging/context.cc
index 446e0df..0628e0e 100644
--- a/aos/logging/context.cc
+++ b/aos/logging/context.cc
@@ -42,7 +42,7 @@
 
   char thread_name_array[kThreadNameLength + 1];
   if (prctl(PR_GET_NAME, thread_name_array) != 0) {
-    PDie("prctl(PR_GET_NAME, %p) failed", thread_name_array);
+    PLOG(FATAL) << "prctl(PR_GET_NAME, " << thread_name_array << ") failed";
   }
 #if __has_feature(memory_sanitizer)
   // msan doesn't understand PR_GET_NAME, so help it along.
@@ -73,7 +73,7 @@
 
 }  // namespace
 
-Context::Context() : implementation(GetImplementation()), sequence(0) {}
+Context::Context() : sequence(0) {}
 
 // Used in aos/linux_code/init.cc when a thread's name is changed.
 void ReloadThreadName() {
diff --git a/aos/logging/context.h b/aos/logging/context.h
index 43ac54f..cc770ba 100644
--- a/aos/logging/context.h
+++ b/aos/logging/context.h
@@ -45,7 +45,8 @@
   static void DeleteNow();
 
   // Which one to log to right now.
-  // Will be NULL if there is no logging implementation to use right now.
+  // Will be NULL if there is no logging implementation to use right now and we
+  // should use stderr instead.
   std::shared_ptr<LogImplementation> implementation;
 
   // A name representing this task/(process and thread).
diff --git a/aos/logging/implementations.cc b/aos/logging/implementations.cc
index 096127a..be54eec 100644
--- a/aos/logging/implementations.cc
+++ b/aos/logging/implementations.cc
@@ -5,33 +5,17 @@
 
 #include <algorithm>
 #include <chrono>
-#include <mutex>
 
-#include "absl/base/call_once.h"
-#include "aos/die.h"
 #include "aos/logging/printf_formats.h"
-#include "aos/stl_mutex/stl_mutex.h"
 #include "aos/time/time.h"
 
 namespace aos {
 namespace logging {
+namespace internal {
 namespace {
 
 namespace chrono = ::std::chrono;
 
-struct GlobalState {
-  std::shared_ptr<LogImplementation> implementation;
-  aos::stl_mutex lock;
-  static GlobalState *Get() {
-    static GlobalState r;
-    return &r;
-  }
-};
-
-}  // namespace
-namespace internal {
-namespace {
-
 void FillInMessageBase(log_level level,
                        monotonic_clock::time_point monotonic_now,
                        LogMessage *message) {
@@ -61,21 +45,15 @@
 
   message->message_length =
       ExecuteFormat(message->message, sizeof(message->message), format, ap);
-  message->type = LogMessage::Type::kString;
 }
 
 void PrintMessage(FILE *output, const LogMessage &message) {
-#define BASE_ARGS                                                              \
-  AOS_LOGGING_BASE_ARGS(                                                       \
-      message.name_length, message.name, static_cast<int32_t>(message.source), \
-      message.sequence, message.level, message.seconds, message.nseconds)
-  switch (message.type) {
-    case LogMessage::Type::kString:
-      fprintf(output, AOS_LOGGING_BASE_FORMAT "%.*s", BASE_ARGS,
-              static_cast<int>(message.message_length), message.message);
-      break;
-  }
-#undef BASE_ARGS
+  fprintf(output, AOS_LOGGING_BASE_FORMAT "%.*s",
+          AOS_LOGGING_BASE_ARGS(message.name_length, message.name,
+                                static_cast<int32_t>(message.source),
+                                message.sequence, message.level,
+                                message.seconds, message.nseconds),
+          static_cast<int>(message.message_length), message.message);
 }
 
 }  // namespace internal
@@ -95,55 +73,13 @@
 }
 
 void SetImplementation(std::shared_ptr<LogImplementation> implementation) {
-  Init();
-  GlobalState *const global = GlobalState::Get();
-  std::unique_lock<aos::stl_mutex> locker(global->lock);
-  global->implementation = std::move(implementation);
-}
-
-std::shared_ptr<LogImplementation> SwapImplementation(
-    std::shared_ptr<LogImplementation> implementation) {
-  std::shared_ptr<LogImplementation> result;
-  {
-    GlobalState *const global = GlobalState::Get();
-    std::unique_lock<aos::stl_mutex> locker(global->lock);
-    result = std::move(global->implementation);
-    global->implementation = std::move(implementation);
-  }
-  Cleanup();
-  return result;
+  internal::Context *context = internal::Context::Get();
+  context->implementation = std::move(implementation);
 }
 
 std::shared_ptr<LogImplementation> GetImplementation() {
-  GlobalState *const global = GlobalState::Get();
-  std::unique_lock<aos::stl_mutex> locker(global->lock);
-  CHECK(global->implementation);
-  return global->implementation;
-}
-
-namespace {
-
-struct DoInit {
-  DoInit() {
-    GlobalState *const global = GlobalState::Get();
-    std::unique_lock<aos::stl_mutex> locker(global->lock);
-    CHECK(!global->implementation);
-    global->implementation = std::make_shared<StreamLogImplementation>(stdout);
-  }
-};
-
-}  // namespace
-
-void Init() { static DoInit do_init; }
-
-void Load() { internal::Context::Get(); }
-
-void Cleanup() { internal::Context::Delete(); }
-
-void RegisterCallbackImplementation(
-    const ::std::function<void(const LogMessage &)> &callback) {
-  Init();
-  SetImplementation(std::make_shared<CallbackLogImplementation>(callback));
+  internal::Context *context = internal::Context::Get();
+  return context->implementation;
 }
 
 }  // namespace logging
diff --git a/aos/logging/implementations.h b/aos/logging/implementations.h
index 962982b..6395c37 100644
--- a/aos/logging/implementations.h
+++ b/aos/logging/implementations.h
@@ -35,8 +35,6 @@
 
 // Contains all of the information about a given logging call.
 struct LogMessage {
-  enum class Type : uint8_t { kString };
-
   int32_t seconds, nseconds;
   // message_length is just the length of the actual data (which member depends
   // on the type).
@@ -45,26 +43,9 @@
   static_assert(sizeof(source) == 4, "that's how they get printed");
   // Per task/thread.
   uint16_t sequence;
-  Type type;
   log_level level;
   char name[LOG_MESSAGE_NAME_LEN];
-  union {
-    char message[LOG_MESSAGE_LEN];
-    struct {
-      uint32_t type_id;
-      size_t string_length;
-      // The message string and then the serialized structure.
-      char serialized[LOG_MESSAGE_LEN - sizeof(type) - sizeof(string_length)];
-    } structure;
-    struct {
-      // The type ID of the element type.
-      uint32_t type;
-      int rows, cols;
-      size_t string_length;
-      // The message string and then the serialized matrix.
-      char data[LOG_MESSAGE_LEN - sizeof(type) - sizeof(rows) - sizeof(cols)];
-    } matrix;
-  };
+  char message[LOG_MESSAGE_LEN];
 };
 static_assert(shm_ok<LogMessage>::value, "it's going in a queue");
 
@@ -118,20 +99,13 @@
   FILE *const stream_;
 };
 
+// Returns the current implementation.
 std::shared_ptr<LogImplementation> GetImplementation();
 
 // Sets the current implementation.
 void SetImplementation(std::shared_ptr<LogImplementation> implementation);
 
-// Updates the log implementation, returning the current implementation.
-std::shared_ptr<LogImplementation> SwapImplementation(
-    std::shared_ptr<LogImplementation> implementation);
-
-// Must be called at least once per process/load before anything else is
-// called. This function is safe to call multiple times from multiple
-// tasks/threads.
-void Init();
-
+// A logging implementation which just uses a callback.
 class CallbackLogImplementation : public HandleMessageLogImplementation {
  public:
   CallbackLogImplementation(
@@ -144,27 +118,13 @@
   ::std::function<void(const LogMessage &)> callback_;
 };
 
-// Resets all information in this task/thread to its initial state.
-// NOTE: This is not the opposite of Init(). The state that this deletes is
-// lazily created when needed. It is actually the opposite of Load().
-void Cleanup();
-
-void RegisterCallbackImplementation(
-    const ::std::function<void(const LogMessage &)> &callback);
-
 class ScopedLogRestorer {
  public:
-  ScopedLogRestorer() = default;
-
-  ~ScopedLogRestorer() {
-    if (prev_impl_) {
-      SetImplementation(std::move(prev_impl_));
-    }
-    Cleanup();
-  }
+  ScopedLogRestorer() : prev_impl_(GetImplementation()) {}
+  ~ScopedLogRestorer() { SetImplementation(std::move(prev_impl_)); }
 
   void Swap(std::shared_ptr<LogImplementation> new_impl) {
-    prev_impl_ = SwapImplementation(std::move(new_impl));
+    SetImplementation(std::move(new_impl));
   }
 
  private:
diff --git a/aos/logging/implementations_test.cc b/aos/logging/implementations_test.cc
index f6cdc6f..058baab 100644
--- a/aos/logging/implementations_test.cc
+++ b/aos/logging/implementations_test.cc
@@ -92,7 +92,6 @@
   }
   void TearDown() override {
     SetImplementation(nullptr);
-    Cleanup();
     internal::Context::DeleteNow();
     CHECK_EQ(log_implementation.use_count(), 1);
     log_implementation.reset();
diff --git a/aos/logging/interface.cc b/aos/logging/interface.cc
index b2f7b75..0cb0cd8 100644
--- a/aos/logging/interface.cc
+++ b/aos/logging/interface.cc
@@ -9,6 +9,8 @@
 
 #include "aos/die.h"
 #include "aos/logging/context.h"
+#include "aos/logging/implementations.h"
+#include "glog/logging.h"
 
 namespace aos {
 namespace logging {
@@ -21,8 +23,8 @@
   const int ret = vsnprintf(output, size, format, ap);
   typedef ::std::common_type<int, size_t>::type RetType;
   if (ret < 0) {
-    AOS_PLOG(FATAL, "vsnprintf(%p, %zd, %s, args) failed", output, size,
-             format);
+    PLOG(FATAL) << "vsnprintf(" << output << ", " << size << ", " << format
+                << ", args) failed";
   } else if (static_cast<RetType>(ret) >= static_cast<RetType>(size)) {
     // Overwrite the '\0' at the end of the existing data and
     // copy in the one on the end of continued.
@@ -31,39 +33,32 @@
   return ::std::min<RetType>(ret, size);
 }
 
-void RunWithCurrentImplementation(
-    ::std::function<void(LogImplementation *)> function) {
-  Context *context = Context::Get();
-
-  const std::shared_ptr<LogImplementation> implementation =
-      context->implementation;
-  if (implementation == NULL) {
-    Die("no logging implementation to use\n");
-  }
-  function(implementation.get());
-}
-
 }  // namespace internal
 
 using internal::Context;
 
-void LogImplementation::DoVLog(log_level level, const char *format,
-                               va_list ap) {
-  auto log_impl = [&](LogImplementation *implementation) {
-    va_list ap1;
-    va_copy(ap1, ap);
-    implementation->DoLog(level, format, ap1);
-    va_end(ap1);
-
-    if (level == FATAL) {
-      VDie(format, ap);
-    }
-  };
-  internal::RunWithCurrentImplementation(::std::ref(log_impl));
-}
-
 void VLog(log_level level, const char *format, va_list ap) {
-  LogImplementation::DoVLog(level, format, ap);
+  va_list ap1;
+  va_copy(ap1, ap);
+
+  Context *context = Context::Get();
+
+  const std::shared_ptr<LogImplementation> implementation =
+      context->implementation;
+  // Log to the implementation if we have it, and stderr as a backup.
+  if (implementation) {
+    implementation->DoLog(level, format, ap1);
+  } else {
+    aos::logging::LogMessage message;
+    aos::logging::internal::FillInMessage(level, aos::monotonic_clock::now(),
+                                          format, ap, &message);
+    aos::logging::internal::PrintMessage(stderr, message);
+  }
+  va_end(ap1);
+
+  if (level == FATAL) {
+    VDie(format, ap);
+  }
 }
 
 }  // namespace logging
diff --git a/aos/logging/interface.h b/aos/logging/interface.h
index 3695e40..387e55d 100644
--- a/aos/logging/interface.h
+++ b/aos/logging/interface.h
@@ -21,30 +21,15 @@
 // for the current LogImplementation.
 void VLog(log_level level, const char *format, va_list ap)
     __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 2, 0)));
-// Adds to the saved up message.
-void VCork(int line, const char *function, const char *format, va_list ap)
-    __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)));
-// Actually logs the saved up message.
-void VUnCork(int line, const char *function, log_level level, const char *file,
-             const char *format, va_list ap)
-    __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 5, 0)));
 
 // Represents a system that can actually take log messages and do something
 // useful with them.
-// All of the code (transitively too!) in the DoLog here can make
-// normal LOG and LOG_DYNAMIC calls but can NOT call LOG_CORK/LOG_UNCORK. These
-// calls will not result in DoLog recursing. However, implementations must be
-// safe to call from multiple threads/tasks at the same time. Also, any other
-// overriden methods may end up logging through a given implementation's DoLog.
 class LogImplementation {
  public:
   LogImplementation() {}
 
   virtual ~LogImplementation() {}
 
-  virtual bool fill_type_cache() { return true; }
-
- protected:
   // Actually logs the given message. Implementations should somehow create a
   // LogMessage and then call internal::FillInMessage.
   __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) virtual void DoLog(
@@ -56,15 +41,6 @@
     DoLog(level, format, ap);
     va_end(ap);
   }
-
- private:
-  // These functions call similar methods on the "current" LogImplementation or
-  // Die if they can't find one.
-  // levels is how many LogImplementations to not use off the stack.
-  static void DoVLog(log_level, const char *format, va_list ap)
-      __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 2, 0)));
-
-  friend void VLog(log_level, const char *, va_list);
 };
 
 namespace internal {
@@ -75,12 +51,6 @@
                      va_list ap)
     __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)));
 
-// Runs the given function with the current LogImplementation (handles switching
-// it out while running function etc).
-// levels is how many LogImplementations to not use off the stack.
-void RunWithCurrentImplementation(
-    int levels, ::std::function<void(LogImplementation *)> function);
-
 }  // namespace internal
 }  // namespace logging
 }  // namespace aos
diff --git a/aos/mutex/mutex_test.cc b/aos/mutex/mutex_test.cc
index 07de9dc..c0ec66e 100644
--- a/aos/mutex/mutex_test.cc
+++ b/aos/mutex/mutex_test.cc
@@ -66,7 +66,6 @@
 
 // Sees what happens with multiple unlocks.
 TEST_F(MutexDeathTest, RepeatUnlock) {
-  logging::Init();
   ASSERT_FALSE(test_mutex_.Lock());
   test_mutex_.Unlock();
   EXPECT_DEATH(
@@ -80,7 +79,6 @@
 
 // Sees what happens if you unlock without ever locking (or unlocking) it.
 TEST_F(MutexDeathTest, NeverLock) {
-  logging::Init();
   EXPECT_DEATH(
       {
         logging::SetImplementation(
diff --git a/aos/starter/starter.cc b/aos/starter/starter.cc
index 4bfb3d2..6431a25 100644
--- a/aos/starter/starter.cc
+++ b/aos/starter/starter.cc
@@ -714,8 +714,6 @@
 
 void Run();
 void Main() {
-  logging::Init();
-
   // Set UID to 0 so we can run things as root down below. Since the starter
   // program on the roborio runs starter.sh under "lvuser", it will continuously
   // fail due to lack of permissions if we do not manually set the UID to admin.
diff --git a/aos/testing/test_logging.cc b/aos/testing/test_logging.cc
index 77bc4e9..b5b71ff 100644
--- a/aos/testing/test_logging.cc
+++ b/aos/testing/test_logging.cc
@@ -22,19 +22,7 @@
  public:
   const ::std::vector<LogMessage> &messages() { return messages_; }
 
-  // Sets the current thread's time to be monotonic_now for logging.
-  void MockTime(::aos::monotonic_clock::time_point monotonic_now) {
-    mock_time_ = true;
-    monotonic_now_ = monotonic_now;
-  }
-
-  // Clears any mock time for the current thread.
-  void UnMockTime() { mock_time_ = false; }
-
   ::aos::monotonic_clock::time_point monotonic_now() const override {
-    if (mock_time_) {
-      return monotonic_now_;
-    }
     return ::aos::monotonic_clock::now();
   }
 
@@ -70,8 +58,6 @@
     }
   }
 
-  bool fill_type_cache() override { return false; }
-
   void PrintMessagesAsTheyComeIn() { print_as_messages_come_in_ = true; }
 
   // Don't call these from outside this class.
@@ -95,18 +81,8 @@
   bool print_as_messages_come_in_ = false;
   FILE *output_file_ = stdout;
   ::aos::Mutex messages_mutex_;
-
-  // Thread local storage for mock time.  This is thread local because if
-  // someone spawns a thread and goes to town in parallel with a simulated event
-  // loop, we want to just print the actual monotonic clock out.
-  static AOS_THREAD_LOCAL bool mock_time_;
-  static AOS_THREAD_LOCAL ::aos::monotonic_clock::time_point monotonic_now_;
 };
 
-AOS_THREAD_LOCAL bool TestLogImplementation::mock_time_ = false;
-AOS_THREAD_LOCAL ::aos::monotonic_clock::time_point
-    TestLogImplementation::monotonic_now_ = ::aos::monotonic_clock::min_time;
-
 class MyTestEventListener : public ::testing::EmptyTestEventListener {
   virtual void OnTestStart(const ::testing::TestInfo & /*test_info*/) {
     TestLogImplementation::GetInstance()->ClearMessages();
@@ -138,7 +114,6 @@
 };
 
 void *DoEnableTestLogging() {
-  logging::Init();
   logging::SetImplementation(TestLogImplementation::GetInstance());
 
   ::testing::UnitTest::GetInstance()->listeners().Append(
@@ -163,10 +138,5 @@
   TestLogImplementation::GetInstance()->PrintMessagesAsTheyComeIn();
 }
 
-void MockTime(::aos::monotonic_clock::time_point monotonic_now) {
-  TestLogImplementation::GetInstance()->MockTime(monotonic_now);
-}
-void UnMockTime() { TestLogImplementation::GetInstance()->UnMockTime(); }
-
 }  // namespace testing
 }  // namespace aos
diff --git a/aos/testing/test_logging.h b/aos/testing/test_logging.h
index e7059d1..0a26b07 100644
--- a/aos/testing/test_logging.h
+++ b/aos/testing/test_logging.h
@@ -23,13 +23,6 @@
 // we want to use graphing tools to verify what's happening.
 void ForcePrintLogsDuringTests();
 
-// Sets the current mock logging time to monotonic_now.  This only applies to
-// the current thread.
-void MockTime(::aos::monotonic_clock::time_point monotonic_now);
-// Clears the mock logging time for the current thread and goes back to using
-// monotonic_clock::now().
-void UnMockTime();
-
 }  // namespace testing
 }  // namespace aos
 
diff --git a/aos/transaction/BUILD b/aos/transaction/BUILD
deleted file mode 100644
index 81dc8a0..0000000
--- a/aos/transaction/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-cc_library(
-    name = "transaction",
-    hdrs = [
-        "transaction.h",
-    ],
-    deps = [
-        "//aos/logging",
-        "//aos/util:compiler_memory_barrier",
-    ],
-)
-
-cc_test(
-    name = "transaction_test",
-    srcs = [
-        "transaction_test.cc",
-    ],
-    deps = [
-        ":transaction",
-        "//aos/logging",
-        "//aos/testing:googletest",
-        "//aos/util:death_test_log_implementation",
-    ],
-)
diff --git a/aos/transaction/transaction.h b/aos/transaction/transaction.h
deleted file mode 100644
index e0266f9..0000000
--- a/aos/transaction/transaction.h
+++ /dev/null
@@ -1,103 +0,0 @@
-#ifndef AOS_TRANSACTION_H_
-#define AOS_TRANSACTION_H_
-
-#include <stdint.h>
-
-#include <array>
-
-#include "aos/util/compiler_memory_barrier.h"
-#include "aos/logging/logging.h"
-
-namespace aos {
-namespace transaction {
-
-// Manages a LIFO stack of Work objects. Designed to help implement transactions
-// by providing a safe way to undo things etc.
-//
-// number_works Work objects are created statically and then Create is called on
-// each as it is added to the stack. When the work should do whatever it does,
-// DoWork() will be called. The work objects get no notification when they are
-// dropped off of the stack.
-//
-// Work::DoWork() must be idempotent because it may get called multiple times if
-// CompleteWork() is interrupted part of the way through.
-//
-// This class handles compiler memory barriers etc to make sure only fully
-// created works are ever invoked, and each work will be fully created by the
-// time AddWork returns. This does not mean it's safe for multiple threads to
-// interact with an instance of this class at the same time.
-template <class Work, int number_works>
-class WorkStack {
- public:
-  // Calls DoWork() on all the works that have been added and then removes them
-  // all from the stack.
-  void CompleteWork() {
-    int current = stack_index_;
-    while (current > 0) {
-      stack_.at(--current).DoWork();
-    }
-    aos_compiler_memory_barrier();
-    stack_index_ = 0;
-    aos_compiler_memory_barrier();
-  }
-
-  // Drops all works that have been added.
-  void DropWork() {
-    stack_index_ = 0;
-    aos_compiler_memory_barrier();
-  }
-
-  // Returns true if we have any works to complete right now.
-  bool HasWork() const { return stack_index_ != 0; }
-
-  // Forwards all of its arguments to Work::Create, which it calls on the next
-  // work to be added.
-  template <class... A>
-  void AddWork(A &&... a) {
-    if (stack_index_ >= number_works) {
-      AOS_LOG(FATAL, "too many works\n");
-    }
-    stack_.at(stack_index_).Create(::std::forward<A>(a)...);
-    aos_compiler_memory_barrier();
-    ++stack_index_;
-    aos_compiler_memory_barrier();
-  }
-
- private:
-  // The next index into stack_ for a new work to be added.
-  int stack_index_ = 0;
-  ::std::array<Work, number_works> stack_;
-};
-
-// When invoked, sets *pointer to the value it had when this work was Created.
-template <class T>
-class RestoreValueWork {
- public:
-  void Create(T *pointer) {
-    pointer_ = pointer;
-    value_ = *pointer;
-  }
-  void DoWork() {
-    *pointer_ = value_;
-  }
-
- private:
-  T *pointer_;
-  T value_;
-};
-
-// Handles the casting necessary to restore any kind of pointer.
-class RestorePointerWork : public RestoreValueWork<void *> {
- public:
-  template <class T>
-  void Create(T **pointer) {
-    static_assert(sizeof(T *) == sizeof(void *),
-                  "that's a weird pointer");
-    RestoreValueWork<void *>::Create(reinterpret_cast<void **>(pointer));
-  }
-};
-
-}  // namespace transaction
-}  // namespace aos
-
-#endif  // AOS_TRANSACTION_H_
diff --git a/aos/transaction/transaction_test.cc b/aos/transaction/transaction_test.cc
deleted file mode 100644
index 52b297e..0000000
--- a/aos/transaction/transaction_test.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-#include "aos/transaction/transaction.h"
-
-#include <vector>
-
-#include "gtest/gtest.h"
-
-#include "aos/util/death_test_log_implementation.h"
-
-namespace aos {
-namespace transaction {
-namespace testing {
-
-class WorkStackTest : public ::testing::Test {
- public:
-  // Contains an index which it adds to the created_works and invoked_works
-  // vectors of its containing WorkStackTest.
-  class TestWork {
-   public:
-    void Create(WorkStackTest *test, int i) {
-      test->created_works()->push_back(i);
-      i_ = i;
-      test_ = test;
-    }
-    void DoWork() { test_->invoked_works()->push_back(i_); }
-
-    int i() const { return i_; }
-
-   private:
-    int i_;
-    WorkStackTest *test_;
-  };
-
-  ::std::vector<int> *created_works() { return &created_works_; }
-  ::std::vector<int> *invoked_works() { return &invoked_works_; }
-  WorkStack<TestWork, 20> *work_stack() { return &work_stack_; }
-
-  // Creates a TestWork with index i and adds it to work_stack().
-  void CreateWork(int i) { work_stack_.AddWork(this, i); }
-
- private:
-  ::std::vector<int> created_works_, invoked_works_;
-  WorkStack<TestWork, 20> work_stack_;
-};
-
-typedef WorkStackTest WorkStackDeathTest;
-
-TEST_F(WorkStackTest, Basic) {
-  EXPECT_FALSE(work_stack()->HasWork());
-  EXPECT_EQ(0u, created_works()->size());
-  EXPECT_EQ(0u, invoked_works()->size());
-
-  CreateWork(971);
-  EXPECT_TRUE(work_stack()->HasWork());
-  EXPECT_EQ(1u, created_works()->size());
-  EXPECT_EQ(0u, invoked_works()->size());
-  EXPECT_EQ(971, created_works()->at(0));
-
-  work_stack()->CompleteWork();
-  EXPECT_FALSE(work_stack()->HasWork());
-  EXPECT_EQ(1u, created_works()->size());
-  EXPECT_EQ(1u, invoked_works()->size());
-  EXPECT_EQ(971, invoked_works()->at(0));
-}
-
-TEST_F(WorkStackTest, DropWork) {
-  CreateWork(971);
-  CreateWork(254);
-  EXPECT_EQ(2u, created_works()->size());
-
-  work_stack()->DropWork();
-  EXPECT_FALSE(work_stack()->HasWork());
-  work_stack()->CompleteWork();
-  EXPECT_EQ(0u, invoked_works()->size());
-}
-
-// Tests that the works get run in the correct order.
-TEST_F(WorkStackTest, InvocationOrder) {
-  CreateWork(971);
-  CreateWork(254);
-  CreateWork(1678);
-
-  work_stack()->CompleteWork();
-  EXPECT_EQ((::std::vector<int>{971, 254, 1678}), *created_works());
-  EXPECT_EQ((::std::vector<int>{1678, 254, 971}), *invoked_works());
-}
-
-// Tests that it handles adding too many works intelligently.
-TEST_F(WorkStackDeathTest, TooManyWorks) {
-  logging::Init();
-  EXPECT_DEATH(
-      {
-        logging::SetImplementation(
-            std::make_shared<util::DeathTestLogImplementation>());
-        for (int i = 0; i < 1000; ++i) {
-          CreateWork(i);
-        }
-      },
-      ".*too many works.*");
-}
-
-}  // namespace testing
-}  // namespace transaction
-}  // namespace aos
diff --git a/aos/util/BUILD b/aos/util/BUILD
index b23b720..01be701 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -237,28 +237,6 @@
     ],
 )
 
-cc_library(
-    name = "linked_list",
-    hdrs = [
-        "linked_list.h",
-    ],
-    deps = [
-        "//aos/transaction",
-    ],
-)
-
-cc_test(
-    name = "linked_list_test",
-    srcs = [
-        "linked_list_test.cc",
-    ],
-    deps = [
-        ":linked_list",
-        "//aos/logging",
-        "//aos/testing:googletest",
-    ],
-)
-
 cc_test(
     name = "phased_loop_test",
     srcs = [
diff --git a/aos/util/linked_list.h b/aos/util/linked_list.h
deleted file mode 100644
index 14f1788..0000000
--- a/aos/util/linked_list.h
+++ /dev/null
@@ -1,113 +0,0 @@
-#ifndef AOS_UTIL_LINKED_LIST_H_
-#define AOS_UTIL_LINKED_LIST_H_
-
-#include <functional>
-
-#include "aos/transaction/transaction.h"
-
-namespace aos {
-namespace util {
-
-// Handles manipulating an intrusive linked list. T must look like the
-// following:
-// struct T {
-//   ...
-//   T *next;
-//   ...
-// };
-// This class doesn't deal with creating or destroying them, so
-// constructors/destructors/other members variables/member functions are all
-// fine, but the next pointer must be there for this class to work.
-// This class will handle all manipulations of next. It does not need to be
-// initialized before calling Add and should not be changed afterwards.
-// next can (and probably should) be private if the appropriate instantiation of
-// this class is friended.
-template <class T>
-class LinkedList {
- public:
-  T *head() const { return head_; }
-
-  bool Empty() const { return head() == nullptr; }
-
-  void Add(T *t) {
-    Add<0>(t, nullptr);
-  }
-
-  // restore_points (if non-null) will be used so the operation can be safely
-  // reverted at any point.
-  template <int number_works>
-  void Add(T *t, transaction::WorkStack<transaction::RestorePointerWork,
-                                        number_works> *restore_pointers) {
-    if (restore_pointers != nullptr) restore_pointers->AddWork(&t->next);
-    t->next = head();
-    if (restore_pointers != nullptr) restore_pointers->AddWork(&head_);
-    head_ = t;
-  }
-
-  void Remove(T *t) {
-    Remove<0>(t, nullptr);
-  }
-
-  // restore_points (if non-null) will be used so the operation can be safely
-  // reverted at any point.
-  template <int number_works>
-  void Remove(T *t, transaction::WorkStack<transaction::RestorePointerWork,
-                                           number_works> *restore_pointers) {
-    T **pointer = &head_;
-    while (*pointer != nullptr) {
-      if (*pointer == t) {
-        if (restore_pointers != nullptr) {
-          restore_pointers->AddWork(pointer);
-        }
-        *pointer = t->next;
-        return;
-      }
-      pointer = &(*pointer)->next;
-    }
-    AOS_LOG(FATAL, "%p is not in the list\n", t);
-  }
-
-  // Calls function for each element of the list.
-  // function can modify these elements in any way except touching the next
-  // pointer (including by calling other methods of this object).
-  void Each(::std::function<void(T *)> function) const {
-    T *c = head();
-    while (c != nullptr) {
-      T *const next = c->next;
-      function(c);
-      c = next;
-    }
-  }
-
-  // Returns the first element of the list where function returns true or
-  // nullptr if it returns false for all.
-  T *Find(::std::function<bool(const T *)> function) const {
-    T *c = head();
-    while (c != nullptr) {
-      if (function(c)) return c;
-      c = c->next;
-    }
-    return nullptr;
-  }
-
- private:
-  T *head_ = nullptr;
-};
-
-// Keeps track of something along with a next pointer. Useful for things that
-// either have types without next pointers or for storing pointers to things
-// that belong in multiple lists.
-template <class V>
-struct LinkedListReference {
-  V item;
-
- private:
-  friend class LinkedList<LinkedListReference>;
-
-  LinkedListReference *next;
-};
-
-}  // namespace util
-}  // namespace aos
-
-#endif  // AOS_UTIL_LINKED_LIST_H_
diff --git a/aos/util/linked_list_test.cc b/aos/util/linked_list_test.cc
deleted file mode 100644
index 6959628..0000000
--- a/aos/util/linked_list_test.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-#include "aos/util/linked_list.h"
-
-#include <vector>
-
-#include "gtest/gtest.h"
-
-namespace aos {
-namespace util {
-namespace testing {
-
-class LinkedListTest : public ::testing::Test {
- public:
-  virtual ~LinkedListTest() {
-    while (list.head() != nullptr) {
-      RemoveElement(list.head());
-    }
-  }
-
-  struct Member {
-    Member(int i) : i(i) {}
-
-    int i;
-    Member *next = nullptr;
-  };
-  LinkedList<Member> list;
-
-  Member *AddElement(int i) {
-    Member *member = new Member(i);
-    list.Add(member);
-    return member;
-  }
-
-  void RemoveElement(Member *member) {
-    list.Remove(member);
-    delete member;
-  }
-
-  Member *GetMember(int i) const {
-    return list.Find([i](const Member *member) { return member->i == i; });
-  }
-
-  bool HasMember(int i) const { return GetMember(i) != nullptr; }
-
-  ::std::vector<int> GetMembers() {
-    ::std::vector<int> r;
-    list.Each([&r](Member *member) { r.push_back(member->i); });
-    return r;
-  }
-};
-
-// Tests that adding and removing elements works correctly.
-TEST_F(LinkedListTest, Basic) {
-  EXPECT_TRUE(list.Empty());
-  AddElement(971);
-  EXPECT_FALSE(list.Empty());
-  AddElement(254);
-  EXPECT_FALSE(list.Empty());
-  AddElement(1678);
-  EXPECT_FALSE(list.Empty());
-
-  EXPECT_EQ((::std::vector<int>{1678, 254, 971}), GetMembers());
-
-  EXPECT_EQ(1678, list.head()->i);
-  RemoveElement(list.head());
-  EXPECT_EQ(254, list.head()->i);
-  EXPECT_FALSE(list.Empty());
-  RemoveElement(list.head());
-  EXPECT_EQ(971, list.head()->i);
-  EXPECT_FALSE(list.Empty());
-  RemoveElement(list.head());
-  EXPECT_TRUE(list.Empty());
-}
-
-TEST_F(LinkedListTest, Each) {
-  ::std::vector<int> found;
-  auto add_to_found = [&found](Member *member) {
-    found.push_back(member->i);
-  };
-
-  AddElement(971);
-  found.clear();
-  list.Each(add_to_found);
-  EXPECT_EQ((::std::vector<int>{971}), found);
-
-  AddElement(254);
-  found.clear();
-  list.Each(add_to_found);
-  EXPECT_EQ((::std::vector<int>{254, 971}), found);
-
-  AddElement(1678);
-  found.clear();
-  list.Each(add_to_found);
-  EXPECT_EQ((::std::vector<int>{1678, 254, 971}), found);
-}
-
-TEST_F(LinkedListTest, Find) {
-  auto find_254 = [](const Member *member) { return member->i == 254; };
-
-  AddElement(971);
-  EXPECT_EQ(nullptr, list.Find(find_254));
-  Member *member = AddElement(254);
-  EXPECT_EQ(member, list.Find(find_254));
-  AddElement(1678);
-  EXPECT_EQ(member, list.Find(find_254));
-}
-
-// Removing an element from the middle of the list used to break it.
-TEST_F(LinkedListTest, RemoveFromMiddle) {
-  AddElement(971);
-  auto in_middle = AddElement(254);
-  AddElement(1678);
-  RemoveElement(in_middle);
-
-  EXPECT_EQ((::std::vector<int>{1678, 971}), GetMembers());
-}
-
-}  // namespace testing
-}  // namespace util
-}  // namespace aos
diff --git a/aos/vision/tools/camera_primer.cc b/aos/vision/tools/camera_primer.cc
index 027f9dc..fcd7d0b 100644
--- a/aos/vision/tools/camera_primer.cc
+++ b/aos/vision/tools/camera_primer.cc
@@ -26,8 +26,6 @@
 // camera_primer
 // target_sender
 int main(int argc, char **argv) {
-  ::aos::logging::Init();
-
   aos::vision::CameraParams params;
 
   if (argc != 2) {
diff --git a/frc971/control_loops/drivetrain/drivetrain.cc b/frc971/control_loops/drivetrain/drivetrain.cc
index 8c0599f..88e7a02 100644
--- a/frc971/control_loops/drivetrain/drivetrain.cc
+++ b/frc971/control_loops/drivetrain/drivetrain.cc
@@ -297,9 +297,16 @@
        localizer_->right_velocity())
           .finished();
 
-  dt_spline_.Update(
-      output != nullptr && controller_type == ControllerType::SPLINE_FOLLOWER,
-      trajectory_state, kf_.X_hat().block<2, 1>(4, 0));
+  {
+    // TODO(james): The regular Kalman Filter's voltage error terms are
+    // currently unusable--either don't use voltage error at all for the spline
+    // following code, or use the EKF's voltage error estimates.
+    const Eigen::Matrix<double, 2, 1> voltage_error =
+        0 * kf_.X_hat().block<2, 1>(4, 0);
+    dt_spline_.Update(
+        output != nullptr && controller_type == ControllerType::SPLINE_FOLLOWER,
+        trajectory_state, voltage_error);
+  }
 
   dt_line_follow_.Update(monotonic_now, trajectory_state);
 
diff --git a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
index 9857196..bfa3d26 100644
--- a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
+++ b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
@@ -901,6 +901,8 @@
 
 // Tests that simple spline converges when we introduce a straight voltage
 // error.
+// TODO(james): Reenable this once we decide what to do with the voltage error
+// terms.
 TEST_F(DrivetrainTest, SplineVoltageError) {
   SetEnabled(true);
   drivetrain_plant_.set_left_voltage_offset(1.0);
@@ -947,7 +949,21 @@
   WaitForTrajectoryPlan();
 
   RunFor(chrono::milliseconds(5000));
-  VerifyNearSplineGoal();
+  // Since the voltage error compensation is disabled, expect that we will have
+  // *failed* to reach our goal.
+  drivetrain_status_fetcher_.Fetch();
+  const double expected_x =
+      CHECK_NOTNULL(drivetrain_status_fetcher_->trajectory_logging())->x();
+  const double expected_y =
+      CHECK_NOTNULL(drivetrain_status_fetcher_->trajectory_logging())->y();
+  const double estimated_x = drivetrain_status_fetcher_->x();
+  const double estimated_y = drivetrain_status_fetcher_->y();
+  const ::Eigen::Vector2d actual = drivetrain_plant_.GetPosition();
+  // Expect the x position comparison to fail; everything else to succeed.
+  EXPECT_GT(std::abs(estimated_x - expected_x), spline_control_tolerance_);
+  EXPECT_NEAR(estimated_y, expected_y, spline_control_tolerance_);
+  EXPECT_NEAR(actual(0), estimated_x, spline_estimate_tolerance_);
+  EXPECT_NEAR(actual(1), estimated_y, spline_estimate_tolerance_);
 }
 
 // Tests that a multispline converges on a goal.
diff --git a/frc971/control_loops/drivetrain/libspline.cc b/frc971/control_loops/drivetrain/libspline.cc
index a9820f7..280171f 100644
--- a/frc971/control_loops/drivetrain/libspline.cc
+++ b/frc971/control_loops/drivetrain/libspline.cc
@@ -205,7 +205,6 @@
 
   // Util
   void SetUpLogging() {
-    ::aos::logging::Init();
     ::aos::network::OverrideTeamNumber(971);
   }
 }
diff --git a/frc971/control_loops/drivetrain/trajectory_plot.cc b/frc971/control_loops/drivetrain/trajectory_plot.cc
index 60bd21f..6aff128 100644
--- a/frc971/control_loops/drivetrain/trajectory_plot.cc
+++ b/frc971/control_loops/drivetrain/trajectory_plot.cc
@@ -247,7 +247,6 @@
 
 int main(int argc, char **argv) {
   ::gflags::ParseCommandLineFlags(&argc, &argv, false);
-  ::aos::logging::Init();
   ::aos::network::OverrideTeamNumber(971);
   ::frc971::control_loops::drivetrain::Main();
   return 0;
diff --git a/third_party/diff-from-upstream.sh b/third_party/diff-from-upstream.sh
new file mode 100755
index 0000000..373cc00
--- /dev/null
+++ b/third_party/diff-from-upstream.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+# This script will run `git diff` from the latest upstream to HEAD on the
+# git-subtreed directory given as its first argument, passing all of the
+# other arguments on.
+#
+# This will not work on non-squashed git-subtree directories.
+
+# Copied from
+# https://github.com/git/git/blob/master/contrib/subtree/git-subtree.sh.
+find_latest_squash()
+{
+	dir="$1"
+	sq=
+	main=
+	sub=
+	git log --grep="^git-subtree-dir: $dir/*\$" \
+		--pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+	while read a b junk; do
+		case "$a" in
+			START) sq="$b" ;;
+			git-subtree-mainline:) main="$b" ;;
+			git-subtree-split:) sub="$b" ;;
+			END)
+				if [ -n "$sub" ]; then
+					if [ -n "$main" ]; then
+						# a rejoin commit?
+						# Pretend its sub was a squash.
+						sq="$sub"
+					fi
+					echo "$sq" "$sub"
+					break
+				fi
+				sq=
+				main=
+				sub=
+				;;
+		esac
+	done
+}
+
+DIR="${1%/}"
+shift
+DIFF_ARGS="$@"
+
+SPLIT="$(find_latest_squash "${DIR}")"
+if [ -z "${SPLIT}" ]; then
+	echo "${DIR} does not appear to be git-subtreed in."
+	exit 1
+fi
+
+set ${SPLIT}
+SQUASHED_UPSTREAM=$1
+
+git diff ${DIFF_ARGS} ${SQUASHED_UPSTREAM}..HEAD:${DIR}
diff --git a/y2016/vision/target_sender.cc b/y2016/vision/target_sender.cc
index 3783e9f..bc2a695 100644
--- a/y2016/vision/target_sender.cc
+++ b/y2016/vision/target_sender.cc
@@ -7,8 +7,6 @@
 #include <thread>
 #include <vector>
 
-#include "aos/logging/implementations.h"
-#include "aos/logging/logging.h"
 #include "aos/time/time.h"
 #include "aos/vision/events/socket_types.h"
 #include "aos/vision/events/udp.h"
@@ -225,7 +223,6 @@
 int main(int, char **) {
   using namespace y2016::vision;
   StereoGeometry stereo("./stereo_rig.calib");
-  ::aos::logging::Init();
   std::thread cam0([stereo]() {
     RunCamera(0, GetCameraParams(stereo.calibration()),
               stereo.calibration().right_camera_name(),
diff --git a/y2017/vision/target_sender.cc b/y2017/vision/target_sender.cc
index 1c5f3f7..7a24d37 100644
--- a/y2017/vision/target_sender.cc
+++ b/y2017/vision/target_sender.cc
@@ -219,7 +219,6 @@
 int main(int, char **) {
   using namespace y2017::vision;
 
-  ::aos::logging::Init();
   VisionConfig cfg;
   if (ReadConfiguration("ConfigFile.pb.ascii", &cfg)) {
     if (cfg.robot_configs().count("Laptop") != 1) {
diff --git a/y2018/vision/image_streamer.cc b/y2018/vision/image_streamer.cc
index 06a74c4..fb60f1d 100644
--- a/y2018/vision/image_streamer.cc
+++ b/y2018/vision/image_streamer.cc
@@ -288,9 +288,6 @@
 
 int main(int argc, char ** argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, false);
-  ::aos::logging::Init();
-  ::aos::logging::SetImplementation(
-      std::make_shared<::aos::logging::StreamLogImplementation>(stderr));
 
   TCPServer<MjpegDataSocket> tcp_server_(80);
   aos::vision::CameraParams params0;
diff --git a/y2019/image_streamer/image_streamer.cc b/y2019/image_streamer/image_streamer.cc
index 1252627..6640df0 100644
--- a/y2019/image_streamer/image_streamer.cc
+++ b/y2019/image_streamer/image_streamer.cc
@@ -307,9 +307,6 @@
 
 int main(int argc, char **argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, false);
-  ::aos::logging::Init();
-  ::aos::logging::SetImplementation(
-      std::make_shared<::aos::logging::StreamLogImplementation>(stderr));
   TCPServer<MjpegDataSocket> tcp_server_(80);
   aos::vision::CameraParams params0;
   params0.set_exposure(FLAGS_camera0_exposure);
diff --git a/y2019/vision/debug_serial.cc b/y2019/vision/debug_serial.cc
index 18670c0..d1c70c2 100644
--- a/y2019/vision/debug_serial.cc
+++ b/y2019/vision/debug_serial.cc
@@ -23,9 +23,6 @@
   using namespace y2019::vision;
   using namespace frc971::jevois;
   // gflags::ParseCommandLineFlags(&argc, &argv, false);
-  ::aos::logging::Init();
-  ::aos::logging::SetImplementation(
-      std::make_shared<::aos::logging::StreamLogImplementation>(stderr));
 
   int flags = fcntl(0, F_GETFL, 0);
   fcntl(0, F_SETFL, flags | O_NONBLOCK);
diff --git a/y2019/vision/global_calibration.cc b/y2019/vision/global_calibration.cc
index af2ad54..e1e5ca8 100644
--- a/y2019/vision/global_calibration.cc
+++ b/y2019/vision/global_calibration.cc
@@ -102,10 +102,6 @@
       FLAGS_image_count,
   };
 
-  ::aos::logging::Init();
-  ::aos::logging::SetImplementation(
-      std::make_shared<::aos::logging::StreamLogImplementation>(stderr));
-
   TargetFinder target_finder;
 
   ceres::Problem problem;