Add outgoing TWCC loss and missing packet feedback plots to event log analyzer.

Bug: webrtc:12707
Change-Id: I737177e6b6737c8c2e7d8803a68e29e9998ba9f8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/321140
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Commit-Queue: Diep Bui <diepbp@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40925}
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.cc b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
index d78fea4..01adfc5 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.cc
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
@@ -12,6 +12,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <deque>
 #include <limits>
 #include <map>
 #include <memory>
@@ -406,6 +407,12 @@
   return rtp_packet;
 }
 
+struct PacketLossSummary {
+  size_t num_packets = 0;
+  size_t num_lost_packets = 0;
+  Timestamp base_time = Timestamp::MinusInfinity();
+};
+
 }  // namespace
 
 EventLogAnalyzer::EventLogAnalyzer(const ParsedRtcEventLog& log,
@@ -1298,6 +1305,100 @@
   plot->SetTitle("Simulated BWE behavior");
 }
 
+void EventLogAnalyzer::CreateOutgoingTWCCLossRateGraph(Plot* plot) {
+  TimeSeries loss_rate_series("Loss rate (from packet feedback)",
+                              LineStyle::kLine, PointStyle::kHighlight);
+  TimeSeries average_loss_rate_series("Average loss rate last 5s",
+                                      LineStyle::kLine, PointStyle::kHighlight);
+  TimeSeries missing_feedback_series("Missing feedback", LineStyle::kNone,
+                                     PointStyle::kHighlight);
+  PacketLossSummary window_summary;
+  Timestamp last_observation_receive_time = Timestamp::Zero();
+
+  // Use loss based bwe 2 observation duration and observation window size.
+  constexpr TimeDelta kObservationDuration = TimeDelta::Millis(250);
+  constexpr uint32_t kObservationWindowSize = 20;
+  std::deque<PacketLossSummary> observations;
+  SeqNumUnwrapper<uint16_t> unwrapper;
+  int64_t last_acked = 1;
+  if (!parsed_log_.transport_feedbacks(kIncomingPacket).empty()) {
+    last_acked =
+        unwrapper.Unwrap(parsed_log_.transport_feedbacks(kIncomingPacket)[0]
+                             .transport_feedback.GetBaseSequence());
+  }
+  for (auto& feedback : parsed_log_.transport_feedbacks(kIncomingPacket)) {
+    const rtcp::TransportFeedback& transport_feedback =
+        feedback.transport_feedback;
+    size_t base_seq_num =
+        unwrapper.Unwrap(transport_feedback.GetBaseSequence());
+    // Collect packets that do not have feedback, which are from the last acked
+    // packet, to the current base packet.
+    for (size_t seq_num = last_acked; seq_num < base_seq_num; ++seq_num) {
+      missing_feedback_series.points.emplace_back(
+          config_.GetCallTimeSec(feedback.timestamp),
+          100 + seq_num - last_acked);
+    }
+    last_acked = base_seq_num + transport_feedback.GetPacketStatusCount();
+
+    // Compute loss rate from the transport feedback.
+    auto loss_rate =
+        static_cast<float>((transport_feedback.GetPacketStatusCount() -
+                            transport_feedback.GetReceivedPackets().size()) *
+                           100.0 / transport_feedback.GetPacketStatusCount());
+    loss_rate_series.points.emplace_back(
+        config_.GetCallTimeSec(feedback.timestamp), loss_rate);
+
+    // Compute loss rate in a window of kObservationWindowSize.
+    if (window_summary.num_packets == 0) {
+      window_summary.base_time = feedback.log_time();
+    }
+    window_summary.num_packets += transport_feedback.GetPacketStatusCount();
+    window_summary.num_lost_packets +=
+        transport_feedback.GetPacketStatusCount() -
+        transport_feedback.GetReceivedPackets().size();
+
+    const Timestamp last_received_time = feedback.log_time();
+    const TimeDelta observation_duration =
+        window_summary.base_time == Timestamp::Zero()
+            ? TimeDelta::Zero()
+            : last_received_time - window_summary.base_time;
+    if (observation_duration > kObservationDuration) {
+      last_observation_receive_time = last_received_time;
+      observations.push_back(window_summary);
+      if (observations.size() > kObservationWindowSize) {
+        observations.pop_front();
+      }
+
+      // Compute average loss rate in a number of windows.
+      int total_packets = 0;
+      int total_loss = 0;
+      for (const auto& observation : observations) {
+        total_loss += observation.num_lost_packets;
+        total_packets += observation.num_packets;
+      }
+      if (total_packets > 0) {
+        float average_loss_rate = total_loss * 100.0 / total_packets;
+        average_loss_rate_series.points.emplace_back(
+            config_.GetCallTimeSec(feedback.timestamp), average_loss_rate);
+      } else {
+        average_loss_rate_series.points.emplace_back(
+            config_.GetCallTimeSec(feedback.timestamp), 0);
+      }
+      window_summary = PacketLossSummary();
+    }
+  }
+  // Add the data set to the plot.
+  plot->AppendTimeSeriesIfNotEmpty(std::move(loss_rate_series));
+  plot->AppendTimeSeriesIfNotEmpty(std::move(average_loss_rate_series));
+  plot->AppendTimeSeriesIfNotEmpty(std::move(missing_feedback_series));
+
+  plot->SetXAxis(config_.CallBeginTimeSec(), config_.CallEndTimeSec(),
+                 "Time (s)", kLeftMargin, kRightMargin);
+  plot->SetSuggestedYAxis(0, 100, "Loss rate (percent)", kBottomMargin,
+                          kTopMargin);
+  plot->SetTitle("Outgoing loss rate (from TWCC feedback)");
+}
+
 void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) {
   using RtpPacketType = LoggedRtpPacketOutgoing;
   using TransportFeedbackType = LoggedRtcpPacketTransportFeedback;
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.h b/rtc_tools/rtc_event_log_visualizer/analyzer.h
index e58a482..f63e9a6 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.h
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.h
@@ -67,6 +67,7 @@
   void CreateStreamBitrateGraph(PacketDirection direction, Plot* plot);
   void CreateBitrateAllocationGraph(PacketDirection direction, Plot* plot);
 
+  void CreateOutgoingTWCCLossRateGraph(Plot* plot);
   void CreateGoogCcSimulationGraph(Plot* plot);
   void CreateSendSideBweSimulationGraph(Plot* plot);
   void CreateReceiveSideBweSimulationGraph(Plot* plot);
diff --git a/rtc_tools/rtc_event_log_visualizer/main.cc b/rtc_tools/rtc_event_log_visualizer/main.cc
index aa52527..3e4f3e4 100644
--- a/rtc_tools/rtc_event_log_visualizer/main.cc
+++ b/rtc_tools/rtc_event_log_visualizer/main.cc
@@ -227,7 +227,7 @@
       {"sendside_bwe",
        {"outgoing_packet_sizes", "outgoing_bitrate", "outgoing_stream_bitrate",
         "simulated_sendside_bwe", "network_delay_feedback",
-        "fraction_loss_feedback"}},
+        "fraction_loss_feedback", "outgoing_twcc_loss"}},
       {"receiveside_bwe",
        {"incoming_packet_sizes", "incoming_delay", "incoming_loss_rate",
         "incoming_bitrate", "incoming_stream_bitrate",
@@ -377,6 +377,9 @@
   plots.RegisterPlot("simulated_goog_cc", [&](Plot* plot) {
     analyzer.CreateGoogCcSimulationGraph(plot);
   });
+  plots.RegisterPlot("outgoing_twcc_loss", [&](Plot* plot) {
+    analyzer.CreateOutgoingTWCCLossRateGraph(plot);
+  });
   plots.RegisterPlot("network_delay_feedback", [&](Plot* plot) {
     analyzer.CreateNetworkDelayFeedbackGraph(plot);
   });