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  }