Add realtime replay support to SimulatedEventLoopFactory

"realtime" is heavily overloaded here, but this adds support
for making it so that you can play a SimulatedEventLoopFactory at
realtime speed (rather than just "as fast as possible"). This
can be useful in a variety of situations (e.g., debugging
tooling that will run in realtime on a robot).

Adds a demonstration of using this in an piece of AOS tooling for
plotting (this change also makes it so that that binary no longer spins
at 100% CPU indefinitely by consequence of better integrating
the EPoll object into the log replay).

Change-Id: Ia01ecd850a50c9b78dd72bfb0e8862672a716067
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/network/log_web_proxy_main.cc b/aos/network/log_web_proxy_main.cc
index 1cc8e17..34e40a2 100644
--- a/aos/network/log_web_proxy_main.cc
+++ b/aos/network/log_web_proxy_main.cc
@@ -17,6 +17,9 @@
 DEFINE_int32(buffer_size, -1, "-1 if infinite, in # of messages / channel.");
 DEFINE_double(monotonic_start_time, -1.0, "Start time (sec)");
 DEFINE_double(monotonic_end_time, -1.0, "End time (sec)");
+DEFINE_double(
+    replay_rate, -1,
+    "-1 to replay as fast as possible; 1.0 = realtime, 0.5 = half speed.");
 
 int main(int argc, char **argv) {
   aos::InitGoogle(&argc, &argv);
@@ -31,6 +34,12 @@
 
   reader.Register();
 
+  // If going for "as fast as possible" don't actually use infinity, because we
+  // don't want the log reading blocking our use of the epoll handlers.
+  reader.SetRealtimeReplayRate(FLAGS_replay_rate == -1.0
+                                   ? std::numeric_limits<double>::max()
+                                   : FLAGS_replay_rate);
+
   std::unique_ptr<aos::EventLoop> event_loop;
 
   if (FLAGS_node.empty()) {
@@ -55,13 +64,12 @@
   }
 
   aos::web_proxy::WebProxy web_proxy(
-      event_loop.get(), aos::web_proxy::StoreHistory::kYes, FLAGS_buffer_size);
+      event_loop.get(),
+      reader.event_loop_factory()->scheduler_epoll(),
+      aos::web_proxy::StoreHistory::kYes, FLAGS_buffer_size);
 
   web_proxy.SetDataPath(FLAGS_data_dir.c_str());
 
-  // Keep the web proxy alive past when we finish reading the logfile.
-  reader.set_exit_on_finish(false);
-
   if (FLAGS_monotonic_end_time > 0) {
     event_loop->AddTimer([&web_proxy]() { web_proxy.StopRecording(); })
         ->Setup(aos::monotonic_clock::time_point(
@@ -70,4 +78,11 @@
   }
 
   reader.event_loop_factory()->Run();
+
+  // Keep the web proxy alive past when we finish reading the logfile, but crank
+  // down the replay rate so that we don't peg our entire CPU just trying to
+  // service timers in the web proxy code.
+  reader.set_exit_on_finish(false);
+  reader.SetRealtimeReplayRate(1.0);
+  reader.event_loop_factory()->Run();
 }
diff --git a/aos/network/web_proxy.h b/aos/network/web_proxy.h
index 0c1d1dc..2b57c05 100644
--- a/aos/network/web_proxy.h
+++ b/aos/network/web_proxy.h
@@ -77,6 +77,8 @@
            int per_channel_buffer_size_bytes);
   WebProxy(aos::ShmEventLoop *event_loop, StoreHistory store_history,
            int per_channel_buffer_size_bytes);
+  WebProxy(aos::EventLoop *event_loop, aos::internal::EPoll *epoll,
+           StoreHistory store_history, int per_channel_buffer_size_bytes);
   ~WebProxy();
 
   void SetDataPath(const char *path) { server_.setStaticPath(path); }
@@ -85,9 +87,6 @@
   void StopRecording();
 
  private:
-  WebProxy(aos::EventLoop *event_loop, aos::internal::EPoll *epoll,
-           StoreHistory store_history, int per_channel_buffer_size_bytes);
-
   aos::internal::EPoll internal_epoll_;
   aos::internal::EPoll *const epoll_;
   ::seasocks::Server server_;