Add string-based IDs for event log visualizer graphs and update command line flags.

The IDs be more stable than the plot titles and could be used to identify specific graphs in scripts.

Change event_log_visualizer command line interface to control which plots are generated.
Old interface had one command line flag per plot as well as a set of 'profiles' that enabled
of disabled sets of plots. New interface has a command line flag
which takes a string of all the plot names or profiles that should be enabled.
In some cases, there are also slight naming changes for the plots.

For example, the former command
event_log_visualizer --plot_profile=sendside_bwe --plot_incoming_packet_sizes <filename> | python
is now
event_log_visualizer --plot=sendside_bwe,incoming_packet_sizes <filename> | python

The former command
event_log_visualizer --plot_profile=none --plot_incoming_packet_sizes <filename> | python
is now
event_log_visualizer --plot=incoming_packet_sizes <filename> | python

The former command
event_log_visualizer --plot_profile=all <filename> | python
is now
event_log_visualizer --plot=all <filename> | python


Bug: webrtc:10623
Change-Id: Ife432c1e51edfce64af565a769f1764a16655bb6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/140886
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Commit-Queue: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28237}
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index 790fe66..2cd4991 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -360,6 +360,7 @@
         "../test:field_trial",
         "../test:fileutils",
         "../test:test_support",
+        "//third_party/abseil-cpp/absl/algorithm:container",
       ]
     }
   }
diff --git a/rtc_tools/event_log_visualizer/main.cc b/rtc_tools/event_log_visualizer/main.cc
index e7de720..2fbd83a 100644
--- a/rtc_tools/event_log_visualizer/main.cc
+++ b/rtc_tools/event_log_visualizer/main.cc
@@ -17,6 +17,7 @@
 #include <string>
 #include <utility>
 
+#include "absl/algorithm/container.h"
 #include "logging/rtc_event_log/rtc_event_log.h"
 #include "logging/rtc_event_log/rtc_event_log_parser.h"
 #include "modules/audio_coding/neteq/include/neteq.h"
