1 /*
2  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include <stdio.h>
12 #include <string.h>
13 
14 #include <iostream>
15 #include <map>
16 #include <memory>
17 #include <string>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/algorithm/container.h"
22 #include "absl/flags/flag.h"
23 #include "absl/flags/parse.h"
24 #include "absl/flags/usage.h"
25 #include "absl/flags/usage_config.h"
26 #include "absl/strings/match.h"
27 #include "api/neteq/neteq.h"
28 #include "api/rtc_event_log/rtc_event_log.h"
29 #include "logging/rtc_event_log/rtc_event_log_parser.h"
30 #include "modules/rtp_rtcp/source/rtcp_packet/report_block.h"
31 #include "rtc_base/checks.h"
32 #include "rtc_base/logging.h"
33 #include "rtc_tools/rtc_event_log_visualizer/alerts.h"
34 #include "rtc_tools/rtc_event_log_visualizer/analyze_audio.h"
35 #include "rtc_tools/rtc_event_log_visualizer/analyzer.h"
36 #include "rtc_tools/rtc_event_log_visualizer/plot_base.h"
37 #include "system_wrappers/include/field_trial.h"
38 #include "test/field_trial.h"
39 #include "test/testsupport/file_utils.h"
40 
41 ABSL_FLAG(std::string,
42           plot,
43           "default",
44           "A comma separated list of plot names. See --list_plots for valid "
45           "options.");
46 
47 ABSL_FLAG(
48     std::string,
49     force_fieldtrials,
50     "",
51     "Field trials control experimental feature code which can be forced. "
52     "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enabled/"
53     " will assign the group Enabled to field trial WebRTC-FooFeature. Multiple "
54     "trials are separated by \"/\"");
55 ABSL_FLAG(std::string,
56           wav_filename,
57           "",
58           "Path to wav file used for simulation of jitter buffer");
59 
60 ABSL_FLAG(bool,
61           show_detector_state,
62           false,
63           "Show the state of the delay based BWE detector on the total "
64           "bitrate graph");
65 
66 ABSL_FLAG(bool,
67           show_alr_state,
68           false,
69           "Show the state ALR state on the total bitrate graph");
70 
71 ABSL_FLAG(bool,
72           parse_unconfigured_header_extensions,
73           true,
74           "Attempt to parse unconfigured header extensions using the default "
75           "WebRTC mapping. This can give very misleading results if the "
76           "application negotiates a different mapping.");
77 
78 ABSL_FLAG(bool,
79           print_triage_alerts,
80           true,
81           "Print triage alerts, i.e. a list of potential problems.");
82 
83 ABSL_FLAG(bool,
84           normalize_time,
85           true,
86           "Normalize the log timestamps so that the call starts at time 0.");
87 
88 ABSL_FLAG(bool,
89           shared_xaxis,
90           false,
91           "Share x-axis between all plots so that zooming in one plot "
92           "updates all the others too. A downside is that certain "
93           "operations like panning become much slower.");
94 
95 ABSL_FLAG(bool,
96           protobuf_output,
97           false,
98           "Output charts as protobuf instead of python code.");
99 
100 ABSL_FLAG(bool,
101           list_plots,
102           false,
103           "List of registered plots (for use with the --plot flag)");
104 
105 using webrtc::Plot;
106 
107 namespace {
StrSplit(const std::string & s,const std::string & delimiter)108 std::vector<std::string> StrSplit(const std::string& s,
109                                   const std::string& delimiter) {
110   std::vector<std::string> v;
111   size_t pos = 0;
112   while (pos < s.length()) {
113     const std::string token = s.substr(pos, s.find(delimiter, pos) - pos);
114     pos += token.length() + delimiter.length();
115     v.push_back(token);
116   }
117   return v;
118 }
119 
120 struct PlotDeclaration {
PlotDeclaration__anon5c69794f0111::PlotDeclaration121   PlotDeclaration(const std::string& label, std::function<void(Plot*)> f)
122       : label(label), enabled(false), plot_func(f) {}
123   const std::string label;
124   bool enabled;
125   // TODO(terelius): Add a help text/explanation.
126   const std::function<void(Plot*)> plot_func;
127 };
128 
129 class PlotMap {
130  public:
RegisterPlot(const std::string & label,std::function<void (Plot *)> f)131   void RegisterPlot(const std::string& label, std::function<void(Plot*)> f) {
132     for (const auto& plot : plots_) {
133       RTC_DCHECK(plot.label != label)
134           << "Can't use the same label for multiple plots";
135     }
136     plots_.push_back({label, f});
137   }
138 
EnablePlotsByFlags(const std::vector<std::string> & flags,const std::map<std::string,std::vector<std::string>> & flag_aliases)139   bool EnablePlotsByFlags(
140       const std::vector<std::string>& flags,
141       const std::map<std::string, std::vector<std::string>>& flag_aliases) {
142     bool status = true;
143     for (const std::string& flag : flags) {
144       auto alias_it = flag_aliases.find(flag);
145       if (alias_it != flag_aliases.end()) {
146         const auto& replacements = alias_it->second;
147         for (const auto& replacement : replacements) {
148           status &= EnablePlotByFlag(replacement);
149         }
150       } else {
151         status &= EnablePlotByFlag(flag);
152       }
153     }
154     return status;
155   }
156 
EnableAllPlots()157   void EnableAllPlots() {
158     for (auto& plot : plots_) {
159       plot.enabled = true;
160     }
161   }
162 
begin()163   std::vector<PlotDeclaration>::iterator begin() { return plots_.begin(); }
end()164   std::vector<PlotDeclaration>::iterator end() { return plots_.end(); }
165 
166  private:
EnablePlotByFlag(const std::string & flag)167   bool EnablePlotByFlag(const std::string& flag) {
168     for (auto& plot : plots_) {
169       if (plot.label == flag) {
170         plot.enabled = true;
171         return true;
172       }
173     }
174     if (flag == "simulated_neteq_jitter_buffer_delay") {
175       // This flag is handled separately.
176       return true;
177     }
178     std::cerr << "Unrecognized plot name \'" << flag << "\'. Aborting."
179               << std::endl;
180     return false;
181   }
182 
183   std::vector<PlotDeclaration> plots_;
184 };
185 
ContainsHelppackageFlags(absl::string_view filename)186 bool ContainsHelppackageFlags(absl::string_view filename) {
187   return absl::EndsWith(filename, "main.cc");
188 }
189 
190 }  // namespace
191 
main(int argc,char * argv[])192 int main(int argc, char* argv[]) {
193   absl::SetProgramUsageMessage(
194       "A tool for visualizing WebRTC event logs.\n"
195       "Example usage:\n"
196       "./event_log_visualizer <logfile> | python\n");
197   absl::FlagsUsageConfig flag_config;
198   flag_config.contains_help_flags = &ContainsHelppackageFlags;
199   absl::SetFlagsUsageConfig(flag_config);
200   std::vector<char*> args = absl::ParseCommandLine(argc, argv);
201 
202   // Print RTC_LOG warnings and errors even in release builds.
203   if (rtc::LogMessage::GetLogToDebug() > rtc::LS_WARNING) {
204     rtc::LogMessage::LogToDebug(rtc::LS_WARNING);
205   }
206   rtc::LogMessage::SetLogToStderr(true);
207 
208   // Flag replacements
209   std::map<std::string, std::vector<std::string>> flag_aliases = {
210       {"default",
211        {"incoming_delay", "incoming_loss_rate", "incoming_bitrate",
212         "outgoing_bitrate", "incoming_stream_bitrate",
213         "outgoing_stream_bitrate", "network_delay_feedback",
214         "fraction_loss_feedback"}},
215       {"sendside_bwe",
216        {"outgoing_packet_sizes", "outgoing_bitrate", "outgoing_stream_bitrate",
217         "simulated_sendside_bwe", "network_delay_feedback",
218         "fraction_loss_feedback"}},
219       {"receiveside_bwe",
220        {"incoming_packet_sizes", "incoming_delay", "incoming_loss_rate",
221         "incoming_bitrate", "incoming_stream_bitrate",
222         "simulated_receiveside_bwe"}},
223       {"rtcp_details",
224        {"incoming_rtcp_fraction_lost", "outgoing_rtcp_fraction_lost",
225         "incoming_rtcp_cumulative_lost", "outgoing_rtcp_cumulative_lost",
226         "incoming_rtcp_highest_seq_number", "outgoing_rtcp_highest_seq_number",
227         "incoming_rtcp_delay_since_last_sr",
228         "outgoing_rtcp_delay_since_last_sr"}},
229       {"simulated_neteq_stats",
230        {"simulated_neteq_jitter_buffer_delay",
231         "simulated_neteq_preferred_buffer_size",
232         "simulated_neteq_concealment_events",
233         "simulated_neteq_packet_loss_rate", "simulated_neteq_preemptive_rate",
234         "simulated_neteq_accelerate_rate", "simulated_neteq_speech_expand_rate",
235         "simulated_neteq_expand_rate"}}};
236 
237   std::vector<std::string> plot_flags =
238       StrSplit(absl::GetFlag(FLAGS_plot), ",");
239 
240   // InitFieldTrialsFromString stores the char*, so the char array must outlive
241   // the application.
242   const std::string field_trials = absl::GetFlag(FLAGS_force_fieldtrials);
243   webrtc::field_trial::InitFieldTrialsFromString(field_trials.c_str());
244 
245   webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions header_extensions =
246       webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions::kDontParse;
247   if (absl::GetFlag(FLAGS_parse_unconfigured_header_extensions)) {
248     header_extensions = webrtc::ParsedRtcEventLog::
249         UnconfiguredHeaderExtensions::kAttemptWebrtcDefaultConfig;
250   }
251   webrtc::ParsedRtcEventLog parsed_log(header_extensions,
252                                        /*allow_incomplete_logs*/ true);
253 
254   if (args.size() == 2) {
255     std::string filename = args[1];
256     auto status = parsed_log.ParseFile(filename);
257     if (!status.ok()) {
258       std::cerr << "Failed to parse " << filename << ": " << status.message()
259                 << std::endl;
260       return -1;
261     }
262   }
263 
264   webrtc::AnalyzerConfig config;
265   config.window_duration_ = 250000;
266   config.step_ = 10000;
267   config.normalize_time_ = absl::GetFlag(FLAGS_normalize_time);
268   config.begin_time_ = parsed_log.first_timestamp();
269   config.end_time_ = parsed_log.last_timestamp();
270   if (config.end_time_ < config.begin_time_) {
271     RTC_LOG(LS_WARNING) << "Log end time " << config.end_time_
272                         << " not after begin time " << config.begin_time_
273                         << ". Nothing to analyze. Is the log broken?";
274     return -1;
275   }
276 
277   webrtc::EventLogAnalyzer analyzer(parsed_log, config);
278   webrtc::PlotCollection collection;
279 
280   PlotMap plots;
281   plots.RegisterPlot("incoming_packet_sizes", [&](Plot* plot) {
282     analyzer.CreatePacketGraph(webrtc::kIncomingPacket, plot);
283   });
284 
285   plots.RegisterPlot("outgoing_packet_sizes", [&](Plot* plot) {
286     analyzer.CreatePacketGraph(webrtc::kOutgoingPacket, plot);
287   });
288   plots.RegisterPlot("incoming_rtcp_types", [&](Plot* plot) {
289     analyzer.CreateRtcpTypeGraph(webrtc::kIncomingPacket, plot);
290   });
291   plots.RegisterPlot("outgoing_rtcp_types", [&](Plot* plot) {
292     analyzer.CreateRtcpTypeGraph(webrtc::kOutgoingPacket, plot);
293   });
294   plots.RegisterPlot("incoming_packet_count", [&](Plot* plot) {
295     analyzer.CreateAccumulatedPacketsGraph(webrtc::kIncomingPacket, plot);
296   });
297   plots.RegisterPlot("outgoing_packet_count", [&](Plot* plot) {
298     analyzer.CreateAccumulatedPacketsGraph(webrtc::kOutgoingPacket, plot);
299   });
300   plots.RegisterPlot("incoming_packet_rate", [&](Plot* plot) {
301     analyzer.CreatePacketRateGraph(webrtc::kIncomingPacket, plot);
302   });
303   plots.RegisterPlot("outgoing_packet_rate", [&](Plot* plot) {
304     analyzer.CreatePacketRateGraph(webrtc::kOutgoingPacket, plot);
305   });
306   plots.RegisterPlot("total_incoming_packet_rate", [&](Plot* plot) {
307     analyzer.CreateTotalPacketRateGraph(webrtc::kIncomingPacket, plot);
308   });
309   plots.RegisterPlot("total_outgoing_packet_rate", [&](Plot* plot) {
310     analyzer.CreateTotalPacketRateGraph(webrtc::kOutgoingPacket, plot);
311   });
312   plots.RegisterPlot("audio_playout",
313                      [&](Plot* plot) { analyzer.CreatePlayoutGraph(plot); });
314   plots.RegisterPlot("incoming_audio_level", [&](Plot* plot) {
315     analyzer.CreateAudioLevelGraph(webrtc::kIncomingPacket, plot);
316   });
317   plots.RegisterPlot("outgoing_audio_level", [&](Plot* plot) {
318     analyzer.CreateAudioLevelGraph(webrtc::kOutgoingPacket, plot);
319   });
320   plots.RegisterPlot("incoming_sequence_number_delta", [&](Plot* plot) {
321     analyzer.CreateSequenceNumberGraph(plot);
322   });
323   plots.RegisterPlot("incoming_delay", [&](Plot* plot) {
324     analyzer.CreateIncomingDelayGraph(plot);
325   });
326   plots.RegisterPlot("incoming_loss_rate", [&](Plot* plot) {
327     analyzer.CreateIncomingPacketLossGraph(plot);
328   });
329   plots.RegisterPlot("incoming_bitrate", [&](Plot* plot) {
330     analyzer.CreateTotalIncomingBitrateGraph(plot);
331   });
332   plots.RegisterPlot("outgoing_bitrate", [&](Plot* plot) {
333     analyzer.CreateTotalOutgoingBitrateGraph(
334         plot, absl::GetFlag(FLAGS_show_detector_state),
335         absl::GetFlag(FLAGS_show_alr_state));
336   });
337   plots.RegisterPlot("incoming_stream_bitrate", [&](Plot* plot) {
338     analyzer.CreateStreamBitrateGraph(webrtc::kIncomingPacket, plot);
339   });
340   plots.RegisterPlot("outgoing_stream_bitrate", [&](Plot* plot) {
341     analyzer.CreateStreamBitrateGraph(webrtc::kOutgoingPacket, plot);
342   });
343   plots.RegisterPlot("incoming_layer_bitrate_allocation", [&](Plot* plot) {
344     analyzer.CreateBitrateAllocationGraph(webrtc::kIncomingPacket, plot);
345   });
346   plots.RegisterPlot("outgoing_layer_bitrate_allocation", [&](Plot* plot) {
347     analyzer.CreateBitrateAllocationGraph(webrtc::kOutgoingPacket, plot);
348   });
349   plots.RegisterPlot("simulated_receiveside_bwe", [&](Plot* plot) {
350     analyzer.CreateReceiveSideBweSimulationGraph(plot);
351   });
352   plots.RegisterPlot("simulated_sendside_bwe", [&](Plot* plot) {
353     analyzer.CreateSendSideBweSimulationGraph(plot);
354   });
355   plots.RegisterPlot("simulated_goog_cc", [&](Plot* plot) {
356     analyzer.CreateGoogCcSimulationGraph(plot);
357   });
358   plots.RegisterPlot("network_delay_feedback", [&](Plot* plot) {
359     analyzer.CreateNetworkDelayFeedbackGraph(plot);
360   });
361   plots.RegisterPlot("fraction_loss_feedback", [&](Plot* plot) {
362     analyzer.CreateFractionLossGraph(plot);
363   });
364   plots.RegisterPlot("incoming_timestamps", [&](Plot* plot) {
365     analyzer.CreateTimestampGraph(webrtc::kIncomingPacket, plot);
366   });
367   plots.RegisterPlot("outgoing_timestamps", [&](Plot* plot) {
368     analyzer.CreateTimestampGraph(webrtc::kOutgoingPacket, plot);
369   });
370 
371   auto GetFractionLost = [](const webrtc::rtcp::ReportBlock& block) -> float {
372     return static_cast<double>(block.fraction_lost()) / 256 * 100;
373   };
374   plots.RegisterPlot("incoming_rtcp_fraction_lost", [&](Plot* plot) {
375     analyzer.CreateSenderAndReceiverReportPlot(
376         webrtc::kIncomingPacket, GetFractionLost,
377         "Fraction lost (incoming RTCP)", "Loss rate (percent)", plot);
378   });
379   plots.RegisterPlot("outgoing_rtcp_fraction_lost", [&](Plot* plot) {
380     analyzer.CreateSenderAndReceiverReportPlot(
381         webrtc::kOutgoingPacket, GetFractionLost,
382         "Fraction lost (outgoing RTCP)", "Loss rate (percent)", plot);
383   });
384   auto GetCumulativeLost = [](const webrtc::rtcp::ReportBlock& block) -> float {
385     return block.cumulative_lost_signed();
386   };
387   plots.RegisterPlot("incoming_rtcp_cumulative_lost", [&](Plot* plot) {
388     analyzer.CreateSenderAndReceiverReportPlot(
389         webrtc::kIncomingPacket, GetCumulativeLost,
390         "Cumulative lost packets (incoming RTCP)", "Packets", plot);
391   });
392   plots.RegisterPlot("outgoing_rtcp_cumulative_lost", [&](Plot* plot) {
393     analyzer.CreateSenderAndReceiverReportPlot(
394         webrtc::kOutgoingPacket, GetCumulativeLost,
395         "Cumulative lost packets (outgoing RTCP)", "Packets", plot);
396   });
397 
398   auto GetHighestSeqNumber =
399       [](const webrtc::rtcp::ReportBlock& block) -> float {
400     return block.extended_high_seq_num();
401   };
402   plots.RegisterPlot("incoming_rtcp_highest_seq_number", [&](Plot* plot) {
403     analyzer.CreateSenderAndReceiverReportPlot(
404         webrtc::kIncomingPacket, GetHighestSeqNumber,
405         "Highest sequence number (incoming RTCP)", "Sequence number", plot);
406   });
407   plots.RegisterPlot("outgoing_rtcp_highest_seq_number", [&](Plot* plot) {
408     analyzer.CreateSenderAndReceiverReportPlot(
409         webrtc::kOutgoingPacket, GetHighestSeqNumber,
410         "Highest sequence number (outgoing RTCP)", "Sequence number", plot);
411   });
412 
413   auto DelaySinceLastSr = [](const webrtc::rtcp::ReportBlock& block) -> float {
414     return static_cast<double>(block.delay_since_last_sr()) / 65536;
415   };
416   plots.RegisterPlot("incoming_rtcp_delay_since_last_sr", [&](Plot* plot) {
417     analyzer.CreateSenderAndReceiverReportPlot(
418         webrtc::kIncomingPacket, DelaySinceLastSr,
419         "Delay since last received sender report (incoming RTCP)", "Time (s)",
420         plot);
421   });
422   plots.RegisterPlot("outgoing_rtcp_delay_since_last_sr", [&](Plot* plot) {
423     analyzer.CreateSenderAndReceiverReportPlot(
424         webrtc::kOutgoingPacket, DelaySinceLastSr,
425         "Delay since last received sender report (outgoing RTCP)", "Time (s)",
426         plot);
427   });
428 
429   plots.RegisterPlot("pacer_delay",
430                      [&](Plot* plot) { analyzer.CreatePacerDelayGraph(plot); });
431   plots.RegisterPlot("audio_encoder_bitrate", [&](Plot* plot) {
432     CreateAudioEncoderTargetBitrateGraph(parsed_log, config, plot);
433   });
434   plots.RegisterPlot("audio_encoder_frame_length", [&](Plot* plot) {
435     CreateAudioEncoderFrameLengthGraph(parsed_log, config, plot);
436   });
437   plots.RegisterPlot("audio_encoder_packet_loss", [&](Plot* plot) {
438     CreateAudioEncoderPacketLossGraph(parsed_log, config, plot);
439   });
440   plots.RegisterPlot("audio_encoder_fec", [&](Plot* plot) {
441     CreateAudioEncoderEnableFecGraph(parsed_log, config, plot);
442   });
443   plots.RegisterPlot("audio_encoder_dtx", [&](Plot* plot) {
444     CreateAudioEncoderEnableDtxGraph(parsed_log, config, plot);
445   });
446   plots.RegisterPlot("audio_encoder_num_channels", [&](Plot* plot) {
447     CreateAudioEncoderNumChannelsGraph(parsed_log, config, plot);
448   });
449 
450   plots.RegisterPlot("ice_candidate_pair_config", [&](Plot* plot) {
451     analyzer.CreateIceCandidatePairConfigGraph(plot);
452   });
453   plots.RegisterPlot("ice_connectivity_check", [&](Plot* plot) {
454     analyzer.CreateIceConnectivityCheckGraph(plot);
455   });
456   plots.RegisterPlot("dtls_transport_state", [&](Plot* plot) {
457     analyzer.CreateDtlsTransportStateGraph(plot);
458   });
459   plots.RegisterPlot("dtls_writable_state", [&](Plot* plot) {
460     analyzer.CreateDtlsWritableStateGraph(plot);
461   });
462 
463   std::string wav_path;
464   if (!absl::GetFlag(FLAGS_wav_filename).empty()) {
465     wav_path = absl::GetFlag(FLAGS_wav_filename);
466   } else {
467     wav_path = webrtc::test::ResourcePath(
468         "audio_processing/conversational_speech/EN_script2_F_sp2_B1", "wav");
469   }
470   absl::optional<webrtc::NetEqStatsGetterMap> neteq_stats;
471 
472   plots.RegisterPlot("simulated_neteq_expand_rate", [&](Plot* plot) {
473     if (!neteq_stats) {
474       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
475     }
476     webrtc::CreateNetEqNetworkStatsGraph(
477         parsed_log, config, *neteq_stats,
478         [](const webrtc::NetEqNetworkStatistics& stats) {
479           return stats.expand_rate / 16384.f;
480         },
481         "Expand rate", plot);
482   });
483 
484   plots.RegisterPlot("simulated_neteq_speech_expand_rate", [&](Plot* plot) {
485     if (!neteq_stats) {
486       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
487     }
488     webrtc::CreateNetEqNetworkStatsGraph(
489         parsed_log, config, *neteq_stats,
490         [](const webrtc::NetEqNetworkStatistics& stats) {
491           return stats.speech_expand_rate / 16384.f;
492         },
493         "Speech expand rate", plot);
494   });
495 
496   plots.RegisterPlot("simulated_neteq_accelerate_rate", [&](Plot* plot) {
497     if (!neteq_stats) {
498       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
499     }
500     webrtc::CreateNetEqNetworkStatsGraph(
501         parsed_log, config, *neteq_stats,
502         [](const webrtc::NetEqNetworkStatistics& stats) {
503           return stats.accelerate_rate / 16384.f;
504         },
505         "Accelerate rate", plot);
506   });
507 
508   plots.RegisterPlot("simulated_neteq_preemptive_rate", [&](Plot* plot) {
509     if (!neteq_stats) {
510       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
511     }
512     webrtc::CreateNetEqNetworkStatsGraph(
513         parsed_log, config, *neteq_stats,
514         [](const webrtc::NetEqNetworkStatistics& stats) {
515           return stats.preemptive_rate / 16384.f;
516         },
517         "Preemptive rate", plot);
518   });
519 
520   plots.RegisterPlot("simulated_neteq_packet_loss_rate", [&](Plot* plot) {
521     if (!neteq_stats) {
522       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
523     }
524     webrtc::CreateNetEqNetworkStatsGraph(
525         parsed_log, config, *neteq_stats,
526         [](const webrtc::NetEqNetworkStatistics& stats) {
527           return stats.packet_loss_rate / 16384.f;
528         },
529         "Packet loss rate", plot);
530   });
531 
532   plots.RegisterPlot("simulated_neteq_concealment_events", [&](Plot* plot) {
533     if (!neteq_stats) {
534       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
535     }
536     webrtc::CreateNetEqLifetimeStatsGraph(
537         parsed_log, config, *neteq_stats,
538         [](const webrtc::NetEqLifetimeStatistics& stats) {
539           return static_cast<float>(stats.concealment_events);
540         },
541         "Concealment events", plot);
542   });
543 
544   plots.RegisterPlot("simulated_neteq_preferred_buffer_size", [&](Plot* plot) {
545     if (!neteq_stats) {
546       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
547     }
548     webrtc::CreateNetEqNetworkStatsGraph(
549         parsed_log, config, *neteq_stats,
550         [](const webrtc::NetEqNetworkStatistics& stats) {
551           return stats.preferred_buffer_size_ms;
552         },
553         "Preferred buffer size (ms)", plot);
554   });
555 
556   if (absl::c_find(plot_flags, "all") != plot_flags.end()) {
557     plots.EnableAllPlots();
558     // Treated separately since it isn't registered like the other plots.
559     plot_flags.push_back("simulated_neteq_jitter_buffer_delay");
560   } else {
561     bool success = plots.EnablePlotsByFlags(plot_flags, flag_aliases);
562     if (!success) {
563       return 1;
564     }
565   }
566 
567   if (absl::GetFlag(FLAGS_list_plots)) {
568     std::cerr << "List of registered plots (for use with the --plot flag):"
569               << std::endl;
570     for (const auto& plot : plots) {
571       // TODO(terelius): Also print a help text.
572       std::cerr << "  " << plot.label << std::endl;
573     }
574     // The following flag doesn't fit the model used for the other plots.
575     std::cerr << "simulated_neteq_jitter_buffer_delay" << std::endl;
576     std::cerr << "List of plot aliases (for use with the --plot flag):"
577               << std::endl;
578     std::cerr << "  all = every registered plot" << std::endl;
579     for (const auto& alias : flag_aliases) {
580       std::cerr << "  " << alias.first << " = ";
581       for (const auto& replacement : alias.second) {
582         std::cerr << replacement << ",";
583       }
584       std::cerr << std::endl;
585     }
586     return 0;
587   }
588   if (args.size() != 2) {
589     // Print usage information.
590     std::cerr << absl::ProgramUsageMessage();
591     return 1;
592   }
593 
594   for (const auto& plot : plots) {
595     if (plot.enabled) {
596       Plot* output = collection.AppendNewPlot();
597       plot.plot_func(output);
598       output->SetId(plot.label);
599     }
600   }
601 
602   // The model we use for registering plots assumes that the each plot label
603   // can be mapped to a lambda that will produce exactly one plot. The
604   // simulated_neteq_jitter_buffer_delay plot doesn't fit this model since it
605   // creates multiple plots, and would need some state kept between the lambda
606   // calls.
607   if (absl::c_find(plot_flags, "simulated_neteq_jitter_buffer_delay") !=
608       plot_flags.end()) {
609     if (!neteq_stats) {
610       neteq_stats = webrtc::SimulateNetEq(parsed_log, config, wav_path, 48000);
611     }
612     for (webrtc::NetEqStatsGetterMap::const_iterator it = neteq_stats->cbegin();
613          it != neteq_stats->cend(); ++it) {
614       webrtc::CreateAudioJitterBufferGraph(parsed_log, config, it->first,
615                                            it->second.get(),
616                                            collection.AppendNewPlot());
617     }
618   }
619 
620   if (absl::GetFlag(FLAGS_protobuf_output)) {
621     webrtc::analytics::ChartCollection proto_charts;
622     collection.ExportProtobuf(&proto_charts);
623     std::cout << proto_charts.SerializeAsString();
624   } else {
625     collection.PrintPythonCode(absl::GetFlag(FLAGS_shared_xaxis));
626   }
627 
628   if (absl::GetFlag(FLAGS_print_triage_alerts)) {
629     webrtc::TriageHelper triage_alerts(config);
630     triage_alerts.AnalyzeLog(parsed_log);
631     triage_alerts.Print(stderr);
632   }
633 
634   return 0;
635 }
636