Almost full implementation of BBR's core, missing receiver side implementation, pacer, and BitrateObserver class which is responsible for communication between BBR and pacer/encoder. Significant changes: Recovery mode and a separate bucket for the high gain phase.

BUG=webrtc:7713

Review-Url: https://codereview.webrtc.org/2990163002
Cr-Commit-Position: refs/heads/master@{#19349}
This commit is contained in:
gnish 2017-08-15 02:26:22 -07:00 committed by Commit Bot
parent 2ee076dfa3
commit 53d76c6190
7 changed files with 470 additions and 81 deletions

View File

@ -12,6 +12,7 @@
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.h"
#include <stdlib.h>
#include <algorithm>
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/congestion_window.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/max_bandwidth_filter.h"
@ -21,7 +22,7 @@ namespace webrtc {
namespace testing {
namespace bwe {
namespace {
const int kFeedbackIntervalsMs = 3;
const int kFeedbackIntervalsMs = 5;
// BBR uses this value to double sending rate each round trip. Design document
// suggests using this value.
const float kHighGain = 2.885f;
@ -35,7 +36,7 @@ const int kMaxRoundsWithoutGrowth = 3;
// Pacing gain values for Probe Bandwidth mode.
const float kPacingGain[] = {1.25, 0.75, 1, 1, 1, 1, 1, 1};
const size_t kGainCycleLength = sizeof(kPacingGain) / sizeof(kPacingGain[0]);
// The least amount of rounds PROBE_RTT mode should last.
// Least number of rounds PROBE_RTT should last.
const int kProbeRttDurationRounds = 1;
// The least amount of milliseconds PROBE_RTT mode should last.
const int kProbeRttDurationMs = 200;
@ -46,9 +47,20 @@ const float kTargetCongestionWindowGain = 1;
// work, it is nice to have some extra room in congestion window for full link
// utilization. Value chosen by observations on different tests.
const float kCruisingCongestionWindowGain = 1.5f;
// Expiration time for min_rtt sample, which is set to 10 seconds according to
// BBR design doc.
const int64_t kMinRttFilterSizeMs = 10000;
// Pacing gain specific for Recovery mode. Chosen by experiments in simulation
// tool.
const float kRecoveryPacingGain = 0.5f;
// Congestion window gain specific for Recovery mode. Chosen by experiments in
// simulation tool.
const float kRecoveryCongestionWindowGain = 1.5f;
// Number of rounds over which average RTT is stored for Recovery mode.
const size_t kPastRttsFilterSize = 1;
// Threshold to assume average RTT has increased for a round. Chosen by
// experiments in simulation tool.
const float kRttIncreaseThreshold = 3;
// Threshold to assume average RTT has decreased for a round. Chosen by
// experiments in simulation tool.
const float kRttDecreaseThreshold = 1.5f;
} // namespace
BbrBweSender::BbrBweSender(Clock* clock)
@ -60,15 +72,32 @@ BbrBweSender::BbrBweSender(Clock* clock)
congestion_window_(new CongestionWindow()),
rand_(new Random(time(NULL))),
round_count_(0),
last_packet_sent_(0),
round_trip_end_(0),
full_bandwidth_reached_(false),
cycle_start_time_ms_(0),
cycle_index_(0),
prior_in_flight_(0),
bytes_acked_(0),
probe_rtt_start_time_ms_(0),
minimum_congestion_window_start_time_ms_(),
minimum_congestion_window_start_round_(0) {
minimum_congestion_window_start_time_ms_(0),
minimum_congestion_window_start_round_(0),
bytes_sent_(0),
last_packet_sent_sequence_number_(0),
last_packet_acked_sequence_number_(0),
last_packet_ack_time_(0),
last_packet_send_time_(0),
pacing_rate_bps_(0),
last_packet_send_time_during_high_gain_ms_(-1),
data_sent_before_high_gain_started_bytes_(-1),
data_sent_before_high_gain_ended_bytes_(-1),
first_packet_ack_time_during_high_gain_ms_(-1),
last_packet_ack_time_during_high_gain_ms_(-1),
data_acked_before_high_gain_started_bytes_(-1),
data_acked_before_high_gain_ended_bytes_(-1),
first_packet_seq_num_during_high_gain_(-1),
last_packet_seq_num_during_high_gain_(-1),
high_gain_over_(false),
packet_stats_(),
past_rtts_() {
// Initially enter Startup mode.
EnterStartup();
}
@ -79,26 +108,101 @@ int BbrBweSender::GetFeedbackIntervalMs() const {
return kFeedbackIntervalsMs;
}
void BbrBweSender::CalculatePacingRate() {
pacing_rate_bps_ =
max_bandwidth_filter_->max_bandwidth_estimate_bps() * pacing_gain_;
}
void BbrBweSender::HandleLoss(uint64_t last_acked_packet,
uint64_t recently_acked_packet) {
// Logic specific to wrapping sequence numbers.
if (!last_acked_packet)
return;
for (uint16_t i = last_acked_packet + 1;
AheadOrAt<uint16_t>(recently_acked_packet - 1, i); i++) {
congestion_window_->AckReceived(packet_stats_[i].payload_size_bytes);
}
}
void BbrBweSender::AddToPastRtts(int64_t rtt_sample_ms) {
uint64_t last_round = 0;
if (!past_rtts_.empty())
last_round = past_rtts_.back().round;
// Try to add the sample to the last round.
if (last_round == round_count_ && !past_rtts_.empty()) {
past_rtts_.back().sum_of_rtts_ms += rtt_sample_ms;
past_rtts_.back().num_samples++;
} else {
// If the sample belongs to a new round, keep number of rounds in the window
// equal to |kPastRttsFilterSize|.
if (past_rtts_.size() == kPastRttsFilterSize)
past_rtts_.pop_front();
past_rtts_.push_back(
BbrBweSender::AverageRtt(rtt_sample_ms, 1, round_count_));
}
}
void BbrBweSender::GiveFeedback(const FeedbackPacket& feedback) {
int64_t now_ms = clock_->TimeInMilliseconds();
last_packet_ack_time_ = now_ms;
const BbrBweFeedback& fb = static_cast<const BbrBweFeedback&>(feedback);
// feedback_vector holds values of acknowledged packets' sequence numbers.
const std::vector<uint64_t>& feedback_vector = fb.packet_feedback_vector();
// Check if new round started for the connection. Round is the period of time
// from sending packet to its acknowledgement.
// Go through all the packets acked, update variables/containers accordingly.
for (uint16_t sequence_number : feedback_vector) {
// Completing packet information with a recently received ack.
PacketStats* packet = &packet_stats_[sequence_number];
bytes_acked_ += packet->payload_size_bytes;
packet->data_sent_bytes = bytes_sent_;
packet->last_sent_packet_send_time_ms = last_packet_send_time_;
packet->data_acked_bytes = bytes_acked_;
packet->ack_time_ms = now_ms;
// Logic specific to applying "bucket" to high gain, in order to have
// quicker ramp-up. We check if we started receiving acks for the packets
// sent during high gain phase.
if (packet->sequence_number == first_packet_seq_num_during_high_gain_) {
first_packet_ack_time_during_high_gain_ms_ = now_ms;
// Substracting half of the packet's size to avoid overestimation.
data_acked_before_high_gain_started_bytes_ =
bytes_acked_ - packet->payload_size_bytes / 2;
}
// If the last packet of high gain phase has been acked, high gain phase is
// over.
if (packet->sequence_number == last_packet_seq_num_during_high_gain_) {
last_packet_ack_time_during_high_gain_ms_ = now_ms;
data_acked_before_high_gain_ended_bytes_ =
bytes_acked_ - packet->payload_size_bytes / 2;
high_gain_over_ = true;
}
// Notify pacer that an ack was received, to adjust data inflight.
// TODO(gnish): Add implementation for BitrateObserver class, to notify
// pacer about incoming acks.
congestion_window_->AckReceived(packet->payload_size_bytes);
HandleLoss(last_packet_acked_sequence_number_, packet->sequence_number);
last_packet_acked_sequence_number_ = packet->sequence_number;
// Logic for wrapping sequence numbers. If round started with packet number
// x, it can never end on y, if x > y. That could happen when sequence
// numbers are wrapped after some point.
if (packet->sequence_number == 0)
round_trip_end_ = 0;
}
// Check if new round started for the connection.
bool new_round_started = false;
if (!feedback_vector.empty()) {
uint64_t last_acked_packet = *feedback_vector.rbegin();
if (last_acked_packet > round_trip_end_) {
if (last_packet_acked_sequence_number_ > round_trip_end_) {
new_round_started = true;
round_count_++;
round_trip_end_ = last_packet_sent_;
round_trip_end_ = last_packet_sent_sequence_number_;
}
}
bool min_rtt_expired = false;
min_rtt_expired =
UpdateBandwidthAndMinRtt(now_ms, feedback_vector, bytes_acked_);
if (new_round_started && !full_bandwidth_reached_) {
full_bandwidth_reached_ = max_bandwidth_filter_->FullBandwidthReached(
kStartupGrowthTarget, kMaxRoundsWithoutGrowth);
}
int now_ms = clock_->TimeInMilliseconds();
switch (mode_) {
break;
case STARTUP:
@ -111,12 +215,25 @@ void BbrBweSender::GiveFeedback(const FeedbackPacket& feedback) {
TryUpdatingCyclePhase(now_ms);
break;
case PROBE_RTT:
TryExitingProbeRtt(now_ms, 0);
TryExitingProbeRtt(now_ms, round_count_);
break;
case RECOVERY:
TryExitingRecovery(new_round_started);
break;
}
TryEnteringProbeRtt(now_ms);
// TODO(gnish): implement functions updating congestion window and pacing rate
// controllers.
TryEnteringRecovery(new_round_started); // Comment this line to disable
// entering Recovery mode.
for (uint64_t f : feedback_vector)
AddToPastRtts(packet_stats_[f].ack_time_ms - packet_stats_[f].send_time_ms);
CalculatePacingRate();
// Make sure we don't get stuck when pacing_rate is 0, because of simulation
// tool specifics.
if (!pacing_rate_bps_)
pacing_rate_bps_ = 100;
BWE_TEST_LOGGING_PLOT(1, "SendRate", now_ms, pacing_rate_bps_ / 1000);
// TODO(gnish): Add implementation for BitrateObserver class to update pacing
// rate for the pacer and the encoder.
}
size_t BbrBweSender::TargetCongestionWindow(float gain) {
@ -127,8 +244,82 @@ size_t BbrBweSender::TargetCongestionWindow(float gain) {
return target_congestion_window;
}
bool BbrBweSender::UpdateBandwidthAndMinRtt() {
return false;
rtc::Optional<int64_t> BbrBweSender::CalculateBandwidthSample(
size_t data_sent_bytes,
int64_t send_time_delta_ms,
size_t data_acked_bytes,
int64_t ack_time_delta_ms) {
rtc::Optional<int64_t> bandwidth_sample;
if (send_time_delta_ms > 0)
*bandwidth_sample = data_sent_bytes * 8000 / send_time_delta_ms;
rtc::Optional<int64_t> ack_rate;
if (ack_time_delta_ms > 0)
*ack_rate = data_acked_bytes * 8000 / ack_time_delta_ms;
// If send rate couldn't be calculated automaticaly set |bandwidth_sample| to
// ack_rate.
if (!bandwidth_sample)
bandwidth_sample = ack_rate;
if (bandwidth_sample && ack_rate)
*bandwidth_sample = std::min(*bandwidth_sample, *ack_rate);
return bandwidth_sample;
}
void BbrBweSender::AddSampleForHighGain() {
if (!high_gain_over_)
return;
high_gain_over_ = false;
// Calculate data sent/acked and time elapsed only for packets sent during
// high gain phase.
size_t data_sent_bytes = data_sent_before_high_gain_ended_bytes_ -
data_sent_before_high_gain_started_bytes_;
int64_t send_time_delta_ms = last_packet_send_time_during_high_gain_ms_ -
*first_packet_send_time_during_high_gain_ms_;
size_t data_acked_bytes = data_acked_before_high_gain_ended_bytes_ -
data_acked_before_high_gain_started_bytes_;
int64_t ack_time_delta_ms = last_packet_ack_time_during_high_gain_ms_ -
first_packet_ack_time_during_high_gain_ms_;
rtc::Optional<int64_t> bandwidth_sample = CalculateBandwidthSample(
data_sent_bytes, send_time_delta_ms, data_acked_bytes, ack_time_delta_ms);
if (bandwidth_sample)
max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample, round_count_);
first_packet_send_time_during_high_gain_ms_.reset();
}
bool BbrBweSender::UpdateBandwidthAndMinRtt(
int64_t now_ms,
const std::vector<uint64_t>& feedback_vector,
int64_t bytes_acked) {
rtc::Optional<int64_t> min_rtt_sample_ms;
for (uint64_t f : feedback_vector) {
PacketStats packet = packet_stats_[f];
size_t data_sent_bytes =
packet.data_sent_bytes - packet.data_sent_before_last_sent_packet_bytes;
int64_t send_time_delta_ms =
packet.last_sent_packet_send_time_ms - packet.send_time_ms;
size_t data_acked_bytes = packet.data_acked_bytes -
packet.data_acked_before_last_acked_packet_bytes;
int64_t ack_time_delta_ms =
packet.ack_time_ms - packet.last_acked_packet_ack_time_ms;
rtc::Optional<int64_t> bandwidth_sample =
CalculateBandwidthSample(data_sent_bytes, send_time_delta_ms,
data_acked_bytes, ack_time_delta_ms);
if (bandwidth_sample)
max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample,
round_count_);
AddSampleForHighGain(); // Comment to disable bucket for high gain.
if (!min_rtt_sample_ms)
*min_rtt_sample_ms = packet.ack_time_ms - packet.send_time_ms;
else
*min_rtt_sample_ms = std::min(*min_rtt_sample_ms,
packet.ack_time_ms - packet.send_time_ms);
BWE_TEST_LOGGING_PLOT(1, "MinRtt", now_ms,
packet.ack_time_ms - packet.send_time_ms);
}
if (!min_rtt_sample_ms)
return false;
min_rtt_filter_->AddRttSample(*min_rtt_sample_ms, now_ms);
bool min_rtt_expired = min_rtt_filter_->MinRttExpired(now_ms);
return min_rtt_expired;
}
void BbrBweSender::EnterStartup() {
@ -174,12 +365,13 @@ void BbrBweSender::TryUpdatingCyclePhase(int64_t now_ms) {
// If BBR was probing and it couldn't increase data inflight sufficiently in
// one min_rtt time, continue probing. BBR design doc isn't clear about this,
// but condition helps in quicker ramp-up and performs better.
if (pacing_gain_ > 1.0 &&
prior_in_flight_ < TargetCongestionWindow(pacing_gain_))
if (pacing_gain_ > 1.0 && congestion_window_->data_inflight() <
TargetCongestionWindow(pacing_gain_))
advance_cycle_phase = false;
// If BBR has already drained queues there is no point in continuing draining
// phase.
if (pacing_gain_ < 1.0 && prior_in_flight_ <= TargetCongestionWindow(1))
if (pacing_gain_ < 1.0 &&
congestion_window_->data_inflight() <= TargetCongestionWindow(1))
advance_cycle_phase = true;
if (advance_cycle_phase) {
cycle_index_++;
@ -190,8 +382,7 @@ void BbrBweSender::TryUpdatingCyclePhase(int64_t now_ms) {
}
void BbrBweSender::TryEnteringProbeRtt(int64_t now_ms) {
if (min_rtt_filter_->min_rtt_expired(now_ms, kMinRttFilterSizeMs) &&
mode_ != PROBE_RTT) {
if (min_rtt_filter_->MinRttExpired(now_ms) && mode_ != PROBE_RTT) {
mode_ = PROBE_RTT;
pacing_gain_ = 1;
probe_rtt_start_time_ms_ = now_ms;
@ -199,9 +390,10 @@ void BbrBweSender::TryEnteringProbeRtt(int64_t now_ms) {
}
}
// minimum_congestion_window_start_time_'s value is set to the first moment when
// data inflight was less then kMinimumCongestionWindowBytes, we should make
// sure that BBR has been in PROBE_RTT mode for at least one round or 200ms.
// |minimum_congestion_window_start_time_|'s value is set to the first moment
// when data inflight was less then
// |CongestionWindow::kMinimumCongestionWindowBytes|, we should make sure that
// BBR has been in PROBE_RTT mode for at least one round or 200ms.
void BbrBweSender::TryExitingProbeRtt(int64_t now_ms, int64_t round) {
if (!minimum_congestion_window_start_time_ms_) {
if (congestion_window_->data_inflight() <=
@ -218,13 +410,74 @@ void BbrBweSender::TryExitingProbeRtt(int64_t now_ms, int64_t round) {
}
}
void BbrBweSender::TryEnteringRecovery(bool new_round_started) {
// If we are already in Recovery don't try to enter.
if (mode_ == RECOVERY || !new_round_started || !full_bandwidth_reached_)
return;
uint64_t increased_rtt_round_counter = 0;
// If average RTT for past |kPastRttsFilterSize| rounds has been more than
// some multiplier of min_rtt_ms enter Recovery.
for (BbrBweSender::AverageRtt i : past_rtts_) {
if (i.sum_of_rtts_ms / (int64_t)i.num_samples >=
*min_rtt_filter_->min_rtt_ms() * kRttIncreaseThreshold)
increased_rtt_round_counter++;
}
if (increased_rtt_round_counter < kPastRttsFilterSize)
return;
mode_ = RECOVERY;
pacing_gain_ = kRecoveryPacingGain;
congestion_window_gain_ = kRecoveryCongestionWindowGain;
}
void BbrBweSender::TryExitingRecovery(bool new_round_started) {
if (mode_ != RECOVERY || !new_round_started || !full_bandwidth_reached_)
return;
// If average RTT for the past round has decreased sufficiently exit Recovery.
if (!past_rtts_.empty()) {
BbrBweSender::AverageRtt last_round_sample = past_rtts_.back();
if (last_round_sample.sum_of_rtts_ms / last_round_sample.num_samples <=
*min_rtt_filter_->min_rtt_ms() * kRttDecreaseThreshold) {
EnterProbeBw(clock_->TimeInMilliseconds());
}
}
}
int64_t BbrBweSender::TimeUntilNextProcess() {
return 100;
return 50;
}
void BbrBweSender::OnPacketsSent(const Packets& packets) {
last_packet_sent_ =
static_cast<const MediaPacket*>(packets.back())->sequence_number();
for (Packet* packet : packets) {
if (packet->GetPacketType() == Packet::kMedia) {
MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
bytes_sent_ += media_packet->payload_size();
PacketStats packet_stats = PacketStats(
media_packet->sequence_number(), 0,
media_packet->sender_timestamp_ms(), 0, last_packet_ack_time_,
media_packet->payload_size(), 0, bytes_sent_, 0, bytes_acked_);
packet_stats_[media_packet->sequence_number()] = packet_stats;
last_packet_send_time_ = media_packet->sender_timestamp_ms();
last_packet_sent_sequence_number_ = media_packet->sequence_number();
// If this is the first packet sent for high gain phase, save data for it.
if (!first_packet_send_time_during_high_gain_ms_ && pacing_gain_ > 1) {
*first_packet_send_time_during_high_gain_ms_ = last_packet_send_time_;
data_sent_before_high_gain_started_bytes_ =
bytes_sent_ - media_packet->payload_size() / 2;
first_packet_seq_num_during_high_gain_ =
media_packet->sequence_number();
}
// This condition ensures that |last_packet_seq_num_during_high_gain_|
// will contain a sequence number of the last packet sent during high gain
// phase.
if (pacing_gain_ > 1) {
last_packet_send_time_during_high_gain_ms_ = last_packet_send_time_;
data_sent_before_high_gain_ended_bytes_ =
bytes_sent_ - media_packet->payload_size() / 2;
last_packet_seq_num_during_high_gain_ = media_packet->sequence_number();
}
congestion_window_->PacketSent(media_packet->payload_size());
}
}
}
void BbrBweSender::Process() {}

View File

@ -12,11 +12,13 @@
#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_BBR_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_BBR_H_
#include <list>
#include <map>
#include <memory>
#include <vector>
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/modules/video_coding/sequence_number_util.h"
#include "webrtc/rtc_base/optional.h"
#include "webrtc/rtc_base/random.h"
@ -39,15 +41,69 @@ class BbrBweSender : public BweSender {
PROBE_BW,
// Temporarily limiting congestion window size in order to measure
// minimum RTT.
PROBE_RTT
PROBE_RTT,
// Temporarily reducing pacing rate and congestion window, in order to
// ensure no queues are built.
RECOVERY
};
struct PacketStats {
PacketStats() {}
PacketStats(int64_t send_time_, size_t payload_size_)
: send_time(send_time_), payload_size(payload_size_) {}
int64_t send_time;
size_t payload_size;
PacketStats(uint16_t sequence_number_,
int64_t last_sent_packet_send_time_ms_,
int64_t send_time_ms_,
int64_t ack_time_ms_,
int64_t last_acked_packet_ack_time_ms_,
size_t payload_size_bytes_,
size_t data_sent_bytes_,
size_t data_sent_before_last_sent_packet_bytes_,
size_t data_acked_bytes_,
size_t data_acked_before_last_acked_packet_bytes_)
: sequence_number(sequence_number_),
last_sent_packet_send_time_ms(last_sent_packet_send_time_ms_),
send_time_ms(send_time_ms_),
ack_time_ms(ack_time_ms_),
last_acked_packet_ack_time_ms(last_acked_packet_ack_time_ms_),
payload_size_bytes(payload_size_bytes_),
data_sent_bytes(data_sent_bytes_),
data_sent_before_last_sent_packet_bytes(
data_sent_before_last_sent_packet_bytes_),
data_acked_bytes(data_acked_bytes_),
data_acked_before_last_acked_packet_bytes(
data_acked_before_last_acked_packet_bytes_) {}
// Sequence number of this packet.
uint16_t sequence_number;
// Send time of the last sent packet at ack time of this packet.
int64_t last_sent_packet_send_time_ms;
// Send time of this packet.
int64_t send_time_ms;
// Ack time of this packet.
int64_t ack_time_ms;
// Ack time of the last acked packet at send time of this packet.
int64_t last_acked_packet_ack_time_ms;
// Payload size of this packet.
size_t payload_size_bytes;
// Amount of data sent before this packet was sent.
size_t data_sent_bytes;
// Amount of data sent, before last sent packet.
size_t data_sent_before_last_sent_packet_bytes;
// Amount of data acked, before this packet was acked.
size_t data_acked_bytes;
// Amount of data acked, before last acked packet.
size_t data_acked_before_last_acked_packet_bytes;
};
struct AverageRtt {
AverageRtt() {}
AverageRtt(int64_t sum_of_rtts_ms_, int64_t num_samples_, uint64_t round_)
: sum_of_rtts_ms(sum_of_rtts_ms_),
num_samples(num_samples_),
round(round_) {}
// Sum of RTTs over the round.
int64_t sum_of_rtts_ms;
// Number of RTT samples over the round.
int64_t num_samples;
// The number of the round average RTT is recorded for.
uint64_t round;
};
void OnPacketsSent(const Packets& packets) override;
int GetFeedbackIntervalMs() const override;
@ -57,14 +113,38 @@ class BbrBweSender : public BweSender {
private:
void EnterStartup();
bool UpdateBandwidthAndMinRtt();
bool UpdateBandwidthAndMinRtt(int64_t now_ms,
const std::vector<uint64_t>& feedback_vector,
int64_t bytes_acked);
void TryExitingStartup();
void TryExitingDrain(int64_t now_ms);
void EnterProbeBw(int64_t now_ms);
void TryUpdatingCyclePhase(int64_t now_ms);
void TryEnteringProbeRtt(int64_t now_ms);
void TryExitingProbeRtt(int64_t now_ms, int64_t round);
void TryEnteringRecovery(bool new_round_started);
void TryExitingRecovery(bool new_round_started);
size_t TargetCongestionWindow(float gain);
void CalculatePacingRate();
// Calculates and returns bandwidth sample as minimum between send rate and
// ack rate, returns nothing if sample cannot be calculated.
rtc::Optional<int64_t> CalculateBandwidthSample(size_t data_sent,
int64_t send_time_delta_ms,
size_t data_acked,
int64_t ack_time_delta_ms);
// Calculate and add bandwidth sample only for packets' sent during high gain
// phase. Motivation of having a seperate bucket for high gain phase is to
// achieve quicker ramp up. Slight overestimations may happen due to window
// not being as large as usual.
void AddSampleForHighGain();
// Declares lost packets as acked. Implements simple logic by looking at the
// gap between sequence numbers. If there is a gap between sequence numbers we
// declare those packets as lost immediately.
void HandleLoss(uint64_t last_acked_packet, uint64_t recently_acked_packet);
void AddToPastRtts(int64_t rtt_sample_ms);
Clock* const clock_;
Mode mode_;
std::unique_ptr<MaxBandwidthFilter> max_bandwidth_filter_;
@ -72,7 +152,6 @@ class BbrBweSender : public BweSender {
std::unique_ptr<CongestionWindow> congestion_window_;
std::unique_ptr<Random> rand_;
uint64_t round_count_;
uint64_t last_packet_sent_;
uint64_t round_trip_end_;
float pacing_gain_;
float congestion_window_gain_;
@ -87,9 +166,7 @@ class BbrBweSender : public BweSender {
// Index number of the currently used gain value in PROBE_BW mode, from 0 to
// kGainCycleLength - 1.
int64_t cycle_index_;
// Data inflight prior to the moment when last feedback was received.
size_t prior_in_flight_;
size_t bytes_acked_;
// Time we entered PROBE_RTT mode.
int64_t probe_rtt_start_time_ms_;
@ -101,6 +178,48 @@ class BbrBweSender : public BweSender {
// First round when data inflight decreased below kMinimumCongestionWindow in
// PROBE_RTT mode.
int64_t minimum_congestion_window_start_round_;
size_t bytes_sent_;
uint16_t last_packet_sent_sequence_number_;
uint16_t last_packet_acked_sequence_number_;
int64_t last_packet_ack_time_;
int64_t last_packet_send_time_;
int64_t pacing_rate_bps_;
// Send time of a packet sent first during high gain phase. Also serves as a
// flag, holding value means that we are already in high gain.
rtc::Optional<int64_t> first_packet_send_time_during_high_gain_ms_;
// Send time of a packet sent last during high gain phase.
int64_t last_packet_send_time_during_high_gain_ms_;
// Amount of data sent, before first packet was sent during high gain phase.
int64_t data_sent_before_high_gain_started_bytes_;
// Amount of data sent, before last packet was sent during high gain phase.
int64_t data_sent_before_high_gain_ended_bytes_;
// Ack time of a packet acked first during high gain phase.
int64_t first_packet_ack_time_during_high_gain_ms_;
// Ack time of a packet acked last during high gain phase.
int64_t last_packet_ack_time_during_high_gain_ms_;
// Amount of data acked, before the first packet was acked during high gain
// phase.
int64_t data_acked_before_high_gain_started_bytes_;
// Amount of data acked, before the last packet was acked during high gain
// phase.
int64_t data_acked_before_high_gain_ended_bytes_;
// Sequence number of the first packet sent during high gain phase.
uint16_t first_packet_seq_num_during_high_gain_;
// Sequence number of the last packet sent during high gain phase.
uint16_t last_packet_seq_num_during_high_gain_;
bool high_gain_over_;
std::map<int64_t, PacketStats> packet_stats_;
std::list<AverageRtt> past_rtts_;
};
class BbrBweReceiver : public BweReceiver {
@ -113,6 +232,7 @@ class BbrBweReceiver : public BweReceiver {
private:
SimulatedClock clock_;
std::vector<uint64_t> packet_feedbacks_;
};
} // namespace bwe
} // namespace testing

View File

@ -14,6 +14,9 @@
namespace webrtc {
namespace testing {
namespace bwe {
const size_t MaxBandwidthFilter::kBandwidthWindowFilterSize;
MaxBandwidthFilter::MaxBandwidthFilter()
: bandwidth_last_round_bytes_per_ms_(0),
max_bandwidth_estimate_bps_(0),
@ -23,12 +26,11 @@ MaxBandwidthFilter::~MaxBandwidthFilter() {}
// For detailed explanation about implementing bandwidth filter this way visit
// "Bbr design" doc. |sample_bps| was measured during |round|.
void MaxBandwidthFilter::AddBandwidthSample(int64_t sample_bps,
int64_t round,
size_t filter_size_round) {
void MaxBandwidthFilter::AddBandwidthSample(int64_t sample_bps, int64_t round) {
if (bandwidth_samples_[0].first == 0 ||
sample_bps >= bandwidth_samples_[0].first ||
round - bandwidth_samples_[2].second >= filter_size_round)
round - bandwidth_samples_[2].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize)
bandwidth_samples_[0] = bandwidth_samples_[1] =
bandwidth_samples_[2] = {sample_bps, round};
if (sample_bps >= bandwidth_samples_[1].first) {
@ -38,11 +40,13 @@ void MaxBandwidthFilter::AddBandwidthSample(int64_t sample_bps,
if (sample_bps >= bandwidth_samples_[2].first)
bandwidth_samples_[2] = {sample_bps, round};
}
if (round - bandwidth_samples_[0].second >= filter_size_round) {
if (round - bandwidth_samples_[0].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize) {
bandwidth_samples_[0] = bandwidth_samples_[1];
bandwidth_samples_[1] = bandwidth_samples_[2];
bandwidth_samples_[2] = {sample_bps, round};
if (round - bandwidth_samples_[0].second >= filter_size_round) {
if (round - bandwidth_samples_[0].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize) {
bandwidth_samples_[0] = bandwidth_samples_[1];
bandwidth_samples_[1] = bandwidth_samples_[2];
}
@ -50,13 +54,15 @@ void MaxBandwidthFilter::AddBandwidthSample(int64_t sample_bps,
return;
}
if (bandwidth_samples_[1].first == bandwidth_samples_[0].first &&
round - bandwidth_samples_[1].second > filter_size_round / 4) {
round - bandwidth_samples_[1].second >
MaxBandwidthFilter::kBandwidthWindowFilterSize / 4) {
bandwidth_samples_[2] = bandwidth_samples_[1] = {sample_bps, round};
max_bandwidth_estimate_bps_ = bandwidth_samples_[0].first;
return;
}
if (bandwidth_samples_[2].first == bandwidth_samples_[1].first &&
round - bandwidth_samples_[2].second > filter_size_round / 2)
round - bandwidth_samples_[2].second >
MaxBandwidthFilter::kBandwidthWindowFilterSize / 2)
bandwidth_samples_[2] = {sample_bps, round};
max_bandwidth_estimate_bps_ = bandwidth_samples_[0].first;
}

View File

@ -28,13 +28,15 @@ namespace testing {
namespace bwe {
class MaxBandwidthFilter {
public:
MaxBandwidthFilter();
// Number of rounds for bandwidth estimate to expire.
static const size_t kBandwidthWindowFilterSize = 10;
MaxBandwidthFilter();
~MaxBandwidthFilter();
int64_t max_bandwidth_estimate_bps() { return max_bandwidth_estimate_bps_; }
// Adds bandwidth sample to the bandwidth filter.
void AddBandwidthSample(int64_t sample, int64_t round, size_t filter_size);
void AddBandwidthSample(int64_t sample, int64_t round);
// Checks if bandwidth has grown by certain multiplier for past x rounds,
// to decide whether or full bandwidth was reached.

View File

@ -22,61 +22,61 @@ TEST(MaxBandwidthFilterTest, InitializationCheck) {
TEST(MaxBandwidthFilterTest, AddOneBandwidthSample) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(13, 4, 10);
max_bandwidth_filter.AddBandwidthSample(13, 4);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 13);
}
TEST(MaxBandwidthFilterTest, AddSeveralBandwidthSamples) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(10, 5, 10);
max_bandwidth_filter.AddBandwidthSample(13, 6, 10);
max_bandwidth_filter.AddBandwidthSample(10, 5);
max_bandwidth_filter.AddBandwidthSample(13, 6);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 13);
}
TEST(MaxBandwidthFilterTest, FirstSampleTimeOut) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(13, 5, 10);
max_bandwidth_filter.AddBandwidthSample(10, 15, 10);
max_bandwidth_filter.AddBandwidthSample(13, 5);
max_bandwidth_filter.AddBandwidthSample(10, 15);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 10);
}
TEST(MaxBandwidthFilterTest, SecondSampleBecomesTheFirst) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(4, 5, 10);
max_bandwidth_filter.AddBandwidthSample(3, 10, 10);
max_bandwidth_filter.AddBandwidthSample(2, 15, 10);
max_bandwidth_filter.AddBandwidthSample(4, 5);
max_bandwidth_filter.AddBandwidthSample(3, 10);
max_bandwidth_filter.AddBandwidthSample(2, 15);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 3);
}
TEST(MaxBandwidthFilterTest, ThirdSampleBecomesTheFirst) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(4, 5, 10);
max_bandwidth_filter.AddBandwidthSample(3, 10, 10);
max_bandwidth_filter.AddBandwidthSample(2, 25, 10);
max_bandwidth_filter.AddBandwidthSample(4, 5);
max_bandwidth_filter.AddBandwidthSample(3, 10);
max_bandwidth_filter.AddBandwidthSample(2, 25);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 2);
}
TEST(MaxBandwidthFilterTest, FullBandwidthReached) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(100, 1, 10);
max_bandwidth_filter.AddBandwidthSample(100, 1);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(110, 2, 10);
max_bandwidth_filter.AddBandwidthSample(110, 2);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(120, 3, 10);
max_bandwidth_filter.AddBandwidthSample(120, 3);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(124, 4, 10);
max_bandwidth_filter.AddBandwidthSample(124, 4);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), true);
}
TEST(MaxBandwidthFilterTest, FullBandwidthNotReached) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(100, 1, 10);
max_bandwidth_filter.AddBandwidthSample(100, 1);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(110, 2, 10);
max_bandwidth_filter.AddBandwidthSample(110, 2);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(120, 3, 10);
max_bandwidth_filter.AddBandwidthSample(120, 3);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(125, 4, 10);
max_bandwidth_filter.AddBandwidthSample(125, 4);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
}
} // namespace bwe

View File

@ -20,14 +20,19 @@
namespace webrtc {
namespace testing {
namespace bwe {
// Expiration time for min_rtt sample, which is set to 10 seconds according to
// BBR design doc.
const int64_t kMinRttFilterSizeMs = 10000;
class MinRttFilter {
public:
MinRttFilter() {}
~MinRttFilter() {}
rtc::Optional<int64_t> min_rtt_ms() { return min_rtt_ms_; }
void add_rtt_sample(int64_t rtt_ms, int64_t now_ms) {
if (!min_rtt_ms_ || rtt_ms <= *min_rtt_ms_) {
void AddRttSample(int64_t rtt_ms, int64_t now_ms) {
if (!min_rtt_ms_ || rtt_ms <= *min_rtt_ms_ || MinRttExpired(now_ms)) {
min_rtt_ms_.emplace(rtt_ms);
discovery_time_ms_ = now_ms;
}
@ -36,8 +41,8 @@ class MinRttFilter {
// Checks whether or not last discovered min_rtt value is older than x
// milliseconds.
bool min_rtt_expired(int64_t now_ms, int64_t min_rtt_filter_window_size_ms) {
return now_ms - discovery_time_ms_ >= min_rtt_filter_window_size_ms;
bool MinRttExpired(int64_t now_ms) {
return now_ms - discovery_time_ms_ >= kMinRttFilterSizeMs;
}
private:

View File

@ -23,20 +23,23 @@ TEST(MinRttFilterTest, InitializationCheck) {
TEST(MinRttFilterTest, AddRttSample) {
MinRttFilter min_rtt_filter;
min_rtt_filter.add_rtt_sample(120, 5);
min_rtt_filter.AddRttSample(120, 5);
EXPECT_EQ(min_rtt_filter.min_rtt_ms(), 120);
EXPECT_EQ(min_rtt_filter.discovery_time(), 5);
min_rtt_filter.add_rtt_sample(121, 6);
min_rtt_filter.AddRttSample(121, 6);
EXPECT_EQ(min_rtt_filter.discovery_time(), 5);
min_rtt_filter.add_rtt_sample(119, 7);
min_rtt_filter.AddRttSample(119, 7);
EXPECT_EQ(min_rtt_filter.discovery_time(), 7);
min_rtt_filter.AddRttSample(140, 10007);
EXPECT_EQ(min_rtt_filter.discovery_time(), 10007);
EXPECT_EQ(min_rtt_filter.min_rtt_ms(), 140);
}
TEST(MinRttFilterTest, MinRttExpired) {
MinRttFilter min_rtt_filter;
min_rtt_filter.add_rtt_sample(120, 5);
EXPECT_EQ(min_rtt_filter.min_rtt_expired(10, 5), true);
EXPECT_EQ(min_rtt_filter.min_rtt_expired(9, 5), false);
min_rtt_filter.AddRttSample(120, 5);
EXPECT_EQ(min_rtt_filter.MinRttExpired(10006), true);
EXPECT_EQ(min_rtt_filter.MinRttExpired(10), false);
}
} // namespace bwe
} // namespace testing