@@ -32,150 +33,9 @@
 #include "test/testsupport/file_utils.h"
 
 WEBRTC_DEFINE_string(
-    plot_profile,
+    plot,
     "default",
-    "A profile that selects a certain subset of the plots. Currently "
-    "defined profiles are \"all\", \"none\", \"sendside_bwe\","
-    "\"receiveside_bwe\" and \"default\"");
-
-WEBRTC_DEFINE_bool(plot_incoming_packet_sizes,
-                   false,
-                   "Plot bar graph showing the size of each incoming packet.");
-WEBRTC_DEFINE_bool(plot_outgoing_packet_sizes,
-                   false,
-                   "Plot bar graph showing the size of each outgoing packet.");
-WEBRTC_DEFINE_bool(plot_incoming_rtcp_types,
-                   false,
-                   "Plot the RTCP block types for incoming RTCP packets.");
-WEBRTC_DEFINE_bool(plot_outgoing_rtcp_types,
-                   false,
-                   "Plot the RTCP block types for outgoing RTCP packets.");
-WEBRTC_DEFINE_bool(
-    plot_incoming_packet_count,
-    false,
-    "Plot the accumulated number of packets for each incoming stream.");
-WEBRTC_DEFINE_bool(
-    plot_outgoing_packet_count,
-    false,
-    "Plot the accumulated number of packets for each outgoing stream.");
-WEBRTC_DEFINE_bool(
-    plot_audio_playout,
-    false,
-    "Plot bar graph showing the time between each audio playout.");
-WEBRTC_DEFINE_bool(
-    plot_audio_level,
-    false,
-    "Plot line graph showing the audio level of incoming audio.");
-WEBRTC_DEFINE_bool(
-    plot_incoming_sequence_number_delta,
-    false,
-    "Plot the sequence number difference between consecutive incoming "
-    "packets.");
-WEBRTC_DEFINE_bool(
-    plot_incoming_delay,
-    true,
-    "Plot the 1-way path delay for incoming packets, normalized so "
-    "that the first packet has delay 0.");
-WEBRTC_DEFINE_bool(
-    plot_incoming_loss_rate,
-    true,
-    "Compute the loss rate for incoming packets using a method that's "
-    "similar to the one used for RTCP SR and RR fraction lost. Note "
-    "that the loss rate can be negative if packets are duplicated or "
-    "reordered.");
-WEBRTC_DEFINE_bool(plot_incoming_bitrate,
-                   true,
-                   "Plot the total bitrate used by all incoming streams.");
-WEBRTC_DEFINE_bool(plot_outgoing_bitrate,
-                   true,
-                   "Plot the total bitrate used by all outgoing streams.");
-WEBRTC_DEFINE_bool(plot_incoming_stream_bitrate,
-                   true,
-                   "Plot the bitrate used by each incoming stream.");
-WEBRTC_DEFINE_bool(plot_outgoing_stream_bitrate,
-                   true,
-                   "Plot the bitrate used by each outgoing stream.");
-WEBRTC_DEFINE_bool(plot_incoming_layer_bitrate_allocation,
-                   false,
-                   "Plot the target bitrate for each incoming layer. Requires "
-                   "incoming RTCP XR with target bitrate to be populated.");
-WEBRTC_DEFINE_bool(plot_outgoing_layer_bitrate_allocation,
-                   false,
-                   "Plot the target bitrate for each outgoing layer. Requires "
-                   "outgoing RTCP XR with target bitrate to be populated.");
-WEBRTC_DEFINE_bool(
-    plot_simulated_receiveside_bwe,
-    false,
-    "Run the receive-side bandwidth estimator with the incoming rtp "
-    "packets and plot the resulting estimate.");
-WEBRTC_DEFINE_bool(
-    plot_simulated_sendside_bwe,
-    false,
-    "Run the send-side bandwidth estimator with the outgoing rtp and "
-    "incoming rtcp and plot the resulting estimate.");
-WEBRTC_DEFINE_bool(plot_simulated_goog_cc,
-                   false,
-                   "Run the GoogCC congestion controller based on the logged "
-                   "events and plot the target bitrate.");
-WEBRTC_DEFINE_bool(
-    plot_network_delay_feedback,
-    true,
-    "Compute network delay based on sent packets and the received "
-    "transport feedback.");
-WEBRTC_DEFINE_bool(
-    plot_fraction_loss_feedback,
-    true,
-    "Plot packet loss in percent for outgoing packets (as perceived by "
-    "the send-side bandwidth estimator).");
-WEBRTC_DEFINE_bool(
-    plot_pacer_delay,
-    false,
-    "Plot the time each sent packet has spent in the pacer (based on "
-    "the difference between the RTP timestamp and the send "
-    "timestamp).");
-WEBRTC_DEFINE_bool(
-    plot_timestamps,
-    false,
-    "Plot the rtp timestamps of all rtp and rtcp packets over time.");
-WEBRTC_DEFINE_bool(
-    plot_rtcp_details,
-    false,
-    "Plot the contents of all report blocks in all sender and receiver "
-    "reports. This includes fraction lost, cumulative number of lost "
-    "packets, extended highest sequence number and time since last "
-    "received SR.");
-WEBRTC_DEFINE_bool(plot_audio_encoder_bitrate_bps,
-                   false,
-                   "Plot the audio encoder target bitrate.");
-WEBRTC_DEFINE_bool(plot_audio_encoder_frame_length_ms,
-                   false,
-                   "Plot the audio encoder frame length.");
-WEBRTC_DEFINE_bool(
-    plot_audio_encoder_packet_loss,
-    false,
-    "Plot the uplink packet loss fraction which is sent to the audio encoder.");
-WEBRTC_DEFINE_bool(plot_audio_encoder_fec,
-                   false,
-                   "Plot the audio encoder FEC.");
-WEBRTC_DEFINE_bool(plot_audio_encoder_dtx,
-                   false,
-                   "Plot the audio encoder DTX.");
-WEBRTC_DEFINE_bool(plot_audio_encoder_num_channels,
-                   false,
-                   "Plot the audio encoder number of channels.");
-WEBRTC_DEFINE_bool(plot_neteq_stats, false, "Plot the NetEq statistics.");
-WEBRTC_DEFINE_bool(plot_ice_candidate_pair_config,
-                   false,
-                   "Plot the ICE candidate pair config events.");
-WEBRTC_DEFINE_bool(plot_ice_connectivity_check,
-                   false,
-                   "Plot the ICE candidate pair connectivity checks.");
-WEBRTC_DEFINE_bool(plot_dtls_transport_state,
-                   false,
-                   "Plot DTLS transport state changes.");
-WEBRTC_DEFINE_bool(plot_dtls_writable_state,
-                   false,
-                   "Plot DTLS writable state changes.");
+    "A comma separated list of plot names. See below for valid options.");
 
 WEBRTC_DEFINE_string(
     force_fieldtrials,
@@ -225,7 +85,87 @@
                    false,
                    "Output charts as protobuf instead of python code.");
 
