Add support for RFC8888 feedback to loss event log plot
Bug: webrtc:436707095
Change-Id: I622d5ffa3d1027c601f81610c1c41c50465bc324
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/403560
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45310}
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.cc b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
index 2395046..916ace1 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.cc
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
@@ -616,8 +616,11 @@
plots_.RegisterPlot("simulated_goog_cc", [this](Plot* plot) {
this->CreateGoogCcSimulationGraph(plot);
});
+ plots_.RegisterPlot("outgoing_loss", [this](Plot* plot) {
+ this->CreateOutgoingLossRateGraph(plot);
+ });
plots_.RegisterPlot("outgoing_twcc_loss", [this](Plot* plot) {
- this->CreateOutgoingTWCCLossRateGraph(plot);
+ this->CreateOutgoingLossRateGraph(plot);
});
plots_.RegisterPlot("outgoing_ecn_feedback", [this](Plot* plot) {
this->CreateOutgoingEcnFeedbackGraph(plot);
@@ -1627,7 +1630,68 @@
kTopMargin);
}
-void EventLogAnalyzer::CreateOutgoingTWCCLossRateGraph(Plot* plot) const {
+void EventLogAnalyzer::CreateOutgoingLossRateGraph(Plot* plot) const {
+ struct PacketLossPerFeedback {
+ Timestamp timestamp; // Time when this feedback was received.
+ int num_packets_in_feedback = 0; // Includes lost packets.
+ int num_lost_packets = 0; // In this specific feedback.
+ int num_reordered_packets = 0; // Packets received in this feedback, but
+ // was previously reported as lost.
+ int num_missing_feedback =
+ 0; // Packets missing feedback between this report and the previous.
+ };
+
+ class LossFeedbackBuilder {
+ public:
+ void AddPacket(uint16_t sequence_number, TimeDelta arrival_time_delta) {
+ last_unwrapped_sequence_number_ =
+ sequence_number_unwrapper_.Unwrap(sequence_number);
+ if (!first_sequence_number_.has_value()) {
+ first_sequence_number_ = last_unwrapped_sequence_number_;
+ }
+ ++num_packets_;
+ if (arrival_time_delta.IsInfinite()) {
+ lost_sequence_numbers_.insert(last_unwrapped_sequence_number_);
+ } else {
+ num_reordered_packets_ += previous_lost_sequence_numbers_.count(
+ last_unwrapped_sequence_number_);
+ }
+ }
+
+ void Update(PacketLossPerFeedback& feedback) {
+ feedback.num_packets_in_feedback += num_packets_;
+ feedback.num_lost_packets += lost_sequence_numbers_.size();
+ feedback.num_reordered_packets += num_reordered_packets_;
+ if (first_sequence_number_.has_value() &&
+ previous_feedback_highest_seq_number_.has_value()) {
+ feedback.num_missing_feedback +=
+ *first_sequence_number_ - *previous_feedback_highest_seq_number_ -
+ 1;
+ }
+
+ // Prepare for next feedback.
+ first_sequence_number_ = std::nullopt;
+ previous_lost_sequence_numbers_.insert(lost_sequence_numbers_.begin(),
+ lost_sequence_numbers_.end());
+ previous_feedback_highest_seq_number_ = last_unwrapped_sequence_number_;
+ lost_sequence_numbers_.clear();
+ num_reordered_packets_ = 0;
+ num_packets_ = 0;
+ }
+
+ private:
+ int64_t last_unwrapped_sequence_number_ = 0;
+ int num_reordered_packets_ = 0;
+ int num_packets_ = 0;
+ std::optional<int64_t> first_sequence_number_;
+
+ std::unordered_set<int64_t> lost_sequence_numbers_;
+ std::unordered_set<int64_t> previous_lost_sequence_numbers_;
+ std::optional<int64_t> previous_feedback_highest_seq_number_;
+
+ RtpSequenceNumberUnwrapper sequence_number_unwrapper_;
+ };
+
TimeSeries loss_rate_series("Loss rate (from packet feedback)",
LineStyle::kLine, PointStyle::kHighlight);
TimeSeries reordered_packets_between_feedback(
@@ -1637,6 +1701,47 @@
LineStyle::kLine, PointStyle::kHighlight);
TimeSeries missing_feedback_series("Missing feedback", LineStyle::kNone,
PointStyle::kHighlight);
+
+ std::vector<PacketLossPerFeedback> loss_per_feedback;
+
+ if (!parsed_log_.congestion_feedback(kIncomingPacket).empty()) {
+ plot->SetTitle("Outgoing loss rate (from CCFB)");
+
+ std::map</*ssrc*/ uint32_t, LossFeedbackBuilder> per_ssrc_builder;
+ for (const LoggedRtcpCongestionControlFeedback& feedback :
+ parsed_log_.congestion_feedback(kIncomingPacket)) {
+ const rtcp::CongestionControlFeedback& transport_feedback =
+ feedback.congestion_feedback;
+
+ PacketLossPerFeedback packet_loss_per_feedback = {
+ .timestamp = feedback.log_time()};
+ for (const rtcp::CongestionControlFeedback::PacketInfo& packet :
+ transport_feedback.packets()) {
+ per_ssrc_builder[packet.ssrc].AddPacket(packet.sequence_number,
+ packet.arrival_time_offset);
+ }
+ for (auto& [ssrc, builder] : per_ssrc_builder) {
+ builder.Update(packet_loss_per_feedback);
+ }
+ loss_per_feedback.push_back(packet_loss_per_feedback);
+ }
+ } else if (!parsed_log_.transport_feedbacks(kIncomingPacket).empty()) {
+ plot->SetTitle("Outgoing loss rate (from TWCC)");
+
+ LossFeedbackBuilder builder;
+ for (const LoggedRtcpPacketTransportFeedback& feedback :
+ parsed_log_.transport_feedbacks(kIncomingPacket)) {
+ feedback.transport_feedback.ForAllPackets(
+ [&](uint16_t sequence_number, TimeDelta receive_time_delta) {
+ builder.AddPacket(sequence_number, receive_time_delta);
+ });
+ PacketLossPerFeedback packet_loss_per_feedback = {
+ .timestamp = feedback.log_time()};
+ builder.Update(packet_loss_per_feedback);
+ loss_per_feedback.push_back(packet_loss_per_feedback);
+ }
+ }
+
PacketLossSummary window_summary;
Timestamp last_observation_receive_time = Timestamp::Zero();
@@ -1644,66 +1749,37 @@
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());
- }
- std::unordered_set<int64_t> previous_feedback_lost_sequence_numbers;
- size_t previous_feedback_size = 0;
- for (auto& feedback : parsed_log_.transport_feedbacks(kIncomingPacket)) {
- const rtcp::TransportFeedback& transport_feedback =
- feedback.transport_feedback;
- int64_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 (int64_t seq_num = last_acked; seq_num < base_seq_num; ++seq_num) {
+ int previous_feedback_size = 0;
+ for (const PacketLossPerFeedback& feedback : loss_per_feedback) {
+ for (int64_t num = 0; num < feedback.num_missing_feedback; ++num) {
missing_feedback_series.points.emplace_back(
- config_.GetCallTimeSec(feedback.timestamp),
- 100 + seq_num - last_acked);
+ config_.GetCallTimeSec(feedback.timestamp), 100 + num);
}
- last_acked = base_seq_num + transport_feedback.GetPacketStatusCount();
-
- int num_reordered_packets = 0;
- std::unordered_set<int64_t> lost_sequence_numbers;
- transport_feedback.ForAllPackets(
- [&](uint16_t sequence_number, TimeDelta receive_time_delta) {
- int64_t unwrapped_seq = unwrapper.Unwrap(sequence_number);
- if (receive_time_delta.IsInfinite()) {
- lost_sequence_numbers.insert(unwrapped_seq);
- } else {
- num_reordered_packets +=
- previous_feedback_lost_sequence_numbers.count(unwrapped_seq);
- }
- });
// Compute loss rate from the transport feedback.
- float loss_rate =
- static_cast<float>(lost_sequence_numbers.size() * 100.0 /
- transport_feedback.GetPacketStatusCount());
+ float loss_rate = static_cast<float>(feedback.num_lost_packets * 100.0 /
+ feedback.num_packets_in_feedback);
loss_rate_series.points.emplace_back(
config_.GetCallTimeSec(feedback.timestamp), loss_rate);
float reordered_rate =
previous_feedback_size == 0
? 0
- : static_cast<float>(num_reordered_packets * 100.0 /
+ : static_cast<float>(feedback.num_reordered_packets * 100.0 /
previous_feedback_size);
+ previous_feedback_size = feedback.num_packets_in_feedback;
reordered_packets_between_feedback.points.emplace_back(
config_.GetCallTimeSec(feedback.timestamp), reordered_rate);
// Compute loss rate in a window of kObservationWindowSize.
if (window_summary.num_packets == 0) {
- window_summary.base_time = feedback.log_time();
+ window_summary.base_time = feedback.timestamp;
}
- window_summary.num_packets += transport_feedback.GetPacketStatusCount();
+ window_summary.num_packets += feedback.num_packets_in_feedback;
window_summary.num_lost_packets +=
- lost_sequence_numbers.size() - num_reordered_packets;
+ feedback.num_lost_packets - feedback.num_reordered_packets;
- const Timestamp last_received_time = feedback.log_time();
+ const Timestamp last_received_time = feedback.timestamp;
const TimeDelta observation_duration =
window_summary.base_time == Timestamp::Zero()
? TimeDelta::Zero()
@@ -1732,8 +1808,6 @@
}
window_summary = PacketLossSummary();
}
- std::swap(previous_feedback_lost_sequence_numbers, lost_sequence_numbers);
- previous_feedback_size = transport_feedback.GetPacketStatusCount();
}
// Add the data set to the plot.
plot->AppendTimeSeriesIfNotEmpty(std::move(loss_rate_series));
@@ -1746,7 +1820,6 @@
"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) const {
@@ -2231,8 +2304,8 @@
}
// TODO(qingsi): There can be a large number of candidate pairs generated by
- // certain calls and the frontend cannot render the chart in this case due to
- // the failure of generating a palette with the same number of colors.
+ // certain calls and the frontend cannot render the chart in this case due
+ // to the failure of generating a palette with the same number of colors.
for (auto& kv : configs_by_cp_id) {
plot->AppendTimeSeries(std::move(kv.second));
}
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.h b/rtc_tools/rtc_event_log_visualizer/analyzer.h
index 134a45f..1055821 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.h
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.h
@@ -116,7 +116,7 @@
void CreateBitrateAllocationGraph(PacketDirection direction,
Plot* plot) const;
- void CreateOutgoingTWCCLossRateGraph(Plot* plot) const;
+ void CreateOutgoingLossRateGraph(Plot* plot) const;
void CreateOutgoingEcnFeedbackGraph(Plot* plot) const;
void CreateIncomingEcnFeedbackGraph(Plot* plot) const;
void CreateGoogCcSimulationGraph(Plot* plot) const;