874 lines
31 KiB
Go
874 lines
31 KiB
Go
package congestion
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/metacubex/quic-go/congestion"
|
|
)
|
|
|
|
const (
|
|
infRTT = time.Duration(math.MaxInt64)
|
|
defaultConnectionStateMapQueueSize = 256
|
|
defaultCandidatesBufferSize = 256
|
|
)
|
|
|
|
type roundTripCount uint64
|
|
|
|
// SendTimeState is a subset of ConnectionStateOnSentPacket which is returned
|
|
// to the caller when the packet is acked or lost.
|
|
type sendTimeState struct {
|
|
// Whether other states in this object is valid.
|
|
isValid bool
|
|
// Whether the sender is app limited at the time the packet was sent.
|
|
// App limited bandwidth sample might be artificially low because the sender
|
|
// did not have enough data to send in order to saturate the link.
|
|
isAppLimited bool
|
|
// Total number of sent bytes at the time the packet was sent.
|
|
// Includes the packet itself.
|
|
totalBytesSent congestion.ByteCount
|
|
// Total number of acked bytes at the time the packet was sent.
|
|
totalBytesAcked congestion.ByteCount
|
|
// Total number of lost bytes at the time the packet was sent.
|
|
totalBytesLost congestion.ByteCount
|
|
// Total number of inflight bytes at the time the packet was sent.
|
|
// Includes the packet itself.
|
|
// It should be equal to |total_bytes_sent| minus the sum of
|
|
// |total_bytes_acked|, |total_bytes_lost| and total neutered bytes.
|
|
bytesInFlight congestion.ByteCount
|
|
}
|
|
|
|
func newSendTimeState(
|
|
isAppLimited bool,
|
|
totalBytesSent congestion.ByteCount,
|
|
totalBytesAcked congestion.ByteCount,
|
|
totalBytesLost congestion.ByteCount,
|
|
bytesInFlight congestion.ByteCount,
|
|
) *sendTimeState {
|
|
return &sendTimeState{
|
|
isValid: true,
|
|
isAppLimited: isAppLimited,
|
|
totalBytesSent: totalBytesSent,
|
|
totalBytesAcked: totalBytesAcked,
|
|
totalBytesLost: totalBytesLost,
|
|
bytesInFlight: bytesInFlight,
|
|
}
|
|
}
|
|
|
|
type extraAckedEvent struct {
|
|
// The excess bytes acknowlwedged in the time delta for this event.
|
|
extraAcked congestion.ByteCount
|
|
|
|
// The bytes acknowledged and time delta from the event.
|
|
bytesAcked congestion.ByteCount
|
|
timeDelta time.Duration
|
|
// The round trip of the event.
|
|
round roundTripCount
|
|
}
|
|
|
|
func maxExtraAckedEventFunc(a, b extraAckedEvent) int {
|
|
if a.extraAcked > b.extraAcked {
|
|
return 1
|
|
} else if a.extraAcked < b.extraAcked {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// BandwidthSample
|
|
type bandwidthSample struct {
|
|
// The bandwidth at that particular sample. Zero if no valid bandwidth sample
|
|
// is available.
|
|
bandwidth Bandwidth
|
|
// The RTT measurement at this particular sample. Zero if no RTT sample is
|
|
// available. Does not correct for delayed ack time.
|
|
rtt time.Duration
|
|
// |send_rate| is computed from the current packet being acked('P') and an
|
|
// earlier packet that is acked before P was sent.
|
|
sendRate Bandwidth
|
|
// States captured when the packet was sent.
|
|
stateAtSend sendTimeState
|
|
}
|
|
|
|
func newBandwidthSample() *bandwidthSample {
|
|
return &bandwidthSample{
|
|
sendRate: infBandwidth,
|
|
}
|
|
}
|
|
|
|
// MaxAckHeightTracker is part of the BandwidthSampler. It is called after every
|
|
// ack event to keep track the degree of ack aggregation(a.k.a "ack height").
|
|
type maxAckHeightTracker struct {
|
|
// Tracks the maximum number of bytes acked faster than the estimated
|
|
// bandwidth.
|
|
maxAckHeightFilter *WindowedFilter[extraAckedEvent, roundTripCount]
|
|
// The time this aggregation started and the number of bytes acked during it.
|
|
aggregationEpochStartTime time.Time
|
|
aggregationEpochBytes congestion.ByteCount
|
|
// The last sent packet number before the current aggregation epoch started.
|
|
lastSentPacketNumberBeforeEpoch congestion.PacketNumber
|
|
// The number of ack aggregation epochs ever started, including the ongoing
|
|
// one. Stats only.
|
|
numAckAggregationEpochs uint64
|
|
ackAggregationBandwidthThreshold float64
|
|
startNewAggregationEpochAfterFullRound bool
|
|
reduceExtraAckedOnBandwidthIncrease bool
|
|
}
|
|
|
|
func newMaxAckHeightTracker(windowLength roundTripCount) *maxAckHeightTracker {
|
|
return &maxAckHeightTracker{
|
|
maxAckHeightFilter: NewWindowedFilter(windowLength, maxExtraAckedEventFunc),
|
|
lastSentPacketNumberBeforeEpoch: invalidPacketNumber,
|
|
ackAggregationBandwidthThreshold: 1.0,
|
|
}
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) Get() congestion.ByteCount {
|
|
return m.maxAckHeightFilter.GetBest().extraAcked
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) Update(
|
|
bandwidthEstimate Bandwidth,
|
|
isNewMaxBandwidth bool,
|
|
roundTripCount roundTripCount,
|
|
lastSentPacketNumber congestion.PacketNumber,
|
|
lastAckedPacketNumber congestion.PacketNumber,
|
|
ackTime time.Time,
|
|
bytesAcked congestion.ByteCount,
|
|
) congestion.ByteCount {
|
|
forceNewEpoch := false
|
|
|
|
if m.reduceExtraAckedOnBandwidthIncrease && isNewMaxBandwidth {
|
|
// Save and clear existing entries.
|
|
best := m.maxAckHeightFilter.GetBest()
|
|
secondBest := m.maxAckHeightFilter.GetSecondBest()
|
|
thirdBest := m.maxAckHeightFilter.GetThirdBest()
|
|
m.maxAckHeightFilter.Clear()
|
|
|
|
// Reinsert the heights into the filter after recalculating.
|
|
expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, best.timeDelta)
|
|
if expectedBytesAcked < best.bytesAcked {
|
|
best.extraAcked = best.bytesAcked - expectedBytesAcked
|
|
m.maxAckHeightFilter.Update(best, best.round)
|
|
}
|
|
expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, secondBest.timeDelta)
|
|
if expectedBytesAcked < secondBest.bytesAcked {
|
|
secondBest.extraAcked = secondBest.bytesAcked - expectedBytesAcked
|
|
m.maxAckHeightFilter.Update(secondBest, secondBest.round)
|
|
}
|
|
expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, thirdBest.timeDelta)
|
|
if expectedBytesAcked < thirdBest.bytesAcked {
|
|
thirdBest.extraAcked = thirdBest.bytesAcked - expectedBytesAcked
|
|
m.maxAckHeightFilter.Update(thirdBest, thirdBest.round)
|
|
}
|
|
}
|
|
|
|
// If any packet sent after the start of the epoch has been acked, start a new
|
|
// epoch.
|
|
if m.startNewAggregationEpochAfterFullRound &&
|
|
m.lastSentPacketNumberBeforeEpoch != invalidPacketNumber &&
|
|
lastAckedPacketNumber != invalidPacketNumber &&
|
|
lastAckedPacketNumber > m.lastSentPacketNumberBeforeEpoch {
|
|
forceNewEpoch = true
|
|
}
|
|
if m.aggregationEpochStartTime.IsZero() || forceNewEpoch {
|
|
m.aggregationEpochBytes = bytesAcked
|
|
m.aggregationEpochStartTime = ackTime
|
|
m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber
|
|
m.numAckAggregationEpochs++
|
|
return 0
|
|
}
|
|
|
|
// Compute how many bytes are expected to be delivered, assuming max bandwidth
|
|
// is correct.
|
|
aggregationDelta := ackTime.Sub(m.aggregationEpochStartTime)
|
|
expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, aggregationDelta)
|
|
// Reset the current aggregation epoch as soon as the ack arrival rate is less
|
|
// than or equal to the max bandwidth.
|
|
if m.aggregationEpochBytes <= congestion.ByteCount(m.ackAggregationBandwidthThreshold*float64(expectedBytesAcked)) {
|
|
// Reset to start measuring a new aggregation epoch.
|
|
m.aggregationEpochBytes = bytesAcked
|
|
m.aggregationEpochStartTime = ackTime
|
|
m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber
|
|
m.numAckAggregationEpochs++
|
|
return 0
|
|
}
|
|
|
|
m.aggregationEpochBytes += bytesAcked
|
|
|
|
// Compute how many extra bytes were delivered vs max bandwidth.
|
|
extraBytesAcked := m.aggregationEpochBytes - expectedBytesAcked
|
|
newEvent := extraAckedEvent{
|
|
extraAcked: expectedBytesAcked,
|
|
bytesAcked: m.aggregationEpochBytes,
|
|
timeDelta: aggregationDelta,
|
|
}
|
|
m.maxAckHeightFilter.Update(newEvent, roundTripCount)
|
|
return extraBytesAcked
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) SetFilterWindowLength(length roundTripCount) {
|
|
m.maxAckHeightFilter.SetWindowLength(length)
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) Reset(newHeight congestion.ByteCount, newTime roundTripCount) {
|
|
newEvent := extraAckedEvent{
|
|
extraAcked: newHeight,
|
|
round: newTime,
|
|
}
|
|
m.maxAckHeightFilter.Reset(newEvent, newTime)
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) SetAckAggregationBandwidthThreshold(threshold float64) {
|
|
m.ackAggregationBandwidthThreshold = threshold
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) SetStartNewAggregationEpochAfterFullRound(value bool) {
|
|
m.startNewAggregationEpochAfterFullRound = value
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) SetReduceExtraAckedOnBandwidthIncrease(value bool) {
|
|
m.reduceExtraAckedOnBandwidthIncrease = value
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) AckAggregationBandwidthThreshold() float64 {
|
|
return m.ackAggregationBandwidthThreshold
|
|
}
|
|
|
|
func (m *maxAckHeightTracker) NumAckAggregationEpochs() uint64 {
|
|
return m.numAckAggregationEpochs
|
|
}
|
|
|
|
// AckPoint represents a point on the ack line.
|
|
type ackPoint struct {
|
|
ackTime time.Time
|
|
totalBytesAcked congestion.ByteCount
|
|
}
|
|
|
|
// RecentAckPoints maintains the most recent 2 ack points at distinct times.
|
|
type recentAckPoints struct {
|
|
ackPoints [2]ackPoint
|
|
}
|
|
|
|
func (r *recentAckPoints) Update(ackTime time.Time, totalBytesAcked congestion.ByteCount) {
|
|
if ackTime.Before(r.ackPoints[1].ackTime) {
|
|
r.ackPoints[1].ackTime = ackTime
|
|
} else if ackTime.After(r.ackPoints[1].ackTime) {
|
|
r.ackPoints[0] = r.ackPoints[1]
|
|
r.ackPoints[1].ackTime = ackTime
|
|
}
|
|
|
|
r.ackPoints[1].totalBytesAcked = totalBytesAcked
|
|
}
|
|
|
|
func (r *recentAckPoints) Clear() {
|
|
r.ackPoints[0] = ackPoint{}
|
|
r.ackPoints[1] = ackPoint{}
|
|
}
|
|
|
|
func (r *recentAckPoints) MostRecentPoint() *ackPoint {
|
|
return &r.ackPoints[1]
|
|
}
|
|
|
|
func (r *recentAckPoints) LessRecentPoint() *ackPoint {
|
|
if r.ackPoints[0].totalBytesAcked != 0 {
|
|
return &r.ackPoints[0]
|
|
}
|
|
|
|
return &r.ackPoints[1]
|
|
}
|
|
|
|
// 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.
|
|
type connectionStateOnSentPacket struct {
|
|
// Time at which the packet is sent.
|
|
sentTime time.Time
|
|
// Size of the packet.
|
|
size congestion.ByteCount
|
|
// The value of |totalBytesSentAtLastAckedPacket| at the time the
|
|
// packet was sent.
|
|
totalBytesSentAtLastAckedPacket congestion.ByteCount
|
|
// The value of |lastAckedPacketSentTime| at the time the packet was
|
|
// sent.
|
|
lastAckedPacketSentTime time.Time
|
|
// The value of |lastAckedPacketAckTime| at the time the packet was
|
|
// sent.
|
|
lastAckedPacketAckTime time.Time
|
|
// Send time states that are returned to the congestion controller when the
|
|
// packet is acked or lost.
|
|
sendTimeState sendTimeState
|
|
}
|
|
|
|
// Snapshot constructor. Records the current state of the bandwidth
|
|
// sampler.
|
|
// |bytes_in_flight| is the bytes in flight right after the packet is sent.
|
|
func newConnectionStateOnSentPacket(
|
|
sentTime time.Time,
|
|
size congestion.ByteCount,
|
|
bytesInFlight congestion.ByteCount,
|
|
sampler *bandwidthSampler,
|
|
) *connectionStateOnSentPacket {
|
|
return &connectionStateOnSentPacket{
|
|
sentTime: sentTime,
|
|
size: size,
|
|
totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket,
|
|
lastAckedPacketSentTime: sampler.lastAckedPacketSentTime,
|
|
lastAckedPacketAckTime: sampler.lastAckedPacketAckTime,
|
|
sendTimeState: *newSendTimeState(
|
|
sampler.isAppLimited,
|
|
sampler.totalBytesSent,
|
|
sampler.totalBytesAcked,
|
|
sampler.totalBytesLost,
|
|
bytesInFlight,
|
|
),
|
|
}
|
|
}
|
|
|
|
// 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 bytes
|
|
// sent over time, and the number of bytes 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 bytes 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 = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0))
|
|
// ack_rate = (bytes(A_1) - bytes(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.
|
|
|
|
type congestionEventSample struct {
|
|
// The maximum bandwidth sample from all acked packets.
|
|
// QuicBandwidth::Zero() if no samples are available.
|
|
sampleMaxBandwidth Bandwidth
|
|
// Whether |sample_max_bandwidth| is from a app-limited sample.
|
|
sampleIsAppLimited bool
|
|
// The minimum rtt sample from all acked packets.
|
|
// QuicTime::Delta::Infinite() if no samples are available.
|
|
sampleRtt time.Duration
|
|
// For each packet p in acked packets, this is the max value of INFLIGHT(p),
|
|
// where INFLIGHT(p) is the number of bytes acked while p is inflight.
|
|
sampleMaxInflight congestion.ByteCount
|
|
// The send state of the largest packet in acked_packets, unless it is
|
|
// empty. If acked_packets is empty, it's the send state of the largest
|
|
// packet in lost_packets.
|
|
lastPacketSendState sendTimeState
|
|
// The number of extra bytes acked from this ack event, compared to what is
|
|
// expected from the flow's bandwidth. Larger value means more ack
|
|
// aggregation.
|
|
extraAcked congestion.ByteCount
|
|
}
|
|
|
|
func newCongestionEventSample() *congestionEventSample {
|
|
return &congestionEventSample{
|
|
sampleRtt: infRTT,
|
|
}
|
|
}
|
|
|
|
type bandwidthSampler struct {
|
|
// The total number of congestion controlled bytes sent during the connection.
|
|
totalBytesSent congestion.ByteCount
|
|
|
|
// The total number of congestion controlled bytes which were acknowledged.
|
|
totalBytesAcked congestion.ByteCount
|
|
|
|
// The total number of congestion controlled bytes which were lost.
|
|
totalBytesLost congestion.ByteCount
|
|
|
|
// The total number of congestion controlled bytes which have been neutered.
|
|
totalBytesNeutered congestion.ByteCount
|
|
|
|
// The value of |total_bytes_sent_| at the time the last acknowledged packet
|
|
// was sent. Valid only when |last_acked_packet_sent_time_| is valid.
|
|
totalBytesSentAtLastAckedPacket congestion.ByteCount
|
|
|
|
// The time at which the last acknowledged packet was sent. Set to
|
|
// QuicTime::Zero() if no valid timestamp is available.
|
|
lastAckedPacketSentTime time.Time
|
|
|
|
// The time at which the most recent packet was acknowledged.
|
|
lastAckedPacketAckTime time.Time
|
|
|
|
// The most recently sent packet.
|
|
lastSentPacket congestion.PacketNumber
|
|
|
|
// The most recently acked packet.
|
|
lastAckedPacket congestion.PacketNumber
|
|
|
|
// Indicates whether the bandwidth sampler is currently in an app-limited
|
|
// phase.
|
|
isAppLimited bool
|
|
|
|
// The packet that will be acknowledged after this one will cause the sampler
|
|
// to exit the app-limited phase.
|
|
endOfAppLimitedPhase congestion.PacketNumber
|
|
|
|
// Record of the connection state at the point where each packet in flight was
|
|
// sent, indexed by the packet number.
|
|
connectionStateMap *packetNumberIndexedQueue[connectionStateOnSentPacket]
|
|
|
|
recentAckPoints recentAckPoints
|
|
a0Candidates RingBuffer[ackPoint]
|
|
|
|
// Maximum number of tracked packets.
|
|
maxTrackedPackets congestion.ByteCount
|
|
|
|
maxAckHeightTracker *maxAckHeightTracker
|
|
totalBytesAckedAfterLastAckEvent congestion.ByteCount
|
|
|
|
// True if connection option 'BSAO' is set.
|
|
overestimateAvoidance bool
|
|
|
|
// True if connection option 'BBRB' is set.
|
|
limitMaxAckHeightTrackerBySendRate bool
|
|
}
|
|
|
|
func newBandwidthSampler(maxAckHeightTrackerWindowLength roundTripCount) *bandwidthSampler {
|
|
b := &bandwidthSampler{
|
|
maxAckHeightTracker: newMaxAckHeightTracker(maxAckHeightTrackerWindowLength),
|
|
connectionStateMap: newPacketNumberIndexedQueue[connectionStateOnSentPacket](defaultConnectionStateMapQueueSize),
|
|
lastSentPacket: invalidPacketNumber,
|
|
lastAckedPacket: invalidPacketNumber,
|
|
endOfAppLimitedPhase: invalidPacketNumber,
|
|
}
|
|
|
|
b.a0Candidates.Init(defaultCandidatesBufferSize)
|
|
|
|
return b
|
|
}
|
|
|
|
func (b *bandwidthSampler) MaxAckHeight() congestion.ByteCount {
|
|
return b.maxAckHeightTracker.Get()
|
|
}
|
|
|
|
func (b *bandwidthSampler) NumAckAggregationEpochs() uint64 {
|
|
return b.maxAckHeightTracker.NumAckAggregationEpochs()
|
|
}
|
|
|
|
func (b *bandwidthSampler) SetMaxAckHeightTrackerWindowLength(length roundTripCount) {
|
|
b.maxAckHeightTracker.SetFilterWindowLength(length)
|
|
}
|
|
|
|
func (b *bandwidthSampler) ResetMaxAckHeightTracker(newHeight congestion.ByteCount, newTime roundTripCount) {
|
|
b.maxAckHeightTracker.Reset(newHeight, newTime)
|
|
}
|
|
|
|
func (b *bandwidthSampler) SetStartNewAggregationEpochAfterFullRound(value bool) {
|
|
b.maxAckHeightTracker.SetStartNewAggregationEpochAfterFullRound(value)
|
|
}
|
|
|
|
func (b *bandwidthSampler) SetLimitMaxAckHeightTrackerBySendRate(value bool) {
|
|
b.limitMaxAckHeightTrackerBySendRate = value
|
|
}
|
|
|
|
func (b *bandwidthSampler) SetReduceExtraAckedOnBandwidthIncrease(value bool) {
|
|
b.maxAckHeightTracker.SetReduceExtraAckedOnBandwidthIncrease(value)
|
|
}
|
|
|
|
func (b *bandwidthSampler) EnableOverestimateAvoidance() {
|
|
if b.overestimateAvoidance {
|
|
return
|
|
}
|
|
|
|
b.overestimateAvoidance = true
|
|
b.maxAckHeightTracker.SetAckAggregationBandwidthThreshold(2.0)
|
|
}
|
|
|
|
func (b *bandwidthSampler) IsOverestimateAvoidanceEnabled() bool {
|
|
return b.overestimateAvoidance
|
|
}
|
|
|
|
func (b *bandwidthSampler) OnPacketSent(
|
|
sentTime time.Time,
|
|
packetNumber congestion.PacketNumber,
|
|
bytes congestion.ByteCount,
|
|
bytesInFlight congestion.ByteCount,
|
|
isRetransmittable bool,
|
|
) {
|
|
b.lastSentPacket = packetNumber
|
|
|
|
if !isRetransmittable {
|
|
return
|
|
}
|
|
|
|
b.totalBytesSent += bytes
|
|
|
|
// 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 bytesInFlight == 0 {
|
|
b.lastAckedPacketAckTime = sentTime
|
|
if b.overestimateAvoidance {
|
|
b.recentAckPoints.Clear()
|
|
b.recentAckPoints.Update(sentTime, b.totalBytesAcked)
|
|
b.a0Candidates.Clear()
|
|
b.a0Candidates.PushBack(*b.recentAckPoints.MostRecentPoint())
|
|
}
|
|
b.totalBytesSentAtLastAckedPacket = b.totalBytesSent
|
|
|
|
// In this situation ack compression is not a concern, set send rate to
|
|
// effectively infinite.
|
|
b.lastAckedPacketSentTime = sentTime
|
|
}
|
|
|
|
b.connectionStateMap.Emplace(packetNumber, newConnectionStateOnSentPacket(
|
|
sentTime,
|
|
bytes,
|
|
bytesInFlight+bytes,
|
|
b,
|
|
))
|
|
}
|
|
|
|
func (b *bandwidthSampler) OnCongestionEvent(
|
|
ackTime time.Time,
|
|
ackedPackets []congestion.AckedPacketInfo,
|
|
lostPackets []congestion.LostPacketInfo,
|
|
maxBandwidth Bandwidth,
|
|
estBandwidthUpperBound Bandwidth,
|
|
roundTripCount roundTripCount,
|
|
) congestionEventSample {
|
|
eventSample := newCongestionEventSample()
|
|
|
|
var lastLostPacketSendState sendTimeState
|
|
|
|
for _, p := range lostPackets {
|
|
sendState := b.OnPacketLost(p.PacketNumber, p.BytesLost)
|
|
if sendState.isValid {
|
|
lastLostPacketSendState = sendState
|
|
}
|
|
}
|
|
|
|
if len(ackedPackets) == 0 {
|
|
// Only populate send state for a loss-only event.
|
|
eventSample.lastPacketSendState = lastLostPacketSendState
|
|
return *eventSample
|
|
}
|
|
|
|
var lastAckedPacketSendState sendTimeState
|
|
var maxSendRate Bandwidth
|
|
|
|
for _, p := range ackedPackets {
|
|
sample := b.onPacketAcknowledged(ackTime, p.PacketNumber)
|
|
if !sample.stateAtSend.isValid {
|
|
continue
|
|
}
|
|
|
|
lastAckedPacketSendState = sample.stateAtSend
|
|
|
|
if sample.rtt != 0 {
|
|
eventSample.sampleRtt = Min(eventSample.sampleRtt, sample.rtt)
|
|
}
|
|
if sample.bandwidth > eventSample.sampleMaxBandwidth {
|
|
eventSample.sampleMaxBandwidth = sample.bandwidth
|
|
eventSample.sampleIsAppLimited = sample.stateAtSend.isAppLimited
|
|
}
|
|
if sample.sendRate != infBandwidth {
|
|
maxSendRate = Max(maxSendRate, sample.sendRate)
|
|
}
|
|
inflightSample := b.totalBytesAcked - lastAckedPacketSendState.totalBytesAcked
|
|
if inflightSample > eventSample.sampleMaxInflight {
|
|
eventSample.sampleMaxInflight = inflightSample
|
|
}
|
|
}
|
|
|
|
if !lastLostPacketSendState.isValid {
|
|
eventSample.lastPacketSendState = lastAckedPacketSendState
|
|
} else if !lastAckedPacketSendState.isValid {
|
|
eventSample.lastPacketSendState = lastLostPacketSendState
|
|
} else {
|
|
// If two packets are inflight and an alarm is armed to lose a packet and it
|
|
// wakes up late, then the first of two in flight packets could have been
|
|
// acknowledged before the wakeup, which re-evaluates loss detection, and
|
|
// could declare the later of the two lost.
|
|
if lostPackets[len(lostPackets)-1].PacketNumber > ackedPackets[len(ackedPackets)-1].PacketNumber {
|
|
eventSample.lastPacketSendState = lastLostPacketSendState
|
|
} else {
|
|
eventSample.lastPacketSendState = lastAckedPacketSendState
|
|
}
|
|
}
|
|
|
|
isNewMaxBandwidth := eventSample.sampleMaxBandwidth > maxBandwidth
|
|
maxBandwidth = Max(maxBandwidth, eventSample.sampleMaxBandwidth)
|
|
if b.limitMaxAckHeightTrackerBySendRate {
|
|
maxBandwidth = Max(maxBandwidth, maxSendRate)
|
|
}
|
|
|
|
eventSample.extraAcked = b.onAckEventEnd(Min(estBandwidthUpperBound, maxBandwidth), isNewMaxBandwidth, roundTripCount)
|
|
|
|
return *eventSample
|
|
}
|
|
|
|
func (b *bandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber, bytesLost congestion.ByteCount) (s sendTimeState) {
|
|
b.totalBytesLost += bytesLost
|
|
if sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber); sentPacketPointer != nil {
|
|
sentPacketToSendTimeState(sentPacketPointer, &s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (b *bandwidthSampler) OnPacketNeutered(packetNumber congestion.PacketNumber) {
|
|
b.connectionStateMap.Remove(packetNumber, func(sentPacket connectionStateOnSentPacket) {
|
|
b.totalBytesNeutered += sentPacket.size
|
|
})
|
|
}
|
|
|
|
func (b *bandwidthSampler) OnAppLimited() {
|
|
b.isAppLimited = true
|
|
b.endOfAppLimitedPhase = b.lastSentPacket
|
|
}
|
|
|
|
func (b *bandwidthSampler) RemoveObsoletePackets(leastUnacked congestion.PacketNumber) {
|
|
// A packet can become obsolete when it is removed from QuicUnackedPacketMap's
|
|
// view of inflight before it is acked or marked as lost. For example, when
|
|
// QuicSentPacketManager::RetransmitCryptoPackets retransmits a crypto packet,
|
|
// the packet is removed from QuicUnackedPacketMap's inflight, but is not
|
|
// marked as acked or lost in the BandwidthSampler.
|
|
b.connectionStateMap.RemoveUpTo(leastUnacked)
|
|
}
|
|
|
|
func (b *bandwidthSampler) TotalBytesSent() congestion.ByteCount {
|
|
return b.totalBytesSent
|
|
}
|
|
|
|
func (b *bandwidthSampler) TotalBytesLost() congestion.ByteCount {
|
|
return b.totalBytesLost
|
|
}
|
|
|
|
func (b *bandwidthSampler) TotalBytesAcked() congestion.ByteCount {
|
|
return b.totalBytesAcked
|
|
}
|
|
|
|
func (b *bandwidthSampler) TotalBytesNeutered() congestion.ByteCount {
|
|
return b.totalBytesNeutered
|
|
}
|
|
|
|
func (b *bandwidthSampler) IsAppLimited() bool {
|
|
return b.isAppLimited
|
|
}
|
|
|
|
func (b *bandwidthSampler) EndOfAppLimitedPhase() congestion.PacketNumber {
|
|
return b.endOfAppLimitedPhase
|
|
}
|
|
|
|
func (b *bandwidthSampler) max_ack_height() congestion.ByteCount {
|
|
return b.maxAckHeightTracker.Get()
|
|
}
|
|
|
|
func (b *bandwidthSampler) chooseA0Point(totalBytesAcked congestion.ByteCount, a0 *ackPoint) bool {
|
|
if b.a0Candidates.Empty() {
|
|
return false
|
|
}
|
|
|
|
if b.a0Candidates.Len() == 1 {
|
|
*a0 = *b.a0Candidates.Front()
|
|
return true
|
|
}
|
|
|
|
for i := 1; i < b.a0Candidates.Len(); i++ {
|
|
if b.a0Candidates.Offset(i).totalBytesAcked > totalBytesAcked {
|
|
*a0 = *b.a0Candidates.Offset(i - 1)
|
|
if i > 1 {
|
|
for j := 0; j < i-1; j++ {
|
|
b.a0Candidates.PopFront()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
*a0 = *b.a0Candidates.Back()
|
|
for k := 0; k < b.a0Candidates.Len()-1; k++ {
|
|
b.a0Candidates.PopFront()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *bandwidthSampler) onPacketAcknowledged(ackTime time.Time, packetNumber congestion.PacketNumber) bandwidthSample {
|
|
sample := newBandwidthSample()
|
|
b.lastAckedPacket = packetNumber
|
|
sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber)
|
|
if sentPacketPointer == nil {
|
|
return *sample
|
|
}
|
|
|
|
// OnPacketAcknowledgedInner
|
|
b.totalBytesAcked += sentPacketPointer.size
|
|
b.totalBytesSentAtLastAckedPacket = sentPacketPointer.sendTimeState.totalBytesSent
|
|
b.lastAckedPacketSentTime = sentPacketPointer.sentTime
|
|
b.lastAckedPacketAckTime = ackTime
|
|
if b.overestimateAvoidance {
|
|
b.recentAckPoints.Update(ackTime, b.totalBytesAcked)
|
|
}
|
|
|
|
if b.isAppLimited {
|
|
// Exit app-limited phase in two cases:
|
|
// (1) end_of_app_limited_phase_ is not initialized, i.e., so far all
|
|
// packets are sent while there are buffered packets or pending data.
|
|
// (2) The current acked packet is after the sent packet marked as the end
|
|
// of the app limit phase.
|
|
if b.endOfAppLimitedPhase == invalidPacketNumber ||
|
|
packetNumber > b.endOfAppLimitedPhase {
|
|
b.isAppLimited = 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 sentPacketPointer.lastAckedPacketSentTime.IsZero() {
|
|
return *sample
|
|
}
|
|
|
|
// Infinite rate indicates that the sampler is supposed to discard the
|
|
// current send rate sample and use only the ack rate.
|
|
sendRate := infBandwidth
|
|
if sentPacketPointer.sentTime.After(sentPacketPointer.lastAckedPacketSentTime) {
|
|
sendRate = BandwidthFromDelta(
|
|
sentPacketPointer.sendTimeState.totalBytesSent-sentPacketPointer.totalBytesSentAtLastAckedPacket,
|
|
sentPacketPointer.sentTime.Sub(sentPacketPointer.lastAckedPacketSentTime))
|
|
}
|
|
|
|
var a0 ackPoint
|
|
if b.overestimateAvoidance && b.chooseA0Point(sentPacketPointer.sendTimeState.totalBytesAcked, &a0) {
|
|
} else {
|
|
a0.ackTime = sentPacketPointer.lastAckedPacketAckTime
|
|
a0.totalBytesAcked = sentPacketPointer.sendTimeState.totalBytesAcked
|
|
}
|
|
|
|
// 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 ackTime.Sub(a0.ackTime) <= 0 {
|
|
return *sample
|
|
}
|
|
|
|
ackRate := BandwidthFromDelta(b.totalBytesAcked-a0.totalBytesAcked, ackTime.Sub(a0.ackTime))
|
|
|
|
sample.bandwidth = Min(sendRate, ackRate)
|
|
// 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 = ackTime.Sub(sentPacketPointer.sentTime)
|
|
sample.sendRate = sendRate
|
|
sentPacketToSendTimeState(sentPacketPointer, &sample.stateAtSend)
|
|
|
|
return *sample
|
|
}
|
|
|
|
func (b *bandwidthSampler) onAckEventEnd(
|
|
bandwidthEstimate Bandwidth,
|
|
isNewMaxBandwidth bool,
|
|
roundTripCount roundTripCount,
|
|
) congestion.ByteCount {
|
|
newlyAckedBytes := b.totalBytesAcked - b.totalBytesAckedAfterLastAckEvent
|
|
if newlyAckedBytes == 0 {
|
|
return 0
|
|
}
|
|
b.totalBytesAckedAfterLastAckEvent = b.totalBytesAcked
|
|
extraAcked := b.maxAckHeightTracker.Update(
|
|
bandwidthEstimate,
|
|
isNewMaxBandwidth,
|
|
roundTripCount,
|
|
b.lastSentPacket,
|
|
b.lastAckedPacket,
|
|
b.lastAckedPacketAckTime,
|
|
newlyAckedBytes)
|
|
// If |extra_acked| is zero, i.e. this ack event marks the start of a new ack
|
|
// aggregation epoch, save LessRecentPoint, which is the last ack point of the
|
|
// previous epoch, as a A0 candidate.
|
|
if b.overestimateAvoidance && extraAcked == 0 {
|
|
b.a0Candidates.PushBack(*b.recentAckPoints.LessRecentPoint())
|
|
}
|
|
return extraAcked
|
|
}
|
|
|
|
func sentPacketToSendTimeState(sentPacket *connectionStateOnSentPacket, sendTimeState *sendTimeState) {
|
|
*sendTimeState = sentPacket.sendTimeState
|
|
sendTimeState.isValid = true
|
|
}
|
|
|
|
// BytesFromBandwidthAndTimeDelta calculates the bytes
|
|
// from a bandwidth(bits per second) and a time delta
|
|
func bytesFromBandwidthAndTimeDelta(bandwidth Bandwidth, delta time.Duration) congestion.ByteCount {
|
|
return (congestion.ByteCount(bandwidth) * congestion.ByteCount(delta)) /
|
|
(congestion.ByteCount(time.Second) * 8)
|
|
}
|
|
|
|
func timeDeltaFromBytesAndBandwidth(bytes congestion.ByteCount, bandwidth Bandwidth) time.Duration {
|
|
return time.Duration(bytes*8) * time.Second / time.Duration(bandwidth)
|
|
}
|