-void SetAllPlotFlags(bool setting);
+using webrtc::Plot;
+
+namespace {
+std::vector<std::string> StrSplit(const std::string& s,
+                                  const std::string& delimiter) {
+  std::vector<std::string> v;
+  size_t pos = 0;
+  while (pos < s.length()) {
+    const std::string token = s.substr(pos, s.find(delimiter, pos) - pos);
+    pos += token.length() + delimiter.length();
+    v.push_back(token);
+  }
+  return v;
+}
+
+struct PlotDeclaration {
+  PlotDeclaration(const std::string& label, std::function<void(Plot*)> f)
+      : label(label), enabled(false), plot_func(f) {}
+  const std::string label;
+  bool enabled;
+  // TODO(terelius): Add a help text/explanation.
+  const std::function<void(Plot*)> plot_func;
+};
+
+class PlotMap {
+ public:
+  void RegisterPlot(const std::string& label, std::function<void(Plot*)> f) {
+    for (const auto& plot : plots_) {
+      RTC_DCHECK(plot.label != label)
+          << "Can't use the same label for multiple plots";
+    }
+    plots_.push_back({label, f});
+  }
+
+  bool EnablePlotsByFlags(
+      const std::vector<std::string>& flags,
+      const std::map<std::string, std::vector<std::string>>& flag_aliases) {
+    bool status = true;
+    for (const std::string& flag : flags) {
+      auto alias_it = flag_aliases.find(flag);
+      if (alias_it != flag_aliases.end()) {
+        const auto& replacements = alias_it->second;
+        for (const auto& replacement : replacements) {
+          status &= EnablePlotByFlag(replacement);
+        }
+      } else {
+        status &= EnablePlotByFlag(flag);
+      }
+    }
+    return status;
+  }
+
+  void EnableAllPlots() {
+    for (auto& plot : plots_) {
+      plot.enabled = true;
+    }
+  }
+
+  std::vector<PlotDeclaration>::iterator begin() { return plots_.begin(); }
+  std::vector<PlotDeclaration>::iterator end() { return plots_.end(); }
+
+ private:
+  bool EnablePlotByFlag(const std::string& flag) {
+    for (auto& plot : plots_) {
+      if (plot.label == flag) {
+        plot.enabled = true;
+        return true;
+      }
+    }
+    if (flag == "simulated_neteq_jitter_buffer_delay") {
+      // This flag is handled separately.
+      return true;
+    }
+    std::cerr << "Unrecognized plot name \'" << flag << "\'. Aborting."
+              << std::endl;
+    return false;
+  }
+
+  std::vector<PlotDeclaration> plots_;
+};
+}  // namespace
 
 int main(int argc, char* argv[]) {
   std::string program_name = argv[0];
@@ -235,53 +175,57 @@
       program_name + " <logfile> | python\n" + "Run " + program_name +
       " --help for a list of command line options\n";
 
-  // Parse command line flags without removing them. We're only interested in
-  // the |plot_profile| flag.
-  rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, false);
-  if (strcmp(FLAG_plot_profile, "all") == 0) {
-    SetAllPlotFlags(true);
-  } else if (strcmp(FLAG_plot_profile, "none") == 0) {
-    SetAllPlotFlags(false);
-  } else if (strcmp(FLAG_plot_profile, "sendside_bwe") == 0) {
-    SetAllPlotFlags(false);
-    FLAG_plot_outgoing_packet_sizes = true;
-    FLAG_plot_outgoing_bitrate = true;
-    FLAG_plot_outgoing_stream_bitrate = true;
-    FLAG_plot_simulated_sendside_bwe = true;
-    FLAG_plot_network_delay_feedback = true;
-    FLAG_plot_fraction_loss_feedback = true;
-  } else if (strcmp(FLAG_plot_profile, "receiveside_bwe") == 0) {
-    SetAllPlotFlags(false);
-    FLAG_plot_incoming_packet_sizes = true;
-    FLAG_plot_incoming_delay = true;
-    FLAG_plot_incoming_loss_rate = true;
-    FLAG_plot_incoming_bitrate = true;
-    FLAG_plot_incoming_stream_bitrate = true;
-    FLAG_plot_simulated_receiveside_bwe = true;
-  } else if (strcmp(FLAG_plot_profile, "default") == 0) {
-    // Do nothing.
-  } else {
-    rtc::Flag* plot_profile_flag = rtc::FlagList::Lookup("plot_profile");
-    RTC_CHECK(plot_profile_flag);
-    plot_profile_flag->Print(false);
-  }
-  // Parse the remaining flags. They are applied relative to the chosen profile.
   rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
 
-  if (argc != 2 || FLAG_help) {
-    // Print usage information.
-    std::cout << usage;
-    if (FLAG_help)
-      rtc::FlagList::Print(nullptr, false);
-    return 0;
-  }
+  // Flag replacements
+  std::map<std::string, std::vector<std::string>> flag_aliases = {
+      {"default",
+       {"incoming_delay", "incoming_loss_rate", "incoming_bitrate",
+        "outgoing_bitrate,"
+        "incoming_stream_bitrate",
+        "outgoing_stream_bitrate", "network_delay_feedback",
+        "fraction_loss_feedback"}},
+      {"sendside_bwe",
+       {"outgoing_packet_sizes", "outgoing_bitrate", "outgoing_stream_bitrate",
+        "simulated_sendside_bwe", "network_delay_feedback",
+        "fraction_loss_feedback"}},
+      {"receiveside_bwe",
+       {"incoming_packet_sizes", "incoming_delay", "incoming_loss_rate",
+        "incoming_"
+        "bitrate",
+        "incoming_stream_bitrate", "simulated_receiveside_bwe"}},
+      {"rtcp_details",
+       {"incoming_rtcp_fraction_lost", "outgoing_rtcp_fraction_lost",
+        "incoming_rtcp_"
+        "cumulative_lost",
+        "outgoing_rtcp_cumulative_lost",
+        "incoming_rtcp_highest_"
+        "seq_number",
+        "outgoing_rtcp_highest_seq_number",
+        "incoming_rtcp_delay_since_"
+        "last_sr",
+        "outgoing_rtcp_delay_since_last_sr"}},
+      {"simulated_neteq_stats",
+       {"simulated_neteq_jitter_buffer_delay",
+        "simulated_neteq_preferred_buffer_"
+        "size",
+        "simulated_neteq_"
+        "concealment_events",
+        "simulated_neteq_packet_loss_rate",
+        "simulated_neteq_"
+        "preemptive_rate",
+        "simulated_neteq_"
+        "accelerate_rate",
+        "simulated_neteq_speech_expand_rate",
+        "simulated_neteq_"
+        "expand_rate"}}};
+
+  std::vector<std::string> plot_flags = StrSplit(FLAG_plot, ",");
 
   // InitFieldTrialsFromString stores the char*, so the char array must outlive
   // the application.
   webrtc::field_trial::InitFieldTrialsFromString(FLAG_force_fieldtrials);
 
