github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/transport/tuic/congestion/bandwidth_sampler.go (about) 1 package congestion 2 3 import ( 4 "math" 5 "time" 6 7 "github.com/sagernet/quic-go/congestion" 8 ) 9 10 var InfiniteBandwidth = Bandwidth(math.MaxUint64) 11 12 // SendTimeState is a subset of ConnectionStateOnSentPacket which is returned 13 // to the caller when the packet is acked or lost. 14 type SendTimeState struct { 15 // Whether other states in this object is valid. 16 isValid bool 17 // Whether the sender is app limited at the time the packet was sent. 18 // App limited bandwidth sample might be artificially low because the sender 19 // did not have enough data to send in order to saturate the link. 20 isAppLimited bool 21 // Total number of sent bytes at the time the packet was sent. 22 // Includes the packet itself. 23 totalBytesSent congestion.ByteCount 24 // Total number of acked bytes at the time the packet was sent. 25 totalBytesAcked congestion.ByteCount 26 // Total number of lost bytes at the time the packet was sent. 27 totalBytesLost congestion.ByteCount 28 } 29 30 // ConnectionStateOnSentPacket represents the information about a sent packet 31 // and the state of the connection at the moment the packet was sent, 32 // specifically the information about the most recently acknowledged packet at 33 // that moment. 34 type ConnectionStateOnSentPacket struct { 35 packetNumber congestion.PacketNumber 36 // Time at which the packet is sent. 37 sendTime time.Time 38 // Size of the packet. 39 size congestion.ByteCount 40 // The value of |totalBytesSentAtLastAckedPacket| at the time the 41 // packet was sent. 42 totalBytesSentAtLastAckedPacket congestion.ByteCount 43 // The value of |lastAckedPacketSentTime| at the time the packet was 44 // sent. 45 lastAckedPacketSentTime time.Time 46 // The value of |lastAckedPacketAckTime| at the time the packet was 47 // sent. 48 lastAckedPacketAckTime time.Time 49 // Send time states that are returned to the congestion controller when the 50 // packet is acked or lost. 51 sendTimeState SendTimeState 52 } 53 54 // BandwidthSample 55 type BandwidthSample struct { 56 // The bandwidth at that particular sample. Zero if no valid bandwidth sample 57 // is available. 58 bandwidth Bandwidth 59 // The RTT measurement at this particular sample. Zero if no RTT sample is 60 // available. Does not correct for delayed ack time. 61 rtt time.Duration 62 // States captured when the packet was sent. 63 stateAtSend SendTimeState 64 } 65 66 func NewBandwidthSample() *BandwidthSample { 67 return &BandwidthSample{ 68 // FIXME: the default value of original code is zero. 69 rtt: InfiniteRTT, 70 } 71 } 72 73 // BandwidthSampler keeps track of sent and acknowledged packets and outputs a 74 // bandwidth sample for every packet acknowledged. The samples are taken for 75 // individual packets, and are not filtered; the consumer has to filter the 76 // bandwidth samples itself. In certain cases, the sampler will locally severely 77 // underestimate the bandwidth, hence a maximum filter with a size of at least 78 // one RTT is recommended. 79 // 80 // This class bases its samples on the slope of two curves: the number of bytes 81 // sent over time, and the number of bytes acknowledged as received over time. 82 // It produces a sample of both slopes for every packet that gets acknowledged, 83 // based on a slope between two points on each of the corresponding curves. Note 84 // that due to the packet loss, the number of bytes on each curve might get 85 // further and further away from each other, meaning that it is not feasible to 86 // compare byte values coming from different curves with each other. 87 // 88 // The obvious points for measuring slope sample are the ones corresponding to 89 // the packet that was just acknowledged. Let us denote them as S_1 (point at 90 // which the current packet was sent) and A_1 (point at which the current packet 91 // was acknowledged). However, taking a slope requires two points on each line, 92 // so estimating bandwidth requires picking a packet in the past with respect to 93 // which the slope is measured. 94 // 95 // For that purpose, BandwidthSampler always keeps track of the most recently 96 // acknowledged packet, and records it together with every outgoing packet. 97 // When a packet gets acknowledged (A_1), it has not only information about when 98 // it itself was sent (S_1), but also the information about the latest 99 // acknowledged packet right before it was sent (S_0 and A_0). 100 // 101 // Based on that data, send and ack rate are estimated as: 102 // 103 // send_rate = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0)) 104 // ack_rate = (bytes(A_1) - bytes(A_0)) / (time(A_1) - time(A_0)) 105 // 106 // Here, the ack rate is intuitively the rate we want to treat as bandwidth. 107 // However, in certain cases (e.g. ack compression) the ack rate at a point may 108 // end up higher than the rate at which the data was originally sent, which is 109 // not indicative of the real bandwidth. Hence, we use the send rate as an upper 110 // bound, and the sample value is 111 // 112 // rate_sample = min(send_rate, ack_rate) 113 // 114 // An important edge case handled by the sampler is tracking the app-limited 115 // samples. There are multiple meaning of "app-limited" used interchangeably, 116 // hence it is important to understand and to be able to distinguish between 117 // them. 118 // 119 // Meaning 1: connection state. The connection is said to be app-limited when 120 // there is no outstanding data to send. This means that certain bandwidth 121 // samples in the future would not be an accurate indication of the link 122 // capacity, and it is important to inform consumer about that. Whenever 123 // connection becomes app-limited, the sampler is notified via OnAppLimited() 124 // method. 125 // 126 // Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth 127 // sampler becomes notified about the connection being app-limited, it enters 128 // app-limited phase. In that phase, all *sent* packets are marked as 129 // app-limited. Note that the connection itself does not have to be 130 // app-limited during the app-limited phase, and in fact it will not be 131 // (otherwise how would it send packets?). The boolean flag below indicates 132 // whether the sampler is in that phase. 133 // 134 // Meaning 3: a flag on the sent packet and on the sample. If a sent packet is 135 // sent during the app-limited phase, the resulting sample related to the 136 // packet will be marked as app-limited. 137 // 138 // With the terminology issue out of the way, let us consider the question of 139 // what kind of situation it addresses. 140 // 141 // Consider a scenario where we first send packets 1 to 20 at a regular 142 // bandwidth, and then immediately run out of data. After a few seconds, we send 143 // packets 21 to 60, and only receive ack for 21 between sending packets 40 and 144 // 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0 145 // we use to compute the slope is going to be packet 20, a few seconds apart 146 // from the current packet, hence the resulting estimate would be extremely low 147 // and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21, 148 // meaning that the bandwidth sample would exclude the quiescence. 149 // 150 // Based on the analysis of that scenario, we implement the following rule: once 151 // OnAppLimited() is called, all sent packets will produce app-limited samples 152 // up until an ack for a packet that was sent after OnAppLimited() was called. 153 // Note that while the scenario above is not the only scenario when the 154 // connection is app-limited, the approach works in other cases too. 155 type BandwidthSampler struct { 156 // The total number of congestion controlled bytes sent during the connection. 157 totalBytesSent congestion.ByteCount 158 // The total number of congestion controlled bytes which were acknowledged. 159 totalBytesAcked congestion.ByteCount 160 // The total number of congestion controlled bytes which were lost. 161 totalBytesLost congestion.ByteCount 162 // The value of |totalBytesSent| at the time the last acknowledged packet 163 // was sent. Valid only when |lastAckedPacketSentTime| is valid. 164 totalBytesSentAtLastAckedPacket congestion.ByteCount 165 // The time at which the last acknowledged packet was sent. Set to 166 // QuicTime::Zero() if no valid timestamp is available. 167 lastAckedPacketSentTime time.Time 168 // The time at which the most recent packet was acknowledged. 169 lastAckedPacketAckTime time.Time 170 // The most recently sent packet. 171 lastSendPacket congestion.PacketNumber 172 // Indicates whether the bandwidth sampler is currently in an app-limited 173 // phase. 174 isAppLimited bool 175 // The packet that will be acknowledged after this one will cause the sampler 176 // to exit the app-limited phase. 177 endOfAppLimitedPhase congestion.PacketNumber 178 // Record of the connection state at the point where each packet in flight was 179 // sent, indexed by the packet number. 180 connectionStats *ConnectionStates 181 } 182 183 func NewBandwidthSampler() *BandwidthSampler { 184 return &BandwidthSampler{ 185 connectionStats: &ConnectionStates{ 186 stats: make(map[congestion.PacketNumber]*ConnectionStateOnSentPacket), 187 }, 188 } 189 } 190 191 // OnPacketSent Inputs the sent packet information into the sampler. Assumes that all 192 // packets are sent in order. The information about the packet will not be 193 // released from the sampler until it the packet is either acknowledged or 194 // declared lost. 195 func (s *BandwidthSampler) OnPacketSent(sentTime time.Time, lastSentPacket congestion.PacketNumber, sentBytes, bytesInFlight congestion.ByteCount, hasRetransmittableData bool) { 196 s.lastSendPacket = lastSentPacket 197 198 if !hasRetransmittableData { 199 return 200 } 201 202 s.totalBytesSent += sentBytes 203 204 // If there are no packets in flight, the time at which the new transmission 205 // opens can be treated as the A_0 point for the purpose of bandwidth 206 // sampling. This underestimates bandwidth to some extent, and produces some 207 // artificially low samples for most packets in flight, but it provides with 208 // samples at important points where we would not have them otherwise, most 209 // importantly at the beginning of the connection. 210 if bytesInFlight == 0 { 211 s.lastAckedPacketAckTime = sentTime 212 s.totalBytesSentAtLastAckedPacket = s.totalBytesSent 213 214 // In this situation ack compression is not a concern, set send rate to 215 // effectively infinite. 216 s.lastAckedPacketSentTime = sentTime 217 } 218 219 s.connectionStats.Insert(lastSentPacket, sentTime, sentBytes, s) 220 } 221 222 // OnPacketAcked Notifies the sampler that the |lastAckedPacket| is acknowledged. Returns a 223 // bandwidth sample. If no bandwidth sample is available, 224 // QuicBandwidth::Zero() is returned. 225 func (s *BandwidthSampler) OnPacketAcked(ackTime time.Time, lastAckedPacket congestion.PacketNumber) *BandwidthSample { 226 sentPacketState := s.connectionStats.Get(lastAckedPacket) 227 if sentPacketState == nil { 228 return NewBandwidthSample() 229 } 230 231 sample := s.onPacketAckedInner(ackTime, lastAckedPacket, sentPacketState) 232 s.connectionStats.Remove(lastAckedPacket) 233 234 return sample 235 } 236 237 // onPacketAckedInner Handles the actual bandwidth calculations, whereas the outer method handles 238 // retrieving and removing |sentPacket|. 239 func (s *BandwidthSampler) onPacketAckedInner(ackTime time.Time, lastAckedPacket congestion.PacketNumber, sentPacket *ConnectionStateOnSentPacket) *BandwidthSample { 240 s.totalBytesAcked += sentPacket.size 241 242 s.totalBytesSentAtLastAckedPacket = sentPacket.sendTimeState.totalBytesSent 243 s.lastAckedPacketSentTime = sentPacket.sendTime 244 s.lastAckedPacketAckTime = ackTime 245 246 // Exit app-limited phase once a packet that was sent while the connection is 247 // not app-limited is acknowledged. 248 if s.isAppLimited && lastAckedPacket > s.endOfAppLimitedPhase { 249 s.isAppLimited = false 250 } 251 252 // There might have been no packets acknowledged at the moment when the 253 // current packet was sent. In that case, there is no bandwidth sample to 254 // make. 255 if sentPacket.lastAckedPacketSentTime.IsZero() { 256 return NewBandwidthSample() 257 } 258 259 // Infinite rate indicates that the sampler is supposed to discard the 260 // current send rate sample and use only the ack rate. 261 sendRate := InfiniteBandwidth 262 if sentPacket.sendTime.After(sentPacket.lastAckedPacketSentTime) { 263 sendRate = BandwidthFromDelta(sentPacket.sendTimeState.totalBytesSent-sentPacket.totalBytesSentAtLastAckedPacket, sentPacket.sendTime.Sub(sentPacket.lastAckedPacketSentTime)) 264 } 265 266 // During the slope calculation, ensure that ack time of the current packet is 267 // always larger than the time of the previous packet, otherwise division by 268 // zero or integer underflow can occur. 269 if !ackTime.After(sentPacket.lastAckedPacketAckTime) { 270 // TODO(wub): Compare this code count before and after fixing clock jitter 271 // issue. 272 // if sentPacket.lastAckedPacketAckTime.Equal(sentPacket.sendTime) { 273 // This is the 1st packet after quiescense. 274 // QUIC_CODE_COUNT_N(quic_prev_ack_time_larger_than_current_ack_time, 1, 2); 275 // } else { 276 // QUIC_CODE_COUNT_N(quic_prev_ack_time_larger_than_current_ack_time, 2, 2); 277 // } 278 279 return NewBandwidthSample() 280 } 281 282 ackRate := BandwidthFromDelta(s.totalBytesAcked-sentPacket.sendTimeState.totalBytesAcked, 283 ackTime.Sub(sentPacket.lastAckedPacketAckTime)) 284 285 // Note: this sample does not account for delayed acknowledgement time. This 286 // means that the RTT measurements here can be artificially high, especially 287 // on low bandwidth connections. 288 sample := &BandwidthSample{ 289 bandwidth: minBandwidth(sendRate, ackRate), 290 rtt: ackTime.Sub(sentPacket.sendTime), 291 } 292 293 SentPacketToSendTimeState(sentPacket, &sample.stateAtSend) 294 return sample 295 } 296 297 // OnPacketLost Informs the sampler that a packet is considered lost and it should no 298 // longer keep track of it. 299 func (s *BandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber) SendTimeState { 300 ok, sentPacket := s.connectionStats.Remove(packetNumber) 301 sendTimeState := SendTimeState{ 302 isValid: ok, 303 } 304 if sentPacket != nil { 305 s.totalBytesLost += sentPacket.size 306 SentPacketToSendTimeState(sentPacket, &sendTimeState) 307 } 308 309 return sendTimeState 310 } 311 312 // OnAppLimited Informs the sampler that the connection is currently app-limited, causing 313 // the sampler to enter the app-limited phase. The phase will expire by 314 // itself. 315 func (s *BandwidthSampler) OnAppLimited() { 316 s.isAppLimited = true 317 s.endOfAppLimitedPhase = s.lastSendPacket 318 } 319 320 // SentPacketToSendTimeState Copy a subset of the (private) ConnectionStateOnSentPacket to the (public) 321 // SendTimeState. Always set send_time_state->is_valid to true. 322 func SentPacketToSendTimeState(sentPacket *ConnectionStateOnSentPacket, sendTimeState *SendTimeState) { 323 sendTimeState.isAppLimited = sentPacket.sendTimeState.isAppLimited 324 sendTimeState.totalBytesSent = sentPacket.sendTimeState.totalBytesSent 325 sendTimeState.totalBytesAcked = sentPacket.sendTimeState.totalBytesAcked 326 sendTimeState.totalBytesLost = sentPacket.sendTimeState.totalBytesLost 327 sendTimeState.isValid = true 328 } 329 330 // ConnectionStates Record of the connection state at the point where each packet in flight was 331 // sent, indexed by the packet number. 332 // FIXME: using LinkedList replace map to fast remove all the packets lower than the specified packet number. 333 type ConnectionStates struct { 334 stats map[congestion.PacketNumber]*ConnectionStateOnSentPacket 335 } 336 337 func (s *ConnectionStates) Insert(packetNumber congestion.PacketNumber, sentTime time.Time, bytes congestion.ByteCount, sampler *BandwidthSampler) bool { 338 if _, ok := s.stats[packetNumber]; ok { 339 return false 340 } 341 342 s.stats[packetNumber] = NewConnectionStateOnSentPacket(packetNumber, sentTime, bytes, sampler) 343 return true 344 } 345 346 func (s *ConnectionStates) Get(packetNumber congestion.PacketNumber) *ConnectionStateOnSentPacket { 347 return s.stats[packetNumber] 348 } 349 350 func (s *ConnectionStates) Remove(packetNumber congestion.PacketNumber) (bool, *ConnectionStateOnSentPacket) { 351 state, ok := s.stats[packetNumber] 352 if ok { 353 delete(s.stats, packetNumber) 354 } 355 return ok, state 356 } 357 358 func NewConnectionStateOnSentPacket(packetNumber congestion.PacketNumber, sentTime time.Time, bytes congestion.ByteCount, sampler *BandwidthSampler) *ConnectionStateOnSentPacket { 359 return &ConnectionStateOnSentPacket{ 360 packetNumber: packetNumber, 361 sendTime: sentTime, 362 size: bytes, 363 lastAckedPacketSentTime: sampler.lastAckedPacketSentTime, 364 lastAckedPacketAckTime: sampler.lastAckedPacketAckTime, 365 totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket, 366 sendTimeState: SendTimeState{ 367 isValid: true, 368 isAppLimited: sampler.isAppLimited, 369 totalBytesSent: sampler.totalBytesSent, 370 totalBytesAcked: sampler.totalBytesAcked, 371 totalBytesLost: sampler.totalBytesLost, 372 }, 373 } 374 }