quic/congestion_control/Bbr.h (159 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <quic/congestion_control/Bandwidth.h>
#include <quic/congestion_control/CongestionController.h>
#include <quic/congestion_control/third_party/windowed_filter.h>
#include <quic/state/StateData.h>
#include <quic/state/TransportSettings.h>
namespace quic {
// Cwnd and pacing gain during STARTUP
constexpr float kStartupGain = 2.885f; // 2/ln(2)
// Cwnd gain during ProbeBw
constexpr float kProbeBwGain = 2.0f;
// The expected of bandwidth growth in each round trip time during STARTUP
constexpr float kExpectedStartupGrowth = 1.25f;
// How many rounds of rtt to stay in STARUP when the bandwidth isn't growing as
// fast as kExpectedStartupGrowth
constexpr uint8_t kStartupSlowGrowRoundLimit = 3;
// Default number of pacing cycles
constexpr uint8_t kNumOfCycles = 8;
// Default pacing cycles
constexpr std::array<float, kNumOfCycles> kPacingGainCycles =
{1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
// Background mode number of pacing cycles. Probe for the available BW less
// frequently (once every 32 cycles).
constexpr uint8_t kBGNumOfCycles = 32;
// During ProbeRtt, we need to stay in low inflight condition for at least
// kProbeRttDuration.
constexpr std::chrono::milliseconds kProbeRttDuration{200};
// The cwnd gain to use when BbrConfig.largeProbeRttCwnd is set.
constexpr float kLargeProbeRttCwndGain = 0.75f;
// Bandwidth WindowFilter length, in unit of RTT. This value is from Chromium
// code. I don't know why.
constexpr uint8_t bandwidthWindowLength(const uint8_t numOfCycles) {
return numOfCycles + 2;
}
// RTT Sampler default expiration
constexpr std::chrono::seconds kDefaultRttSamplerExpiration{10};
// 64K, used in sendQuantum calculation:
constexpr uint64_t k64K = 64 * 1024;
// TODO: rate based startup mode
// TODO: send extra bandwidth probers when pipe isn't sufficiently full
class BbrCongestionController : public CongestionController {
public:
/**
* A class to collect RTT samples, tracks the minimal one among them, and
* expire the min rtt sample after some period of time.
*/
class MinRttSampler {
public:
virtual ~MinRttSampler() = default;
virtual std::chrono::microseconds minRtt() const = 0;
/**
* Returns: true iff we have min rtt sample and it has expired.
*/
virtual bool minRttExpired() const = 0;
/**
* rttSample: current rtt sample
* sampledTime: the time point this sample is collected
*
* return: whether min rtt is updated by the new sample
*/
virtual bool newRttSample(
std::chrono::microseconds rttSample,
TimePoint sampledTime) noexcept = 0;
/**
* Mark timestamp as the minrtt time stamp. Min rtt will expire at
* timestampe + expiration_duration.
*/
virtual void timestampMinRtt(TimePoint timestamp) noexcept = 0;
};
class BandwidthSampler {
public:
virtual ~BandwidthSampler() = default;
virtual Bandwidth getBandwidth() const = 0;
[[nodiscard]] virtual Bandwidth getLatestSample() const = 0;
virtual void onPacketAcked(
const CongestionController::AckEvent&,
uint64_t roundTripCounter) = 0;
virtual void onAppLimited() = 0;
virtual bool isAppLimited() const = 0;
virtual void setWindowLength(const uint64_t windowLength) noexcept = 0;
};
explicit BbrCongestionController(QuicConnectionStateBase& conn);
explicit BbrCongestionController(
QuicConnectionStateBase& conn,
uint64_t cwndBytes,
std::chrono::microseconds minRtt);
// TODO: these should probably come in as part of a builder. but I'm not sure
// if the sampler interface is here to stay atm, so bear with me
void setRttSampler(std::unique_ptr<MinRttSampler> sampler) noexcept;
void setBandwidthSampler(std::unique_ptr<BandwidthSampler> sampler) noexcept;
enum class BbrState : uint8_t {
Startup,
Drain,
ProbeBw,
ProbeRtt,
};
enum class RecoveryState : uint8_t {
NOT_RECOVERY = 0,
CONSERVATIVE = 1,
GROWTH = 2,
};
void onRemoveBytesFromInflight(uint64_t bytesToRemove) override;
void onPacketSent(const OutstandingPacket&) override;
void onPacketAckOrLoss(
const AckEvent* FOLLY_NULLABLE,
const LossEvent* FOLLY_NULLABLE) override;
void onPacketAckOrLoss(
folly::Optional<AckEvent> ack,
folly::Optional<LossEvent> loss) {
onPacketAckOrLoss(ack.get_pointer(), loss.get_pointer());
}
uint64_t getWritableBytes() const noexcept override;
uint64_t getCongestionWindow() const noexcept override;
CongestionControlType type() const noexcept override;
void setAppIdle(bool idle, TimePoint eventTime) noexcept override;
void setAppLimited() override;
void setExperimental(bool experimental) override;
/**
* Sets a factor of the measured bottleneck BW that the congestion controller
* should make use of. Can be used to leave headroom for other flows and to
* reduce the risk of queuing in the case of network condition changes.
*
* A factor less than 1.0 makes BBR less aggressive:
* - During startup, BBR will grow its pacing rate and congestion window
* more slowly, and StartupGain is halved.
* - After exiting the startup phase:
* - ProbeBW NumberOfCycles is set to 32 instead of 8. This causes BBR
* to probe for bandwidth less frequently.
* - ProbeBW PacingGainCycles values=factor after probing. If factor < 1,
*. then BBR will only use a fraction of the measured bandwidth.
*
* If bandwidthUtilizationFactor >= 1.0, background mode is disabled.
* If bandwidthUtilizationFactor < 0.25, a value of 0.25 is used instead.
*/
void setBandwidthUtilizationFactor(
float bandwidthUtilizationFactor) noexcept override;
bool isAppLimited() const noexcept override;
void getStats(CongestionControllerStats& stats) const override;
// TODO: some of these do not have to be in public API.
bool inRecovery() const noexcept;
BbrState state() const noexcept;
[[nodiscard]] bool isInBackgroundMode() const noexcept override;
protected:
[[nodiscard]] virtual Bandwidth bandwidth() const noexcept;
std::unique_ptr<MinRttSampler> minRttSampler_;
std::unique_ptr<BandwidthSampler> bandwidthSampler_;
float cwndGain_{kStartupGain};
float pacingGain_{kStartupGain};
// Whether we have found the bottleneck link bandwidth
bool btlbwFound_{false};
QuicConnectionStateBase& conn_;
BbrState state_{BbrState::Startup};
RecoveryState recoveryState_{RecoveryState::NOT_RECOVERY};
private:
/* prevInflightBytes: the inflightBytes value before the current
* onPacketAckOrLoss invocation.
* hasLoss: whether current onPacketAckOrLoss has loss.
*/
void
onPacketAcked(const AckEvent& ack, uint64_t prevInflightBytes, bool hasLoss);
void onPacketLoss(const LossEvent&, uint64_t ackedBytes);
void updatePacing() noexcept;
/**
* Update the ack aggregation states
*
* return: the excessive bytes from ack aggregation.
*
* Ack Aggregation: starts when ack arrival rate is slower than estimated
* bandwidth, lasts until it's faster than estimated bandwidth.
*/
uint64_t updateAckAggregation(const AckEvent& ack);
/**
* Check if we have found the bottleneck link bandwidth with the current ack.
*/
void detectBottleneckBandwidth(bool);
bool shouldExitStartup() noexcept;
bool shouldExitDrain() noexcept;
bool shouldProbeRtt(TimePoint ackTime) noexcept;
void transitToDrain() noexcept;
void transitToProbeBw(TimePoint congestionEventTime);
void transitToProbeRtt() noexcept;
void transitToStartup() noexcept;
// Pick a random pacing cycle except 1
size_t pickRandomCycle();
// Special handling of AckEvent when connection is in ProbeRtt state
void handleAckInProbeRtt(bool newRoundTrip, TimePoint ackTime) noexcept;
/**
* Special handling of AckEvent when connection is in ProbeBw state.
*
* prevInflightBytes: the inflightBytes value before the current
* onPacketAckOrLoss invocation.
* hasLoss: whether the current onpacketAckOrLoss has loss.
*/
void handleAckInProbeBw(
TimePoint ackTime,
uint64_t prevInflightBytes,
bool hasLoss) noexcept;
/*
* Return if we are at the start of a new round trip.
*/
bool updateRoundTripCounter(TimePoint largestAckedSentTime) noexcept;
void updateRecoveryWindowWithAck(uint64_t bytesAcked) noexcept;
uint64_t calculateTargetCwnd(float gain) const noexcept;
void updateCwnd(uint64_t ackedBytes, uint64_t excessiveBytes) noexcept;
std::chrono::microseconds minRtt() const noexcept;
bool isExperimental_{false};
// Number of round trips the connection has witnessed
uint64_t roundTripCounter_{0};
// When a packet with send time later than endOfRoundTrip_ is acked, the
// current round strip is ended.
TimePoint endOfRoundTrip_;
// When a packet with send time later than endOfRecovery_ is acked, the
// connection is no longer in recovery
folly::Optional<TimePoint> endOfRecovery_;
// Cwnd in bytes
uint64_t cwnd_;
// Initial cwnd in bytes
uint64_t initialCwnd_;
// Congestion window when the connection is in recovery
uint64_t recoveryWindow_;
// Number of bytes we expect to send over one RTT when paced write.
uint64_t pacingWindow_{0};
// ProbeBw parameters
uint64_t numOfCycles_{kNumOfCycles};
std::vector<float> pacingGainCycles_;
float bandwidthUtilizationFactor_{1.0};
uint64_t sendQuantum_{0};
Bandwidth previousStartupBandwidth_;
// Counter of continuous round trips in STARTUP that bandwidth isn't growing
// fast enough
uint8_t slowStartupRoundCounter_{0};
// Current cycle index in kPacingGainCycles
size_t pacingCycleIndex_{0};
// The starting time of this pacing cycle. The cycle index will proceed by 1
// when we are one minrtt away from this time point.
TimePoint cycleStart_;
// Once in ProbeRtt state, we cannot exit ProbeRtt before at least we spend
// some duration with low inflight bytes. earliestTimeToExitProbeRtt_ is that
// time point.
folly::Optional<TimePoint> earliestTimeToExitProbeRtt_;
// We also cannot exit ProbeRtt if are not at least at the low inflight bytes
// mode for one RTT round. probeRttRound_ tracks that.
folly::Optional<uint64_t> probeRttRound_;
WindowedFilter<
uint64_t /* ack bytes count */,
MaxFilter<uint64_t>,
uint64_t /* roundtrip count */,
uint64_t /* roundtrip count */>
maxAckHeightFilter_;
folly::Optional<TimePoint> ackAggregationStartTime_;
uint64_t aggregatedAckBytes_{0};
bool appLimitedSinceProbeRtt_{false};
// The connection was very inactive and we are leaving that.
bool exitingQuiescene_{false};
friend std::ostream& operator<<(
std::ostream& os,
const BbrCongestionController& bbr);
};
std::ostream& operator<<(std::ostream& os, const BbrCongestionController& bbr);
std::string bbrStateToString(BbrCongestionController::BbrState state);
std::string bbrRecoveryStateToString(
BbrCongestionController::RecoveryState recoveryState);
} // namespace quic