-  std::string filename = argv[1];
-
   webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions header_extensions =
       webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions::kDontParse;
   if (FLAG_parse_unconfigured_header_extensions) {
@@ -290,9 +234,12 @@
   }
   webrtc::ParsedRtcEventLog parsed_log(header_extensions);
 
-  if (!parsed_log.ParseFile(filename)) {
-    std::cerr << "Could not parse the entire log file." << std::endl;
-    std::cerr << "Only the parsable events will be analyzed." << std::endl;
+  if (argc == 2) {
+    std::string filename = argv[1];
+    if (!parsed_log.ParseFile(filename)) {
+      std::cerr << "Could not parse the entire log file." << std::endl;
+      std::cerr << "Only the parsable events will be analyzed." << std::endl;
+    }
   }
 
   webrtc::EventLogAnalyzer analyzer(parsed_log, FLAG_normalize_time);
@@ -303,238 +250,330 @@
     collection.reset(new webrtc::PythonPlotCollection(FLAG_shared_xaxis));
   }
 
-  if (FLAG_plot_incoming_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::kIncomingPacket,
-                               collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::kOutgoingPacket,
-                               collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_rtcp_types) {
-    analyzer.CreateRtcpTypeGraph(webrtc::kIncomingPacket,
-                                 collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_rtcp_types) {
-    analyzer.CreateRtcpTypeGraph(webrtc::kOutgoingPacket,
-                                 collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(webrtc::kIncomingPacket,
-                                           collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(webrtc::kOutgoingPacket,
-                                           collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_playout) {
-    analyzer.CreatePlayoutGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_level) {
-    analyzer.CreateAudioLevelGraph(webrtc::kIncomingPacket,
-                                   collection->AppendNewPlot());
-    analyzer.CreateAudioLevelGraph(webrtc::kOutgoingPacket,
-                                   collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_sequence_number_delta) {
-    analyzer.CreateSequenceNumberGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_delay) {
-    analyzer.CreateIncomingDelayGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_loss_rate) {
-    analyzer.CreateIncomingPacketLossGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_bitrate) {
-    analyzer.CreateTotalIncomingBitrateGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_bitrate) {
-    analyzer.CreateTotalOutgoingBitrateGraph(collection->AppendNewPlot(),
-                                             FLAG_show_detector_state,
+  PlotMap plots;
+  plots.RegisterPlot("incoming_packet_sizes", [&](Plot* plot) {
+    analyzer.CreatePacketGraph(webrtc::kIncomingPacket, plot);
+  });
+
+  plots.RegisterPlot("outgoing_packet_sizes", [&](Plot* plot) {
+    analyzer.CreatePacketGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("incoming_rtcp_types", [&](Plot* plot) {
+    analyzer.CreateRtcpTypeGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_rtcp_types", [&](Plot* plot) {
+    analyzer.CreateRtcpTypeGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("incoming_packet_count", [&](Plot* plot) {
+    analyzer.CreateAccumulatedPacketsGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_packet_count", [&](Plot* plot) {
+    analyzer.CreateAccumulatedPacketsGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("audio_playout",
+                     [&](Plot* plot) { analyzer.CreatePlayoutGraph(plot); });
+  plots.RegisterPlot("incoming_audio_level", [&](Plot* plot) {
+    analyzer.CreateAudioLevelGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_audio_level", [&](Plot* plot) {
+    analyzer.CreateAudioLevelGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("incoming_sequence_number_delta", [&](Plot* plot) {
+    analyzer.CreateSequenceNumberGraph(plot);
+  });
+  plots.RegisterPlot("incoming_delay", [&](Plot* plot) {
+    analyzer.CreateIncomingDelayGraph(plot);
+  });
+  plots.RegisterPlot("incoming_loss_rate", [&](Plot* plot) {
+    analyzer.CreateIncomingPacketLossGraph(plot);
+  });
+  plots.RegisterPlot("incoming_bitrate", [&](Plot* plot) {
+    analyzer.CreateTotalIncomingBitrateGraph(plot);
+  });
+  plots.RegisterPlot("outgoing_bitrate", [&](Plot* plot) {
+    analyzer.CreateTotalOutgoingBitrateGraph(plot, FLAG_show_detector_state,
                                              FLAG_show_alr_state);
-  }
-  if (FLAG_plot_incoming_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::kIncomingPacket,
-                                      collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::kOutgoingPacket,
-                                      collection->AppendNewPlot());
-  }
-  if (FLAG_plot_incoming_layer_bitrate_allocation) {
-    analyzer.CreateBitrateAllocationGraph(webrtc::kIncomingPacket,
-                                          collection->AppendNewPlot());
-  }
-  if (FLAG_plot_outgoing_layer_bitrate_allocation) {
-    analyzer.CreateBitrateAllocationGraph(webrtc::kOutgoingPacket,
-                                          collection->AppendNewPlot());
-  }
-  if (FLAG_plot_simulated_receiveside_bwe) {
-    analyzer.CreateReceiveSideBweSimulationGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_simulated_sendside_bwe) {
-    analyzer.CreateSendSideBweSimulationGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_simulated_goog_cc) {
-    analyzer.CreateGoogCcSimulationGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_network_delay_feedback) {
-    analyzer.CreateNetworkDelayFeedbackGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_fraction_loss_feedback) {
-    analyzer.CreateFractionLossGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_timestamps) {
-    analyzer.CreateTimestampGraph(webrtc::kIncomingPacket,
-                                  collection->AppendNewPlot());
-    analyzer.CreateTimestampGraph(webrtc::kOutgoingPacket,
-                                  collection->AppendNewPlot());
-  }
-  if (FLAG_plot_rtcp_details) {
-    auto GetFractionLost = [](const webrtc::rtcp::ReportBlock& block) -> float {
-      return static_cast<double>(block.fraction_lost()) / 256 * 100;
-    };
+  });
+  plots.RegisterPlot("incoming_stream_bitrate", [&](Plot* plot) {
+    analyzer.CreateStreamBitrateGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_stream_bitrate", [&](Plot* plot) {
+    analyzer.CreateStreamBitrateGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("incoming_layer_bitrate_allocation", [&](Plot* plot) {
+    analyzer.CreateBitrateAllocationGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_layer_bitrate_allocation", [&](Plot* plot) {
+    analyzer.CreateBitrateAllocationGraph(webrtc::kOutgoingPacket, plot);
+  });
+  plots.RegisterPlot("simulated_receiveside_bwe", [&](Plot* plot) {
+    analyzer.CreateReceiveSideBweSimulationGraph(plot);
+  });
+  plots.RegisterPlot("simulated_sendside_bwe", [&](Plot* plot) {
+    analyzer.CreateSendSideBweSimulationGraph(plot);
+  });
+  plots.RegisterPlot("simulated_goog_cc", [&](Plot* plot) {
+    analyzer.CreateGoogCcSimulationGraph(plot);
+  });
+  plots.RegisterPlot("network_delay_feedback", [&](Plot* plot) {
+    analyzer.CreateNetworkDelayFeedbackGraph(plot);
+  });
+  plots.RegisterPlot("fraction_loss_feedback", [&](Plot* plot) {
+    analyzer.CreateFractionLossGraph(plot);
+  });
+  plots.RegisterPlot("incoming_timestamps", [&](Plot* plot) {
+    analyzer.CreateTimestampGraph(webrtc::kIncomingPacket, plot);
+  });
+  plots.RegisterPlot("outgoing_timestamps", [&](Plot* plot) {
+    analyzer.CreateTimestampGraph(webrtc::kOutgoingPacket, plot);
+  });
+
+  auto GetFractionLost = [](const webrtc::rtcp::ReportBlock& block) -> float {
+    return static_cast<double>(block.fraction_lost()) / 256 * 100;
+  };
+  plots.RegisterPlot("incoming_rtcp_fraction_lost", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kIncomingPacket, GetFractionLost,
-        "Fraction lost (incoming RTCP)", "Loss rate (percent)",
-        collection->AppendNewPlot());
+        "Fraction lost (incoming RTCP)", "Loss rate (percent)", plot);
+  });
+  plots.RegisterPlot("outgoing_rtcp_fraction_lost", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kOutgoingPacket, GetFractionLost,
-        "Fraction lost (outgoing RTCP)", "Loss rate (percent)",
-        collection->AppendNewPlot());
-
-    auto GetCumulativeLost =
-        [](const webrtc::rtcp::ReportBlock& block) -> float {
-      return block.cumulative_lost_signed();
-    };
+        "Fraction lost (outgoing RTCP)", "Loss rate (percent)", plot);
+  });
+  auto GetCumulativeLost = [](const webrtc::rtcp::ReportBlock& block) -> float {
+    return block.cumulative_lost_signed();
+  };
+  plots.RegisterPlot("incoming_rtcp_cumulative_lost", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kIncomingPacket, GetCumulativeLost,
-        "Cumulative lost packets (incoming RTCP)", "Packets",
-        collection->AppendNewPlot());
+        "Cumulative lost packets (incoming RTCP)", "Packets", plot);
+  });
+  plots.RegisterPlot("outgoing_rtcp_cumulative_lost", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kOutgoingPacket, GetCumulativeLost,
-        "Cumulative lost packets (outgoing RTCP)", "Packets",
-        collection->AppendNewPlot());
+        "Cumulative lost packets (outgoing RTCP)", "Packets", plot);
+  });
 
-    auto GetHighestSeqNumber =
-        [](const webrtc::rtcp::ReportBlock& block) -> float {
-      return block.extended_high_seq_num();
-    };
+  auto GetHighestSeqNumber =
+      [](const webrtc::rtcp::ReportBlock& block) -> float {
+    return block.extended_high_seq_num();
+  };
+  plots.RegisterPlot("incoming_rtcp_highest_seq_number", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kIncomingPacket, GetHighestSeqNumber,
-        "Highest sequence number (incoming RTCP)", "Sequence number",
-        collection->AppendNewPlot());
+        "Highest sequence number (incoming RTCP)", "Sequence number", plot);
+  });
+  plots.RegisterPlot("outgoing_rtcp_highest_seq_number", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kOutgoingPacket, GetHighestSeqNumber,
-        "Highest sequence number (outgoing RTCP)", "Sequence number",
-        collection->AppendNewPlot());
+        "Highest sequence number (outgoing RTCP)", "Sequence number", plot);
+  });
 
