diff --git a/webrtc/rtc_tools/event_log_visualizer/analyzer.cc b/webrtc/rtc_tools/event_log_visualizer/analyzer.cc index 78993f4f7c..b42f0820f4 100644 --- a/webrtc/rtc_tools/event_log_visualizer/analyzer.cc +++ b/webrtc/rtc_tools/event_log_visualizer/analyzer.cc @@ -898,7 +898,8 @@ void EventLogAnalyzer::CreateFractionLossGraph(Plot* plot) { // Plot the total bandwidth used by all RTP streams. void EventLogAnalyzer::CreateTotalBitrateGraph( PacketDirection desired_direction, - Plot* plot) { + Plot* plot, + bool show_detector_state) { struct TimestampSize { TimestampSize(uint64_t t, size_t s) : timestamp(t), size(s) {} uint64_t timestamp; @@ -958,13 +959,46 @@ void EventLogAnalyzer::CreateTotalBitrateGraph( } TimeSeries delay_series("Delay-based estimate", LINE_STEP_GRAPH); + IntervalSeries overusing_series("Overusing", "#ff8e82", + IntervalSeries::kHorizontal); + IntervalSeries underusing_series("Underusing", "#5092fc", + IntervalSeries::kHorizontal); + IntervalSeries normal_series("Normal", "#c4ffc4", + IntervalSeries::kHorizontal); + IntervalSeries* last_series = &normal_series; + double last_detector_switch = 0.0; + + BandwidthUsage last_detector_state = BandwidthUsage::kBwNormal; + for (auto& delay_update : bwe_delay_updates_) { float x = static_cast(delay_update.timestamp - begin_time_) / 1000000; float y = static_cast(delay_update.bitrate_bps) / 1000; + + if (last_detector_state != delay_update.detector_state) { + last_series->intervals.emplace_back(last_detector_switch, x); + last_detector_state = delay_update.detector_state; + last_detector_switch = x; + + switch (delay_update.detector_state) { + case BandwidthUsage::kBwNormal: + last_series = &normal_series; + break; + case BandwidthUsage::kBwUnderusing: + last_series = &underusing_series; + break; + case BandwidthUsage::kBwOverusing: + last_series = &overusing_series; + break; + } + } + delay_series.points.emplace_back(x, y); } + RTC_CHECK(last_series); + last_series->intervals.emplace_back(last_detector_switch, end_time_); + TimeSeries created_series("Probe cluster created.", DOT_GRAPH); for (auto& cluster : bwe_probe_cluster_created_events_) { float x = static_cast(cluster.timestamp - begin_time_) / 1000000; @@ -980,6 +1014,14 @@ void EventLogAnalyzer::CreateTotalBitrateGraph( result_series.points.emplace_back(x, y); } } + + if (show_detector_state) { + plot->AppendIntervalSeries(std::move(overusing_series)); + plot->AppendIntervalSeries(std::move(underusing_series)); + plot->AppendIntervalSeries(std::move(normal_series)); + } + + plot->AppendTimeSeries(std::move(bitrate_series)); plot->AppendTimeSeries(std::move(loss_series)); plot->AppendTimeSeries(std::move(delay_series)); plot->AppendTimeSeries(std::move(created_series)); diff --git a/webrtc/rtc_tools/event_log_visualizer/analyzer.h b/webrtc/rtc_tools/event_log_visualizer/analyzer.h index 0c3fa46ea5..ce4868f3f9 100644 --- a/webrtc/rtc_tools/event_log_visualizer/analyzer.h +++ b/webrtc/rtc_tools/event_log_visualizer/analyzer.h @@ -85,7 +85,9 @@ class EventLogAnalyzer { void CreateFractionLossGraph(Plot* plot); - void CreateTotalBitrateGraph(PacketDirection desired_direction, Plot* plot); + void CreateTotalBitrateGraph(PacketDirection desired_direction, + Plot* plot, + bool show_detector_state = false); void CreateStreamBitrateGraph(PacketDirection desired_direction, Plot* plot); diff --git a/webrtc/rtc_tools/event_log_visualizer/main.cc b/webrtc/rtc_tools/event_log_visualizer/main.cc index 6e78c890b2..a4490aaeb5 100644 --- a/webrtc/rtc_tools/event_log_visualizer/main.cc +++ b/webrtc/rtc_tools/event_log_visualizer/main.cc @@ -90,6 +90,11 @@ DEFINE_string( "trials are separated by \"/\""); DEFINE_bool(help, false, "prints this message"); +DEFINE_bool( + show_detector_state, + false, + "Mark the delay based bwe detector state on the total bitrate graph"); + int main(int argc, char* argv[]) { std::string program_name = argv[0]; std::string usage = @@ -178,11 +183,13 @@ int main(int argc, char* argv[]) { if (FLAG_plot_all || FLAG_plot_total_bitrate) { if (FLAG_incoming) { analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kIncomingPacket, - collection->AppendNewPlot()); + collection->AppendNewPlot(), + FLAG_show_detector_state); } if (FLAG_outgoing) { analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kOutgoingPacket, - collection->AppendNewPlot()); + collection->AppendNewPlot(), + FLAG_show_detector_state); } } diff --git a/webrtc/rtc_tools/event_log_visualizer/plot_base.cc b/webrtc/rtc_tools/event_log_visualizer/plot_base.cc index 8bf3533a8f..783f2b87ac 100644 --- a/webrtc/rtc_tools/event_log_visualizer/plot_base.cc +++ b/webrtc/rtc_tools/event_log_visualizer/plot_base.cc @@ -75,6 +75,10 @@ void Plot::AppendTimeSeries(TimeSeries&& time_series) { series_list_.emplace_back(std::move(time_series)); } +void Plot::AppendIntervalSeries(IntervalSeries&& interval_series) { + interval_list_.emplace_back(std::move(interval_series)); +} + void Plot::AppendTimeSeriesIfNotEmpty(TimeSeries&& time_series) { if (time_series.points.size() > 0) { series_list_.emplace_back(std::move(time_series)); diff --git a/webrtc/rtc_tools/event_log_visualizer/plot_base.h b/webrtc/rtc_tools/event_log_visualizer/plot_base.h index bdcea2e5c3..347fca4cf8 100644 --- a/webrtc/rtc_tools/event_log_visualizer/plot_base.h +++ b/webrtc/rtc_tools/event_log_visualizer/plot_base.h @@ -53,6 +53,29 @@ struct TimeSeries { std::vector points; }; +struct Interval { + Interval() = default; + Interval(double begin, double end) : begin(begin), end(end) {} + + double begin; + double end; +}; + +struct IntervalSeries { + enum Orientation { kHorizontal, kVertical }; + + IntervalSeries() = default; + IntervalSeries(const std::string& label, + const std::string& color, + IntervalSeries::Orientation orientation) + : label(label), color(color), orientation(orientation) {} + + std::string label; + std::string color; + Orientation orientation; + std::vector intervals; +}; + // A container that represents a general graph, with axes, title and one or // more data series. A subclass should define the output format by overriding // the Draw() method. @@ -109,6 +132,9 @@ class Plot { // Add a new TimeSeries to the plot. void AppendTimeSeries(TimeSeries&& time_series); + // Add a new IntervalSeries to the plot. + void AppendIntervalSeries(IntervalSeries&& interval_series); + // Add a new TimeSeries to the plot if the series contains contains data. // Otherwise, the call has no effect and the timeseries is destroyed. void AppendTimeSeriesIfNotEmpty(TimeSeries&& time_series); @@ -122,6 +148,7 @@ class Plot { std::string yaxis_label_; std::string title_; std::vector series_list_; + std::vector interval_list_; }; class PlotCollection { diff --git a/webrtc/rtc_tools/event_log_visualizer/plot_python.cc b/webrtc/rtc_tools/event_log_visualizer/plot_python.cc index d4ae043a1b..dae285526e 100644 --- a/webrtc/rtc_tools/event_log_visualizer/plot_python.cc +++ b/webrtc/rtc_tools/event_log_visualizer/plot_python.cc @@ -14,6 +14,8 @@ #include +#include "webrtc/rtc_base/checks.h" + namespace webrtc { namespace plotting { @@ -74,6 +76,8 @@ void PythonPlot::Draw() { printf("y%zu = [v for dup in y%zu for v in [dup, dup]]\n", i, i); printf( "plt.plot(x%zu[1:], y%zu[:-1], color=rgb_colors[%zu], " + "path_effects=[pe.Stroke(linewidth=2, foreground='black'), " + "pe.Normal()], " "label=\'%s\')\n", i, i, i, series_list_[i].label.c_str()); } else if (series_list_[i].style == DOT_GRAPH) { @@ -85,6 +89,47 @@ void PythonPlot::Draw() { printf("raise Exception(\"Unknown graph type\")\n"); } } + + // IntervalSeries + printf("interval_colors = ['#ff8e82','#5092fc','#c4ffc4']\n"); + RTC_CHECK_LE(interval_list_.size(), 3); + // To get the intervals to show up in the legend we have to created patches + // for them. + printf("legend_patches = []\n"); + for (size_t i = 0; i < interval_list_.size(); i++) { + // List intervals + printf("\n# === IntervalSeries: %s ===\n", + interval_list_[i].label.c_str()); + printf("ival%zu = [", i); + if (interval_list_[i].intervals.size() > 0) { + printf("(%G, %G)", interval_list_[i].intervals[0].begin, + interval_list_[i].intervals[0].end); + } + for (size_t j = 1; j < interval_list_[i].intervals.size(); j++) { + printf(", (%G, %G)", interval_list_[i].intervals[j].begin, + interval_list_[i].intervals[j].end); + } + printf("]\n"); + + printf("for i in range(0, %zu):\n", interval_list_[i].intervals.size()); + if (interval_list_[i].orientation == IntervalSeries::kVertical) { + printf( + " plt.axhspan(ival%zu[i][0], ival%zu[i][1], " + "facecolor=interval_colors[%zu], " + "alpha=0.3)\n", + i, i, i); + } else { + printf( + " plt.axvspan(ival%zu[i][0], ival%zu[i][1], " + "facecolor=interval_colors[%zu], " + "alpha=0.3)\n", + i, i, i); + } + printf( + "legend_patches.append(mpatches.Patch(ec=\'black\', " + "fc=interval_colors[%zu], label='%s'))\n", + i, interval_list_[i].label.c_str()); + } } printf("plt.xlim(%f, %f)\n", xaxis_min_, xaxis_max_); @@ -92,8 +137,12 @@ void PythonPlot::Draw() { printf("plt.xlabel(\'%s\')\n", xaxis_label_.c_str()); printf("plt.ylabel(\'%s\')\n", yaxis_label_.c_str()); printf("plt.title(\'%s\')\n", title_.c_str()); - if (!series_list_.empty()) { - printf("plt.legend(loc=\'best\', fontsize=\'small\')\n"); + if (!series_list_.empty() || !interval_list_.empty()) { + printf("handles, labels = plt.gca().get_legend_handles_labels()\n"); + printf("for lp in legend_patches:\n"); + printf(" handles.append(lp)\n"); + printf(" labels.append(lp.get_label())\n"); + printf("plt.legend(handles, labels, loc=\'best\', fontsize=\'small\')\n"); } } @@ -103,6 +152,8 @@ PythonPlotCollection::~PythonPlotCollection() {} void PythonPlotCollection::Draw() { printf("import matplotlib.pyplot as plt\n"); + printf("import matplotlib.patches as mpatches\n"); + printf("import matplotlib.patheffects as pe\n"); printf("import colorsys\n"); for (size_t i = 0; i < plots_.size(); i++) { printf("plt.figure(%zu)\n", i);