Cleanup: Removes unused BBR congestion controller.
This was introduced on trial but turned out to perform badly for WebRTC purposes and never used in production. Bug: webrtc:9883 Change-Id: Ib72acddf4d90fc9ac042084dddf526c04661f290 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/173680 Reviewed-by: Erik Språng <sprang@webrtc.org> Commit-Queue: Sebastian Jansson <srte@webrtc.org> Cr-Commit-Position: refs/heads/master@{#31085}
This commit is contained in:
parent
ce0a11d5f9
commit
5c356bb9b1
@ -49,7 +49,6 @@ if (rtc_include_tests) {
|
||||
"../../test:test_support",
|
||||
"../../test/scenario",
|
||||
"../pacing",
|
||||
"bbr:bbr_unittests",
|
||||
"goog_cc:estimators",
|
||||
"goog_cc:goog_cc_unittests",
|
||||
"pcc:pcc_unittests",
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import("../../../webrtc.gni")
|
||||
|
||||
rtc_library("bbr") {
|
||||
sources = [
|
||||
"bbr_factory.cc",
|
||||
"bbr_factory.h",
|
||||
]
|
||||
deps = [
|
||||
":bbr_controller",
|
||||
"../../../api/transport:network_control",
|
||||
"../../../api/units:time_delta",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("bbr_controller") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"bbr_network_controller.cc",
|
||||
"bbr_network_controller.h",
|
||||
]
|
||||
deps = [
|
||||
":bandwidth_sampler",
|
||||
":loss_rate_filter",
|
||||
":rtt_stats",
|
||||
":windowed_filter",
|
||||
"../../../api/transport:network_control",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base/experiments:field_trial_parser",
|
||||
"../../../system_wrappers:field_trial",
|
||||
"//third_party/abseil-cpp/absl/base:core_headers",
|
||||
"//third_party/abseil-cpp/absl/types:optional",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("bandwidth_sampler") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"bandwidth_sampler.cc",
|
||||
"bandwidth_sampler.h",
|
||||
]
|
||||
deps = [
|
||||
":packet_number_indexed_queue",
|
||||
"../../../api/units:data_rate",
|
||||
"../../../api/units:data_size",
|
||||
"../../../api/units:time_delta",
|
||||
"../../../api/units:timestamp",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"//third_party/abseil-cpp/absl/types:optional",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("data_transfer_tracker") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"data_transfer_tracker.cc",
|
||||
"data_transfer_tracker.h",
|
||||
]
|
||||
deps = [
|
||||
"../../../api/units:data_size",
|
||||
"../../../api/units:time_delta",
|
||||
"../../../api/units:timestamp",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("packet_number_indexed_queue") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [ "packet_number_indexed_queue.h" ]
|
||||
deps = [ "../../../rtc_base:checks" ]
|
||||
}
|
||||
|
||||
rtc_library("loss_rate_filter") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"loss_rate_filter.cc",
|
||||
"loss_rate_filter.h",
|
||||
]
|
||||
deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
|
||||
}
|
||||
rtc_library("rtt_stats") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"rtt_stats.cc",
|
||||
"rtt_stats.h",
|
||||
]
|
||||
deps = [
|
||||
"../../../api/units:time_delta",
|
||||
"../../../api/units:timestamp",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
rtc_source_set("windowed_filter") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [ "windowed_filter.h" ]
|
||||
}
|
||||
if (rtc_include_tests) {
|
||||
rtc_library("bbr_unittests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"bandwidth_sampler_unittest.cc",
|
||||
"bbr_network_controller_unittest.cc",
|
||||
"data_transfer_tracker_unittest.cc",
|
||||
"loss_rate_filter_unittest.cc",
|
||||
"packet_number_indexed_queue_unittest.cc",
|
||||
"rtt_stats_unittest.cc",
|
||||
"windowed_filter_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":bandwidth_sampler",
|
||||
":bbr",
|
||||
":bbr_controller",
|
||||
":data_transfer_tracker",
|
||||
":loss_rate_filter",
|
||||
":packet_number_indexed_queue",
|
||||
":rtt_stats",
|
||||
":windowed_filter",
|
||||
"../../../api/units:data_rate",
|
||||
"../../../api/units:time_delta",
|
||||
"../../../api/units:timestamp",
|
||||
"../../../rtc_base:logging",
|
||||
"../../../test:test_support",
|
||||
"../../../test/scenario",
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/bbr/bandwidth_sampler.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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::MinusInfinity()),
|
||||
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
|
||||
@ -1,261 +0,0 @@
|
||||
/*
|
||||
* 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 "absl/types/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.
|
||||
absl::optional<Timestamp> last_acked_packet_sent_time;
|
||||
|
||||
// The value of |last_acked_packet_ack_time_| at the time the packet was
|
||||
// sent.
|
||||
absl::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.
|
||||
absl::optional<Timestamp> last_acked_packet_sent_time_;
|
||||
|
||||
// The time at which the most recent packet was acknowledged.
|
||||
absl::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_
|
||||
@ -1,337 +0,0 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/bbr/bandwidth_sampler.h"
|
||||
|
||||
#include <stddef.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::Millis(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::Millis(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::Millis(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::Millis(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::Micros(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::Millis(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::Millis(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::Millis(1);
|
||||
const TimeDelta rtt = TimeDelta::Millis(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::Millis(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
|
||||
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/bbr_factory.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/congestion_controller/bbr/bbr_network_controller.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
BbrNetworkControllerFactory::BbrNetworkControllerFactory() {}
|
||||
|
||||
std::unique_ptr<NetworkControllerInterface> BbrNetworkControllerFactory::Create(
|
||||
NetworkControllerConfig config) {
|
||||
return std::make_unique<bbr::BbrNetworkController>(config);
|
||||
}
|
||||
|
||||
TimeDelta BbrNetworkControllerFactory::GetProcessInterval() const {
|
||||
return TimeDelta::PlusInfinity();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_BBR_FACTORY_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_BBR_FACTORY_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/transport/network_control.h"
|
||||
#include "api/units/time_delta.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class BbrNetworkControllerFactory : public NetworkControllerFactoryInterface {
|
||||
public:
|
||||
BbrNetworkControllerFactory();
|
||||
std::unique_ptr<NetworkControllerInterface> Create(
|
||||
NetworkControllerConfig config) override;
|
||||
TimeDelta GetProcessInterval() const override;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_BBR_FACTORY_H_
|
||||
@ -1,955 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/bbr_network_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace {
|
||||
|
||||
// If greater than zero, mean RTT variation is multiplied by the specified
|
||||
// factor and added to the congestion window limit.
|
||||
const double kBbrRttVariationWeight = 0.0f;
|
||||
|
||||
// Congestion window gain for QUIC BBR during PROBE_BW phase.
|
||||
const double kProbeBWCongestionWindowGain = 2.0f;
|
||||
|
||||
// The maximum packet size of any QUIC packet, based on ethernet's max size,
|
||||
// minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an
|
||||
// additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's
|
||||
// max packet size is 1500 bytes, 1500 - 48 = 1452.
|
||||
const DataSize kMaxPacketSize = DataSize::Bytes(1452);
|
||||
|
||||
// Default maximum packet size used in the Linux TCP implementation.
|
||||
// Used in QUIC for congestion window computations in bytes.
|
||||
constexpr DataSize kDefaultTCPMSS = DataSize::Bytes(1460);
|
||||
// Constants based on TCP defaults.
|
||||
constexpr DataSize kMaxSegmentSize = kDefaultTCPMSS;
|
||||
|
||||
// The gain used for the slow start, equal to 2/ln(2).
|
||||
const double kHighGain = 2.885f;
|
||||
// The gain used in STARTUP after loss has been detected.
|
||||
// 1.5 is enough to allow for 25% exogenous loss and still observe a 25% growth
|
||||
// in measured bandwidth.
|
||||
const double kStartupAfterLossGain = 1.5;
|
||||
// The gain used to drain the queue after the slow start.
|
||||
const double kDrainGain = 1.f / kHighGain;
|
||||
|
||||
// The length of the gain cycle.
|
||||
const size_t kGainCycleLength = 8;
|
||||
// The size of the bandwidth filter window, in round-trips.
|
||||
const BbrRoundTripCount kBandwidthWindowSize = kGainCycleLength + 2;
|
||||
|
||||
// The time after which the current min_rtt value expires.
|
||||
constexpr int64_t kMinRttExpirySeconds = 10;
|
||||
// The minimum time the connection can spend in PROBE_RTT mode.
|
||||
constexpr int64_t kProbeRttTimeMs = 200;
|
||||
// If the bandwidth does not increase by the factor of |kStartupGrowthTarget|
|
||||
// within |kRoundTripsWithoutGrowthBeforeExitingStartup| rounds, the connection
|
||||
// will exit the STARTUP mode.
|
||||
const double kStartupGrowthTarget = 1.25;
|
||||
// Coefficient to determine if a new RTT is sufficiently similar to min_rtt that
|
||||
// we don't need to enter PROBE_RTT.
|
||||
const double kSimilarMinRttThreshold = 1.125;
|
||||
|
||||
constexpr int64_t kInitialBandwidthKbps = 300;
|
||||
|
||||
const int64_t kInitialCongestionWindowPackets = 32;
|
||||
// The minimum CWND to ensure delayed acks don't reduce bandwidth measurements.
|
||||
// Does not inflate the pacing rate.
|
||||
const int64_t kDefaultMinCongestionWindowPackets = 4;
|
||||
const int64_t kDefaultMaxCongestionWindowPackets = 2000;
|
||||
|
||||
const char kBbrConfigTrial[] = "WebRTC-BweBbrConfig";
|
||||
|
||||
} // namespace
|
||||
|
||||
BbrNetworkController::BbrControllerConfig::BbrControllerConfig(
|
||||
std::string field_trial)
|
||||
: probe_bw_pacing_gain_offset("probe_bw_pacing_gain_offset", 0.25),
|
||||
encoder_rate_gain("encoder_rate_gain", 1),
|
||||
encoder_rate_gain_in_probe_rtt("encoder_rate_gain_in_probe_rtt", 1),
|
||||
exit_startup_rtt_threshold("exit_startup_rtt_threshold",
|
||||
TimeDelta::PlusInfinity()),
|
||||
initial_congestion_window(
|
||||
"initial_cwin",
|
||||
kInitialCongestionWindowPackets * kDefaultTCPMSS),
|
||||
min_congestion_window(
|
||||
"min_cwin",
|
||||
kDefaultMinCongestionWindowPackets * kDefaultTCPMSS),
|
||||
max_congestion_window(
|
||||
"max_cwin",
|
||||
kDefaultMaxCongestionWindowPackets * kDefaultTCPMSS),
|
||||
probe_rtt_congestion_window_gain("probe_rtt_cwin_gain", 0.75),
|
||||
pacing_rate_as_target("pacing_rate_as_target", false),
|
||||
exit_startup_on_loss("exit_startup_on_loss", true),
|
||||
num_startup_rtts("num_startup_rtts", 3),
|
||||
rate_based_recovery("rate_based_recovery", false),
|
||||
max_aggregation_bytes_multiplier("max_aggregation_bytes_multiplier", 0),
|
||||
slower_startup("slower_startup", false),
|
||||
rate_based_startup("rate_based_startup", false),
|
||||
initial_conservation_in_startup("initial_conservation",
|
||||
CONSERVATION,
|
||||
{
|
||||
{"NOT_IN_RECOVERY", NOT_IN_RECOVERY},
|
||||
{"CONSERVATION", CONSERVATION},
|
||||
{"MEDIUM_GROWTH", MEDIUM_GROWTH},
|
||||
{"GROWTH", GROWTH},
|
||||
}),
|
||||
fully_drain_queue("fully_drain_queue", false),
|
||||
max_ack_height_window_multiplier("max_ack_height_window_multiplier", 1),
|
||||
probe_rtt_based_on_bdp("probe_rtt_based_on_bdp", false),
|
||||
probe_rtt_skipped_if_similar_rtt("probe_rtt_skipped_if_similar_rtt",
|
||||
false),
|
||||
probe_rtt_disabled_if_app_limited("probe_rtt_disabled_if_app_limited",
|
||||
false) {
|
||||
ParseFieldTrial(
|
||||
{
|
||||
&exit_startup_on_loss,
|
||||
&encoder_rate_gain,
|
||||
&encoder_rate_gain_in_probe_rtt,
|
||||
&exit_startup_rtt_threshold,
|
||||
&fully_drain_queue,
|
||||
&initial_congestion_window,
|
||||
&initial_conservation_in_startup,
|
||||
&max_ack_height_window_multiplier,
|
||||
&max_aggregation_bytes_multiplier,
|
||||
&max_congestion_window,
|
||||
&min_congestion_window,
|
||||
&num_startup_rtts,
|
||||
&pacing_rate_as_target,
|
||||
&probe_bw_pacing_gain_offset,
|
||||
&probe_rtt_based_on_bdp,
|
||||
&probe_rtt_congestion_window_gain,
|
||||
&probe_rtt_disabled_if_app_limited,
|
||||
&probe_rtt_skipped_if_similar_rtt,
|
||||
&rate_based_recovery,
|
||||
&rate_based_startup,
|
||||
&slower_startup,
|
||||
},
|
||||
field_trial);
|
||||
}
|
||||
BbrNetworkController::BbrControllerConfig::~BbrControllerConfig() = default;
|
||||
BbrNetworkController::BbrControllerConfig::BbrControllerConfig(
|
||||
const BbrControllerConfig&) = default;
|
||||
BbrNetworkController::BbrControllerConfig
|
||||
BbrNetworkController::BbrControllerConfig::FromTrial() {
|
||||
return BbrControllerConfig(
|
||||
webrtc::field_trial::FindFullName(kBbrConfigTrial));
|
||||
}
|
||||
|
||||
BbrNetworkController::DebugState::DebugState(const BbrNetworkController& sender)
|
||||
: mode(sender.mode_),
|
||||
max_bandwidth(sender.max_bandwidth_.GetBest()),
|
||||
round_trip_count(sender.round_trip_count_),
|
||||
gain_cycle_index(sender.cycle_current_offset_),
|
||||
congestion_window(sender.congestion_window_),
|
||||
is_at_full_bandwidth(sender.is_at_full_bandwidth_),
|
||||
bandwidth_at_last_round(sender.bandwidth_at_last_round_),
|
||||
rounds_without_bandwidth_gain(sender.rounds_without_bandwidth_gain_),
|
||||
min_rtt(sender.min_rtt_),
|
||||
min_rtt_timestamp(sender.min_rtt_timestamp_),
|
||||
recovery_state(sender.recovery_state_),
|
||||
recovery_window(sender.recovery_window_),
|
||||
last_sample_is_app_limited(sender.last_sample_is_app_limited_),
|
||||
end_of_app_limited_phase(sender.sampler_->end_of_app_limited_phase()) {}
|
||||
|
||||
BbrNetworkController::DebugState::DebugState(const DebugState& state) = default;
|
||||
|
||||
BbrNetworkController::BbrNetworkController(NetworkControllerConfig config)
|
||||
: config_(BbrControllerConfig::FromTrial()),
|
||||
rtt_stats_(),
|
||||
random_(10),
|
||||
loss_rate_(),
|
||||
mode_(STARTUP),
|
||||
sampler_(new BandwidthSampler()),
|
||||
round_trip_count_(0),
|
||||
last_sent_packet_(0),
|
||||
current_round_trip_end_(0),
|
||||
max_bandwidth_(kBandwidthWindowSize, DataRate::Zero(), 0),
|
||||
default_bandwidth_(DataRate::KilobitsPerSec(kInitialBandwidthKbps)),
|
||||
max_ack_height_(kBandwidthWindowSize, DataSize::Zero(), 0),
|
||||
aggregation_epoch_start_time_(),
|
||||
aggregation_epoch_bytes_(DataSize::Zero()),
|
||||
bytes_acked_since_queue_drained_(DataSize::Zero()),
|
||||
max_aggregation_bytes_multiplier_(0),
|
||||
min_rtt_(TimeDelta::Zero()),
|
||||
last_rtt_(TimeDelta::Zero()),
|
||||
min_rtt_timestamp_(Timestamp::MinusInfinity()),
|
||||
congestion_window_(config_.initial_congestion_window),
|
||||
initial_congestion_window_(config_.initial_congestion_window),
|
||||
min_congestion_window_(config_.min_congestion_window),
|
||||
max_congestion_window_(config_.max_congestion_window),
|
||||
pacing_rate_(DataRate::Zero()),
|
||||
pacing_gain_(1),
|
||||
congestion_window_gain_constant_(kProbeBWCongestionWindowGain),
|
||||
rtt_variance_weight_(kBbrRttVariationWeight),
|
||||
cycle_current_offset_(0),
|
||||
last_cycle_start_(Timestamp::MinusInfinity()),
|
||||
is_at_full_bandwidth_(false),
|
||||
rounds_without_bandwidth_gain_(0),
|
||||
bandwidth_at_last_round_(DataRate::Zero()),
|
||||
exiting_quiescence_(false),
|
||||
exit_probe_rtt_at_(),
|
||||
probe_rtt_round_passed_(false),
|
||||
last_sample_is_app_limited_(false),
|
||||
recovery_state_(NOT_IN_RECOVERY),
|
||||
end_recovery_at_(),
|
||||
recovery_window_(max_congestion_window_),
|
||||
app_limited_since_last_probe_rtt_(false),
|
||||
min_rtt_since_last_probe_rtt_(TimeDelta::PlusInfinity()) {
|
||||
RTC_LOG(LS_INFO) << "Creating BBR controller";
|
||||
if (config.constraints.starting_rate)
|
||||
default_bandwidth_ = *config.constraints.starting_rate;
|
||||
constraints_ = config.constraints;
|
||||
Reset();
|
||||
}
|
||||
|
||||
BbrNetworkController::~BbrNetworkController() {}
|
||||
|
||||
void BbrNetworkController::Reset() {
|
||||
round_trip_count_ = 0;
|
||||
rounds_without_bandwidth_gain_ = 0;
|
||||
if (config_.num_startup_rtts > 0) {
|
||||
is_at_full_bandwidth_ = false;
|
||||
EnterStartupMode();
|
||||
} else {
|
||||
is_at_full_bandwidth_ = true;
|
||||
EnterProbeBandwidthMode(constraints_->at_time);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::CreateRateUpdate(
|
||||
Timestamp at_time) const {
|
||||
DataRate bandwidth = BandwidthEstimate();
|
||||
if (bandwidth.IsZero())
|
||||
bandwidth = default_bandwidth_;
|
||||
TimeDelta rtt = GetMinRtt();
|
||||
DataRate pacing_rate = PacingRate();
|
||||
DataRate target_rate =
|
||||
config_.pacing_rate_as_target ? pacing_rate : bandwidth;
|
||||
|
||||
if (mode_ == PROBE_RTT)
|
||||
target_rate = target_rate * config_.encoder_rate_gain_in_probe_rtt;
|
||||
else
|
||||
target_rate = target_rate * config_.encoder_rate_gain;
|
||||
target_rate = std::min(target_rate, pacing_rate);
|
||||
|
||||
if (constraints_) {
|
||||
if (constraints_->max_data_rate) {
|
||||
target_rate = std::min(target_rate, *constraints_->max_data_rate);
|
||||
pacing_rate = std::min(pacing_rate, *constraints_->max_data_rate);
|
||||
}
|
||||
if (constraints_->min_data_rate) {
|
||||
target_rate = std::max(target_rate, *constraints_->min_data_rate);
|
||||
pacing_rate = std::max(pacing_rate, *constraints_->min_data_rate);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkControlUpdate update;
|
||||
|
||||
TargetTransferRate target_rate_msg;
|
||||
target_rate_msg.network_estimate.at_time = at_time;
|
||||
target_rate_msg.network_estimate.round_trip_time = rtt;
|
||||
|
||||
// TODO(srte): Fill in field below with proper value.
|
||||
target_rate_msg.network_estimate.loss_rate_ratio = 0;
|
||||
// In in PROBE_BW, target bandwidth is expected to vary over the cycle period.
|
||||
// In other modes the is no given period, therefore the same value as in
|
||||
// PROBE_BW is used for consistency.
|
||||
target_rate_msg.network_estimate.bwe_period =
|
||||
rtt * static_cast<int64_t>(kGainCycleLength);
|
||||
|
||||
target_rate_msg.target_rate = target_rate;
|
||||
target_rate_msg.at_time = at_time;
|
||||
update.target_rate = target_rate_msg;
|
||||
|
||||
PacerConfig pacer_config;
|
||||
// A small time window ensures an even pacing rate.
|
||||
pacer_config.time_window = rtt * 0.25;
|
||||
pacer_config.data_window = pacer_config.time_window * pacing_rate;
|
||||
|
||||
if (IsProbingForMoreBandwidth())
|
||||
pacer_config.pad_window = pacer_config.data_window;
|
||||
else
|
||||
pacer_config.pad_window = DataSize::Zero();
|
||||
|
||||
pacer_config.at_time = at_time;
|
||||
update.pacer_config = pacer_config;
|
||||
|
||||
update.congestion_window = GetCongestionWindow();
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnNetworkAvailability(
|
||||
NetworkAvailability msg) {
|
||||
Reset();
|
||||
rtt_stats_.OnConnectionMigration();
|
||||
return CreateRateUpdate(msg.at_time);
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnNetworkRouteChange(
|
||||
NetworkRouteChange msg) {
|
||||
constraints_ = msg.constraints;
|
||||
Reset();
|
||||
if (msg.constraints.starting_rate)
|
||||
default_bandwidth_ = *msg.constraints.starting_rate;
|
||||
|
||||
rtt_stats_.OnConnectionMigration();
|
||||
return CreateRateUpdate(msg.at_time);
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnProcessInterval(
|
||||
ProcessInterval msg) {
|
||||
return CreateRateUpdate(msg.at_time);
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnStreamsConfig(StreamsConfig msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnTargetRateConstraints(
|
||||
TargetRateConstraints msg) {
|
||||
constraints_ = msg;
|
||||
return CreateRateUpdate(msg.at_time);
|
||||
}
|
||||
|
||||
bool BbrNetworkController::InSlowStart() const {
|
||||
return mode_ == STARTUP;
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnSentPacket(SentPacket msg) {
|
||||
last_sent_packet_ = msg.sequence_number;
|
||||
|
||||
if (msg.data_in_flight.IsZero() && sampler_->is_app_limited()) {
|
||||
exiting_quiescence_ = true;
|
||||
}
|
||||
|
||||
if (!aggregation_epoch_start_time_) {
|
||||
aggregation_epoch_start_time_ = msg.send_time;
|
||||
}
|
||||
|
||||
sampler_->OnPacketSent(msg.send_time, msg.sequence_number, msg.size,
|
||||
msg.data_in_flight);
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
bool BbrNetworkController::CanSend(DataSize bytes_in_flight) {
|
||||
return bytes_in_flight < GetCongestionWindow();
|
||||
}
|
||||
|
||||
DataRate BbrNetworkController::PacingRate() const {
|
||||
if (pacing_rate_.IsZero()) {
|
||||
return kHighGain * initial_congestion_window_ / GetMinRtt();
|
||||
}
|
||||
return pacing_rate_;
|
||||
}
|
||||
|
||||
DataRate BbrNetworkController::BandwidthEstimate() const {
|
||||
return max_bandwidth_.GetBest();
|
||||
}
|
||||
|
||||
DataSize BbrNetworkController::GetCongestionWindow() const {
|
||||
if (mode_ == PROBE_RTT) {
|
||||
return ProbeRttCongestionWindow();
|
||||
}
|
||||
|
||||
if (InRecovery() && !config_.rate_based_recovery &&
|
||||
!(config_.rate_based_startup && mode_ == STARTUP)) {
|
||||
return std::min(congestion_window_, recovery_window_);
|
||||
}
|
||||
|
||||
return congestion_window_;
|
||||
}
|
||||
|
||||
double BbrNetworkController::GetPacingGain(int round_offset) const {
|
||||
if (round_offset == 0)
|
||||
return 1 + config_.probe_bw_pacing_gain_offset;
|
||||
else if (round_offset == 1)
|
||||
return 1 - config_.probe_bw_pacing_gain_offset;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool BbrNetworkController::InRecovery() const {
|
||||
return recovery_state_ != NOT_IN_RECOVERY;
|
||||
}
|
||||
|
||||
bool BbrNetworkController::IsProbingForMoreBandwidth() const {
|
||||
return (mode_ == PROBE_BW && pacing_gain_ > 1) || mode_ == STARTUP;
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback msg) {
|
||||
if (msg.packet_feedbacks.empty())
|
||||
return NetworkControlUpdate();
|
||||
|
||||
Timestamp feedback_recv_time = msg.feedback_time;
|
||||
SentPacket last_sent_packet = msg.PacketsWithFeedback().back().sent_packet;
|
||||
|
||||
Timestamp send_time = last_sent_packet.send_time;
|
||||
TimeDelta send_delta = feedback_recv_time - send_time;
|
||||
rtt_stats_.UpdateRtt(send_delta, TimeDelta::Zero(), feedback_recv_time);
|
||||
|
||||
const DataSize total_data_acked_before = sampler_->total_data_acked();
|
||||
|
||||
bool is_round_start = false;
|
||||
bool min_rtt_expired = false;
|
||||
|
||||
std::vector<PacketResult> lost_packets = msg.LostWithSendInfo();
|
||||
DiscardLostPackets(lost_packets);
|
||||
|
||||
std::vector<PacketResult> acked_packets = msg.ReceivedWithSendInfo();
|
||||
|
||||
int packets_sent =
|
||||
static_cast<int>(lost_packets.size() + acked_packets.size());
|
||||
int packets_lost = static_cast<int>(lost_packets.size());
|
||||
loss_rate_.UpdateWithLossStatus(msg.feedback_time.ms(), packets_sent,
|
||||
packets_lost);
|
||||
|
||||
// Input the new data into the BBR model of the connection.
|
||||
if (!acked_packets.empty()) {
|
||||
int64_t last_acked_packet =
|
||||
acked_packets.rbegin()->sent_packet.sequence_number;
|
||||
|
||||
is_round_start = UpdateRoundTripCounter(last_acked_packet);
|
||||
min_rtt_expired =
|
||||
UpdateBandwidthAndMinRtt(msg.feedback_time, acked_packets);
|
||||
UpdateRecoveryState(last_acked_packet, !lost_packets.empty(),
|
||||
is_round_start);
|
||||
|
||||
const DataSize data_acked =
|
||||
sampler_->total_data_acked() - total_data_acked_before;
|
||||
|
||||
UpdateAckAggregationBytes(msg.feedback_time, data_acked);
|
||||
if (max_aggregation_bytes_multiplier_ > 0) {
|
||||
if (msg.data_in_flight <=
|
||||
1.25 * GetTargetCongestionWindow(pacing_gain_)) {
|
||||
bytes_acked_since_queue_drained_ = DataSize::Zero();
|
||||
} else {
|
||||
bytes_acked_since_queue_drained_ += data_acked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle logic specific to PROBE_BW mode.
|
||||
if (mode_ == PROBE_BW) {
|
||||
UpdateGainCyclePhase(msg.feedback_time, msg.prior_in_flight,
|
||||
!lost_packets.empty());
|
||||
}
|
||||
|
||||
// Handle logic specific to STARTUP and DRAIN modes.
|
||||
if (is_round_start && !is_at_full_bandwidth_) {
|
||||
CheckIfFullBandwidthReached();
|
||||
}
|
||||
MaybeExitStartupOrDrain(msg);
|
||||
|
||||
// Handle logic specific to PROBE_RTT.
|
||||
MaybeEnterOrExitProbeRtt(msg, is_round_start, min_rtt_expired);
|
||||
|
||||
// Calculate number of packets acked and lost.
|
||||
DataSize data_acked = sampler_->total_data_acked() - total_data_acked_before;
|
||||
DataSize data_lost = DataSize::Zero();
|
||||
for (const PacketResult& packet : lost_packets) {
|
||||
data_lost += packet.sent_packet.size;
|
||||
}
|
||||
|
||||
// After the model is updated, recalculate the pacing rate and congestion
|
||||
// window.
|
||||
CalculatePacingRate();
|
||||
CalculateCongestionWindow(data_acked);
|
||||
CalculateRecoveryWindow(data_acked, data_lost, msg.data_in_flight);
|
||||
// Cleanup internal state.
|
||||
if (!acked_packets.empty()) {
|
||||
sampler_->RemoveObsoletePackets(
|
||||
acked_packets.back().sent_packet.sequence_number);
|
||||
}
|
||||
return CreateRateUpdate(msg.feedback_time);
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnRemoteBitrateReport(
|
||||
RemoteBitrateReport msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
NetworkControlUpdate BbrNetworkController::OnRoundTripTimeUpdate(
|
||||
RoundTripTimeUpdate msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
NetworkControlUpdate BbrNetworkController::OnTransportLossReport(
|
||||
TransportLossReport msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnReceivedPacket(
|
||||
ReceivedPacket msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate BbrNetworkController::OnNetworkStateEstimate(
|
||||
NetworkStateEstimate msg) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
TimeDelta BbrNetworkController::GetMinRtt() const {
|
||||
return !min_rtt_.IsZero() ? min_rtt_
|
||||
: TimeDelta::Micros(rtt_stats_.initial_rtt_us());
|
||||
}
|
||||
|
||||
DataSize BbrNetworkController::GetTargetCongestionWindow(double gain) const {
|
||||
DataSize bdp = GetMinRtt() * BandwidthEstimate();
|
||||
DataSize congestion_window = gain * bdp;
|
||||
|
||||
// BDP estimate will be zero if no bandwidth samples are available yet.
|
||||
if (congestion_window.IsZero()) {
|
||||
congestion_window = gain * initial_congestion_window_;
|
||||
}
|
||||
|
||||
return std::max(congestion_window, min_congestion_window_);
|
||||
}
|
||||
|
||||
DataSize BbrNetworkController::ProbeRttCongestionWindow() const {
|
||||
if (config_.probe_rtt_based_on_bdp) {
|
||||
return GetTargetCongestionWindow(config_.probe_rtt_congestion_window_gain);
|
||||
}
|
||||
return min_congestion_window_;
|
||||
}
|
||||
|
||||
void BbrNetworkController::EnterStartupMode() {
|
||||
mode_ = STARTUP;
|
||||
pacing_gain_ = kHighGain;
|
||||
congestion_window_gain_ = kHighGain;
|
||||
}
|
||||
|
||||
void BbrNetworkController::EnterProbeBandwidthMode(Timestamp now) {
|
||||
mode_ = PROBE_BW;
|
||||
congestion_window_gain_ = congestion_window_gain_constant_;
|
||||
|
||||
// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
|
||||
// excluded because in that case increased gain and decreased gain would not
|
||||
// follow each other.
|
||||
cycle_current_offset_ = random_.Rand(kGainCycleLength - 2);
|
||||
if (cycle_current_offset_ >= 1) {
|
||||
cycle_current_offset_ += 1;
|
||||
}
|
||||
|
||||
last_cycle_start_ = now;
|
||||
pacing_gain_ = GetPacingGain(cycle_current_offset_);
|
||||
}
|
||||
|
||||
void BbrNetworkController::DiscardLostPackets(
|
||||
const std::vector<PacketResult>& lost_packets) {
|
||||
for (const PacketResult& packet : lost_packets) {
|
||||
sampler_->OnPacketLost(packet.sent_packet.sequence_number);
|
||||
}
|
||||
}
|
||||
|
||||
bool BbrNetworkController::UpdateRoundTripCounter(int64_t last_acked_packet) {
|
||||
if (last_acked_packet > current_round_trip_end_) {
|
||||
round_trip_count_++;
|
||||
current_round_trip_end_ = last_sent_packet_;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BbrNetworkController::UpdateBandwidthAndMinRtt(
|
||||
Timestamp now,
|
||||
const std::vector<PacketResult>& acked_packets) {
|
||||
TimeDelta sample_rtt = TimeDelta::PlusInfinity();
|
||||
for (const auto& packet : acked_packets) {
|
||||
BandwidthSample bandwidth_sample =
|
||||
sampler_->OnPacketAcknowledged(now, packet.sent_packet.sequence_number);
|
||||
last_sample_is_app_limited_ = bandwidth_sample.is_app_limited;
|
||||
if (!bandwidth_sample.rtt.IsZero()) {
|
||||
sample_rtt = std::min(sample_rtt, bandwidth_sample.rtt);
|
||||
}
|
||||
|
||||
if (!bandwidth_sample.is_app_limited ||
|
||||
bandwidth_sample.bandwidth > BandwidthEstimate()) {
|
||||
max_bandwidth_.Update(bandwidth_sample.bandwidth, round_trip_count_);
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the RTT samples are valid, return immediately.
|
||||
if (sample_rtt.IsInfinite()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
last_rtt_ = sample_rtt;
|
||||
min_rtt_since_last_probe_rtt_ =
|
||||
std::min(min_rtt_since_last_probe_rtt_, sample_rtt);
|
||||
|
||||
const TimeDelta kMinRttExpiry = TimeDelta::Seconds(kMinRttExpirySeconds);
|
||||
// Do not expire min_rtt if none was ever available.
|
||||
bool min_rtt_expired =
|
||||
!min_rtt_.IsZero() && (now > (min_rtt_timestamp_ + kMinRttExpiry));
|
||||
|
||||
if (min_rtt_expired || sample_rtt < min_rtt_ || min_rtt_.IsZero()) {
|
||||
if (ShouldExtendMinRttExpiry()) {
|
||||
min_rtt_expired = false;
|
||||
} else {
|
||||
min_rtt_ = sample_rtt;
|
||||
}
|
||||
min_rtt_timestamp_ = now;
|
||||
// Reset since_last_probe_rtt fields.
|
||||
min_rtt_since_last_probe_rtt_ = TimeDelta::PlusInfinity();
|
||||
app_limited_since_last_probe_rtt_ = false;
|
||||
}
|
||||
|
||||
return min_rtt_expired;
|
||||
}
|
||||
|
||||
bool BbrNetworkController::ShouldExtendMinRttExpiry() const {
|
||||
if (config_.probe_rtt_disabled_if_app_limited &&
|
||||
app_limited_since_last_probe_rtt_) {
|
||||
// Extend the current min_rtt if we've been app limited recently.
|
||||
return true;
|
||||
}
|
||||
const bool min_rtt_increased_since_last_probe =
|
||||
min_rtt_since_last_probe_rtt_ > min_rtt_ * kSimilarMinRttThreshold;
|
||||
if (config_.probe_rtt_skipped_if_similar_rtt &&
|
||||
app_limited_since_last_probe_rtt_ &&
|
||||
!min_rtt_increased_since_last_probe) {
|
||||
// Extend the current min_rtt if we've been app limited recently and an rtt
|
||||
// has been measured in that time that's less than 12.5% more than the
|
||||
// current min_rtt.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BbrNetworkController::UpdateGainCyclePhase(Timestamp now,
|
||||
DataSize prior_in_flight,
|
||||
bool has_losses) {
|
||||
// In most cases, the cycle is advanced after an RTT passes.
|
||||
bool should_advance_gain_cycling = now - last_cycle_start_ > GetMinRtt();
|
||||
|
||||
// If the pacing gain is above 1.0, the connection is trying to probe the
|
||||
// bandwidth by increasing the number of bytes in flight to at least
|
||||
// pacing_gain * BDP. Make sure that it actually reaches the target, as long
|
||||
// as there are no losses suggesting that the buffers are not able to hold
|
||||
// that much.
|
||||
if (pacing_gain_ > 1.0 && !has_losses &&
|
||||
prior_in_flight < GetTargetCongestionWindow(pacing_gain_)) {
|
||||
should_advance_gain_cycling = false;
|
||||
}
|
||||
|
||||
// If pacing gain is below 1.0, the connection is trying to drain the extra
|
||||
// queue which could have been incurred by probing prior to it. If the number
|
||||
// of bytes in flight falls down to the estimated BDP value earlier, conclude
|
||||
// that the queue has been successfully drained and exit this cycle early.
|
||||
if (pacing_gain_ < 1.0 && prior_in_flight <= GetTargetCongestionWindow(1)) {
|
||||
should_advance_gain_cycling = true;
|
||||
}
|
||||
|
||||
if (should_advance_gain_cycling) {
|
||||
cycle_current_offset_ = (cycle_current_offset_ + 1) % kGainCycleLength;
|
||||
last_cycle_start_ = now;
|
||||
// Stay in low gain mode until the target BDP is hit.
|
||||
// Low gain mode will be exited immediately when the target BDP is achieved.
|
||||
if (config_.fully_drain_queue && pacing_gain_ < 1 &&
|
||||
GetPacingGain(cycle_current_offset_) == 1 &&
|
||||
prior_in_flight > GetTargetCongestionWindow(1)) {
|
||||
return;
|
||||
}
|
||||
pacing_gain_ = GetPacingGain(cycle_current_offset_);
|
||||
}
|
||||
}
|
||||
|
||||
void BbrNetworkController::CheckIfFullBandwidthReached() {
|
||||
if (last_sample_is_app_limited_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataRate target = bandwidth_at_last_round_ * kStartupGrowthTarget;
|
||||
if (BandwidthEstimate() >= target) {
|
||||
bandwidth_at_last_round_ = BandwidthEstimate();
|
||||
rounds_without_bandwidth_gain_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
rounds_without_bandwidth_gain_++;
|
||||
if ((rounds_without_bandwidth_gain_ >= config_.num_startup_rtts) ||
|
||||
(config_.exit_startup_on_loss && InRecovery())) {
|
||||
is_at_full_bandwidth_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void BbrNetworkController::MaybeExitStartupOrDrain(
|
||||
const TransportPacketsFeedback& msg) {
|
||||
TimeDelta exit_threshold = config_.exit_startup_rtt_threshold;
|
||||
TimeDelta rtt_delta = last_rtt_ - min_rtt_;
|
||||
if (mode_ == STARTUP &&
|
||||
(is_at_full_bandwidth_ || rtt_delta > exit_threshold)) {
|
||||
if (rtt_delta > exit_threshold)
|
||||
RTC_LOG(LS_INFO) << "Exiting startup due to rtt increase from: "
|
||||
<< ToString(min_rtt_) << " to:" << ToString(last_rtt_)
|
||||
<< " > " << ToString(min_rtt_ + exit_threshold);
|
||||
mode_ = DRAIN;
|
||||
pacing_gain_ = kDrainGain;
|
||||
congestion_window_gain_ = kHighGain;
|
||||
}
|
||||
if (mode_ == DRAIN && msg.data_in_flight <= GetTargetCongestionWindow(1)) {
|
||||
EnterProbeBandwidthMode(msg.feedback_time);
|
||||
}
|
||||
}
|
||||
|
||||
void BbrNetworkController::MaybeEnterOrExitProbeRtt(
|
||||
const TransportPacketsFeedback& msg,
|
||||
bool is_round_start,
|
||||
bool min_rtt_expired) {
|
||||
if (min_rtt_expired && !exiting_quiescence_ && mode_ != PROBE_RTT) {
|
||||
mode_ = PROBE_RTT;
|
||||
pacing_gain_ = 1;
|
||||
// Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight|
|
||||
// is at the target small value.
|
||||
exit_probe_rtt_at_.reset();
|
||||
}
|
||||
|
||||
if (mode_ == PROBE_RTT) {
|
||||
sampler_->OnAppLimited();
|
||||
|
||||
if (!exit_probe_rtt_at_) {
|
||||
// If the window has reached the appropriate size, schedule exiting
|
||||
// PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but
|
||||
// we allow an extra packet since QUIC checks CWND before sending a
|
||||
// packet.
|
||||
if (msg.data_in_flight < ProbeRttCongestionWindow() + kMaxPacketSize) {
|
||||
exit_probe_rtt_at_ =
|
||||
msg.feedback_time + TimeDelta::Millis(kProbeRttTimeMs);
|
||||
probe_rtt_round_passed_ = false;
|
||||
}
|
||||
} else {
|
||||
if (is_round_start) {
|
||||
probe_rtt_round_passed_ = true;
|
||||
}
|
||||
if (msg.feedback_time >= *exit_probe_rtt_at_ && probe_rtt_round_passed_) {
|
||||
min_rtt_timestamp_ = msg.feedback_time;
|
||||
if (!is_at_full_bandwidth_) {
|
||||
EnterStartupMode();
|
||||
} else {
|
||||
EnterProbeBandwidthMode(msg.feedback_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exiting_quiescence_ = false;
|
||||
}
|
||||
|
||||
void BbrNetworkController::UpdateRecoveryState(int64_t last_acked_packet,
|
||||
bool has_losses,
|
||||
bool is_round_start) {
|
||||
// Exit recovery when there are no losses for a round.
|
||||
if (has_losses) {
|
||||
end_recovery_at_ = last_sent_packet_;
|
||||
}
|
||||
|
||||
switch (recovery_state_) {
|
||||
case NOT_IN_RECOVERY:
|
||||
// Enter conservation on the first loss.
|
||||
if (has_losses) {
|
||||
recovery_state_ = CONSERVATION;
|
||||
if (mode_ == STARTUP) {
|
||||
recovery_state_ = config_.initial_conservation_in_startup;
|
||||
}
|
||||
// This will cause the |recovery_window_| to be set to the correct
|
||||
// value in CalculateRecoveryWindow().
|
||||
recovery_window_ = DataSize::Zero();
|
||||
// Since the conservation phase is meant to be lasting for a whole
|
||||
// round, extend the current round as if it were started right now.
|
||||
current_round_trip_end_ = last_sent_packet_;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSERVATION:
|
||||
case MEDIUM_GROWTH:
|
||||
if (is_round_start) {
|
||||
recovery_state_ = GROWTH;
|
||||
}
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case GROWTH:
|
||||
// Exit recovery if appropriate.
|
||||
if (!has_losses &&
|
||||
(!end_recovery_at_ || last_acked_packet > *end_recovery_at_)) {
|
||||
recovery_state_ = NOT_IN_RECOVERY;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BbrNetworkController::UpdateAckAggregationBytes(
|
||||
Timestamp ack_time,
|
||||
DataSize newly_acked_bytes) {
|
||||
if (!aggregation_epoch_start_time_) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Received feedback before information about sent packets.";
|
||||
RTC_DCHECK(aggregation_epoch_start_time_.has_value());
|
||||
return;
|
||||
}
|
||||
// Compute how many bytes are expected to be delivered, assuming max bandwidth
|
||||
// is correct.
|
||||
DataSize expected_bytes_acked =
|
||||
max_bandwidth_.GetBest() * (ack_time - *aggregation_epoch_start_time_);
|
||||
// Reset the current aggregation epoch as soon as the ack arrival rate is less
|
||||
// than or equal to the max bandwidth.
|
||||
if (aggregation_epoch_bytes_ <= expected_bytes_acked) {
|
||||
// Reset to start measuring a new aggregation epoch.
|
||||
aggregation_epoch_bytes_ = newly_acked_bytes;
|
||||
aggregation_epoch_start_time_ = ack_time;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute how many extra bytes were delivered vs max bandwidth.
|
||||
// Include the bytes most recently acknowledged to account for stretch acks.
|
||||
aggregation_epoch_bytes_ += newly_acked_bytes;
|
||||
max_ack_height_.Update(aggregation_epoch_bytes_ - expected_bytes_acked,
|
||||
round_trip_count_);
|
||||
}
|
||||
|
||||
void BbrNetworkController::CalculatePacingRate() {
|
||||
if (BandwidthEstimate().IsZero()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataRate target_rate = pacing_gain_ * BandwidthEstimate();
|
||||
if (config_.rate_based_recovery && InRecovery()) {
|
||||
pacing_rate_ = pacing_gain_ * max_bandwidth_.GetThirdBest();
|
||||
}
|
||||
if (is_at_full_bandwidth_) {
|
||||
pacing_rate_ = target_rate;
|
||||
return;
|
||||
}
|
||||
|
||||
// Pace at the rate of initial_window / RTT as soon as RTT measurements are
|
||||
// available.
|
||||
if (pacing_rate_.IsZero() && !rtt_stats_.min_rtt().IsZero()) {
|
||||
pacing_rate_ = initial_congestion_window_ / rtt_stats_.min_rtt();
|
||||
return;
|
||||
}
|
||||
// Slow the pacing rate in STARTUP once loss has ever been detected.
|
||||
const bool has_ever_detected_loss = end_recovery_at_.has_value();
|
||||
if (config_.slower_startup && has_ever_detected_loss) {
|
||||
pacing_rate_ = kStartupAfterLossGain * BandwidthEstimate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not decrease the pacing rate during the startup.
|
||||
pacing_rate_ = std::max(pacing_rate_, target_rate);
|
||||
}
|
||||
|
||||
void BbrNetworkController::CalculateCongestionWindow(DataSize bytes_acked) {
|
||||
if (mode_ == PROBE_RTT) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataSize target_window = GetTargetCongestionWindow(congestion_window_gain_);
|
||||
|
||||
if (rtt_variance_weight_ > 0.f && !BandwidthEstimate().IsZero()) {
|
||||
target_window += rtt_variance_weight_ * rtt_stats_.mean_deviation() *
|
||||
BandwidthEstimate();
|
||||
} else if (max_aggregation_bytes_multiplier_ > 0 && is_at_full_bandwidth_) {
|
||||
// Subtracting only half the bytes_acked_since_queue_drained ensures sending
|
||||
// doesn't completely stop for a long period of time if the queue hasn't
|
||||
// been drained recently.
|
||||
if (max_aggregation_bytes_multiplier_ * max_ack_height_.GetBest() >
|
||||
bytes_acked_since_queue_drained_ / 2) {
|
||||
target_window +=
|
||||
max_aggregation_bytes_multiplier_ * max_ack_height_.GetBest() -
|
||||
bytes_acked_since_queue_drained_ / 2;
|
||||
}
|
||||
} else if (is_at_full_bandwidth_) {
|
||||
target_window += max_ack_height_.GetBest();
|
||||
}
|
||||
|
||||
// Instead of immediately setting the target CWND as the new one, BBR grows
|
||||
// the CWND towards |target_window| by only increasing it |bytes_acked| at a
|
||||
// time.
|
||||
if (is_at_full_bandwidth_) {
|
||||
congestion_window_ =
|
||||
std::min(target_window, congestion_window_ + bytes_acked);
|
||||
} else if (congestion_window_ < target_window ||
|
||||
sampler_->total_data_acked() < initial_congestion_window_) {
|
||||
// If the connection is not yet out of startup phase, do not decrease the
|
||||
// window.
|
||||
congestion_window_ = congestion_window_ + bytes_acked;
|
||||
}
|
||||
|
||||
// Enforce the limits on the congestion window.
|
||||
congestion_window_ = std::max(congestion_window_, min_congestion_window_);
|
||||
congestion_window_ = std::min(congestion_window_, max_congestion_window_);
|
||||
}
|
||||
|
||||
void BbrNetworkController::CalculateRecoveryWindow(DataSize bytes_acked,
|
||||
DataSize bytes_lost,
|
||||
DataSize bytes_in_flight) {
|
||||
if (config_.rate_based_recovery ||
|
||||
(config_.rate_based_startup && mode_ == STARTUP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (recovery_state_ == NOT_IN_RECOVERY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the initial recovery window.
|
||||
if (recovery_window_.IsZero()) {
|
||||
recovery_window_ = bytes_in_flight + bytes_acked;
|
||||
recovery_window_ = std::max(min_congestion_window_, recovery_window_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove losses from the recovery window, while accounting for a potential
|
||||
// integer underflow.
|
||||
recovery_window_ = recovery_window_ >= bytes_lost
|
||||
? recovery_window_ - bytes_lost
|
||||
: kMaxSegmentSize;
|
||||
|
||||
// In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH,
|
||||
// release additional |bytes_acked| to achieve a slow-start-like behavior.
|
||||
// In MEDIUM_GROWTH, release |bytes_acked| / 2 to split the difference.
|
||||
if (recovery_state_ == GROWTH) {
|
||||
recovery_window_ += bytes_acked;
|
||||
} else if (recovery_state_ == MEDIUM_GROWTH) {
|
||||
recovery_window_ += bytes_acked / 2;
|
||||
}
|
||||
|
||||
// Sanity checks. Ensure that we always allow to send at least
|
||||
// |bytes_acked| in response.
|
||||
recovery_window_ = std::max(recovery_window_, bytes_in_flight + bytes_acked);
|
||||
recovery_window_ = std::max(min_congestion_window_, recovery_window_);
|
||||
}
|
||||
|
||||
void BbrNetworkController::OnApplicationLimited(DataSize bytes_in_flight) {
|
||||
if (bytes_in_flight >= GetCongestionWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
app_limited_since_last_probe_rtt_ = true;
|
||||
sampler_->OnAppLimited();
|
||||
|
||||
RTC_LOG(LS_INFO) << "Becoming application limited. Last sent packet: "
|
||||
<< last_sent_packet_
|
||||
<< ", CWND: " << ToString(GetCongestionWindow());
|
||||
}
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,397 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// BBR (Bottleneck Bandwidth and RTT) congestion control algorithm.
|
||||
// Based on the Quic BBR implementation in Chromium.
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_BBR_NETWORK_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_BBR_NETWORK_CONTROLLER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/transport/network_control.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "modules/congestion_controller/bbr/bandwidth_sampler.h"
|
||||
#include "modules/congestion_controller/bbr/loss_rate_filter.h"
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
#include "modules/congestion_controller/bbr/windowed_filter.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/experiments/field_trial_units.h"
|
||||
#include "rtc_base/random.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
typedef int64_t BbrRoundTripCount;
|
||||
|
||||
// BbrSender implements BBR congestion control algorithm. BBR aims to estimate
|
||||
// the current available Bottleneck Bandwidth and RTT (hence the name), and
|
||||
// regulates the pacing rate and the size of the congestion window based on
|
||||
// those signals.
|
||||
//
|
||||
// BBR relies on pacing in order to function properly. Do not use BBR when
|
||||
// pacing is disabled.
|
||||
class BbrNetworkController : public NetworkControllerInterface {
|
||||
public:
|
||||
enum Mode {
|
||||
// Startup phase of the connection.
|
||||
STARTUP,
|
||||
// After achieving the highest possible bandwidth during the startup, lower
|
||||
// the pacing rate in order to drain the queue.
|
||||
DRAIN,
|
||||
// Cruising mode.
|
||||
PROBE_BW,
|
||||
// Temporarily slow down sending in order to empty the buffer and measure
|
||||
// the real minimum RTT.
|
||||
PROBE_RTT,
|
||||
};
|
||||
|
||||
// Indicates how the congestion control limits the amount of bytes in flight.
|
||||
enum RecoveryState {
|
||||
// Do not limit.
|
||||
NOT_IN_RECOVERY = 0,
|
||||
// Allow an extra outstanding byte for each byte acknowledged.
|
||||
CONSERVATION = 1,
|
||||
// Allow 1.5 extra outstanding bytes for each byte acknowledged.
|
||||
MEDIUM_GROWTH = 2,
|
||||
// Allow two extra outstanding bytes for each byte acknowledged (slow
|
||||
// start).
|
||||
GROWTH = 3
|
||||
};
|
||||
struct BbrControllerConfig {
|
||||
FieldTrialParameter<double> probe_bw_pacing_gain_offset;
|
||||
FieldTrialParameter<double> encoder_rate_gain;
|
||||
FieldTrialParameter<double> encoder_rate_gain_in_probe_rtt;
|
||||
// RTT delta to determine if startup should be exited due to increased RTT.
|
||||
FieldTrialParameter<TimeDelta> exit_startup_rtt_threshold;
|
||||
|
||||
FieldTrialParameter<DataSize> initial_congestion_window;
|
||||
FieldTrialParameter<DataSize> min_congestion_window;
|
||||
FieldTrialParameter<DataSize> max_congestion_window;
|
||||
|
||||
FieldTrialParameter<double> probe_rtt_congestion_window_gain;
|
||||
FieldTrialParameter<bool> pacing_rate_as_target;
|
||||
|
||||
// Configurable in QUIC BBR:
|
||||
FieldTrialParameter<bool> exit_startup_on_loss;
|
||||
// The number of RTTs to stay in STARTUP mode. Defaults to 3.
|
||||
FieldTrialParameter<int> num_startup_rtts;
|
||||
// When true, recovery is rate based rather than congestion window based.
|
||||
FieldTrialParameter<bool> rate_based_recovery;
|
||||
FieldTrialParameter<double> max_aggregation_bytes_multiplier;
|
||||
// When true, pace at 1.5x and disable packet conservation in STARTUP.
|
||||
FieldTrialParameter<bool> slower_startup;
|
||||
// When true, disables packet conservation in STARTUP.
|
||||
FieldTrialParameter<bool> rate_based_startup;
|
||||
// Used as the initial packet conservation mode when first entering
|
||||
// recovery.
|
||||
FieldTrialEnum<RecoveryState> initial_conservation_in_startup;
|
||||
// If true, will not exit low gain mode until bytes_in_flight drops below
|
||||
// BDP or it's time for high gain mode.
|
||||
FieldTrialParameter<bool> fully_drain_queue;
|
||||
|
||||
FieldTrialParameter<double> max_ack_height_window_multiplier;
|
||||
// If true, use a CWND of 0.75*BDP during probe_rtt instead of 4 packets.
|
||||
FieldTrialParameter<bool> probe_rtt_based_on_bdp;
|
||||
// If true, skip probe_rtt and update the timestamp of the existing min_rtt
|
||||
// to now if min_rtt over the last cycle is within 12.5% of the current
|
||||
// min_rtt. Even if the min_rtt is 12.5% too low, the 25% gain cycling and
|
||||
// 2x CWND gain should overcome an overly small min_rtt.
|
||||
FieldTrialParameter<bool> probe_rtt_skipped_if_similar_rtt;
|
||||
// If true, disable PROBE_RTT entirely as long as the connection was
|
||||
// recently app limited.
|
||||
FieldTrialParameter<bool> probe_rtt_disabled_if_app_limited;
|
||||
|
||||
explicit BbrControllerConfig(std::string field_trial);
|
||||
~BbrControllerConfig();
|
||||
BbrControllerConfig(const BbrControllerConfig&);
|
||||
static BbrControllerConfig FromTrial();
|
||||
};
|
||||
|
||||
// Debug state can be exported in order to troubleshoot potential congestion
|
||||
// control issues.
|
||||
struct DebugState {
|
||||
explicit DebugState(const BbrNetworkController& sender);
|
||||
DebugState(const DebugState& state);
|
||||
|
||||
Mode mode;
|
||||
DataRate max_bandwidth;
|
||||
BbrRoundTripCount round_trip_count;
|
||||
int gain_cycle_index;
|
||||
DataSize congestion_window;
|
||||
|
||||
bool is_at_full_bandwidth;
|
||||
DataRate bandwidth_at_last_round;
|
||||
BbrRoundTripCount rounds_without_bandwidth_gain;
|
||||
|
||||
TimeDelta min_rtt;
|
||||
Timestamp min_rtt_timestamp;
|
||||
|
||||
RecoveryState recovery_state;
|
||||
DataSize recovery_window;
|
||||
|
||||
bool last_sample_is_app_limited;
|
||||
int64_t end_of_app_limited_phase;
|
||||
};
|
||||
|
||||
explicit BbrNetworkController(NetworkControllerConfig config);
|
||||
~BbrNetworkController() override;
|
||||
|
||||
// NetworkControllerInterface
|
||||
NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override;
|
||||
NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override;
|
||||
NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override;
|
||||
NetworkControlUpdate OnSentPacket(SentPacket msg) override;
|
||||
NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override;
|
||||
NetworkControlUpdate OnTargetRateConstraints(
|
||||
TargetRateConstraints msg) override;
|
||||
NetworkControlUpdate OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback msg) override;
|
||||
|
||||
// Part of remote bitrate estimation api, not implemented for BBR
|
||||
NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override;
|
||||
NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override;
|
||||
NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override;
|
||||
NetworkControlUpdate OnReceivedPacket(ReceivedPacket msg) override;
|
||||
NetworkControlUpdate OnNetworkStateEstimate(
|
||||
NetworkStateEstimate msg) override;
|
||||
|
||||
NetworkControlUpdate CreateRateUpdate(Timestamp at_time) const;
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool InSlowStart() const;
|
||||
bool InRecovery() const;
|
||||
bool IsProbingForMoreBandwidth() const;
|
||||
|
||||
bool CanSend(DataSize bytes_in_flight);
|
||||
DataRate PacingRate() const;
|
||||
DataRate BandwidthEstimate() const;
|
||||
DataSize GetCongestionWindow() const;
|
||||
|
||||
double GetPacingGain(int round_offset) const;
|
||||
|
||||
void OnApplicationLimited(DataSize bytes_in_flight);
|
||||
// End implementation of SendAlgorithmInterface.
|
||||
|
||||
typedef WindowedFilter<DataRate,
|
||||
MaxFilter<DataRate>,
|
||||
BbrRoundTripCount,
|
||||
BbrRoundTripCount>
|
||||
MaxBandwidthFilter;
|
||||
|
||||
typedef WindowedFilter<TimeDelta,
|
||||
MaxFilter<TimeDelta>,
|
||||
BbrRoundTripCount,
|
||||
BbrRoundTripCount>
|
||||
MaxAckDelayFilter;
|
||||
|
||||
typedef WindowedFilter<DataSize,
|
||||
MaxFilter<DataSize>,
|
||||
BbrRoundTripCount,
|
||||
BbrRoundTripCount>
|
||||
MaxAckHeightFilter;
|
||||
|
||||
// Returns the current estimate of the RTT of the connection. Outside of the
|
||||
// edge cases, this is minimum RTT.
|
||||
TimeDelta GetMinRtt() const;
|
||||
// Returns whether the connection has achieved full bandwidth required to exit
|
||||
// the slow start.
|
||||
bool IsAtFullBandwidth() const;
|
||||
// Computes the target congestion window using the specified gain.
|
||||
DataSize GetTargetCongestionWindow(double gain) const;
|
||||
// The target congestion window during PROBE_RTT.
|
||||
DataSize ProbeRttCongestionWindow() const;
|
||||
// Returns true if the current min_rtt should be kept and we should not enter
|
||||
// PROBE_RTT immediately.
|
||||
bool ShouldExtendMinRttExpiry() const;
|
||||
|
||||
// Enters the STARTUP mode.
|
||||
void EnterStartupMode();
|
||||
// Enters the PROBE_BW mode.
|
||||
void EnterProbeBandwidthMode(Timestamp now);
|
||||
|
||||
// Discards the lost packets from BandwidthSampler state.
|
||||
void DiscardLostPackets(const std::vector<PacketResult>& lost_packets);
|
||||
// Updates the round-trip counter if a round-trip has passed. Returns true if
|
||||
// the counter has been advanced.
|
||||
// |last_acked_packet| is the sequence number of the last acked packet.
|
||||
bool UpdateRoundTripCounter(int64_t last_acked_packet);
|
||||
// Updates the current bandwidth and min_rtt estimate based on the samples for
|
||||
// the received acknowledgements. Returns true if min_rtt has expired.
|
||||
bool UpdateBandwidthAndMinRtt(Timestamp now,
|
||||
const std::vector<PacketResult>& acked_packets);
|
||||
// Updates the current gain used in PROBE_BW mode.
|
||||
void UpdateGainCyclePhase(Timestamp now,
|
||||
DataSize prior_in_flight,
|
||||
bool has_losses);
|
||||
// Tracks for how many round-trips the bandwidth has not increased
|
||||
// significantly.
|
||||
void CheckIfFullBandwidthReached();
|
||||
// Transitions from STARTUP to DRAIN and from DRAIN to PROBE_BW if
|
||||
// appropriate.
|
||||
void MaybeExitStartupOrDrain(const TransportPacketsFeedback&);
|
||||
// Decides whether to enter or exit PROBE_RTT.
|
||||
void MaybeEnterOrExitProbeRtt(const TransportPacketsFeedback& msg,
|
||||
bool is_round_start,
|
||||
bool min_rtt_expired);
|
||||
// Determines whether BBR needs to enter, exit or advance state of the
|
||||
// recovery.
|
||||
void UpdateRecoveryState(int64_t last_acked_packet,
|
||||
bool has_losses,
|
||||
bool is_round_start);
|
||||
|
||||
// Updates the ack aggregation max filter in bytes.
|
||||
void UpdateAckAggregationBytes(Timestamp ack_time,
|
||||
DataSize newly_acked_bytes);
|
||||
|
||||
// Determines the appropriate pacing rate for the connection.
|
||||
void CalculatePacingRate();
|
||||
// Determines the appropriate congestion window for the connection.
|
||||
void CalculateCongestionWindow(DataSize bytes_acked);
|
||||
// Determines the approriate window that constrains the
|
||||
// in-flight during recovery.
|
||||
void CalculateRecoveryWindow(DataSize bytes_acked,
|
||||
DataSize bytes_lost,
|
||||
DataSize bytes_in_flight);
|
||||
|
||||
BbrControllerConfig config_;
|
||||
|
||||
RttStats rtt_stats_;
|
||||
webrtc::Random random_;
|
||||
LossRateFilter loss_rate_;
|
||||
|
||||
absl::optional<TargetRateConstraints> constraints_;
|
||||
|
||||
Mode mode_;
|
||||
|
||||
// Bandwidth sampler provides BBR with the bandwidth measurements at
|
||||
// individual points.
|
||||
std::unique_ptr<BandwidthSampler> sampler_;
|
||||
|
||||
// The number of the round trips that have occurred during the connection.
|
||||
BbrRoundTripCount round_trip_count_ = 0;
|
||||
|
||||
// The packet number of the most recently sent packet.
|
||||
int64_t last_sent_packet_;
|
||||
// Acknowledgement of any packet after |current_round_trip_end_| will cause
|
||||
// the round trip counter to advance.
|
||||
int64_t current_round_trip_end_;
|
||||
|
||||
// The filter that tracks the maximum bandwidth over the multiple recent
|
||||
// round-trips.
|
||||
MaxBandwidthFilter max_bandwidth_;
|
||||
|
||||
DataRate default_bandwidth_;
|
||||
|
||||
// Tracks the maximum number of bytes acked faster than the sending rate.
|
||||
MaxAckHeightFilter max_ack_height_;
|
||||
|
||||
// The time this aggregation started and the number of bytes acked during it.
|
||||
absl::optional<Timestamp> aggregation_epoch_start_time_;
|
||||
DataSize aggregation_epoch_bytes_;
|
||||
|
||||
// The number of bytes acknowledged since the last time bytes in flight
|
||||
// dropped below the target window.
|
||||
DataSize bytes_acked_since_queue_drained_;
|
||||
|
||||
// The muliplier for calculating the max amount of extra CWND to add to
|
||||
// compensate for ack aggregation.
|
||||
double max_aggregation_bytes_multiplier_;
|
||||
|
||||
// Minimum RTT estimate. Automatically expires within 10 seconds (and
|
||||
// triggers PROBE_RTT mode) if no new value is sampled during that period.
|
||||
TimeDelta min_rtt_;
|
||||
TimeDelta last_rtt_;
|
||||
// The time at which the current value of |min_rtt_| was assigned.
|
||||
Timestamp min_rtt_timestamp_;
|
||||
|
||||
// The maximum allowed number of bytes in flight.
|
||||
DataSize congestion_window_;
|
||||
|
||||
// The initial value of the |congestion_window_|.
|
||||
DataSize initial_congestion_window_;
|
||||
|
||||
// The smallest value the |congestion_window_| can achieve.
|
||||
DataSize min_congestion_window_;
|
||||
|
||||
// The largest value the |congestion_window_| can achieve.
|
||||
DataSize max_congestion_window_;
|
||||
|
||||
// The current pacing rate of the connection.
|
||||
DataRate pacing_rate_;
|
||||
|
||||
// The gain currently applied to the pacing rate.
|
||||
double pacing_gain_;
|
||||
// The gain currently applied to the congestion window.
|
||||
double congestion_window_gain_;
|
||||
|
||||
// The gain used for the congestion window during PROBE_BW. Latched from
|
||||
// quic_bbr_cwnd_gain flag.
|
||||
const double congestion_window_gain_constant_;
|
||||
// The coefficient by which mean RTT variance is added to the congestion
|
||||
// window. Latched from quic_bbr_rtt_variation_weight flag.
|
||||
const double rtt_variance_weight_;
|
||||
|
||||
// Number of round-trips in PROBE_BW mode, used for determining the current
|
||||
// pacing gain cycle.
|
||||
int cycle_current_offset_;
|
||||
// The time at which the last pacing gain cycle was started.
|
||||
Timestamp last_cycle_start_;
|
||||
|
||||
// Indicates whether the connection has reached the full bandwidth mode.
|
||||
bool is_at_full_bandwidth_;
|
||||
// Number of rounds during which there was no significant bandwidth increase.
|
||||
BbrRoundTripCount rounds_without_bandwidth_gain_;
|
||||
// The bandwidth compared to which the increase is measured.
|
||||
DataRate bandwidth_at_last_round_;
|
||||
|
||||
// Set to true upon exiting quiescence.
|
||||
bool exiting_quiescence_;
|
||||
|
||||
// Time at which PROBE_RTT has to be exited. Setting it to zero indicates
|
||||
// that the time is yet unknown as the number of packets in flight has not
|
||||
// reached the required value.
|
||||
absl::optional<Timestamp> exit_probe_rtt_at_;
|
||||
// Indicates whether a round-trip has passed since PROBE_RTT became active.
|
||||
bool probe_rtt_round_passed_;
|
||||
|
||||
// Indicates whether the most recent bandwidth sample was marked as
|
||||
// app-limited.
|
||||
bool last_sample_is_app_limited_;
|
||||
|
||||
// Current state of recovery.
|
||||
RecoveryState recovery_state_;
|
||||
// Receiving acknowledgement of a packet after |end_recovery_at_| will cause
|
||||
// BBR to exit the recovery mode. A set value indicates at least one
|
||||
// loss has been detected, so it must not be reset.
|
||||
absl::optional<int64_t> end_recovery_at_;
|
||||
// A window used to limit the number of bytes in flight during loss recovery.
|
||||
DataSize recovery_window_;
|
||||
|
||||
bool app_limited_since_last_probe_rtt_;
|
||||
TimeDelta min_rtt_since_last_probe_rtt_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(BbrNetworkController);
|
||||
};
|
||||
|
||||
// Used in log output
|
||||
std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982)
|
||||
std::ostream& os, // no-presubmit-check TODO(webrtc:8982)
|
||||
const BbrNetworkController::Mode& mode);
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_BBR_NETWORK_CONTROLLER_H_
|
||||
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/bbr_network_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "modules/congestion_controller/bbr/bbr_factory.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
#include "test/scenario/scenario.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Field;
|
||||
using ::testing::Ge;
|
||||
using ::testing::Le;
|
||||
using ::testing::Matcher;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Property;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
const DataRate kInitialBitrate = DataRate::KilobitsPerSec(60);
|
||||
const Timestamp kDefaultStartTime = Timestamp::Millis(10000000);
|
||||
|
||||
constexpr double kDataRateMargin = 0.3;
|
||||
constexpr double kMinDataRateFactor = 1 - kDataRateMargin;
|
||||
constexpr double kMaxDataRateFactor = 1 + kDataRateMargin;
|
||||
inline Matcher<TargetTransferRate> TargetRateCloseTo(DataRate rate) {
|
||||
DataRate min_data_rate = rate * kMinDataRateFactor;
|
||||
DataRate max_data_rate = rate * kMaxDataRateFactor;
|
||||
return Field(&TargetTransferRate::target_rate,
|
||||
AllOf(Ge(min_data_rate), Le(max_data_rate)));
|
||||
}
|
||||
|
||||
NetworkControllerConfig InitialConfig(
|
||||
int starting_bandwidth_kbps = kInitialBitrate.kbps(),
|
||||
int min_data_rate_kbps = 0,
|
||||
int max_data_rate_kbps = 5 * kInitialBitrate.kbps()) {
|
||||
NetworkControllerConfig config;
|
||||
config.constraints.at_time = kDefaultStartTime;
|
||||
config.constraints.min_data_rate =
|
||||
DataRate::KilobitsPerSec(min_data_rate_kbps);
|
||||
config.constraints.max_data_rate =
|
||||
DataRate::KilobitsPerSec(max_data_rate_kbps);
|
||||
config.constraints.starting_rate =
|
||||
DataRate::KilobitsPerSec(starting_bandwidth_kbps);
|
||||
return config;
|
||||
}
|
||||
|
||||
ProcessInterval InitialProcessInterval() {
|
||||
ProcessInterval process_interval;
|
||||
process_interval.at_time = kDefaultStartTime;
|
||||
return process_interval;
|
||||
}
|
||||
|
||||
NetworkRouteChange CreateRouteChange(Timestamp at_time,
|
||||
DataRate start_rate,
|
||||
DataRate min_rate = DataRate::Zero(),
|
||||
DataRate max_rate = DataRate::Infinity()) {
|
||||
NetworkRouteChange route_change;
|
||||
route_change.at_time = at_time;
|
||||
route_change.constraints.at_time = at_time;
|
||||
route_change.constraints.min_data_rate = min_rate;
|
||||
route_change.constraints.max_data_rate = max_rate;
|
||||
route_change.constraints.starting_rate = start_rate;
|
||||
return route_change;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class BbrNetworkControllerTest : public ::testing::Test {
|
||||
protected:
|
||||
BbrNetworkControllerTest() {}
|
||||
~BbrNetworkControllerTest() override {}
|
||||
};
|
||||
|
||||
TEST_F(BbrNetworkControllerTest, SendsConfigurationOnFirstProcess) {
|
||||
std::unique_ptr<NetworkControllerInterface> controller_;
|
||||
controller_.reset(new bbr::BbrNetworkController(InitialConfig()));
|
||||
|
||||
NetworkControlUpdate update =
|
||||
controller_->OnProcessInterval(InitialProcessInterval());
|
||||
EXPECT_THAT(*update.target_rate, TargetRateCloseTo(kInitialBitrate));
|
||||
EXPECT_THAT(*update.pacer_config,
|
||||
Property(&PacerConfig::data_rate, Ge(kInitialBitrate)));
|
||||
EXPECT_THAT(*update.congestion_window, Property(&DataSize::IsFinite, true));
|
||||
}
|
||||
|
||||
TEST_F(BbrNetworkControllerTest, SendsConfigurationOnNetworkRouteChanged) {
|
||||
std::unique_ptr<NetworkControllerInterface> controller_;
|
||||
controller_.reset(new bbr::BbrNetworkController(InitialConfig()));
|
||||
|
||||
NetworkControlUpdate update =
|
||||
controller_->OnProcessInterval(InitialProcessInterval());
|
||||
EXPECT_TRUE(update.target_rate.has_value());
|
||||
EXPECT_TRUE(update.pacer_config.has_value());
|
||||
EXPECT_TRUE(update.congestion_window.has_value());
|
||||
|
||||
DataRate new_bitrate = DataRate::BitsPerSec(200000);
|
||||
update = controller_->OnNetworkRouteChange(
|
||||
CreateRouteChange(kDefaultStartTime, new_bitrate));
|
||||
EXPECT_THAT(*update.target_rate, TargetRateCloseTo(new_bitrate));
|
||||
EXPECT_THAT(*update.pacer_config,
|
||||
Property(&PacerConfig::data_rate, Ge(kInitialBitrate)));
|
||||
EXPECT_TRUE(update.congestion_window.has_value());
|
||||
}
|
||||
|
||||
// Bandwidth estimation is updated when feedbacks are received.
|
||||
// Feedbacks which show an increasing delay cause the estimation to be reduced.
|
||||
TEST_F(BbrNetworkControllerTest, UpdatesTargetSendRate) {
|
||||
BbrNetworkControllerFactory factory;
|
||||
Scenario s("bbr_unit/updates_rate", false);
|
||||
CallClientConfig config;
|
||||
config.transport.cc_factory = &factory;
|
||||
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
|
||||
config.transport.rates.max_rate = DataRate::KilobitsPerSec(1500);
|
||||
config.transport.rates.start_rate = DataRate::KilobitsPerSec(300);
|
||||
auto send_net = s.CreateMutableSimulationNode([](NetworkSimulationConfig* c) {
|
||||
c->bandwidth = DataRate::KilobitsPerSec(500);
|
||||
c->delay = TimeDelta::Millis(100);
|
||||
c->loss_rate = 0.0;
|
||||
});
|
||||
auto ret_net = s.CreateMutableSimulationNode(
|
||||
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
|
||||
auto* client = s.CreateClient("send", config);
|
||||
const DataSize kOverhead = DataSize::Bytes(38); // IPV4 + UDP + SRTP
|
||||
auto routes = s.CreateRoutes(client, {send_net->node()}, kOverhead,
|
||||
s.CreateClient("recv", CallClientConfig()),
|
||||
{ret_net->node()}, kOverhead);
|
||||
s.CreateVideoStream(routes->forward(), VideoStreamConfig());
|
||||
|
||||
s.RunFor(TimeDelta::Seconds(25));
|
||||
EXPECT_NEAR(client->send_bandwidth().kbps(), 450, 100);
|
||||
|
||||
send_net->UpdateConfig([](NetworkSimulationConfig* c) {
|
||||
c->bandwidth = DataRate::KilobitsPerSec(800);
|
||||
c->delay = TimeDelta::Millis(100);
|
||||
});
|
||||
|
||||
s.RunFor(TimeDelta::Seconds(20));
|
||||
EXPECT_NEAR(client->send_bandwidth().kbps(), 750, 150);
|
||||
|
||||
send_net->UpdateConfig([](NetworkSimulationConfig* c) {
|
||||
c->bandwidth = DataRate::KilobitsPerSec(200);
|
||||
c->delay = TimeDelta::Millis(200);
|
||||
});
|
||||
ret_net->UpdateConfig(
|
||||
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(200); });
|
||||
|
||||
s.RunFor(TimeDelta::Seconds(35));
|
||||
EXPECT_NEAR(client->send_bandwidth().kbps(), 170, 50);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/data_transfer_tracker.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
DataTransferTracker::DataTransferTracker() {}
|
||||
|
||||
DataTransferTracker::~DataTransferTracker() {}
|
||||
|
||||
void DataTransferTracker::AddSample(DataSize size_delta,
|
||||
Timestamp send_time,
|
||||
Timestamp ack_time) {
|
||||
size_sum_ += size_delta;
|
||||
|
||||
RTC_DCHECK(samples_.empty() || ack_time >= samples_.back().ack_time);
|
||||
|
||||
if (!samples_.empty() && ack_time == samples_.back().ack_time) {
|
||||
samples_.back().send_time = send_time;
|
||||
samples_.back().size_sum = size_sum_;
|
||||
} else {
|
||||
Sample new_sample;
|
||||
new_sample.ack_time = ack_time;
|
||||
new_sample.send_time = send_time;
|
||||
new_sample.size_delta = size_delta;
|
||||
new_sample.size_sum = size_sum_;
|
||||
samples_.push_back(new_sample);
|
||||
}
|
||||
}
|
||||
|
||||
void DataTransferTracker::ClearOldSamples(Timestamp excluding_end) {
|
||||
while (!samples_.empty() && samples_.front().ack_time < excluding_end) {
|
||||
samples_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
DataTransferTracker::Result DataTransferTracker::GetRatesByAckTime(
|
||||
Timestamp covered_start,
|
||||
Timestamp including_end) {
|
||||
Result res;
|
||||
// Last sample before covered_start.
|
||||
const Sample* window_begin = nullptr;
|
||||
// Sample at end time or first sample after end time-
|
||||
const Sample* window_end = nullptr;
|
||||
// To handle the case when the first sample is after covered_start.
|
||||
if (samples_.front().ack_time < including_end)
|
||||
window_begin = &samples_.front();
|
||||
// To handle the case when the last sample is before including_end.
|
||||
if (samples_.back().ack_time > covered_start)
|
||||
window_end = &samples_.back();
|
||||
for (const auto& sample : samples_) {
|
||||
if (sample.ack_time < covered_start) {
|
||||
window_begin = &sample;
|
||||
} else if (sample.ack_time >= including_end) {
|
||||
window_end = &sample;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (window_begin != nullptr && window_end != nullptr) {
|
||||
res.acked_data = window_end->size_sum - window_begin->size_sum;
|
||||
res.send_timespan = window_end->send_time - window_begin->send_time;
|
||||
res.ack_timespan = window_end->ack_time - window_begin->ack_time;
|
||||
} else {
|
||||
res.acked_data = DataSize::Zero();
|
||||
res.ack_timespan = including_end - covered_start;
|
||||
res.send_timespan = TimeDelta::Zero();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_DATA_TRANSFER_TRACKER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_DATA_TRANSFER_TRACKER_H_
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
class DataTransferTracker {
|
||||
public:
|
||||
struct Result {
|
||||
TimeDelta ack_timespan = TimeDelta::Zero();
|
||||
TimeDelta send_timespan = TimeDelta::Zero();
|
||||
DataSize acked_data = DataSize::Zero();
|
||||
};
|
||||
DataTransferTracker();
|
||||
~DataTransferTracker();
|
||||
void AddSample(DataSize size_delta, Timestamp send_time, Timestamp ack_time);
|
||||
void ClearOldSamples(Timestamp excluding_end);
|
||||
|
||||
// Get the average data rate in the window that starts with the last ack which
|
||||
// comes before covered_start and ends at the first ack that comes after or at
|
||||
// including_end.
|
||||
Result GetRatesByAckTime(Timestamp covered_start, Timestamp including_end);
|
||||
|
||||
private:
|
||||
struct Sample {
|
||||
Timestamp ack_time = Timestamp::PlusInfinity();
|
||||
Timestamp send_time = Timestamp::PlusInfinity();
|
||||
DataSize size_delta = DataSize::Zero();
|
||||
DataSize size_sum = DataSize::Zero();
|
||||
};
|
||||
std::deque<Sample> samples_;
|
||||
DataSize size_sum_ = DataSize::Zero();
|
||||
};
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_DATA_TRANSFER_TRACKER_H_
|
||||
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/data_transfer_tracker.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace test {
|
||||
namespace {
|
||||
struct ResultForTest {
|
||||
int64_t ack_span_ms;
|
||||
int64_t send_span_ms;
|
||||
int64_t acked_bytes;
|
||||
};
|
||||
class DataTransferTrackerForTest : public DataTransferTracker {
|
||||
public:
|
||||
void AddSample(int bytes, int send_time_ms, int ack_time_ms) {
|
||||
DataTransferTracker::AddSample(DataSize::Bytes(bytes),
|
||||
Timestamp::Millis(send_time_ms),
|
||||
Timestamp::Millis(ack_time_ms));
|
||||
}
|
||||
|
||||
void ClearOldSamples(int excluding_end_ms) {
|
||||
DataTransferTracker::ClearOldSamples(Timestamp::Millis(excluding_end_ms));
|
||||
}
|
||||
ResultForTest GetRatesByAckTime(int covered_start_ms, int including_end_ms) {
|
||||
auto result = DataTransferTracker::GetRatesByAckTime(
|
||||
Timestamp::Millis(covered_start_ms),
|
||||
Timestamp::Millis(including_end_ms));
|
||||
return ResultForTest{result.ack_timespan.ms(), result.send_timespan.ms(),
|
||||
result.acked_data.bytes()};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DataTransferTrackerTest, TracksData) {
|
||||
DataTransferTrackerForTest calc;
|
||||
// Since we dont have any previous reference for the first packet, it won't be
|
||||
// counted.
|
||||
calc.AddSample(5555, 100000, 100100);
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100040, 100140);
|
||||
calc.AddSample(1000, 100060, 100160);
|
||||
|
||||
auto result = calc.GetRatesByAckTime(100000, 100200);
|
||||
EXPECT_EQ(result.acked_bytes, 3000);
|
||||
EXPECT_EQ(result.ack_span_ms, 60);
|
||||
EXPECT_EQ(result.send_span_ms, 60);
|
||||
}
|
||||
|
||||
TEST(DataTransferTrackerTest, CoversStartTime) {
|
||||
DataTransferTrackerForTest calc;
|
||||
calc.AddSample(5555, 100000, 100100);
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100040, 100140);
|
||||
calc.AddSample(1000, 100060, 100160);
|
||||
calc.AddSample(1000, 100080, 100180);
|
||||
|
||||
auto result = calc.GetRatesByAckTime(100140, 100200);
|
||||
EXPECT_EQ(result.acked_bytes, 3000);
|
||||
EXPECT_EQ(result.ack_span_ms, 60);
|
||||
EXPECT_EQ(result.send_span_ms, 60);
|
||||
}
|
||||
|
||||
TEST(DataTransferTrackerTest, IncludesEndExcludesPastEnd) {
|
||||
DataTransferTrackerForTest calc;
|
||||
calc.AddSample(5555, 100000, 100100);
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100040, 100140);
|
||||
calc.AddSample(1000, 100060, 100160);
|
||||
calc.AddSample(1000, 100080, 100180);
|
||||
|
||||
auto result = calc.GetRatesByAckTime(100120, 100160);
|
||||
EXPECT_EQ(result.acked_bytes, 3000);
|
||||
EXPECT_EQ(result.ack_span_ms, 60);
|
||||
EXPECT_EQ(result.send_span_ms, 60);
|
||||
}
|
||||
|
||||
TEST(DataTransferTrackerTest, AccumulatesDuplicates) {
|
||||
DataTransferTrackerForTest calc;
|
||||
calc.AddSample(5555, 100000, 100100);
|
||||
// Two packets at same time, should be accumulated.
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100060, 100160);
|
||||
// Two packets at same time, should be accumulated.
|
||||
calc.AddSample(1000, 100100, 100200);
|
||||
calc.AddSample(1000, 100100, 100200);
|
||||
calc.AddSample(1000, 100120, 100220);
|
||||
|
||||
auto result = calc.GetRatesByAckTime(100120, 100200);
|
||||
EXPECT_EQ(result.acked_bytes, 5000);
|
||||
EXPECT_EQ(result.ack_span_ms, 100);
|
||||
EXPECT_EQ(result.send_span_ms, 100);
|
||||
}
|
||||
|
||||
TEST(DataTransferTrackerTest, RemovesOldData) {
|
||||
DataTransferTrackerForTest calc;
|
||||
calc.AddSample(5555, 100000, 100100);
|
||||
calc.AddSample(1000, 100020, 100120);
|
||||
calc.AddSample(1000, 100040, 100140);
|
||||
calc.AddSample(1000, 100060, 100160);
|
||||
calc.AddSample(1000, 100080, 100180);
|
||||
{
|
||||
auto result = calc.GetRatesByAckTime(100120, 100200);
|
||||
EXPECT_EQ(result.acked_bytes, 4000);
|
||||
EXPECT_EQ(result.ack_span_ms, 80);
|
||||
EXPECT_EQ(result.send_span_ms, 80);
|
||||
}
|
||||
// Note that this operation means that the packet acked at 100140 will not be
|
||||
// counted any more, just used as time reference.
|
||||
calc.ClearOldSamples(100140);
|
||||
{
|
||||
auto result = calc.GetRatesByAckTime(100120, 100200);
|
||||
EXPECT_EQ(result.acked_bytes, 2000);
|
||||
EXPECT_EQ(result.ack_span_ms, 40);
|
||||
EXPECT_EQ(result.send_span_ms, 40);
|
||||
}
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "modules/congestion_controller/bbr/loss_rate_filter.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace {
|
||||
// From SendSideBandwidthEstimation.
|
||||
const int kLimitNumPackets = 20;
|
||||
// From RTCPSender video report interval.
|
||||
const int64_t kUpdateIntervalMs = 1000;
|
||||
} // namespace
|
||||
|
||||
LossRateFilter::LossRateFilter()
|
||||
: lost_packets_since_last_loss_update_(0),
|
||||
expected_packets_since_last_loss_update_(0),
|
||||
loss_rate_estimate_(0.0),
|
||||
next_loss_update_ms_(0) {}
|
||||
|
||||
void LossRateFilter::UpdateWithLossStatus(int64_t feedback_time,
|
||||
int packets_sent,
|
||||
int packets_lost) {
|
||||
lost_packets_since_last_loss_update_ += packets_lost;
|
||||
expected_packets_since_last_loss_update_ += packets_sent;
|
||||
|
||||
if (feedback_time >= next_loss_update_ms_ &&
|
||||
expected_packets_since_last_loss_update_ >= kLimitNumPackets) {
|
||||
int64_t lost = lost_packets_since_last_loss_update_;
|
||||
int64_t expected = expected_packets_since_last_loss_update_;
|
||||
loss_rate_estimate_ = static_cast<double>(lost) / expected;
|
||||
next_loss_update_ms_ = feedback_time + kUpdateIntervalMs;
|
||||
lost_packets_since_last_loss_update_ = 0;
|
||||
expected_packets_since_last_loss_update_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
double LossRateFilter::GetLossRate() const {
|
||||
return loss_rate_estimate_;
|
||||
}
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_LOSS_RATE_FILTER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_LOSS_RATE_FILTER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
// Loss rate filter based on the implementation in SendSideBandwidthEstimation
|
||||
// and the RTCPSender receiver report interval for video.
|
||||
class LossRateFilter {
|
||||
public:
|
||||
LossRateFilter();
|
||||
void UpdateWithLossStatus(int64_t feedback_time_ms,
|
||||
int packets_sent,
|
||||
int packets_lost);
|
||||
double GetLossRate() const;
|
||||
|
||||
private:
|
||||
int lost_packets_since_last_loss_update_;
|
||||
int expected_packets_since_last_loss_update_;
|
||||
double loss_rate_estimate_;
|
||||
int64_t next_loss_update_ms_;
|
||||
};
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_LOSS_RATE_FILTER_H_
|
||||
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "modules/congestion_controller/bbr/loss_rate_filter.h"
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
namespace {
|
||||
const Timestamp kTestStartTime = Timestamp::Seconds(100000);
|
||||
} // namespace
|
||||
|
||||
TEST(LossRateFilterTest, AccumulatesToOne) {
|
||||
LossRateFilter filter;
|
||||
Timestamp current_time = kTestStartTime;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
filter.UpdateWithLossStatus(current_time.ms(), 10, 10);
|
||||
current_time += TimeDelta::Seconds(1);
|
||||
}
|
||||
EXPECT_NEAR(filter.GetLossRate(), 1.0, 0.01);
|
||||
}
|
||||
|
||||
TEST(LossRateFilterTest, StaysAtZero) {
|
||||
LossRateFilter filter;
|
||||
Timestamp current_time = kTestStartTime;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
filter.UpdateWithLossStatus(current_time.ms(), 10, 0);
|
||||
current_time += TimeDelta::Seconds(1);
|
||||
}
|
||||
EXPECT_NEAR(filter.GetLossRate(), 0.0, 0.01);
|
||||
}
|
||||
|
||||
TEST(LossRateFilterTest, VariesWithInput) {
|
||||
LossRateFilter filter;
|
||||
Timestamp current_time = kTestStartTime;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
filter.UpdateWithLossStatus(current_time.ms(), 10, 10);
|
||||
current_time += TimeDelta::Seconds(1);
|
||||
}
|
||||
EXPECT_NEAR(filter.GetLossRate(), 1.0, 0.1);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
filter.UpdateWithLossStatus(current_time.ms(), 10, 0);
|
||||
current_time += TimeDelta::Seconds(1);
|
||||
}
|
||||
EXPECT_NEAR(filter.GetLossRate(), 0.0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LossRateFilterTest, DetectsChangingRate) {
|
||||
LossRateFilter filter;
|
||||
Timestamp current_time = kTestStartTime;
|
||||
for (int per_decile = 0; per_decile < 10; per_decile += 1) {
|
||||
// Update every 200 ms for 2 seconds
|
||||
for (int i = 0; i < 10; i++) {
|
||||
current_time += TimeDelta::Millis(200);
|
||||
filter.UpdateWithLossStatus(current_time.ms(), 10, per_decile);
|
||||
}
|
||||
EXPECT_NEAR(filter.GetLossRate(), per_decile / 10.0, 0.05);
|
||||
}
|
||||
}
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,220 +0,0 @@
|
||||
/*
|
||||
* 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_PACKET_NUMBER_INDEXED_QUEUE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
// PacketNumberIndexedQueue is a queue of mostly continuous numbered entries
|
||||
// which supports the following operations:
|
||||
// - adding elements to the end of the queue, or at some point past the end
|
||||
// - removing elements in any order
|
||||
// - retrieving elements
|
||||
// If all elements are inserted in order, all of the operations above are
|
||||
// amortized O(1) time.
|
||||
//
|
||||
// Internally, the data structure is a deque where each element is marked as
|
||||
// present or not. The deque starts at the lowest present index. Whenever an
|
||||
// element is removed, it's marked as not present, and the front of the deque is
|
||||
// cleared of elements that are not present.
|
||||
//
|
||||
// The tail of the queue is not cleared due to the assumption of entries being
|
||||
// inserted in order, though removing all elements of the queue will return it
|
||||
// to its initial state.
|
||||
//
|
||||
// Note that this data structure is inherently hazardous, since an addition of
|
||||
// just two entries will cause it to consume all of the memory available.
|
||||
// Because of that, it is not a general-purpose container and should not be used
|
||||
// as one.
|
||||
template <typename T>
|
||||
class PacketNumberIndexedQueue {
|
||||
public:
|
||||
PacketNumberIndexedQueue()
|
||||
: number_of_present_entries_(0), first_packet_(0) {}
|
||||
|
||||
// Retrieve the entry associated with the packet number. Returns the pointer
|
||||
// to the entry in case of success, or nullptr if the entry does not exist.
|
||||
T* GetEntry(int64_t packet_number);
|
||||
const T* GetEntry(int64_t packet_number) const;
|
||||
|
||||
// Inserts data associated |packet_number| into (or past) the end of the
|
||||
// queue, filling up the missing intermediate entries as necessary. Returns
|
||||
// true if the element has been inserted successfully, false if it was already
|
||||
// in the queue or inserted out of order.
|
||||
template <typename... Args>
|
||||
bool Emplace(int64_t packet_number, Args&&... args);
|
||||
|
||||
// Removes data associated with |packet_number| and frees the slots in the
|
||||
// queue as necessary.
|
||||
bool Remove(int64_t packet_number);
|
||||
|
||||
bool IsEmpty() const { return number_of_present_entries_ == 0; }
|
||||
|
||||
// Returns the number of entries in the queue.
|
||||
size_t number_of_present_entries() const {
|
||||
return number_of_present_entries_;
|
||||
}
|
||||
|
||||
// Returns the number of entries allocated in the underlying deque. This is
|
||||
// proportional to the memory usage of the queue.
|
||||
size_t entry_slots_used() const { return entries_.size(); }
|
||||
|
||||
// Packet number of the first entry in the queue. Zero if the queue is empty.
|
||||
int64_t first_packet() const { return first_packet_; }
|
||||
|
||||
// Packet number of the last entry ever inserted in the queue. Note that the
|
||||
// entry in question may have already been removed. Zero if the queue is
|
||||
// empty.
|
||||
int64_t last_packet() const {
|
||||
if (IsEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return first_packet_ + entries_.size() - 1;
|
||||
}
|
||||
|
||||
private:
|
||||
// Wrapper around T used to mark whether the entry is actually in the map.
|
||||
struct EntryWrapper {
|
||||
T data;
|
||||
bool present;
|
||||
|
||||
EntryWrapper() : data(), present(false) {}
|
||||
|
||||
template <typename... Args>
|
||||
explicit EntryWrapper(Args&&... args)
|
||||
: data(std::forward<Args>(args)...), present(true) {}
|
||||
};
|
||||
|
||||
// Cleans up unused slots in the front after removing an element.
|
||||
void Cleanup();
|
||||
|
||||
const EntryWrapper* GetEntryWrapper(int64_t offset) const;
|
||||
EntryWrapper* GetEntryWrapper(int64_t offset) {
|
||||
const auto* const_this = this;
|
||||
return const_cast<EntryWrapper*>(const_this->GetEntryWrapper(offset));
|
||||
}
|
||||
|
||||
std::deque<EntryWrapper> entries_;
|
||||
size_t number_of_present_entries_;
|
||||
int64_t first_packet_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T* PacketNumberIndexedQueue<T>::GetEntry(int64_t packet_number) {
|
||||
EntryWrapper* entry = GetEntryWrapper(packet_number);
|
||||
if (entry == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return &entry->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* PacketNumberIndexedQueue<T>::GetEntry(int64_t packet_number) const {
|
||||
const EntryWrapper* entry = GetEntryWrapper(packet_number);
|
||||
if (entry == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return &entry->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename... Args>
|
||||
bool PacketNumberIndexedQueue<T>::Emplace(int64_t packet_number,
|
||||
Args&&... args) {
|
||||
if (IsEmpty()) {
|
||||
RTC_DCHECK(entries_.empty());
|
||||
RTC_DCHECK_EQ(0u, first_packet_);
|
||||
|
||||
entries_.emplace_back(std::forward<Args>(args)...);
|
||||
number_of_present_entries_ = 1;
|
||||
first_packet_ = packet_number;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not allow insertion out-of-order.
|
||||
if (packet_number <= last_packet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle potentially missing elements.
|
||||
int64_t offset = packet_number - first_packet_;
|
||||
if (offset > static_cast<int64_t>(entries_.size())) {
|
||||
entries_.resize(offset);
|
||||
}
|
||||
|
||||
number_of_present_entries_++;
|
||||
entries_.emplace_back(std::forward<Args>(args)...);
|
||||
RTC_DCHECK_EQ(packet_number, last_packet());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool PacketNumberIndexedQueue<T>::Remove(int64_t packet_number) {
|
||||
EntryWrapper* entry = GetEntryWrapper(packet_number);
|
||||
if (entry == nullptr) {
|
||||
return false;
|
||||
}
|
||||
entry->present = false;
|
||||
number_of_present_entries_--;
|
||||
|
||||
if (packet_number == first_packet()) {
|
||||
Cleanup();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PacketNumberIndexedQueue<T>::Cleanup() {
|
||||
while (!entries_.empty() && !entries_.front().present) {
|
||||
entries_.pop_front();
|
||||
first_packet_++;
|
||||
}
|
||||
if (entries_.empty()) {
|
||||
first_packet_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto PacketNumberIndexedQueue<T>::GetEntryWrapper(int64_t offset) const
|
||||
-> const EntryWrapper* {
|
||||
if (offset < first_packet_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
offset -= first_packet_;
|
||||
if (offset >= static_cast<int64_t>(entries_.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const EntryWrapper* entry = &entries_[offset];
|
||||
if (!entry->present) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_
|
||||
@ -1,185 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/packet_number_indexed_queue.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace {
|
||||
|
||||
class PacketNumberIndexedQueueTest : public ::testing::Test {
|
||||
public:
|
||||
PacketNumberIndexedQueueTest() {}
|
||||
|
||||
protected:
|
||||
PacketNumberIndexedQueue<std::string> queue_;
|
||||
};
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, InitialState) {
|
||||
EXPECT_TRUE(queue_.IsEmpty());
|
||||
EXPECT_EQ(0u, queue_.first_packet());
|
||||
EXPECT_EQ(0u, queue_.last_packet());
|
||||
EXPECT_EQ(0u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(0u, queue_.entry_slots_used());
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, InsertingContinuousElements) {
|
||||
ASSERT_TRUE(queue_.Emplace(1001, "one"));
|
||||
EXPECT_EQ("one", *queue_.GetEntry(1001));
|
||||
|
||||
ASSERT_TRUE(queue_.Emplace(1002, "two"));
|
||||
EXPECT_EQ("two", *queue_.GetEntry(1002));
|
||||
|
||||
EXPECT_FALSE(queue_.IsEmpty());
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(1002u, queue_.last_packet());
|
||||
EXPECT_EQ(2u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(2u, queue_.entry_slots_used());
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, InsertingOutOfOrder) {
|
||||
queue_.Emplace(1001, "one");
|
||||
|
||||
ASSERT_TRUE(queue_.Emplace(1003, "three"));
|
||||
EXPECT_EQ(nullptr, queue_.GetEntry(1002));
|
||||
EXPECT_EQ("three", *queue_.GetEntry(1003));
|
||||
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(1003u, queue_.last_packet());
|
||||
EXPECT_EQ(2u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(3u, queue_.entry_slots_used());
|
||||
|
||||
ASSERT_FALSE(queue_.Emplace(1002, "two"));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, InsertingIntoPast) {
|
||||
queue_.Emplace(1001, "one");
|
||||
EXPECT_FALSE(queue_.Emplace(1000, "zero"));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, InsertingDuplicate) {
|
||||
queue_.Emplace(1001, "one");
|
||||
EXPECT_FALSE(queue_.Emplace(1001, "one"));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, RemoveInTheMiddle) {
|
||||
queue_.Emplace(1001, "one");
|
||||
queue_.Emplace(1002, "two");
|
||||
queue_.Emplace(1003, "three");
|
||||
|
||||
ASSERT_TRUE(queue_.Remove(1002));
|
||||
EXPECT_EQ(nullptr, queue_.GetEntry(1002));
|
||||
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(1003u, queue_.last_packet());
|
||||
EXPECT_EQ(2u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(3u, queue_.entry_slots_used());
|
||||
|
||||
EXPECT_FALSE(queue_.Emplace(1002, "two"));
|
||||
EXPECT_TRUE(queue_.Emplace(1004, "four"));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, RemoveAtImmediateEdges) {
|
||||
queue_.Emplace(1001, "one");
|
||||
queue_.Emplace(1002, "two");
|
||||
queue_.Emplace(1003, "three");
|
||||
ASSERT_TRUE(queue_.Remove(1001));
|
||||
EXPECT_EQ(nullptr, queue_.GetEntry(1001));
|
||||
ASSERT_TRUE(queue_.Remove(1003));
|
||||
EXPECT_EQ(nullptr, queue_.GetEntry(1003));
|
||||
|
||||
EXPECT_EQ(1002u, queue_.first_packet());
|
||||
EXPECT_EQ(1003u, queue_.last_packet());
|
||||
EXPECT_EQ(1u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(2u, queue_.entry_slots_used());
|
||||
|
||||
EXPECT_TRUE(queue_.Emplace(1004, "four"));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, RemoveAtDistantFront) {
|
||||
queue_.Emplace(1001, "one");
|
||||
queue_.Emplace(1002, "one (kinda)");
|
||||
queue_.Emplace(2001, "two");
|
||||
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(2001u, queue_.last_packet());
|
||||
EXPECT_EQ(3u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(1001u, queue_.entry_slots_used());
|
||||
|
||||
ASSERT_TRUE(queue_.Remove(1002));
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(2001u, queue_.last_packet());
|
||||
EXPECT_EQ(2u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(1001u, queue_.entry_slots_used());
|
||||
|
||||
ASSERT_TRUE(queue_.Remove(1001));
|
||||
EXPECT_EQ(2001u, queue_.first_packet());
|
||||
EXPECT_EQ(2001u, queue_.last_packet());
|
||||
EXPECT_EQ(1u, queue_.number_of_present_entries());
|
||||
EXPECT_EQ(1u, queue_.entry_slots_used());
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, RemoveAtDistantBack) {
|
||||
queue_.Emplace(1001, "one");
|
||||
queue_.Emplace(2001, "two");
|
||||
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(2001u, queue_.last_packet());
|
||||
|
||||
ASSERT_TRUE(queue_.Remove(2001));
|
||||
EXPECT_EQ(1001u, queue_.first_packet());
|
||||
EXPECT_EQ(2001u, queue_.last_packet());
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, ClearAndRepopulate) {
|
||||
queue_.Emplace(1001, "one");
|
||||
queue_.Emplace(2001, "two");
|
||||
|
||||
ASSERT_TRUE(queue_.Remove(1001));
|
||||
ASSERT_TRUE(queue_.Remove(2001));
|
||||
EXPECT_TRUE(queue_.IsEmpty());
|
||||
EXPECT_EQ(0u, queue_.first_packet());
|
||||
EXPECT_EQ(0u, queue_.last_packet());
|
||||
|
||||
EXPECT_TRUE(queue_.Emplace(101, "one"));
|
||||
EXPECT_TRUE(queue_.Emplace(201, "two"));
|
||||
EXPECT_EQ(101u, queue_.first_packet());
|
||||
EXPECT_EQ(201u, queue_.last_packet());
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, FailToRemoveElementsThatNeverExisted) {
|
||||
ASSERT_FALSE(queue_.Remove(1000));
|
||||
queue_.Emplace(1001, "one");
|
||||
ASSERT_FALSE(queue_.Remove(1000));
|
||||
ASSERT_FALSE(queue_.Remove(1002));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, FailToRemoveElementsTwice) {
|
||||
queue_.Emplace(1001, "one");
|
||||
ASSERT_TRUE(queue_.Remove(1001));
|
||||
ASSERT_FALSE(queue_.Remove(1001));
|
||||
ASSERT_FALSE(queue_.Remove(1001));
|
||||
}
|
||||
|
||||
TEST_F(PacketNumberIndexedQueueTest, ConstGetter) {
|
||||
queue_.Emplace(1001, "one");
|
||||
const auto& const_queue = queue_;
|
||||
|
||||
EXPECT_EQ("one", *const_queue.GetEntry(1001));
|
||||
EXPECT_EQ(nullptr, const_queue.GetEntry(1002));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,97 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace {
|
||||
|
||||
// Default initial rtt used before any samples are received.
|
||||
const int kInitialRttMs = 100;
|
||||
const double kAlpha = 0.125;
|
||||
const double kOneMinusAlpha = (1 - kAlpha);
|
||||
const double kBeta = 0.25;
|
||||
const double kOneMinusBeta = (1 - kBeta);
|
||||
const int64_t kNumMicrosPerMilli = 1000;
|
||||
} // namespace
|
||||
|
||||
RttStats::RttStats()
|
||||
: latest_rtt_(TimeDelta::Zero()),
|
||||
min_rtt_(TimeDelta::Zero()),
|
||||
smoothed_rtt_(TimeDelta::Zero()),
|
||||
previous_srtt_(TimeDelta::Zero()),
|
||||
mean_deviation_(TimeDelta::Zero()),
|
||||
initial_rtt_us_(kInitialRttMs * kNumMicrosPerMilli) {}
|
||||
|
||||
void RttStats::ExpireSmoothedMetrics() {
|
||||
mean_deviation_ =
|
||||
std::max(mean_deviation_, (smoothed_rtt_ - latest_rtt_).Abs());
|
||||
smoothed_rtt_ = std::max(smoothed_rtt_, latest_rtt_);
|
||||
}
|
||||
|
||||
// Updates the RTT based on a new sample.
|
||||
void RttStats::UpdateRtt(TimeDelta send_delta,
|
||||
TimeDelta ack_delay,
|
||||
Timestamp now) {
|
||||
if (send_delta.IsInfinite() || send_delta <= TimeDelta::Zero()) {
|
||||
RTC_LOG(LS_WARNING) << "Ignoring measured send_delta, because it's is "
|
||||
"either infinite, zero, or negative. send_delta = "
|
||||
<< ToString(send_delta);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update min_rtt_ first. min_rtt_ does not use an rtt_sample corrected for
|
||||
// ack_delay but the raw observed send_delta, since poor clock granularity at
|
||||
// the client may cause a high ack_delay to result in underestimation of the
|
||||
// min_rtt_.
|
||||
if (min_rtt_.IsZero() || min_rtt_ > send_delta) {
|
||||
min_rtt_ = send_delta;
|
||||
}
|
||||
|
||||
// Correct for ack_delay if information received from the peer results in a
|
||||
// positive RTT sample. Otherwise, we use the send_delta as a reasonable
|
||||
// measure for smoothed_rtt.
|
||||
TimeDelta rtt_sample = send_delta;
|
||||
previous_srtt_ = smoothed_rtt_;
|
||||
|
||||
if (rtt_sample > ack_delay) {
|
||||
rtt_sample = rtt_sample - ack_delay;
|
||||
}
|
||||
latest_rtt_ = rtt_sample;
|
||||
// First time call.
|
||||
if (smoothed_rtt_.IsZero()) {
|
||||
smoothed_rtt_ = rtt_sample;
|
||||
mean_deviation_ = rtt_sample / 2;
|
||||
} else {
|
||||
mean_deviation_ = kOneMinusBeta * mean_deviation_ +
|
||||
kBeta * (smoothed_rtt_ - rtt_sample).Abs();
|
||||
smoothed_rtt_ = kOneMinusAlpha * smoothed_rtt_ + kAlpha * rtt_sample;
|
||||
RTC_LOG(LS_VERBOSE) << " smoothed_rtt(us):" << smoothed_rtt_.us()
|
||||
<< " mean_deviation(us):" << mean_deviation_.us();
|
||||
}
|
||||
}
|
||||
|
||||
void RttStats::OnConnectionMigration() {
|
||||
latest_rtt_ = TimeDelta::Zero();
|
||||
min_rtt_ = TimeDelta::Zero();
|
||||
smoothed_rtt_ = TimeDelta::Zero();
|
||||
mean_deviation_ = TimeDelta::Zero();
|
||||
initial_rtt_us_ = kInitialRttMs * kNumMicrosPerMilli;
|
||||
}
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// A convenience class to store RTT samples and calculate smoothed RTT.
|
||||
// From the Quic BBR implementation in Chromium.
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/constructor_magic.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
class RttStats {
|
||||
public:
|
||||
RttStats();
|
||||
|
||||
// Updates the RTT from an incoming ack which is received |send_delta| after
|
||||
// the packet is sent and the peer reports the ack being delayed |ack_delay|.
|
||||
void UpdateRtt(TimeDelta send_delta, TimeDelta ack_delay, Timestamp now);
|
||||
|
||||
// Causes the smoothed_rtt to be increased to the latest_rtt if the latest_rtt
|
||||
// is larger. The mean deviation is increased to the most recent deviation if
|
||||
// it's larger.
|
||||
void ExpireSmoothedMetrics();
|
||||
|
||||
// Called when connection migrates and RTT measurement needs to be reset.
|
||||
void OnConnectionMigration();
|
||||
|
||||
// Returns the EWMA smoothed RTT for the connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta smoothed_rtt() const { return smoothed_rtt_; }
|
||||
|
||||
// Returns the EWMA smoothed RTT prior to the most recent RTT sample.
|
||||
TimeDelta previous_srtt() const { return previous_srtt_; }
|
||||
|
||||
int64_t initial_rtt_us() const { return initial_rtt_us_; }
|
||||
|
||||
// Sets an initial RTT to be used for SmoothedRtt before any RTT updates.
|
||||
void set_initial_rtt_us(int64_t initial_rtt_us) {
|
||||
RTC_DCHECK_GE(initial_rtt_us, 0);
|
||||
if (initial_rtt_us <= 0) {
|
||||
RTC_LOG(LS_ERROR) << "Attempt to set initial rtt to <= 0.";
|
||||
return;
|
||||
}
|
||||
initial_rtt_us_ = initial_rtt_us;
|
||||
}
|
||||
|
||||
// The most recent RTT measurement.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta latest_rtt() const { return latest_rtt_; }
|
||||
|
||||
// Returns the min_rtt for the entire connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta min_rtt() const { return min_rtt_; }
|
||||
|
||||
TimeDelta mean_deviation() const { return mean_deviation_; }
|
||||
|
||||
private:
|
||||
TimeDelta latest_rtt_;
|
||||
TimeDelta min_rtt_;
|
||||
TimeDelta smoothed_rtt_;
|
||||
TimeDelta previous_srtt_;
|
||||
// Mean RTT deviation during this session.
|
||||
// Approximation of standard deviation, the error is roughly 1.25 times
|
||||
// larger than the standard deviation, for a normally distributed signal.
|
||||
TimeDelta mean_deviation_;
|
||||
int64_t initial_rtt_us_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(RttStats);
|
||||
};
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace test {
|
||||
|
||||
class RttStatsTest : public ::testing::Test {
|
||||
protected:
|
||||
RttStats rtt_stats_;
|
||||
};
|
||||
|
||||
TEST_F(RttStatsTest, DefaultsBeforeUpdate) {
|
||||
EXPECT_LT(0u, rtt_stats_.initial_rtt_us());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, SmoothedRtt) {
|
||||
// Verify that ack_delay is corrected for in Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(300), TimeDelta::Millis(100),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.smoothed_rtt());
|
||||
// Verify that effective RTT of zero does not change Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(200), TimeDelta::Millis(200),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.smoothed_rtt());
|
||||
// Verify that large erroneous ack_delay does not change Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(200), TimeDelta::Millis(300),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
|
||||
// Ensure that the potential rounding artifacts in EWMA calculation do not cause
|
||||
// the SRTT to drift too far from the exact value.
|
||||
TEST_F(RttStatsTest, SmoothedRttStability) {
|
||||
for (int64_t time = 3; time < 20000; time++) {
|
||||
RttStats stats;
|
||||
for (int64_t i = 0; i < 100; i++) {
|
||||
stats.UpdateRtt(TimeDelta::Micros(time), TimeDelta::Millis(0),
|
||||
Timestamp::Millis(0));
|
||||
int64_t time_delta_us = stats.smoothed_rtt().us() - time;
|
||||
ASSERT_LE(std::abs(time_delta_us), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, PreviousSmoothedRtt) {
|
||||
// Verify that ack_delay is corrected for in Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(300), TimeDelta::Millis(100),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.previous_srtt());
|
||||
// Ensure the previous SRTT is 200ms after a 100ms sample.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(100), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(100), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Micros(187500).us(), rtt_stats_.smoothed_rtt().us());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.previous_srtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, MinRtt) {
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(200), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(10), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0) + TimeDelta::Millis(10));
|
||||
EXPECT_EQ(TimeDelta::Millis(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(50), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0) + TimeDelta::Millis(20));
|
||||
EXPECT_EQ(TimeDelta::Millis(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(50), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0) + TimeDelta::Millis(30));
|
||||
EXPECT_EQ(TimeDelta::Millis(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(50), TimeDelta::Zero(),
|
||||
Timestamp::Millis(0) + TimeDelta::Millis(40));
|
||||
EXPECT_EQ(TimeDelta::Millis(10), rtt_stats_.min_rtt());
|
||||
// Verify that ack_delay does not go into recording of min_rtt_.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(7), TimeDelta::Millis(2),
|
||||
Timestamp::Millis(0) + TimeDelta::Millis(50));
|
||||
EXPECT_EQ(TimeDelta::Millis(7), rtt_stats_.min_rtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, ExpireSmoothedMetrics) {
|
||||
TimeDelta initial_rtt = TimeDelta::Millis(10);
|
||||
rtt_stats_.UpdateRtt(initial_rtt, TimeDelta::Zero(), Timestamp::Millis(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
EXPECT_EQ(0.5 * initial_rtt, rtt_stats_.mean_deviation());
|
||||
|
||||
// Update once with a 20ms RTT.
|
||||
TimeDelta doubled_rtt = 2 * initial_rtt;
|
||||
rtt_stats_.UpdateRtt(doubled_rtt, TimeDelta::Zero(), Timestamp::Millis(0));
|
||||
EXPECT_EQ(1.125 * initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
// Expire the smoothed metrics, increasing smoothed rtt and mean deviation.
|
||||
rtt_stats_.ExpireSmoothedMetrics();
|
||||
EXPECT_EQ(doubled_rtt, rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(0.875 * initial_rtt, rtt_stats_.mean_deviation());
|
||||
|
||||
// Now go back down to 5ms and expire the smoothed metrics, and ensure the
|
||||
// mean deviation increases to 15ms.
|
||||
TimeDelta half_rtt = 0.5 * initial_rtt;
|
||||
rtt_stats_.UpdateRtt(half_rtt, TimeDelta::Zero(), Timestamp::Millis(0));
|
||||
EXPECT_GT(doubled_rtt, rtt_stats_.smoothed_rtt());
|
||||
EXPECT_LT(initial_rtt, rtt_stats_.mean_deviation());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, UpdateRttWithBadSendDeltas) {
|
||||
// Make sure we ignore bad RTTs.
|
||||
|
||||
TimeDelta initial_rtt = TimeDelta::Millis(10);
|
||||
rtt_stats_.UpdateRtt(initial_rtt, TimeDelta::Zero(), Timestamp::Millis(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
std::vector<TimeDelta> bad_send_deltas;
|
||||
bad_send_deltas.push_back(TimeDelta::Zero());
|
||||
bad_send_deltas.push_back(TimeDelta::PlusInfinity());
|
||||
bad_send_deltas.push_back(TimeDelta::Micros(-1000));
|
||||
|
||||
for (TimeDelta bad_send_delta : bad_send_deltas) {
|
||||
rtt_stats_.UpdateRtt(bad_send_delta, TimeDelta::Zero(),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, ResetAfterConnectionMigrations) {
|
||||
rtt_stats_.UpdateRtt(TimeDelta::Millis(300), TimeDelta::Millis(100),
|
||||
Timestamp::Millis(0));
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(200), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::Millis(300), rtt_stats_.min_rtt());
|
||||
|
||||
// Reset rtt stats on connection migrations.
|
||||
rtt_stats_.OnConnectionMigration();
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.min_rtt());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -1,168 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
|
||||
// From the Quic BBR implementation in Chromium
|
||||
|
||||
// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum)
|
||||
// estimate of a stream of samples over some fixed time interval. (E.g.,
|
||||
// the minimum RTT over the past five minutes.) The algorithm keeps track of
|
||||
// the best, second best, and third best min (or max) estimates, maintaining an
|
||||
// invariant that the measurement time of the n'th best >= n-1'th best.
|
||||
|
||||
// The algorithm works as follows. On a reset, all three estimates are set to
|
||||
// the same sample. The second best estimate is then recorded in the second
|
||||
// quarter of the window, and a third best estimate is recorded in the second
|
||||
// half of the window, bounding the worst case error when the true min is
|
||||
// monotonically increasing (or true max is monotonically decreasing) over the
|
||||
// window.
|
||||
//
|
||||
// A new best sample replaces all three estimates, since the new best is lower
|
||||
// (or higher) than everything else in the window and it is the most recent.
|
||||
// The window thus effectively gets reset on every new min. The same property
|
||||
// holds true for second best and third best estimates. Specifically, when a
|
||||
// sample arrives that is better than the second best but not better than the
|
||||
// best, it replaces the second and third best estimates but not the best
|
||||
// estimate. Similarly, a sample that is better than the third best estimate
|
||||
// but not the other estimates replaces only the third best estimate.
|
||||
//
|
||||
// Finally, when the best expires, it is replaced by the second best, which in
|
||||
// turn is replaced by the third best. The newest sample replaces the third
|
||||
// best.
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
// Compares two values and returns true if the first is less than or equal
|
||||
// to the second.
|
||||
template <class T>
|
||||
struct MinFilter {
|
||||
bool operator()(const T& lhs, const T& rhs) const { return lhs <= rhs; }
|
||||
};
|
||||
|
||||
// Compares two values and returns true if the first is greater than or equal
|
||||
// to the second.
|
||||
template <class T>
|
||||
struct MaxFilter {
|
||||
bool operator()(const T& lhs, const T& rhs) const { return lhs >= rhs; }
|
||||
};
|
||||
|
||||
// Use the following to construct a windowed filter object of type T.
|
||||
// For example, a min filter using Timestamp as the time type:
|
||||
// WindowedFilter<T, MinFilter<T>, Timestamp, TimeDelta>
|
||||
// ObjectName;
|
||||
// A max filter using 64-bit integers as the time type:
|
||||
// WindowedFilter<T, MaxFilter<T>, uint64_t, int64_t> ObjectName;
|
||||
// Specifically, this template takes four arguments:
|
||||
// 1. T -- type of the measurement that is being filtered.
|
||||
// 2. Compare -- MinFilter<T> or MaxFilter<T>, depending on the type of filter
|
||||
// desired.
|
||||
// 3. TimeT -- the type used to represent timestamps.
|
||||
// 4. TimeDeltaT -- the type used to represent continuous time intervals between
|
||||
// two timestamps. Has to be the type of (a - b) if both |a| and |b| are
|
||||
// of type TimeT.
|
||||
template <class T, class Compare, typename TimeT, typename TimeDeltaT>
|
||||
class WindowedFilter {
|
||||
public:
|
||||
// |window_length| is the period after which a best estimate expires.
|
||||
// |zero_value| is used as the uninitialized value for objects of T.
|
||||
// Importantly, |zero_value| should be an invalid value for a true sample.
|
||||
WindowedFilter(TimeDeltaT window_length, T zero_value, TimeT zero_time)
|
||||
: window_length_(window_length),
|
||||
zero_value_(zero_value),
|
||||
estimates_{Sample(zero_value_, zero_time),
|
||||
Sample(zero_value_, zero_time),
|
||||
Sample(zero_value_, zero_time)} {}
|
||||
|
||||
// Changes the window length. Does not update any current samples.
|
||||
void SetWindowLength(TimeDeltaT window_length) {
|
||||
window_length_ = window_length;
|
||||
}
|
||||
|
||||
// Updates best estimates with |sample|, and expires and updates best
|
||||
// estimates as necessary.
|
||||
void Update(T new_sample, TimeT new_time) {
|
||||
// Reset all estimates if they have not yet been initialized, if new sample
|
||||
// is a new best, or if the newest recorded estimate is too old.
|
||||
if (estimates_[0].sample == zero_value_ ||
|
||||
Compare()(new_sample, estimates_[0].sample) ||
|
||||
new_time - estimates_[2].time > window_length_) {
|
||||
Reset(new_sample, new_time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Compare()(new_sample, estimates_[1].sample)) {
|
||||
estimates_[1] = Sample(new_sample, new_time);
|
||||
estimates_[2] = estimates_[1];
|
||||
} else if (Compare()(new_sample, estimates_[2].sample)) {
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
}
|
||||
|
||||
// Expire and update estimates as necessary.
|
||||
if (new_time - estimates_[0].time > window_length_) {
|
||||
// The best estimate hasn't been updated for an entire window, so promote
|
||||
// second and third best estimates.
|
||||
estimates_[0] = estimates_[1];
|
||||
estimates_[1] = estimates_[2];
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
// Need to iterate one more time. Check if the new best estimate is
|
||||
// outside the window as well, since it may also have been recorded a
|
||||
// long time ago. Don't need to iterate once more since we cover that
|
||||
// case at the beginning of the method.
|
||||
if (new_time - estimates_[0].time > window_length_) {
|
||||
estimates_[0] = estimates_[1];
|
||||
estimates_[1] = estimates_[2];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (estimates_[1].sample == estimates_[0].sample &&
|
||||
new_time - estimates_[1].time > window_length_ >> 2) {
|
||||
// A quarter of the window has passed without a better sample, so the
|
||||
// second-best estimate is taken from the second quarter of the window.
|
||||
estimates_[2] = estimates_[1] = Sample(new_sample, new_time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (estimates_[2].sample == estimates_[1].sample &&
|
||||
new_time - estimates_[2].time > window_length_ >> 1) {
|
||||
// We've passed a half of the window without a better estimate, so take
|
||||
// a third-best estimate from the second half of the window.
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Resets all estimates to new sample.
|
||||
void Reset(T new_sample, TimeT new_time) {
|
||||
estimates_[0] = estimates_[1] = estimates_[2] =
|
||||
Sample(new_sample, new_time);
|
||||
}
|
||||
|
||||
T GetBest() const { return estimates_[0].sample; }
|
||||
T GetSecondBest() const { return estimates_[1].sample; }
|
||||
T GetThirdBest() const { return estimates_[2].sample; }
|
||||
|
||||
private:
|
||||
struct Sample {
|
||||
T sample;
|
||||
TimeT time;
|
||||
Sample(T init_sample, TimeT init_time)
|
||||
: sample(init_sample), time(init_time) {}
|
||||
};
|
||||
|
||||
TimeDeltaT window_length_; // Time length of window.
|
||||
T zero_value_; // Uninitialized value of T.
|
||||
Sample estimates_[3]; // Best estimate is element 0.
|
||||
};
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
@ -1,372 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/windowed_filter.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace test {
|
||||
class WindowedFilterTest : public ::testing::Test {
|
||||
public:
|
||||
// Set the window to 99ms, so 25ms is more than a quarter rtt.
|
||||
WindowedFilterTest()
|
||||
: windowed_min_rtt_(99, TimeDelta::Zero(), 0),
|
||||
windowed_max_bw_(99, DataRate::Zero(), 0) {}
|
||||
|
||||
// Sets up windowed_min_rtt_ to have the following values:
|
||||
// Best = 20ms, recorded at 25ms
|
||||
// Second best = 40ms, recorded at 75ms
|
||||
// Third best = 50ms, recorded at 100ms
|
||||
void InitializeMinFilter() {
|
||||
int64_t now_ms = 0;
|
||||
TimeDelta rtt_sample = TimeDelta::Millis(10);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << ToString(rtt_sample)
|
||||
<< " mins: "
|
||||
" "
|
||||
<< ToString(windowed_min_rtt_.GetBest()) << " "
|
||||
<< ToString(windowed_min_rtt_.GetSecondBest()) << " "
|
||||
<< ToString(windowed_min_rtt_.GetThirdBest());
|
||||
now_ms += 25;
|
||||
rtt_sample = rtt_sample + TimeDelta::Millis(10);
|
||||
}
|
||||
EXPECT_EQ(TimeDelta::Millis(20), windowed_min_rtt_.GetBest());
|
||||
EXPECT_EQ(TimeDelta::Millis(40), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::Millis(50), windowed_min_rtt_.GetThirdBest());
|
||||
}
|
||||
|
||||
// Sets up windowed_max_bw_ to have the following values:
|
||||
// Best = 900 bps, recorded at 25ms
|
||||
// Second best = 700 bps, recorded at 75ms
|
||||
// Third best = 600 bps, recorded at 100ms
|
||||
void InitializeMaxFilter() {
|
||||
int64_t now_ms = 0;
|
||||
DataRate bw_sample = DataRate::BitsPerSec(1000);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << ToString(bw_sample)
|
||||
<< " maxs: "
|
||||
" "
|
||||
<< ToString(windowed_max_bw_.GetBest()) << " "
|
||||
<< ToString(windowed_max_bw_.GetSecondBest()) << " "
|
||||
<< ToString(windowed_max_bw_.GetThirdBest());
|
||||
now_ms += 25;
|
||||
bw_sample = DataRate::BitsPerSec(bw_sample.bps() - 100);
|
||||
}
|
||||
EXPECT_EQ(DataRate::BitsPerSec(900), windowed_max_bw_.GetBest());
|
||||
EXPECT_EQ(DataRate::BitsPerSec(700), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::BitsPerSec(600), windowed_max_bw_.GetThirdBest());
|
||||
}
|
||||
|
||||
protected:
|
||||
WindowedFilter<TimeDelta, MinFilter<TimeDelta>, int64_t, int64_t>
|
||||
windowed_min_rtt_;
|
||||
WindowedFilter<DataRate, MaxFilter<DataRate>, int64_t, int64_t>
|
||||
windowed_max_bw_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
// Test helper function: updates the filter with a lot of small values in order
|
||||
// to ensure that it is not susceptible to noise.
|
||||
void UpdateWithIrrelevantSamples(
|
||||
WindowedFilter<uint64_t, MaxFilter<uint64_t>, uint64_t, uint64_t>* filter,
|
||||
uint64_t max_value,
|
||||
uint64_t time) {
|
||||
for (uint64_t i = 0; i < 1000; i++) {
|
||||
filter->Update(i % max_value, time);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(WindowedFilterTest, UninitializedEstimates) {
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetBest());
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetThirdBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, MonotonicallyIncreasingMin) {
|
||||
int64_t now_ms = 0;
|
||||
TimeDelta rtt_sample = TimeDelta::Millis(10);
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(TimeDelta::Millis(10), windowed_min_rtt_.GetBest());
|
||||
|
||||
// Gradually increase the rtt samples and ensure the windowed min rtt starts
|
||||
// rising.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
now_ms += 25;
|
||||
rtt_sample = rtt_sample + TimeDelta::Millis(10);
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << rtt_sample.ms()
|
||||
<< " mins: "
|
||||
" "
|
||||
<< windowed_min_rtt_.GetBest().ms() << " "
|
||||
<< windowed_min_rtt_.GetSecondBest().ms() << " "
|
||||
<< windowed_min_rtt_.GetThirdBest().ms();
|
||||
if (i < 3) {
|
||||
EXPECT_EQ(TimeDelta::Millis(10), windowed_min_rtt_.GetBest());
|
||||
} else if (i == 3) {
|
||||
EXPECT_EQ(TimeDelta::Millis(20), windowed_min_rtt_.GetBest());
|
||||
} else if (i < 6) {
|
||||
EXPECT_EQ(TimeDelta::Millis(40), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, MonotonicallyDecreasingMax) {
|
||||
int64_t now_ms = 0;
|
||||
DataRate bw_sample = DataRate::BitsPerSec(1000);
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(DataRate::BitsPerSec(1000), windowed_max_bw_.GetBest());
|
||||
|
||||
// Gradually decrease the bw samples and ensure the windowed max bw starts
|
||||
// decreasing.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
now_ms += 25;
|
||||
bw_sample = DataRate::BitsPerSec(bw_sample.bps() - 100);
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << bw_sample.bps()
|
||||
<< " maxs: "
|
||||
" "
|
||||
<< windowed_max_bw_.GetBest().bps() << " "
|
||||
<< windowed_max_bw_.GetSecondBest().bps() << " "
|
||||
<< windowed_max_bw_.GetThirdBest().bps();
|
||||
if (i < 3) {
|
||||
EXPECT_EQ(DataRate::BitsPerSec(1000), windowed_max_bw_.GetBest());
|
||||
} else if (i == 3) {
|
||||
EXPECT_EQ(DataRate::BitsPerSec(900), windowed_max_bw_.GetBest());
|
||||
} else if (i < 6) {
|
||||
EXPECT_EQ(DataRate::BitsPerSec(700), windowed_max_bw_.GetBest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesThirdBestMin) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the third-choice min-rtt sets that, but nothing else.
|
||||
TimeDelta rtt_sample =
|
||||
windowed_min_rtt_.GetThirdBest() - TimeDelta::Millis(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetThirdBest(), TimeDelta::Millis(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(TimeDelta::Millis(40), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::Millis(20), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesThirdBestMax) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the third-choice max sets that, but nothing else.
|
||||
DataRate bw_sample =
|
||||
DataRate::BitsPerSec(windowed_max_bw_.GetThirdBest().bps() + 50);
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(DataRate::BitsPerSec(700), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::BitsPerSec(900), windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesSecondBestMin) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the second-choice min sets that and also
|
||||
// the third-choice min.
|
||||
TimeDelta rtt_sample =
|
||||
windowed_min_rtt_.GetSecondBest() - TimeDelta::Millis(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetSecondBest(), TimeDelta::Millis(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::Millis(20), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesSecondBestMax) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the second-choice max sets that and also
|
||||
// the third-choice max.
|
||||
DataRate bw_sample =
|
||||
DataRate::BitsPerSec(windowed_max_bw_.GetSecondBest().bps() + 50);
|
||||
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::BitsPerSec(900), windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesAllMins) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the first-choice min-rtt sets that and also
|
||||
// the second and third-choice mins.
|
||||
TimeDelta rtt_sample = windowed_min_rtt_.GetBest() - TimeDelta::Millis(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetBest(), TimeDelta::Millis(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesAllMaxs) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the first-choice max sets that and also
|
||||
// the second and third-choice maxs.
|
||||
DataRate bw_sample =
|
||||
DataRate::BitsPerSec(windowed_max_bw_.GetBest().bps() + 50);
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireBestMin) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta old_third_best = windowed_min_rtt_.GetThirdBest();
|
||||
TimeDelta old_second_best = windowed_min_rtt_.GetSecondBest();
|
||||
TimeDelta rtt_sample = old_third_best + TimeDelta::Millis(5);
|
||||
// Best min sample was recorded at 25ms, so expiry time is 124ms.
|
||||
int64_t now_ms = 125;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(old_third_best, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(old_second_best, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireBestMax) {
|
||||
InitializeMaxFilter();
|
||||
DataRate old_third_best = windowed_max_bw_.GetThirdBest();
|
||||
DataRate old_second_best = windowed_max_bw_.GetSecondBest();
|
||||
DataRate bw_sample = DataRate::BitsPerSec(old_third_best.bps() - 50);
|
||||
// Best max sample was recorded at 25ms, so expiry time is 124ms.
|
||||
int64_t now_ms = 125;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(old_third_best, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(old_second_best, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireSecondBestMin) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta old_third_best = windowed_min_rtt_.GetThirdBest();
|
||||
TimeDelta rtt_sample = old_third_best + TimeDelta::Millis(5);
|
||||
// Second best min sample was recorded at 75ms, so expiry time is 174ms.
|
||||
int64_t now_ms = 175;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(old_third_best, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireSecondBestMax) {
|
||||
InitializeMaxFilter();
|
||||
DataRate old_third_best = windowed_max_bw_.GetThirdBest();
|
||||
DataRate bw_sample = DataRate::BitsPerSec(old_third_best.bps() - 50);
|
||||
// Second best max sample was recorded at 75ms, so expiry time is 174ms.
|
||||
int64_t now_ms = 175;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(old_third_best, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireAllMins) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta rtt_sample =
|
||||
windowed_min_rtt_.GetThirdBest() + TimeDelta::Millis(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_LT(windowed_min_rtt_.GetThirdBest(), TimeDelta::PlusInfinity());
|
||||
// Third best min sample was recorded at 100ms, so expiry time is 199ms.
|
||||
int64_t now_ms = 200;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireAllMaxs) {
|
||||
InitializeMaxFilter();
|
||||
DataRate bw_sample =
|
||||
DataRate::BitsPerSec(windowed_max_bw_.GetThirdBest().bps() - 50);
|
||||
// Third best max sample was recorded at 100ms, so expiry time is 199ms.
|
||||
int64_t now_ms = 200;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
// Test the windowed filter where the time used is an exact counter instead of a
|
||||
// timestamp. This is useful if, for example, the time is measured in round
|
||||
// trips.
|
||||
TEST_F(WindowedFilterTest, ExpireCounterBasedMax) {
|
||||
// Create a window which starts at t = 0 and expires after two cycles.
|
||||
WindowedFilter<uint64_t, MaxFilter<uint64_t>, uint64_t, uint64_t> max_filter(
|
||||
2, 0, 0);
|
||||
|
||||
const uint64_t kBest = 50000;
|
||||
// Insert 50000 at t = 1.
|
||||
max_filter.Update(50000, 1);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 1);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
|
||||
// Insert 40000 at t = 2. Nothing is expected to expire.
|
||||
max_filter.Update(40000, 2);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 2);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
|
||||
// Insert 30000 at t = 3. Nothing is expected to expire yet.
|
||||
max_filter.Update(30000, 3);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 3);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
RTC_LOG(LS_VERBOSE) << max_filter.GetSecondBest();
|
||||
RTC_LOG(LS_VERBOSE) << max_filter.GetThirdBest();
|
||||
|
||||
// Insert 20000 at t = 4. 50000 at t = 1 expires, so 40000 becomes the new
|
||||
// maximum.
|
||||
const uint64_t kNewBest = 40000;
|
||||
max_filter.Update(20000, 4);
|
||||
EXPECT_EQ(kNewBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 4);
|
||||
EXPECT_EQ(kNewBest, max_filter.GetBest());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -29,7 +29,6 @@ group("test") {
|
||||
deps += [
|
||||
":test_main",
|
||||
":test_support_unittests",
|
||||
"scenario/scenario_tests",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import("../../../webrtc.gni")
|
||||
|
||||
if (rtc_include_tests) {
|
||||
rtc_test("scenario_tests") {
|
||||
testonly = true
|
||||
sources = [ "bbr_performance.cc" ]
|
||||
deps = [
|
||||
"../:scenario",
|
||||
"../..:test_main",
|
||||
"../../:field_trial",
|
||||
"../../:fileutils",
|
||||
"../../:test_common",
|
||||
"../../:test_support",
|
||||
"../../../modules/congestion_controller/bbr",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base/experiments:field_trial_parser",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,256 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "modules/congestion_controller/bbr/bbr_factory.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/experiments/field_trial_units.h"
|
||||
#include "rtc_base/random.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gtest.h"
|
||||
#include "test/scenario/scenario.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
namespace {
|
||||
constexpr int64_t kRunTimeMs = 60000;
|
||||
|
||||
using ::testing::Combine;
|
||||
using ::testing::make_tuple;
|
||||
using ::testing::tuple;
|
||||
using ::testing::Values;
|
||||
|
||||
using Codec = VideoStreamConfig::Encoder::Codec;
|
||||
using CodecImpl = VideoStreamConfig::Encoder::Implementation;
|
||||
|
||||
struct CallTestConfig {
|
||||
struct Scenario {
|
||||
FieldTrialParameter<int> random_seed;
|
||||
FieldTrialFlag return_traffic;
|
||||
FieldTrialParameter<DataRate> capacity;
|
||||
FieldTrialParameter<TimeDelta> propagation_delay;
|
||||
FieldTrialParameter<DataRate> cross_traffic;
|
||||
FieldTrialParameter<TimeDelta> delay_noise;
|
||||
FieldTrialParameter<double> loss_rate;
|
||||
Scenario()
|
||||
: random_seed("rs", 1),
|
||||
return_traffic("ret"),
|
||||
capacity("bw", DataRate::KilobitsPerSec(300)),
|
||||
propagation_delay("dl", TimeDelta::Millis(100)),
|
||||
cross_traffic("ct", DataRate::Zero()),
|
||||
delay_noise("dn", TimeDelta::Zero()),
|
||||
loss_rate("pl", 0) {}
|
||||
void Parse(std::string config_str) {
|
||||
ParseFieldTrial(
|
||||
{&random_seed, &return_traffic, &capacity, &propagation_delay,
|
||||
&cross_traffic, &delay_noise, &loss_rate},
|
||||
config_str);
|
||||
}
|
||||
} scenario;
|
||||
struct Tuning {
|
||||
FieldTrialFlag use_bbr;
|
||||
FieldTrialFlag bbr_no_target_rate;
|
||||
FieldTrialOptional<DataSize> bbr_initial_window;
|
||||
FieldTrialParameter<double> bbr_encoder_gain;
|
||||
Tuning()
|
||||
: use_bbr("bbr"),
|
||||
bbr_no_target_rate("notr"),
|
||||
bbr_initial_window("iw", DataSize::Bytes(8000)),
|
||||
bbr_encoder_gain("eg", 0.8) {}
|
||||
void Parse(std::string config_str) {
|
||||
ParseFieldTrial(
|
||||
{
|
||||
&use_bbr,
|
||||
&bbr_no_target_rate,
|
||||
&bbr_initial_window,
|
||||
&bbr_encoder_gain,
|
||||
},
|
||||
config_str);
|
||||
}
|
||||
} tuning;
|
||||
|
||||
void Parse(std::string scenario_string, std::string tuning_string) {
|
||||
scenario.Parse(scenario_string);
|
||||
tuning.Parse(tuning_string);
|
||||
scenario_str = scenario_string;
|
||||
tuning_str = tuning_string;
|
||||
}
|
||||
std::string scenario_str;
|
||||
std::string tuning_str;
|
||||
|
||||
std::string BbrTrial() const {
|
||||
char trial_buf[1024];
|
||||
rtc::SimpleStringBuilder trial(trial_buf);
|
||||
trial << "WebRTC-BweBbrConfig/";
|
||||
trial << "encoder_rate_gain_in_probe_rtt:0.5";
|
||||
trial.AppendFormat(",encoder_rate_gain:%.1lf",
|
||||
tuning.bbr_encoder_gain.Get());
|
||||
if (tuning.bbr_no_target_rate)
|
||||
trial << ",pacing_rate_as_target:1";
|
||||
if (tuning.bbr_initial_window)
|
||||
trial << ",initial_cwin:" << tuning.bbr_initial_window->bytes();
|
||||
trial << "/";
|
||||
return trial.str();
|
||||
}
|
||||
std::string FieldTrials() const {
|
||||
std::string trials;
|
||||
if (tuning.use_bbr) {
|
||||
trials +=
|
||||
"WebRTC-BweCongestionController/Enabled,BBR/"
|
||||
"WebRTC-Pacer-DrainQueue/Disabled/"
|
||||
"WebRTC-Pacer-PadInSilence/Enabled/"
|
||||
"WebRTC-Pacer-BlockAudio/Disabled/"
|
||||
"WebRTC-Audio-SendSideBwe/Enabled/"
|
||||
"WebRTC-SendSideBwe-WithOverhead/Enabled/";
|
||||
trials += BbrTrial();
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
std::string Name() const {
|
||||
char raw_name[1024];
|
||||
rtc::SimpleStringBuilder name(raw_name);
|
||||
for (char c : scenario_str + "__tun__" + tuning_str) {
|
||||
if (c == ':') {
|
||||
continue;
|
||||
} else if (c == ',') {
|
||||
name << "_";
|
||||
} else if (c == '%') {
|
||||
name << "p";
|
||||
} else {
|
||||
name << c;
|
||||
}
|
||||
}
|
||||
return name.str();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
class BbrScenarioTest
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<tuple<std::string, std::string>> {
|
||||
public:
|
||||
BbrScenarioTest() {
|
||||
conf_.Parse(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam()));
|
||||
field_trial_.reset(new test::ScopedFieldTrials(conf_.FieldTrials()));
|
||||
}
|
||||
CallTestConfig conf_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<test::ScopedFieldTrials> field_trial_;
|
||||
};
|
||||
|
||||
TEST_P(BbrScenarioTest, ReceivesVideo) {
|
||||
BbrNetworkControllerFactory bbr_factory;
|
||||
Scenario s("bbr_test_gen/bbr__" + conf_.Name());
|
||||
CallClientConfig call_config;
|
||||
if (conf_.tuning.use_bbr) {
|
||||
call_config.transport.cc_factory = &bbr_factory;
|
||||
}
|
||||
call_config.transport.rates.min_rate = DataRate::KilobitsPerSec(30);
|
||||
call_config.transport.rates.max_rate = DataRate::KilobitsPerSec(1800);
|
||||
|
||||
CallClient* alice = s.CreateClient("send", call_config);
|
||||
CallClient* bob = s.CreateClient("return", call_config);
|
||||
NetworkSimulationConfig net_conf;
|
||||
net_conf.bandwidth = conf_.scenario.capacity;
|
||||
net_conf.delay = conf_.scenario.propagation_delay;
|
||||
net_conf.loss_rate = conf_.scenario.loss_rate;
|
||||
net_conf.delay_std_dev = conf_.scenario.delay_noise;
|
||||
auto* send_net = s.CreateMutableSimulationNode(net_conf);
|
||||
auto* ret_net = s.CreateMutableSimulationNode(net_conf);
|
||||
auto route =
|
||||
s.CreateRoutes(alice, {send_net->node()}, bob, {ret_net->node()});
|
||||
|
||||
VideoStreamPair* alice_video =
|
||||
s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) {
|
||||
c->encoder.fake.max_rate = DataRate::KilobitsPerSec(1800);
|
||||
});
|
||||
s.CreateAudioStream(route->forward(), [&](AudioStreamConfig* c) {
|
||||
if (conf_.tuning.use_bbr) {
|
||||
c->stream.in_bandwidth_estimation = true;
|
||||
c->encoder.fixed_rate = DataRate::KilobitsPerSec(31);
|
||||
}
|
||||
});
|
||||
|
||||
VideoStreamPair* bob_video = nullptr;
|
||||
if (conf_.scenario.return_traffic) {
|
||||
bob_video =
|
||||
s.CreateVideoStream(route->reverse(), [&](VideoStreamConfig* c) {
|
||||
c->encoder.fake.max_rate = DataRate::KilobitsPerSec(1800);
|
||||
});
|
||||
s.CreateAudioStream(route->reverse(), [&](AudioStreamConfig* c) {
|
||||
if (conf_.tuning.use_bbr) {
|
||||
c->stream.in_bandwidth_estimation = true;
|
||||
c->encoder.fixed_rate = DataRate::KilobitsPerSec(31);
|
||||
}
|
||||
});
|
||||
}
|
||||
RandomWalkConfig cross_config;
|
||||
cross_config.peak_rate = conf_.scenario.cross_traffic;
|
||||
cross_config.random_seed = conf_.scenario.random_seed;
|
||||
auto* cross_traffic = s.net()->CreateRandomWalkCrossTraffic(
|
||||
s.net()->CreateTrafficRoute({send_net->node()}), cross_config);
|
||||
|
||||
s.CreatePrinter("send.stats.txt", TimeDelta::Millis(100),
|
||||
{alice->StatsPrinter(), alice_video->send()->StatsPrinter(),
|
||||
cross_traffic->StatsPrinter(), send_net->ConfigPrinter()});
|
||||
|
||||
std::vector<ColumnPrinter> return_printers{
|
||||
bob->StatsPrinter(), ColumnPrinter::Fixed("cross_traffic_rate", "0"),
|
||||
ret_net->ConfigPrinter()};
|
||||
if (bob_video)
|
||||
return_printers.push_back(bob_video->send()->StatsPrinter());
|
||||
s.CreatePrinter("return.stats.txt", TimeDelta::Millis(100), return_printers);
|
||||
|
||||
s.RunFor(TimeDelta::Millis(kRunTimeMs));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(Selected,
|
||||
BbrScenarioTest,
|
||||
Values(make_tuple("rs:1,bw:150,dl:100,ct:100",
|
||||
"bbr")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
OneWayTuning,
|
||||
BbrScenarioTest,
|
||||
Values(make_tuple("bw:150,dl:100", "bbr,iw:,eg:100%,notr"),
|
||||
make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:100%,notr"),
|
||||
make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:100%"),
|
||||
make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:80%")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(OneWayTuned,
|
||||
BbrScenarioTest,
|
||||
Values(make_tuple("bw:150,dl:100", "bbr"),
|
||||
make_tuple("bw:150,dl:100", ""),
|
||||
make_tuple("bw:800,dl:100", "bbr"),
|
||||
make_tuple("bw:800,dl:100", "")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(OneWayDegraded,
|
||||
BbrScenarioTest,
|
||||
Values(make_tuple("bw:150,dl:100,dn:30,pl:5%", "bbr"),
|
||||
make_tuple("bw:150,dl:100,dn:30,pl:5%", ""),
|
||||
|
||||
make_tuple("bw:150,ct:100,dl:100", "bbr"),
|
||||
make_tuple("bw:150,ct:100,dl:100", ""),
|
||||
|
||||
make_tuple("bw:800,dl:100,dn:30,pl:5%", "bbr"),
|
||||
make_tuple("bw:800,dl:100,dn:30,pl:5%", ""),
|
||||
|
||||
make_tuple("bw:800,ct:600,dl:100", "bbr"),
|
||||
make_tuple("bw:800,ct:600,dl:100", "")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TwoWay,
|
||||
BbrScenarioTest,
|
||||
Values(make_tuple("ret,bw:150,dl:100", "bbr"),
|
||||
make_tuple("ret,bw:150,dl:100", ""),
|
||||
make_tuple("ret,bw:800,dl:100", "bbr"),
|
||||
make_tuple("ret,bw:800,dl:100", ""),
|
||||
make_tuple("ret,bw:150,dl:50", "bbr"),
|
||||
make_tuple("ret,bw:150,dl:50", "")));
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
Loading…
x
Reference in New Issue
Block a user