-    auto DelaySinceLastSr =
-        [](const webrtc::rtcp::ReportBlock& block) -> float {
-      return static_cast<double>(block.delay_since_last_sr()) / 65536;
-    };
+  auto DelaySinceLastSr = [](const webrtc::rtcp::ReportBlock& block) -> float {
+    return static_cast<double>(block.delay_since_last_sr()) / 65536;
+  };
+  plots.RegisterPlot("incoming_rtcp_delay_since_last_sr", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kIncomingPacket, DelaySinceLastSr,
         "Delay since last received sender report (incoming RTCP)", "Time (s)",
-        collection->AppendNewPlot());
+        plot);
+  });
+  plots.RegisterPlot("outgoing_rtcp_delay_since_last_sr", [&](Plot* plot) {
     analyzer.CreateSenderAndReceiverReportPlot(
         webrtc::kOutgoingPacket, DelaySinceLastSr,
         "Delay since last received sender report (outgoing RTCP)", "Time (s)",
-        collection->AppendNewPlot());
-  }
+        plot);
+  });
 
-  if (FLAG_plot_pacer_delay) {
-    analyzer.CreatePacerDelayGraph(collection->AppendNewPlot());
+  plots.RegisterPlot("pacer_delay",
+                     [&](Plot* plot) { analyzer.CreatePacerDelayGraph(plot); });
+  plots.RegisterPlot("audio_encoder_bitrate", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderTargetBitrateGraph(plot);
+  });
+  plots.RegisterPlot("audio_encoder_frame_length", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderFrameLengthGraph(plot);
+  });
+  plots.RegisterPlot("audio_encoder_packet_loss", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderPacketLossGraph(plot);
+  });
+  plots.RegisterPlot("audio_encoder_fec", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderEnableFecGraph(plot);
+  });
+  plots.RegisterPlot("audio_encoder_dtx", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderEnableDtxGraph(plot);
+  });
+  plots.RegisterPlot("audio_encoder_num_channels", [&](Plot* plot) {
+    analyzer.CreateAudioEncoderNumChannelsGraph(plot);
+  });
+
+  plots.RegisterPlot("ice_candidate_pair_config", [&](Plot* plot) {
+    analyzer.CreateIceCandidatePairConfigGraph(plot);
+  });
+  plots.RegisterPlot("ice_connectivity_check", [&](Plot* plot) {
+    analyzer.CreateIceConnectivityCheckGraph(plot);
+  });
+  plots.RegisterPlot("dtls_transport_state", [&](Plot* plot) {
+    analyzer.CreateDtlsTransportStateGraph(plot);
+  });
+  plots.RegisterPlot("dtls_writable_state", [&](Plot* plot) {
+    analyzer.CreateDtlsWritableStateGraph(plot);
+  });
+
+  std::string wav_path;
+  if (FLAG_wav_filename[0] != '\0') {
+    wav_path = FLAG_wav_filename;
+  } else {
+    wav_path = webrtc::test::ResourcePath(
+        "audio_processing/conversational_speech/EN_script2_F_sp2_B1", "wav");
   }
