Adds BandwidthSampler for BBR.
This prepares for making the BBR implementation more identical to the implementation in Quic, this is to ensure that results are comparable. Bug: webrtc:8415 Change-Id: Ic2dc4394dc9923e5109ffa5f146c23b527f0c395 Reviewed-on: https://webrtc-review.googlesource.com/76582 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Cr-Commit-Position: refs/heads/master@{#23262}
This commit is contained in:
parent
e7659df12f
commit
a8bf169906
@ -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",
|
||||
|
||||
204
modules/congestion_controller/bbr/bandwidth_sampler.cc
Normal file
204
modules/congestion_controller/bbr/bandwidth_sampler.cc
Normal file
@ -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 <algorithm>
|
||||
|
||||
#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
|
||||
261
modules/congestion_controller/bbr/bandwidth_sampler.h
Normal file
261
modules/congestion_controller/bbr/bandwidth_sampler.h
Normal file
@ -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<Timestamp> last_acked_packet_sent_time;
|
||||
|
||||
// The value of |last_acked_packet_ack_time_| at the time the packet was
|
||||
// sent.
|
||||
rtc::Optional<Timestamp> 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<Timestamp> last_acked_packet_sent_time_;
|
||||
|
||||
// The time at which the most recent packet was acknowledged.
|
||||
rtc::Optional<Timestamp> 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<ConnectionStateOnSentPacket> 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_
|
||||
337
modules/congestion_controller/bbr/bandwidth_sampler_unittest.cc
Normal file
337
modules/congestion_controller/bbr/bandwidth_sampler_unittest.cc
Normal file
@ -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 <algorithm>
|
||||
|
||||
#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
|
||||
Loading…
x
Reference in New Issue
Block a user