/* * 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 #include #include #include #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/system/fallthrough.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. const DataSize kDefaultTCPMSS = DataSize::bytes(1460); // Constants based on TCP defaults. const 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::kbps(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.bandwidth = bandwidth; 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(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) { Timestamp feedback_recv_time = msg.feedback_time; absl::optional last_sent_packet = msg.PacketsWithFeedback().back().sent_packet; if (!last_sent_packet.has_value()) { RTC_LOG(LS_WARNING) << "Last ack packet not in history, no RTT update"; } else { 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 lost_packets = msg.LostWithSendInfo(); DiscardLostPackets(lost_packets); std::vector acked_packets = msg.ReceivedWithSendInfo(); int packets_sent = static_cast(lost_packets.size() + acked_packets.size()); int packets_lost = static_cast(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(); } TimeDelta BbrNetworkController::GetMinRtt() const { return !min_rtt_.IsZero() ? min_rtt_ : TimeDelta::us(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& 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& 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::ms(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; } RTC_FALLTHROUGH(); 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