-  if (FLAG_plot_audio_encoder_bitrate_bps) {
-    analyzer.CreateAudioEncoderTargetBitrateGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_encoder_frame_length_ms) {
-    analyzer.CreateAudioEncoderFrameLengthGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_encoder_packet_loss) {
-    analyzer.CreateAudioEncoderPacketLossGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_encoder_fec) {
-    analyzer.CreateAudioEncoderEnableFecGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_encoder_dtx) {
-    analyzer.CreateAudioEncoderEnableDtxGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_audio_encoder_num_channels) {
-    analyzer.CreateAudioEncoderNumChannelsGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_neteq_stats) {
-    std::string wav_path;
-    if (FLAG_wav_filename[0] != '\0') {
-      wav_path = FLAG_wav_filename;
-    } else {
-      wav_path = webrtc::test::ResourcePath(
-          "audio_processing/conversational_speech/EN_script2_F_sp2_B1", "wav");
-    }
-    auto neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
-    for (webrtc::EventLogAnalyzer::NetEqStatsGetterMap::const_iterator it =
-             neteq_stats.cbegin();
-         it != neteq_stats.cend(); ++it) {
-      analyzer.CreateAudioJitterBufferGraph(it->first, it->second.get(),
-                                            collection->AppendNewPlot());
+  absl::optional<webrtc::EventLogAnalyzer::NetEqStatsGetterMap> neteq_stats;
+
+  plots.RegisterPlot("simulated_neteq_expand_rate", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
     }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.expand_rate / 16384.f;
         },
-        "Expand rate", collection->AppendNewPlot());
+        "Expand rate", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_speech_expand_rate", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.speech_expand_rate / 16384.f;
         },
