Make it possible to plot all the nodes in timestamp_plot

It is quite handy to debug the solver with all the data visible.  Add a
--all flag to support that.

Change-Id: Iba8dd3bdb4c2397afb9e774aa138b8f932e99dda
Signed-off-by: Austin Schuh <austin.schuh@bluerivertech.com>
diff --git a/aos/events/logging/timestamp_plot.cc b/aos/events/logging/timestamp_plot.cc
index 63c3de4..9c5f2dd 100644
--- a/aos/events/logging/timestamp_plot.cc
+++ b/aos/events/logging/timestamp_plot.cc
@@ -6,18 +6,51 @@
 
 using frc971::analysis::Plotter;
 
+DEFINE_bool(all, false, "If true, plot *all* the nodes at once");
+DEFINE_bool(bounds, false, "If true, plot the noncausal bounds too.");
+DEFINE_bool(samples, true, "If true, plot the samples too.");
+
+DEFINE_string(offsets, "",
+              "Offsets to add to the monotonic clock for each node.  Use the "
+              "format of node=offset,node=offest");
+
 // Simple C++ application to read the CSV files and use the in process plotter
 // to plot them.  This smokes the pants off gnuplot in terms of interactivity.
 
 namespace aos {
 
+// Returns all the nodes.
+std::vector<std::string> Nodes() {
+  const std::string start_time_file = aos::util::ReadFileToStringOrDie(
+      "/tmp/timestamp_noncausal_starttime.csv");
+  std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
+
+  std::vector<std::string> formatted_nodes;
+  for (const std::string_view n : nodes) {
+    if (n == "") {
+      continue;
+    }
+
+    std::vector<std::string_view> l = absl::StrSplit(n, ", ");
+    CHECK_EQ(l.size(), 2u) << "'" << n << "'";
+    formatted_nodes.emplace_back(l[0]);
+  }
+
+  return formatted_nodes;
+}
+
+std::string SampleFile(std::string_view node1, std::string_view node2) {
+  return absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2,
+                      "_samples.csv");
+}
+
 std::pair<std::vector<double>, std::vector<double>> ReadSamples(
     std::string_view node1, std::string_view node2, bool flip) {
   std::vector<double> samplefile12_t;
   std::vector<double> samplefile12_o;
 
-  const std::string file = aos::util::ReadFileToStringOrDie(absl::StrCat(
-      "/tmp/timestamp_noncausal_", node1, "_", node2, "_samples.csv"));
+  const std::string file =
+      aos::util::ReadFileToStringOrDie(SampleFile(node1, node2));
   bool first = true;
   std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
   samplefile12_t.reserve(lines.size());
@@ -42,7 +75,78 @@
   return std::make_pair(samplefile12_t, samplefile12_o);
 }
 
-std::pair<std::vector<double>, std::vector<double>> ReadLines(
+void Offset(std::vector<double> *v, double offset) {
+  for (double &x : *v) {
+    x += offset;
+  }
+}
+
+// Returns all the nodes which talk to each other.
+std::vector<std::pair<std::string, std::string>> NodeConnections() {
+  const std::vector<std::string> nodes = Nodes();
+  std::vector<std::pair<std::string, std::string>> result;
+  for (size_t i = 1; i < nodes.size(); ++i) {
+    for (size_t j = 0; j < i; ++j) {
+      const std::string_view node1 = nodes[j];
+      const std::string_view node2 = nodes[i];
+      if (aos::util::PathExists(SampleFile(node1, node2))) {
+        result.emplace_back(node1, node2);
+        LOG(INFO) << "Found pairing " << node1 << ", " << node2;
+      }
+    }
+  }
+  return result;
+}
+
+// Class to encapsulate the plotter state to make it easy to plot multiple
+// connections.
+class NodePlotter {
+ public:
+  NodePlotter() : nodes_(Nodes()) {
+    plotter_.AddFigure("Time");
+    if (!FLAGS_offsets.empty()) {
+      for (std::string_view nodeoffset : absl::StrSplit(FLAGS_offsets, ',')) {
+        std::vector<std::string_view> node_offset =
+            absl::StrSplit(nodeoffset, '=');
+        CHECK_EQ(node_offset.size(), 2u);
+        double o;
+        CHECK(absl::SimpleAtod(node_offset[1], &o));
+        offset_.emplace(std::string(node_offset[0]), o);
+      }
+    }
+  }
+
+  void AddNodes(std::string_view node1, std::string_view node2);
+
+  void Serve() {
+    plotter_.Publish();
+    plotter_.Spin();
+  }
+
+ private:
+  std::pair<std::vector<double>, std::vector<double>> ReadLines(
+      std::string_view node1, std::string_view node2, bool flip);
+
+  std::pair<std::vector<double>, std::vector<double>> ReadOffset(
+      std::string_view node1, std::string_view node2);
+
+  double TimeOffset(std::string_view node) {
+    auto it = offset_.find(std::string(node));
+    if (it == offset_.end()) {
+      return 0.0;
+    } else {
+      return it->second;
+    }
+  }
+
+  std::map<std::string, double> offset_;
+
+  Plotter plotter_;
+
+  std::vector<std::string> nodes_;
+};
+
+std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
     std::string_view node1, std::string_view node2, bool flip) {
   std::vector<double> samplefile12_t;
   std::vector<double> samplefile12_o;
@@ -73,28 +177,18 @@
   return std::make_pair(samplefile12_t, samplefile12_o);
 }
 
