github.com/kelleygo/clashcore@v1.0.2/transport/tuic/congestion/bandwidth_sampler.go (about)

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