Add a tool to visualize channels and applications

Timing reports have all the info on who is sending and who is receiving.
We can use that to create a graph of what is sending to what and
visually inspect our as-implemented system.

Change-Id: Ib0a9477870fab7a50fc500ff65485e4265ff7b1c
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/aos/aos_graph_channels.cc b/aos/aos_graph_channels.cc
new file mode 100644
index 0000000..e96494a
--- /dev/null
+++ b/aos/aos_graph_channels.cc
@@ -0,0 +1,223 @@
+#include <iomanip>
+#include <iostream>
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
+#include "aos/events/logging/log_reader.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "aos/time/time.h"
+#include "gflags/gflags.h"
+
+DEFINE_string(skip, "", "Applications to skip, seperated by ;");
+
+struct ChannelState {
+  const aos::Channel *channel = nullptr;
+  double frequency_sum = 0.0;
+  size_t frequency_count = 0;
+};
+
+// List of channels for an application.
+struct Application {
+  std::vector<ChannelState> watchers;
+  std::vector<ChannelState> fetchers;
+  std::vector<ChannelState> senders;
+};
+
+// List of all applications connected to a channel.
+struct ChannelConnections {
+  std::vector<std::pair<std::string, double>> senders;
+  std::vector<std::string> watchers;
+  std::vector<std::string> fetchers;
+};
+
+int main(int argc, char **argv) {
+  gflags::SetUsageMessage(
+      "Usage: \n"
+      "  aos_graph_channels [args] logfile1 logfile2 ...\n"
+      "\n"
+      "The output is in dot format.  Typical usage will be to pipe the results "
+      "to dot\n"
+      "\n"
+      "  aos_graph_channels ./log/ | dot -Tx11");
+
+  aos::InitGoogle(&argc, &argv);
+
+  if (argc < 2) {
+    LOG(FATAL) << "Expected at least 1 logfile as an argument.";
+  }
+
+  const std::vector<std::string> skip_list = absl::StrSplit(FLAGS_skip, ";");
+  aos::logger::LogReader reader(
+      aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
+
+  aos::SimulatedEventLoopFactory factory(reader.configuration());
+  reader.Register(&factory);
+
+  // Now: hook everything up to grab all the timing reports and extract them
+  // into the Application data structure.
+  std::map<std::string, Application> applications;
+
+  std::vector<std::unique_ptr<aos::EventLoop>> loops;
+  for (const aos::Node *node :
+       aos::configuration::GetNodes(factory.configuration())) {
+    std::unique_ptr<aos::EventLoop> event_loop =
+        factory.MakeEventLoop("timing_reports", node);
+    event_loop->SkipTimingReport();
+    event_loop->SkipAosLog();
+
+    event_loop->MakeWatcher("/aos", [&](const aos::timing::Report
+                                            &timing_report) {
+      if (std::find(skip_list.begin(), skip_list.end(),
+                    timing_report.name()->str()) != skip_list.end()) {
+        return;
+      }
+      // Make an application if one doesn't exist.
+      auto it = applications.find(timing_report.name()->str());
+      if (it == applications.end()) {
+        it = applications.emplace(timing_report.name()->str(), Application())
+                 .first;
+      }
+
+      // Add watcher state.
+      if (timing_report.has_watchers()) {
+        for (const aos::timing::Watcher *watcher : *timing_report.watchers()) {
+          const aos::Channel *channel =
+              factory.configuration()->channels()->Get(
+                  watcher->channel_index());
+          auto watcher_it = std::find_if(
+              it->second.watchers.begin(), it->second.watchers.end(),
+              [&](const ChannelState &c) { return c.channel == channel; });
+          if (watcher_it == it->second.watchers.end()) {
+            it->second.watchers.push_back(ChannelState{.channel = channel,
+                                                       .frequency_sum = 0.0,
+                                                       .frequency_count = 0});
+            watcher_it = it->second.watchers.end() - 1;
+          }
+          watcher_it->frequency_sum += watcher->count();
+          ++watcher_it->frequency_count;
+        }
+      }
+
+      // Add sender state.
+      if (timing_report.has_senders()) {
+        for (const aos::timing::Sender *sender : *timing_report.senders()) {
+          const aos::Channel *channel =
+              factory.configuration()->channels()->Get(sender->channel_index());
+          auto sender_it = std::find_if(
+              it->second.senders.begin(), it->second.senders.end(),
+              [&](const ChannelState &c) { return c.channel == channel; });
+          if (sender_it == it->second.senders.end()) {
+            it->second.senders.push_back(ChannelState{.channel = channel,
+                                                      .frequency_sum = 0.0,
+                                                      .frequency_count = 0});
+            sender_it = it->second.senders.end() - 1;
+          }
+          sender_it->frequency_sum += sender->count();
+          ++sender_it->frequency_count;
+        }
+      }
+
+      // Add fetcher state.
+      if (timing_report.has_fetchers()) {
+        for (const aos::timing::Fetcher *fetcher : *timing_report.fetchers()) {
+          const aos::Channel *channel =
+              factory.configuration()->channels()->Get(
+                  fetcher->channel_index());
+          auto fetcher_it = std::find_if(
+              it->second.fetchers.begin(), it->second.fetchers.end(),
+              [&](const ChannelState &c) { return c.channel == channel; });
+          if (fetcher_it == it->second.fetchers.end()) {
+            it->second.fetchers.push_back(ChannelState{.channel = channel,
+                                                       .frequency_sum = 0.0,
+                                                       .frequency_count = 0});
+            fetcher_it = it->second.fetchers.end() - 1;
+          }
+          fetcher_it->frequency_sum += fetcher->count();
+          ++fetcher_it->frequency_count;
+        }
+      }
+    });
+    loops.emplace_back(std::move(event_loop));
+  }
+
+  factory.Run();
+
+  reader.Deregister();
+
+  // Now, we need to flip this graph on it's head to deduplicate and draw the
+  // correct graph.  Build it all up as a list of applications per channel.
+  std::map<const aos::Channel *, ChannelConnections> connections;
+  for (const std::pair<const std::string, Application> &app : applications) {
+    for (const ChannelState &state : app.second.senders) {
+      auto it = connections.find(state.channel);
+      if (it == connections.end()) {
+        it = connections.emplace(state.channel, ChannelConnections()).first;
+      }
+
+      it->second.senders.emplace_back(std::make_pair(
+          app.first, state.frequency_count == 0
+                         ? 0.0
+                         : state.frequency_sum / state.frequency_count));
+    }
+    for (const ChannelState &state : app.second.watchers) {
+      auto it = connections.find(state.channel);
+      if (it == connections.end()) {
+        it = connections.emplace(state.channel, ChannelConnections()).first;
+      }
+
+      it->second.watchers.emplace_back(app.first);
+    }
+    for (const ChannelState &state : app.second.fetchers) {
+      auto it = connections.find(state.channel);
+      if (it == connections.end()) {
+        it = connections.emplace(state.channel, ChannelConnections()).first;
+      }
+
+      it->second.fetchers.emplace_back(app.first);
+    }
+  }
+
+  const std::vector<std::string> color_list = {
+      "red", "blue", "orange", "green", "violet", "gold3", "magenta"};
+
+  // Now generate graphvis compatible output.
+  std::stringstream graph_out;
+  graph_out << "digraph g {" << std::endl;
+  for (const std::pair<const aos::Channel *, ChannelConnections> &c :
+       connections) {
+    const std::string channel = absl::StrCat(
+        c.first->name()->string_view(), "\n", c.first->type()->string_view());
+    for (const std::pair<std::string, double> &sender : c.second.senders) {
+      graph_out << "\t\"" << sender.first << "\" -> \"" << channel
+                << "\" [label=\"" << sender.second << "\" color=\""
+                << color_list[0]
+                << "\" weight=" << static_cast<int>(sender.second) << "];"
+                << std::endl;
+    }
+    for (const std::string &watcher : c.second.watchers) {
+      graph_out << "\t\"" << channel << "\" -> \"" << watcher << "\" [color=\""
+                << color_list[1] << "\"];" << std::endl;
+    }
+    for (const std::string &watcher : c.second.fetchers) {
+      graph_out << "\t\"" << channel << "\" -> \"" << watcher << "\" [color=\""
+                << color_list[2] << "\"];" << std::endl;
+    }
+  }
+
+  size_t index = 0;
+  for (const std::pair<const std::string, Application> &app : applications) {
+    graph_out << "\t\"" << app.first << "\" [color=\"" << color_list[index]
+              << "\" shape=box style=filled];" << std::endl;
+    ++index;
+    if (index >= color_list.size()) {
+      index = 0;
+    }
+  }
+  graph_out << "}" << std::endl;
+
+  std::cout << graph_out.str();
+
+  return 0;
+}