-std::pair<std::vector<double>, std::vector<double>> ReadOffset(
+std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
     std::string_view node1, std::string_view node2) {
   int node1_index = -1;
   int node2_index = -1;
 
   {
-    const std::string start_time_file = aos::util::ReadFileToStringOrDie(
-        "/tmp/timestamp_noncausal_starttime.csv");
-    std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
-
     int index = 0;
-    for (const std::string_view n : nodes) {
-      if (n == "") {
-        continue;
-      }
-
-      std::vector<std::string_view> l = absl::StrSplit(n, ", ");
-      CHECK_EQ(l.size(), 2u) << "'" << n << "'";
-      if (l[0] == node1) {
+    for (const std::string &n : nodes_) {
+      if (n == node1) {
         node1_index = index;
       }
-      if (l[0] == node2) {
+      if (n == node2) {
         node2_index = index;
       }
       ++index;
@@ -134,21 +228,29 @@
   return std::make_pair(offsetfile_t, offsetfile_o);
 }
 
-void AddNodes(Plotter *plotter, std::string_view node1,
-              std::string_view node2) {
-  const std::pair<std::vector<double>, std::vector<double>> samplefile12 =
+void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
+  const double offset1 = TimeOffset(node1);
+  const double offset2 = TimeOffset(node2);
+
+  std::pair<std::vector<double>, std::vector<double>> samplefile12 =
       ReadSamples(node1, node2, false);
-  const std::pair<std::vector<double>, std::vector<double>> samplefile21 =
+  std::pair<std::vector<double>, std::vector<double>> samplefile21 =
       ReadSamples(node2, node1, true);
 
-  const std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
+  std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
       ReadLines(node1, node2, false);
-  const std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
+  std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
       ReadLines(node2, node1, true);
 
-  const std::pair<std::vector<double>, std::vector<double>> offsetfile =
+  std::pair<std::vector<double>, std::vector<double>> offsetfile =
       ReadOffset(node1, node2);
 
+  Offset(&samplefile12.second, offset2 - offset1);
+  Offset(&samplefile21.second, offset2 - offset1);
+  Offset(&noncausalfile12.second, offset2 - offset1);
+  Offset(&noncausalfile21.second, offset2 - offset1);
+  Offset(&offsetfile.second, offset2 - offset1);
+
   CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
   CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
   CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
@@ -157,56 +259,65 @@
   LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
                    noncausalfile12.first.size() + noncausalfile21.first.size()
             << " points";
-  plotter->AddLine(
-      samplefile12.first, samplefile12.second,
-      Plotter::LineOptions{.label = absl::StrCat("sample ", node1, " ", node2),
-                           .line_style = "*",
-                           .color = "purple"});
-  plotter->AddLine(
-      samplefile21.first, samplefile21.second,
-      Plotter::LineOptions{.label = absl::StrCat("sample ", node2, " ", node1),
-                           .line_style = "*",
-                           .color = "green"});
 
-  plotter->AddLine(
-      noncausalfile12.first, noncausalfile12.second,
-      Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
-                           .line_style = "-",
-                           .color = "blue"});
-  plotter->AddLine(
-      noncausalfile21.first, noncausalfile21.second,
-      Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
-                           .line_style = "-",
-                           .color = "orange"});
-
-  plotter->AddLine(offsetfile.first, offsetfile.second,
+  plotter_.AddLine(offsetfile.first, offsetfile.second,
                    Plotter::LineOptions{
                        .label = absl::StrCat("filter ", node2, " ", node1),
                        // TODO(austin): roboRIO compiler wants all the fields
                        // filled out, but other compilers don't...  Sigh.
                        .line_style = "*-",
-                       .color = "yellow"});
+                       .color = "yellow",
+                       .point_size = 2.0});
+
+  if (FLAGS_samples) {
+    plotter_.AddLine(samplefile12.first, samplefile12.second,
+                     Plotter::LineOptions{
+                         .label = absl::StrCat("sample ", node1, " ", node2),
+                         .line_style = "*",
+                         .color = "purple",
+                     });
+    plotter_.AddLine(samplefile21.first, samplefile21.second,
+                     Plotter::LineOptions{
+                         .label = absl::StrCat("sample ", node2, " ", node1),
+                         .line_style = "*",
+                         .color = "green",
+                     });
+  }
+
+  if (FLAGS_bounds) {
+    plotter_.AddLine(
+        noncausalfile12.first, noncausalfile12.second,
+        Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
+                             .line_style = "-",
+                             .color = "blue"});
+    plotter_.AddLine(
+        noncausalfile21.first, noncausalfile21.second,
+        Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
+                             .line_style = "-",
+                             .color = "orange"});
+  }
 }
 
 int Main(int argc, const char *const *argv) {
-  CHECK_EQ(argc, 3);
+  NodePlotter plotter;
 
-  LOG(INFO) << argv[1];
-  LOG(INFO) << argv[2];
+  if (FLAGS_all) {
+    for (std::pair<std::string, std::string> ab : NodeConnections()) {
+      plotter.AddNodes(ab.first, ab.second);
+    }
+  } else {
+    CHECK_EQ(argc, 3);
 
-  // TODO(austin): Find all node pairs and plot them...
+    LOG(INFO) << argv[1];
+    LOG(INFO) << argv[2];
 
-  const std::string_view node1 = argv[1];
-  const std::string_view node2 = argv[2];
+    const std::string_view node1 = argv[1];
+    const std::string_view node2 = argv[2];
 
-  Plotter plotter;
-  plotter.AddFigure("Time");
+    plotter.AddNodes(node1, node2);
+  }
 
-  AddNodes(&plotter, node1, node2);
-
-  plotter.Publish();
-
-  plotter.Spin();
+  plotter.Serve();
 
   return 0;
 }
diff --git a/frc971/analysis/in_process_plotter.cc b/frc971/analysis/in_process_plotter.cc
index 5f08e5d..545740d 100644
--- a/frc971/analysis/in_process_plotter.cc
+++ b/frc971/analysis/in_process_plotter.cc
@@ -107,7 +107,7 @@
 
   LineStyle::Builder style_builder = builder_.MakeBuilder<LineStyle>();
   if (options.line_style.find('*') != options.line_style.npos) {
-    style_builder.add_point_size(3.0);
+    style_builder.add_point_size(options.point_size);
   } else {
     style_builder.add_point_size(0.0);
   }
diff --git a/frc971/analysis/in_process_plotter.h b/frc971/analysis/in_process_plotter.h
index b78a8fb..4f05c19 100644
--- a/frc971/analysis/in_process_plotter.h
+++ b/frc971/analysis/in_process_plotter.h
@@ -46,6 +46,7 @@
     std::string_view label = "";
     std::string_view line_style = "*-";
     std::string_view color = "";
+    double point_size = 3.0;
   };
 
   void AddLine(const std::vector<double> &x, const std::vector<double> &y,