diff --git a/modules/congestion_controller/bbr/BUILD.gn b/modules/congestion_controller/bbr/BUILD.gn index fa0bdbf23a..7c4ad216d3 100644 --- a/modules/congestion_controller/bbr/BUILD.gn +++ b/modules/congestion_controller/bbr/BUILD.gn @@ -38,6 +38,25 @@ rtc_source_set("bbr_controller") { "../../../rtc_base/system:fallthrough", ] } + +rtc_source_set("bandwidth_sampler") { + visibility = [ ":*" ] + sources = [ + "bandwidth_sampler.cc", + "bandwidth_sampler.h", + ] + deps = [ + ":packet_number_indexed_queue", + "../../../api:optional", + "../../../api/units:data_rate", + "../../../api/units:data_size", + "../../../api/units:time_delta", + "../../../api/units:timestamp", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + ] +} + rtc_source_set("data_transfer_tracker") { visibility = [ ":*" ] sources = [ @@ -85,6 +104,7 @@ if (rtc_include_tests) { rtc_source_set("bbr_unittests") { testonly = true sources = [ + "bandwidth_sampler_unittest.cc", "bbr_network_controller_unittest.cc", "data_transfer_tracker_unittest.cc", "packet_number_indexed_queue_unittest.cc", @@ -92,6 +112,7 @@ if (rtc_include_tests) { "windowed_filter_unittest.cc", ] deps = [ + ":bandwidth_sampler", ":bbr", ":bbr_controller", ":data_transfer_tracker", diff --git a/modules/congestion_controller/bbr/bandwidth_sampler.cc b/modules/congestion_controller/bbr/bandwidth_sampler.cc new file mode 100644 index 0000000000..b9e2c27bdd --- /dev/null +++ b/modules/congestion_controller/bbr/bandwidth_sampler.cc @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// Based on the Quic implementation in Chromium. + +#include + +#include "modules/congestion_controller/bbr/bandwidth_sampler.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace bbr { +namespace { +constexpr int64_t kMaxTrackedPackets = 10000; +} + +BandwidthSampler::BandwidthSampler() + : total_data_sent_(DataSize::Zero()), + total_data_acked_(DataSize::Zero()), + total_data_sent_at_last_acked_packet_(DataSize::Zero()), + last_acked_packet_sent_time_(), + last_acked_packet_ack_time_(), + last_sent_packet_(0), + is_app_limited_(false), + end_of_app_limited_phase_(0), + connection_state_map_() {} + +BandwidthSampler::~BandwidthSampler() {} + +void BandwidthSampler::OnPacketSent(Timestamp sent_time, + int64_t packet_number, + DataSize data_size, + DataSize data_in_flight) { + last_sent_packet_ = packet_number; + + total_data_sent_ += data_size; + + // If there are no packets in flight, the time at which the new transmission + // opens can be treated as the A_0 point for the purpose of bandwidth + // sampling. This underestimates bandwidth to some extent, and produces some + // artificially low samples for most packets in flight, but it provides with + // samples at important points where we would not have them otherwise, most + // importantly at the beginning of the connection. + if (data_in_flight.IsZero()) { + last_acked_packet_ack_time_ = sent_time; + total_data_sent_at_last_acked_packet_ = total_data_sent_; + + // In this situation ack compression is not a concern, set send rate to + // effectively infinite. + last_acked_packet_sent_time_ = sent_time; + } + + if (!connection_state_map_.IsEmpty() && + packet_number > + connection_state_map_.last_packet() + kMaxTrackedPackets) { + RTC_LOG(LS_WARNING) + << "BandwidthSampler in-flight packet map has exceeded maximum " + "number " + "of tracked packets."; + } + + bool success = + connection_state_map_.Emplace(packet_number, sent_time, data_size, *this); + if (!success) + RTC_LOG(LS_WARNING) << "BandwidthSampler failed to insert the packet " + "into the map, most likely because it's already " + "in it."; +} + +BandwidthSample BandwidthSampler::OnPacketAcknowledged(Timestamp ack_time, + int64_t packet_number) { + ConnectionStateOnSentPacket* sent_packet_pointer = + connection_state_map_.GetEntry(packet_number); + if (sent_packet_pointer == nullptr) { + return BandwidthSample(); + } + BandwidthSample sample = + OnPacketAcknowledgedInner(ack_time, packet_number, *sent_packet_pointer); + connection_state_map_.Remove(packet_number); + return sample; +} + +BandwidthSample BandwidthSampler::OnPacketAcknowledgedInner( + Timestamp ack_time, + int64_t packet_number, + const ConnectionStateOnSentPacket& sent_packet) { + total_data_acked_ += sent_packet.size; + total_data_sent_at_last_acked_packet_ = sent_packet.total_data_sent; + last_acked_packet_sent_time_ = sent_packet.sent_time; + last_acked_packet_ack_time_ = ack_time; + + // Exit app-limited phase once a packet that was sent while the connection is + // not app-limited is acknowledged. + if (is_app_limited_ && packet_number > end_of_app_limited_phase_) { + is_app_limited_ = false; + } + + // There might have been no packets acknowledged at the moment when the + // current packet was sent. In that case, there is no bandwidth sample to + // make. + if (!sent_packet.last_acked_packet_sent_time || + !sent_packet.last_acked_packet_ack_time) { + return BandwidthSample(); + } + + // Infinite rate indicates that the sampler is supposed to discard the + // current send rate sample and use only the ack rate. + DataRate send_rate = DataRate::Infinity(); + if (sent_packet.sent_time > *sent_packet.last_acked_packet_sent_time) { + DataSize sent_delta = sent_packet.total_data_sent - + sent_packet.total_data_sent_at_last_acked_packet; + TimeDelta time_delta = + sent_packet.sent_time - *sent_packet.last_acked_packet_sent_time; + send_rate = sent_delta / time_delta; + } + + // During the slope calculation, ensure that ack time of the current packet is + // always larger than the time of the previous packet, otherwise division by + // zero or integer underflow can occur. + if (ack_time <= *sent_packet.last_acked_packet_ack_time) { + RTC_LOG(LS_WARNING) + << "Time of the previously acked packet is larger than the time " + "of the current packet."; + return BandwidthSample(); + } + DataSize ack_delta = + total_data_acked_ - sent_packet.total_data_acked_at_the_last_acked_packet; + TimeDelta time_delta = ack_time - *sent_packet.last_acked_packet_ack_time; + DataRate ack_rate = ack_delta / time_delta; + + BandwidthSample sample; + sample.bandwidth = std::min(send_rate, ack_rate); + // Note: this sample does not account for delayed acknowledgement time. This + // means that the RTT measurements here can be artificially high, especially + // on low bandwidth connections. + sample.rtt = ack_time - sent_packet.sent_time; + // A sample is app-limited if the packet was sent during the app-limited + // phase. + sample.is_app_limited = sent_packet.is_app_limited; + return sample; +} + +void BandwidthSampler::OnPacketLost(int64_t packet_number) { + connection_state_map_.Remove(packet_number); +} + +void BandwidthSampler::OnAppLimited() { + is_app_limited_ = true; + end_of_app_limited_phase_ = last_sent_packet_; +} + +void BandwidthSampler::RemoveObsoletePackets(int64_t least_unacked) { + while (!connection_state_map_.IsEmpty() && + connection_state_map_.first_packet() < least_unacked) { + connection_state_map_.Remove(connection_state_map_.first_packet()); + } +} + +DataSize BandwidthSampler::total_data_acked() const { + return total_data_acked_; +} + +bool BandwidthSampler::is_app_limited() const { + return is_app_limited_; +} + +int64_t BandwidthSampler::end_of_app_limited_phase() const { + return end_of_app_limited_phase_; +} + +BandwidthSampler::ConnectionStateOnSentPacket::ConnectionStateOnSentPacket( + Timestamp sent_time, + DataSize size, + const BandwidthSampler& sampler) + : sent_time(sent_time), + size(size), + total_data_sent(sampler.total_data_sent_), + total_data_sent_at_last_acked_packet( + sampler.total_data_sent_at_last_acked_packet_), + last_acked_packet_sent_time(sampler.last_acked_packet_sent_time_), + last_acked_packet_ack_time(sampler.last_acked_packet_ack_time_), + total_data_acked_at_the_last_acked_packet(sampler.total_data_acked_), + is_app_limited(sampler.is_app_limited_) {} + +BandwidthSampler::ConnectionStateOnSentPacket::ConnectionStateOnSentPacket() + : sent_time(Timestamp::ms(0)), + size(DataSize::Zero()), + total_data_sent(DataSize::Zero()), + total_data_sent_at_last_acked_packet(DataSize::Zero()), + last_acked_packet_sent_time(), + last_acked_packet_ack_time(), + total_data_acked_at_the_last_acked_packet(DataSize::Zero()), + is_app_limited(false) {} + +BandwidthSampler::ConnectionStateOnSentPacket::~ConnectionStateOnSentPacket() {} + +} // namespace bbr +} // namespace webrtc diff --git a/modules/congestion_controller/bbr/bandwidth_sampler.h b/modules/congestion_controller/bbr/bandwidth_sampler.h new file mode 100644 index 0000000000..9f448a6956 --- /dev/null +++ b/modules/congestion_controller/bbr/bandwidth_sampler.h @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// Based on the Quic implementation in Chromium. + +#ifndef MODULES_CONGESTION_CONTROLLER_BBR_BANDWIDTH_SAMPLER_H_ +#define MODULES_CONGESTION_CONTROLLER_BBR_BANDWIDTH_SAMPLER_H_ + +#include "api/optional.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/congestion_controller/bbr/packet_number_indexed_queue.h" + +namespace webrtc { +namespace bbr { + +namespace test { +class BandwidthSamplerPeer; +} // namespace test + +struct BandwidthSample { + // The bandwidth at that particular sample. Zero if no valid bandwidth sample + // is available. + DataRate bandwidth; + + // The RTT measurement at this particular sample. Zero if no RTT sample is + // available. Does not correct for delayed ack time. + TimeDelta rtt; + + // Indicates whether the sample might be artificially low because the sender + // did not have enough data to send in order to saturate the link. + bool is_app_limited; + + BandwidthSample() + : bandwidth(DataRate::Zero()), + rtt(TimeDelta::Zero()), + is_app_limited(false) {} +}; + +// BandwidthSampler keeps track of sent and acknowledged packets and outputs a +// bandwidth sample for every packet acknowledged. The samples are taken for +// individual packets, and are not filtered; the consumer has to filter the +// bandwidth samples itself. In certain cases, the sampler will locally severely +// underestimate the bandwidth, hence a maximum filter with a size of at least +// one RTT is recommended. +// +// This class bases its samples on the slope of two curves: the number of +// data_size sent over time, and the number of data_size acknowledged as +// received over time. It produces a sample of both slopes for every packet that +// gets acknowledged, based on a slope between two points on each of the +// corresponding curves. Note that due to the packet loss, the number of +// data_size on each curve might get further and further away from each other, +// meaning that it is not feasible to compare byte values coming from different +// curves with each other. +// +// The obvious points for measuring slope sample are the ones corresponding to +// the packet that was just acknowledged. Let us denote them as S_1 (point at +// which the current packet was sent) and A_1 (point at which the current packet +// was acknowledged). However, taking a slope requires two points on each line, +// so estimating bandwidth requires picking a packet in the past with respect to +// which the slope is measured. +// +// For that purpose, BandwidthSampler always keeps track of the most recently +// acknowledged packet, and records it together with every outgoing packet. +// When a packet gets acknowledged (A_1), it has not only information about when +// it itself was sent (S_1), but also the information about the latest +// acknowledged packet right before it was sent (S_0 and A_0). +// +// Based on that data, send and ack rate are estimated as: +// send_rate = (data_size(S_1) - data_size(S_0)) / (time(S_1) - time(S_0)) +// ack_rate = (data_size(A_1) - data_size(A_0)) / (time(A_1) - time(A_0)) +// +// Here, the ack rate is intuitively the rate we want to treat as bandwidth. +// However, in certain cases (e.g. ack compression) the ack rate at a point may +// end up higher than the rate at which the data was originally sent, which is +// not indicative of the real bandwidth. Hence, we use the send rate as an upper +// bound, and the sample value is +// rate_sample = min(send_rate, ack_rate) +// +// An important edge case handled by the sampler is tracking the app-limited +// samples. There are multiple meaning of "app-limited" used interchangeably, +// hence it is important to understand and to be able to distinguish between +// them. +// +// Meaning 1: connection state. The connection is said to be app-limited when +// there is no outstanding data to send. This means that certain bandwidth +// samples in the future would not be an accurate indication of the link +// capacity, and it is important to inform consumer about that. Whenever +// connection becomes app-limited, the sampler is notified via OnAppLimited() +// method. +// +// Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth +// sampler becomes notified about the connection being app-limited, it enters +// app-limited phase. In that phase, all *sent* packets are marked as +// app-limited. Note that the connection itself does not have to be +// app-limited during the app-limited phase, and in fact it will not be +// (otherwise how would it send packets?). The boolean flag below indicates +// whether the sampler is in that phase. +// +// Meaning 3: a flag on the sent packet and on the sample. If a sent packet is +// sent during the app-limited phase, the resulting sample related to the +// packet will be marked as app-limited. +// +// With the terminology issue out of the way, let us consider the question of +// what kind of situation it addresses. +// +// Consider a scenario where we first send packets 1 to 20 at a regular +// bandwidth, and then immediately run out of data. After a few seconds, we send +// packets 21 to 60, and only receive ack for 21 between sending packets 40 and +// 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0 +// we use to compute the slope is going to be packet 20, a few seconds apart +// from the current packet, hence the resulting estimate would be extremely low +// and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21, +// meaning that the bandwidth sample would exclude the quiescence. +// +// Based on the analysis of that scenario, we implement the following rule: once +// OnAppLimited() is called, all sent packets will produce app-limited samples +// up until an ack for a packet that was sent after OnAppLimited() was called. +// Note that while the scenario above is not the only scenario when the +// connection is app-limited, the approach works in other cases too. +class BandwidthSampler { + public: + BandwidthSampler(); + ~BandwidthSampler(); + // Inputs the sent packet information into the sampler. Assumes that all + // packets are sent in order. The information about the packet will not be + // released from the sampler until the packet is either acknowledged or + // declared lost. + void OnPacketSent(Timestamp sent_time, + int64_t packet_number, + DataSize data_size, + DataSize data_in_flight); + + // Notifies the sampler that the |packet_number| is acknowledged. Returns a + // bandwidth sample. If no bandwidth sample is available, bandwidth is set to + // DataRate::Zero(). + BandwidthSample OnPacketAcknowledged(Timestamp ack_time, + int64_t packet_number); + + // Informs the sampler that a packet is considered lost and it should no + // longer keep track of it. + void OnPacketLost(int64_t packet_number); + + // Informs the sampler that the connection is currently app-limited, causing + // the sampler to enter the app-limited phase. The phase will expire by + // itself. + void OnAppLimited(); + + // Remove all the packets lower than the specified packet number. + void RemoveObsoletePackets(int64_t least_unacked); + + // Total number of data_size currently acknowledged by the receiver. + DataSize total_data_acked() const; + + // Application-limited information exported for debugging. + bool is_app_limited() const; + int64_t end_of_app_limited_phase() const; + + private: + friend class test::BandwidthSamplerPeer; + // ConnectionStateOnSentPacket represents the information about a sent packet + // and the state of the connection at the moment the packet was sent, + // specifically the information about the most recently acknowledged packet at + // that moment. + struct ConnectionStateOnSentPacket { + // Time at which the packet is sent. + Timestamp sent_time; + + // Size of the packet. + DataSize size; + + // The value of |total_data_sent_| at the time the packet was sent. + // Includes the packet itself. + DataSize total_data_sent; + + // The value of |total_data_sent_at_last_acked_packet_| at the time the + // packet was sent. + DataSize total_data_sent_at_last_acked_packet; + + // The value of |last_acked_packet_sent_time_| at the time the packet was + // sent. + rtc::Optional last_acked_packet_sent_time; + + // The value of |last_acked_packet_ack_time_| at the time the packet was + // sent. + rtc::Optional last_acked_packet_ack_time; + + // The value of |total_data_acked_| at the time the packet was + // sent. + DataSize total_data_acked_at_the_last_acked_packet; + + // The value of |is_app_limited_| at the time the packet was + // sent. + bool is_app_limited; + + // Snapshot constructor. Records the current state of the bandwidth + // sampler. + ConnectionStateOnSentPacket(Timestamp sent_time, + DataSize size, + const BandwidthSampler& sampler); + + // Default constructor. Required to put this structure into + // PacketNumberIndexedQueue. + ConnectionStateOnSentPacket(); + ~ConnectionStateOnSentPacket(); + }; + + // The total number of congestion controlled data_size sent during the + // connection. + DataSize total_data_sent_; + + // The total number of congestion controlled data_size which were + // acknowledged. + DataSize total_data_acked_; + + // The value of |total_data_sent_| at the time the last acknowledged packet + // was sent. Valid only when |last_acked_packet_sent_time_| is valid. + DataSize total_data_sent_at_last_acked_packet_; + + // The time at which the last acknowledged packet was sent. Set to + // Timestamp::Zero() if no valid timestamp is available. + rtc::Optional last_acked_packet_sent_time_; + + // The time at which the most recent packet was acknowledged. + rtc::Optional last_acked_packet_ack_time_; + + // The most recently sent packet. + int64_t last_sent_packet_; + + // Indicates whether the bandwidth sampler is currently in an app-limited + // phase. + bool is_app_limited_; + + // The packet that will be acknowledged after this one will cause the sampler + // to exit the app-limited phase. + int64_t end_of_app_limited_phase_; + + // Record of the connection state at the point where each packet in flight was + // sent, indexed by the packet number. + PacketNumberIndexedQueue connection_state_map_; + + // Handles the actual bandwidth calculations, whereas the outer method handles + // retrieving and removing |sent_packet|. + BandwidthSample OnPacketAcknowledgedInner( + Timestamp ack_time, + int64_t packet_number, + const ConnectionStateOnSentPacket& sent_packet); +}; + +} // namespace bbr +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_BBR_BANDWIDTH_SAMPLER_H_ diff --git a/modules/congestion_controller/bbr/bandwidth_sampler_unittest.cc b/modules/congestion_controller/bbr/bandwidth_sampler_unittest.cc new file mode 100644 index 0000000000..45f3f486fd --- /dev/null +++ b/modules/congestion_controller/bbr/bandwidth_sampler_unittest.cc @@ -0,0 +1,337 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// Based on the Quic implementation in Chromium. + +#include + +#include "modules/congestion_controller/bbr/bandwidth_sampler.h" + +#include "test/gtest.h" + +namespace webrtc { +namespace bbr { +namespace test { + +class BandwidthSamplerPeer { + public: + static size_t GetNumberOfTrackedPackets(const BandwidthSampler& sampler) { + return sampler.connection_state_map_.number_of_present_entries(); + } + + static DataSize GetPacketSize(const BandwidthSampler& sampler, + int64_t packet_number) { + return sampler.connection_state_map_.GetEntry(packet_number)->size; + } +}; + +const int64_t kRegularPacketSizeBytes = 1280; +// Enforce divisibility for some of the tests. +static_assert((kRegularPacketSizeBytes & 31) == 0, + "kRegularPacketSizeBytes has to be five times divisible by 2"); + +const DataSize kRegularPacketSize = DataSize::bytes(kRegularPacketSizeBytes); + +// A test fixture with utility methods for BandwidthSampler tests. +class BandwidthSamplerTest : public ::testing::Test { + protected: + BandwidthSamplerTest() + : clock_(Timestamp::seconds(100)), bytes_in_flight_(DataSize::Zero()) {} + + Timestamp clock_; + BandwidthSampler sampler_; + DataSize bytes_in_flight_; + + void SendPacketInner(int64_t packet_number, DataSize bytes) { + sampler_.OnPacketSent(clock_, packet_number, bytes, bytes_in_flight_); + bytes_in_flight_ += bytes; + } + + void SendPacket(int64_t packet_number) { + SendPacketInner(packet_number, kRegularPacketSize); + } + + BandwidthSample AckPacketInner(int64_t packet_number) { + DataSize size = + BandwidthSamplerPeer::GetPacketSize(sampler_, packet_number); + bytes_in_flight_ -= size; + return sampler_.OnPacketAcknowledged(clock_, packet_number); + } + + // Acknowledge receipt of a packet and expect it to be not app-limited. + DataRate AckPacket(int64_t packet_number) { + BandwidthSample sample = AckPacketInner(packet_number); + EXPECT_FALSE(sample.is_app_limited); + return sample.bandwidth; + } + + void LosePacket(int64_t packet_number) { + DataSize size = + BandwidthSamplerPeer::GetPacketSize(sampler_, packet_number); + bytes_in_flight_ -= size; + sampler_.OnPacketLost(packet_number); + } + + // Sends one packet and acks it. Then, send 20 packets. Finally, send + // another 20 packets while acknowledging previous 20. + void Send40PacketsAndAckFirst20(TimeDelta time_between_packets) { + // Send 20 packets at a constant inter-packet time. + for (int64_t i = 1; i <= 20; i++) { + SendPacket(i); + clock_ += time_between_packets; + } + + // Ack packets 1 to 20, while sending new packets at the same rate as + // before. + for (int64_t i = 1; i <= 20; i++) { + AckPacket(i); + SendPacket(i + 20); + clock_ += time_between_packets; + } + } +}; + +// Test the sampler in a simple stop-and-wait sender setting. +TEST_F(BandwidthSamplerTest, SendAndWait) { + TimeDelta time_between_packets = TimeDelta::ms(10); + DataRate expected_bandwidth = + kRegularPacketSize * 100 / TimeDelta::seconds(1); + + // Send packets at the constant bandwidth. + for (int64_t i = 1; i < 20; i++) { + SendPacket(i); + clock_ += time_between_packets; + DataRate current_sample = AckPacket(i); + EXPECT_EQ(expected_bandwidth, current_sample); + } + + // Send packets at the exponentially decreasing bandwidth. + for (int64_t i = 20; i < 25; i++) { + time_between_packets = time_between_packets * 2; + expected_bandwidth = expected_bandwidth * 0.5; + + SendPacket(i); + clock_ += time_between_packets; + DataRate current_sample = AckPacket(i); + EXPECT_EQ(expected_bandwidth, current_sample); + } + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Test the sampler during regular windowed sender scenario with fixed +// CWND of 20. +TEST_F(BandwidthSamplerTest, SendPaced) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + DataRate expected_bandwidth = kRegularPacketSize / time_between_packets; + + Send40PacketsAndAckFirst20(time_between_packets); + + // Ack the packets 21 to 40, arriving at the correct bandwidth. + DataRate last_bandwidth = DataRate::Zero(); + for (int64_t i = 21; i <= 40; i++) { + last_bandwidth = AckPacket(i); + EXPECT_EQ(expected_bandwidth, last_bandwidth); + clock_ += time_between_packets; + } + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Test the sampler in a scenario where 50% of packets is consistently lost. +TEST_F(BandwidthSamplerTest, SendWithLosses) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + DataRate expected_bandwidth = kRegularPacketSize / time_between_packets * 0.5; + + // Send 20 packets, each 1 ms apart. + for (int64_t i = 1; i <= 20; i++) { + SendPacket(i); + clock_ += time_between_packets; + } + + // Ack packets 1 to 20, losing every even-numbered packet, while sending new + // packets at the same rate as before. + for (int64_t i = 1; i <= 20; i++) { + if (i % 2 == 0) { + AckPacket(i); + } else { + LosePacket(i); + } + SendPacket(i + 20); + clock_ += time_between_packets; + } + + // Ack the packets 21 to 40 with the same loss pattern. + DataRate last_bandwidth = DataRate::Zero(); + for (int64_t i = 21; i <= 40; i++) { + if (i % 2 == 0) { + last_bandwidth = AckPacket(i); + EXPECT_EQ(expected_bandwidth, last_bandwidth); + } else { + LosePacket(i); + } + clock_ += time_between_packets; + } + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Simulate a situation where ACKs arrive in burst and earlier than usual, thus +// producing an ACK rate which is higher than the original send rate. +TEST_F(BandwidthSamplerTest, CompressedAck) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + DataRate expected_bandwidth = kRegularPacketSize / time_between_packets; + + Send40PacketsAndAckFirst20(time_between_packets); + + // Simulate an RTT somewhat lower than the one for 1-to-21 transmission. + clock_ += time_between_packets * 15; + + // Ack the packets 21 to 40 almost immediately at once. + DataRate last_bandwidth = DataRate::Zero(); + TimeDelta ridiculously_small_time_delta = TimeDelta::us(20); + for (int64_t i = 21; i <= 40; i++) { + last_bandwidth = AckPacket(i); + clock_ += ridiculously_small_time_delta; + } + EXPECT_EQ(expected_bandwidth, last_bandwidth); + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Tests receiving ACK packets in the reverse order. +TEST_F(BandwidthSamplerTest, ReorderedAck) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + DataRate expected_bandwidth = kRegularPacketSize / time_between_packets; + + Send40PacketsAndAckFirst20(time_between_packets); + + // Ack the packets 21 to 40 in the reverse order, while sending packets 41 to + // 60. + DataRate last_bandwidth = DataRate::Zero(); + for (int64_t i = 0; i < 20; i++) { + last_bandwidth = AckPacket(40 - i); + EXPECT_EQ(expected_bandwidth, last_bandwidth); + SendPacket(41 + i); + clock_ += time_between_packets; + } + + // Ack the packets 41 to 60, now in the regular order. + for (int64_t i = 41; i <= 60; i++) { + last_bandwidth = AckPacket(i); + EXPECT_EQ(expected_bandwidth, last_bandwidth); + clock_ += time_between_packets; + } + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Test the app-limited logic. +TEST_F(BandwidthSamplerTest, AppLimited) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + DataRate expected_bandwidth = kRegularPacketSize / time_between_packets; + + Send40PacketsAndAckFirst20(time_between_packets); + + // We are now app-limited. Ack 21 to 40 as usual, but do not send anything for + // now. + sampler_.OnAppLimited(); + for (int64_t i = 21; i <= 40; i++) { + DataRate current_sample = AckPacket(i); + EXPECT_EQ(expected_bandwidth, current_sample); + clock_ += time_between_packets; + } + + // Enter quiescence. + clock_ += TimeDelta::seconds(1); + + // Send packets 41 to 60, all of which would be marked as app-limited. + for (int64_t i = 41; i <= 60; i++) { + SendPacket(i); + clock_ += time_between_packets; + } + + // Ack packets 41 to 60, while sending packets 61 to 80. 41 to 60 should be + // app-limited and underestimate the bandwidth due to that. + for (int64_t i = 41; i <= 60; i++) { + BandwidthSample sample = AckPacketInner(i); + EXPECT_TRUE(sample.is_app_limited); + EXPECT_LT(sample.bandwidth, 0.7f * expected_bandwidth); + + SendPacket(i + 20); + clock_ += time_between_packets; + } + + // Run out of packets, and then ack packet 61 to 80, all of which should have + // correct non-app-limited samples. + for (int64_t i = 61; i <= 80; i++) { + DataRate last_bandwidth = AckPacket(i); + EXPECT_EQ(expected_bandwidth, last_bandwidth); + clock_ += time_between_packets; + } + + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + EXPECT_TRUE(bytes_in_flight_.IsZero()); +} + +// Test the samples taken at the first flight of packets sent. +TEST_F(BandwidthSamplerTest, FirstRoundTrip) { + const TimeDelta time_between_packets = TimeDelta::ms(1); + const TimeDelta rtt = TimeDelta::ms(800); + const int num_packets = 10; + const DataSize num_bytes = kRegularPacketSize * num_packets; + const DataRate real_bandwidth = num_bytes / rtt; + + for (int64_t i = 1; i <= 10; i++) { + SendPacket(i); + clock_ += time_between_packets; + } + + clock_ += rtt - num_packets * time_between_packets; + + DataRate last_sample = DataRate::Zero(); + for (int64_t i = 1; i <= 10; i++) { + DataRate sample = AckPacket(i); + EXPECT_GT(sample, last_sample); + last_sample = sample; + clock_ += time_between_packets; + } + + // The final measured sample for the first flight of sample is expected to be + // smaller than the real bandwidth, yet it should not lose more than 10%. The + // specific value of the error depends on the difference between the RTT and + // the time it takes to exhaust the congestion window (i.e. in the limit when + // all packets are sent simultaneously, last sample would indicate the real + // bandwidth). + EXPECT_LT(last_sample, real_bandwidth); + EXPECT_GT(last_sample, 0.9f * real_bandwidth); +} + +// Test sampler's ability to remove obsolete packets. +TEST_F(BandwidthSamplerTest, RemoveObsoletePackets) { + SendPacket(1); + SendPacket(2); + SendPacket(3); + SendPacket(4); + SendPacket(5); + + clock_ += TimeDelta::ms(100); + + EXPECT_EQ(5u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + sampler_.RemoveObsoletePackets(4); + EXPECT_EQ(2u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + sampler_.OnPacketLost(4); + EXPECT_EQ(1u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); + AckPacket(5); + EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_)); +} + +} // namespace test +} // namespace bbr +} // namespace webrtc