-        "Speech expand rate", collection->AppendNewPlot());
+        "Speech expand rate", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_accelerate_rate", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.accelerate_rate / 16384.f;
         },
-        "Accelerate rate", collection->AppendNewPlot());
+        "Accelerate rate", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_preemptive_rate", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.preemptive_rate / 16384.f;
         },
-        "Preemptive rate", collection->AppendNewPlot());
+        "Preemptive rate", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_packet_loss_rate", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.packet_loss_rate / 16384.f;
         },
-        "Packet loss rate", collection->AppendNewPlot());
+        "Packet loss rate", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_concealment_events", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqLifetimeStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqLifetimeStatistics& stats) {
           return static_cast<float>(stats.concealment_events);
         },
-        "Concealment events", collection->AppendNewPlot());
+        "Concealment events", plot);
+  });
+
+  plots.RegisterPlot("simulated_neteq_preferred_buffer_size", [&](Plot* plot) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
     analyzer.CreateNetEqNetworkStatsGraph(
-        neteq_stats,
+        *neteq_stats,
         [](const webrtc::NetEqNetworkStatistics& stats) {
           return stats.preferred_buffer_size_ms;
         },
-        "Preferred buffer size (ms)", collection->AppendNewPlot());
+        "Preferred buffer size (ms)", plot);
+  });
+
+  if (absl::c_find(plot_flags, "all") != plot_flags.end()) {
+    plots.EnableAllPlots();
+    // Treated separately since it isn't registered like the other plots.
+    plot_flags.push_back("simulated_neteq_jitter_buffer_delay");
+  } else {
+    bool success = plots.EnablePlotsByFlags(plot_flags, flag_aliases);
+    if (!success) {
+      return 1;
+    }
   }
 
