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) }