forked from quic-go/quic-go
* Add ConnectionStats * remove for loop * Add comments * Update comments --------- Co-authored-by: Marco Munizaga <git@marcopolo.io>
147 lines
4.4 KiB
Go
147 lines
4.4 KiB
Go
package utils
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/quic-go/quic-go/internal/protocol"
|
|
)
|
|
|
|
const (
|
|
rttAlpha = 0.125
|
|
oneMinusAlpha = 1 - rttAlpha
|
|
rttBeta = 0.25
|
|
oneMinusBeta = 1 - rttBeta
|
|
// The default RTT used before an RTT sample is taken.
|
|
defaultInitialRTT = 100 * time.Millisecond
|
|
)
|
|
|
|
// RTTStats provides round-trip statistics
|
|
type RTTStats struct {
|
|
hasMeasurement bool
|
|
|
|
minRTT atomic.Int64 // nanoseconds
|
|
latestRTT atomic.Int64 // nanoseconds
|
|
smoothedRTT atomic.Int64 // nanoseconds
|
|
meanDeviation atomic.Int64 // nanoseconds
|
|
|
|
maxAckDelay atomic.Int64 // nanoseconds
|
|
}
|
|
|
|
// MinRTT Returns the minRTT for the entire connection.
|
|
// May return Zero if no valid updates have occurred.
|
|
func (r *RTTStats) MinRTT() time.Duration {
|
|
return time.Duration(r.minRTT.Load())
|
|
}
|
|
|
|
// LatestRTT returns the most recent rtt measurement.
|
|
// May return Zero if no valid updates have occurred.
|
|
func (r *RTTStats) LatestRTT() time.Duration {
|
|
return time.Duration(r.latestRTT.Load())
|
|
}
|
|
|
|
// SmoothedRTT returns the smoothed RTT for the connection.
|
|
// May return Zero if no valid updates have occurred.
|
|
func (r *RTTStats) SmoothedRTT() time.Duration {
|
|
return time.Duration(r.smoothedRTT.Load())
|
|
}
|
|
|
|
// MeanDeviation gets the mean deviation
|
|
func (r *RTTStats) MeanDeviation() time.Duration {
|
|
return time.Duration(r.meanDeviation.Load())
|
|
}
|
|
|
|
// MaxAckDelay gets the max_ack_delay advertised by the peer
|
|
func (r *RTTStats) MaxAckDelay() time.Duration {
|
|
return time.Duration(r.maxAckDelay.Load())
|
|
}
|
|
|
|
// PTO gets the probe timeout duration.
|
|
func (r *RTTStats) PTO(includeMaxAckDelay bool) time.Duration {
|
|
if r.SmoothedRTT() == 0 {
|
|
return 2 * defaultInitialRTT
|
|
}
|
|
pto := r.SmoothedRTT() + max(4*r.MeanDeviation(), protocol.TimerGranularity)
|
|
if includeMaxAckDelay {
|
|
pto += r.MaxAckDelay()
|
|
}
|
|
return pto
|
|
}
|
|
|
|
// UpdateRTT updates the RTT based on a new sample.
|
|
func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration) {
|
|
if sendDelta <= 0 {
|
|
return
|
|
}
|
|
|
|
// Update r.minRTT first. r.minRTT does not use an rttSample corrected for
|
|
// ackDelay but the raw observed sendDelta, since poor clock granularity at
|
|
// the client may cause a high ackDelay to result in underestimation of the
|
|
// r.minRTT.
|
|
minRTT := time.Duration(r.minRTT.Load())
|
|
if minRTT == 0 || minRTT > sendDelta {
|
|
minRTT = sendDelta
|
|
r.minRTT.Store(int64(sendDelta))
|
|
}
|
|
|
|
// Correct for ackDelay if information received from the peer results in a
|
|
// an RTT sample at least as large as minRTT. Otherwise, only use the
|
|
// sendDelta.
|
|
sample := sendDelta
|
|
if sample-minRTT >= ackDelay {
|
|
sample -= ackDelay
|
|
}
|
|
r.latestRTT.Store(int64(sample))
|
|
// First time call.
|
|
if !r.hasMeasurement {
|
|
r.hasMeasurement = true
|
|
r.smoothedRTT.Store(int64(sample))
|
|
r.meanDeviation.Store(int64(sample / 2))
|
|
} else {
|
|
smoothedRTT := r.SmoothedRTT()
|
|
meanDev := time.Duration(oneMinusBeta*float32(r.MeanDeviation()/time.Microsecond)+rttBeta*float32((smoothedRTT-sample).Abs()/time.Microsecond)) * time.Microsecond
|
|
newSmoothedRTT := time.Duration((float32(smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond
|
|
r.meanDeviation.Store(int64(meanDev))
|
|
r.smoothedRTT.Store(int64(newSmoothedRTT))
|
|
}
|
|
}
|
|
|
|
// SetMaxAckDelay sets the max_ack_delay
|
|
func (r *RTTStats) SetMaxAckDelay(mad time.Duration) {
|
|
r.maxAckDelay.Store(int64(mad))
|
|
}
|
|
|
|
// SetInitialRTT sets the initial RTT.
|
|
// It is used during handshake when restoring the RTT stats from the token.
|
|
func (r *RTTStats) SetInitialRTT(t time.Duration) {
|
|
// On the server side, by the time we get to process the session ticket,
|
|
// we might already have obtained an RTT measurement.
|
|
// This can happen if we received the ClientHello in multiple pieces, and one of those pieces was lost.
|
|
// Discard the restored value. A fresh measurement is always better.
|
|
if r.hasMeasurement {
|
|
return
|
|
}
|
|
r.smoothedRTT.Store(int64(t))
|
|
r.latestRTT.Store(int64(t))
|
|
}
|
|
|
|
func (r *RTTStats) ResetForPathMigration() {
|
|
r.hasMeasurement = false
|
|
r.minRTT.Store(0)
|
|
r.latestRTT.Store(0)
|
|
r.smoothedRTT.Store(0)
|
|
r.meanDeviation.Store(0)
|
|
// max_ack_delay remains valid
|
|
}
|
|
|
|
func (r *RTTStats) Clone() *RTTStats {
|
|
out := &RTTStats{}
|
|
out.hasMeasurement = r.hasMeasurement
|
|
out.minRTT.Store(r.minRTT.Load())
|
|
out.latestRTT.Store(r.latestRTT.Load())
|
|
out.smoothedRTT.Store(r.smoothedRTT.Load())
|
|
out.meanDeviation.Store(r.meanDeviation.Load())
|
|
out.maxAckDelay.Store(r.maxAckDelay.Load())
|
|
return out
|
|
}
|