-  if (FLAG_plot_ice_candidate_pair_config) {
-    analyzer.CreateIceCandidatePairConfigGraph(collection->AppendNewPlot());
-  }
-  if (FLAG_plot_ice_connectivity_check) {
-    analyzer.CreateIceConnectivityCheckGraph(collection->AppendNewPlot());
+  if (argc != 2 || FLAG_help) {
+    // Print usage information.
+    std::cerr << usage;
+    if (FLAG_help) {
+      rtc::FlagList::Print(nullptr, false);
+      std::cerr << "List of registered plots (for use with the --plot flag):"
+                << std::endl;
+      for (const auto& plot : plots) {
+        // TODO(terelius): Also print a help text.
+        std::cerr << "  " << plot.label << std::endl;
+      }
+      // The following flag doesn't fit the model used for the other plots.
+      std::cerr << "simulated_neteq_jitter_buffer_delay" << std::endl;
+      std::cerr << "List of plot aliases (for use with the --plot flag):"
+                << std::endl;
+      for (const auto& alias : flag_aliases) {
+        std::cerr << "  " << alias.first << " = ";
+        for (const auto& replacement : alias.second) {
+          std::cerr << replacement << ",";
+        }
+        std::cerr << std::endl;
+      }
+    }
+    return 0;
   }
 
-  if (FLAG_plot_dtls_transport_state) {
-    analyzer.CreateDtlsTransportStateGraph(collection->AppendNewPlot());
+  for (const auto& plot : plots) {
+    if (plot.enabled) {
+      Plot* output = collection->AppendNewPlot();
+      plot.plot_func(output);
+      output->SetId(plot.label);
+    }
   }
-  if (FLAG_plot_dtls_writable_state) {
-    analyzer.CreateDtlsWritableStateGraph(collection->AppendNewPlot());
+
+  // The model we use for registering plots assumes that the each plot label
+  // can be mapped to a lambda that will produce exactly one plot. The
+  // simulated_neteq_jitter_buffer_delay plot doesn't fit this model since it
+  // creates multiple plots, and would need some state kept between the lambda
+  // calls.
+  if (absl::c_find(plot_flags, "simulated_neteq_jitter_buffer_delay") !=
+      plot_flags.end()) {
+    if (!neteq_stats) {
+      neteq_stats = analyzer.SimulateNetEq(wav_path, 48000);
+    }
+    for (webrtc::EventLogAnalyzer::NetEqStatsGetterMap::const_iterator it =
+             neteq_stats->cbegin();
+         it != neteq_stats->cend(); ++it) {
+      analyzer.CreateAudioJitterBufferGraph(it->first, it->second.get(),
+                                            collection->AppendNewPlot());
+    }
   }
 
   collection->Draw();
@@ -547,39 +586,3 @@
   return 0;
 }
 
-void SetAllPlotFlags(bool setting) {
-  FLAG_plot_incoming_packet_sizes = setting;
-  FLAG_plot_outgoing_packet_sizes = setting;
-  FLAG_plot_incoming_rtcp_types = setting;
-  FLAG_plot_outgoing_rtcp_types = setting;
-  FLAG_plot_incoming_packet_count = setting;
-  FLAG_plot_outgoing_packet_count = setting;
-  FLAG_plot_audio_playout = setting;
-  FLAG_plot_audio_level = setting;
-  FLAG_plot_incoming_sequence_number_delta = setting;
-  FLAG_plot_incoming_delay = setting;
-  FLAG_plot_incoming_loss_rate = setting;
-  FLAG_plot_incoming_bitrate = setting;
-  FLAG_plot_outgoing_bitrate = setting;
-  FLAG_plot_incoming_stream_bitrate = setting;
-  FLAG_plot_outgoing_stream_bitrate = setting;
-  FLAG_plot_incoming_layer_bitrate_allocation = setting;
-  FLAG_plot_outgoing_layer_bitrate_allocation = setting;
-  FLAG_plot_simulated_receiveside_bwe = setting;
-  FLAG_plot_simulated_sendside_bwe = setting;
-  FLAG_plot_simulated_goog_cc = setting;
-  FLAG_plot_network_delay_feedback = setting;
-  FLAG_plot_fraction_loss_feedback = setting;
-  FLAG_plot_timestamps = setting;
-  FLAG_plot_rtcp_details = setting;
-  FLAG_plot_audio_encoder_bitrate_bps = setting;
-  FLAG_plot_audio_encoder_frame_length_ms = setting;
-  FLAG_plot_audio_encoder_packet_loss = setting;
-  FLAG_plot_audio_encoder_fec = setting;
-  FLAG_plot_audio_encoder_dtx = setting;
-  FLAG_plot_audio_encoder_num_channels = setting;
-  FLAG_plot_neteq_stats = setting;
-  FLAG_plot_ice_candidate_pair_config = setting;
-  FLAG_plot_ice_connectivity_check = setting;
-  FLAG_plot_pacer_delay = setting;
-}
diff --git a/rtc_tools/event_log_visualizer/plot_base.cc b/rtc_tools/event_log_visualizer/plot_base.cc
index 9a21393..8313beb 100644
--- a/rtc_tools/event_log_visualizer/plot_base.cc
+++ b/rtc_tools/event_log_visualizer/plot_base.cc
@@ -66,10 +66,14 @@
   SetYAxis(min_value, max_value, label, bottom_margin, top_margin);
 }
 
-void Plot::SetTitle(std::string title) {
+void Plot::SetTitle(const std::string& title) {
   title_ = title;
 }
 
+void Plot::SetId(const std::string& id) {
+  id_ = id;
+}
+
 void Plot::AppendTimeSeries(TimeSeries&& time_series) {
   series_list_.emplace_back(std::move(time_series));
 }
diff --git a/rtc_tools/event_log_visualizer/plot_base.h b/rtc_tools/event_log_visualizer/plot_base.h
index e73f004..72fa575 100644
--- a/rtc_tools/event_log_visualizer/plot_base.h
+++ b/rtc_tools/event_log_visualizer/plot_base.h
@@ -138,7 +138,12 @@
                          float top_margin = 0);
 
   // Sets the title of the plot.
-  void SetTitle(std::string title);
+  void SetTitle(const std::string& title);
+
+  // Sets an unique ID for the plot. The ID is similar to the title except that
+  // the title might change in future releases whereas the ID should be stable
+  // over time.
+  void SetId(const std::string& id);
 
   // Add a new TimeSeries to the plot.
   void AppendTimeSeries(TimeSeries&& time_series);
@@ -158,6 +163,7 @@
   float yaxis_max_;
   std::string yaxis_label_;
   std::string title_;
+  std::string id_;
   std::vector<TimeSeries> series_list_;
   std::vector<IntervalSeries> interval_list_;
 };
diff --git a/rtc_tools/event_log_visualizer/plot_protobuf.cc b/rtc_tools/event_log_visualizer/plot_protobuf.cc
index 309214b..9dc61f7 100644
--- a/rtc_tools/event_log_visualizer/plot_protobuf.cc
+++ b/rtc_tools/event_log_visualizer/plot_protobuf.cc
@@ -58,6 +58,7 @@
   chart->set_xaxis_label(xaxis_label_);
   chart->set_yaxis_label(yaxis_label_);
   chart->set_title(title_);
+  chart->set_id(id_);
 }
 
 ProtobufPlotCollection::ProtobufPlotCollection() {}
diff --git a/rtc_tools/event_log_visualizer/proto/chart.proto b/rtc_tools/event_log_visualizer/proto/chart.proto
index 3a11d78..aa518a7 100644
--- a/rtc_tools/event_log_visualizer/proto/chart.proto
+++ b/rtc_tools/event_log_visualizer/proto/chart.proto
@@ -22,6 +22,7 @@
   float yaxis_max = 6;
   string yaxis_label = 7;
   string title = 8;
+  string id = 9;
 }
 
 message ChartCollection {