forked from quic-go/quic-go
move the RTTStats to the utils package
The RTTStats are used by the logging package. In order to instrument the congestion package, the RTTStats can't be part of that package any more (to avoid an import loop).
This commit is contained in:
127
internal/utils/rtt_stats.go
Normal file
127
internal/utils/rtt_stats.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/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 time.Duration
|
||||
latestRTT time.Duration
|
||||
smoothedRTT time.Duration
|
||||
meanDeviation time.Duration
|
||||
|
||||
maxAckDelay time.Duration
|
||||
}
|
||||
|
||||
// NewRTTStats makes a properly initialized RTTStats object
|
||||
func NewRTTStats() *RTTStats {
|
||||
return &RTTStats{}
|
||||
}
|
||||
|
||||
// MinRTT Returns the minRTT for the entire connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) MinRTT() time.Duration { return r.minRTT }
|
||||
|
||||
// LatestRTT returns the most recent rtt measurement.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) LatestRTT() time.Duration { return r.latestRTT }
|
||||
|
||||
// SmoothedRTT returns the smoothed RTT for the connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) SmoothedRTT() time.Duration { return r.smoothedRTT }
|
||||
|
||||
// MeanDeviation gets the mean deviation
|
||||
func (r *RTTStats) MeanDeviation() time.Duration { return r.meanDeviation }
|
||||
|
||||
// MaxAckDelay gets the max_ack_delay advertised by the peer
|
||||
func (r *RTTStats) MaxAckDelay() time.Duration { return r.maxAckDelay }
|
||||
|
||||
// PTO gets the probe timeout duration.
|
||||
func (r *RTTStats) PTO(includeMaxAckDelay bool) time.Duration {
|
||||
if r.SmoothedRTT() == 0 {
|
||||
return 2 * defaultInitialRTT
|
||||
}
|
||||
pto := r.SmoothedRTT() + MaxDuration(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, now time.Time) {
|
||||
if sendDelta == InfDuration || 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.
|
||||
if r.minRTT == 0 || r.minRTT > sendDelta {
|
||||
r.minRTT = 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-r.minRTT >= ackDelay {
|
||||
sample -= ackDelay
|
||||
}
|
||||
r.latestRTT = sample
|
||||
// First time call.
|
||||
if !r.hasMeasurement {
|
||||
r.hasMeasurement = true
|
||||
r.smoothedRTT = sample
|
||||
r.meanDeviation = sample / 2
|
||||
} else {
|
||||
r.meanDeviation = time.Duration(oneMinusBeta*float32(r.meanDeviation/time.Microsecond)+rttBeta*float32(AbsDuration(r.smoothedRTT-sample)/time.Microsecond)) * time.Microsecond
|
||||
r.smoothedRTT = time.Duration((float32(r.smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxAckDelay sets the max_ack_delay
|
||||
func (r *RTTStats) SetMaxAckDelay(mad time.Duration) {
|
||||
r.maxAckDelay = mad
|
||||
}
|
||||
|
||||
// SetInitialRTT sets the initial RTT.
|
||||
// It is used during the 0-RTT handshake when restoring the RTT stats from the session state.
|
||||
func (r *RTTStats) SetInitialRTT(t time.Duration) {
|
||||
if r.hasMeasurement {
|
||||
panic("initial RTT set after first measurement")
|
||||
}
|
||||
r.smoothedRTT = t
|
||||
r.latestRTT = t
|
||||
}
|
||||
|
||||
// OnConnectionMigration is called when connection migrates and rtt measurement needs to be reset.
|
||||
func (r *RTTStats) OnConnectionMigration() {
|
||||
r.latestRTT = 0
|
||||
r.minRTT = 0
|
||||
r.smoothedRTT = 0
|
||||
r.meanDeviation = 0
|
||||
}
|
||||
|
||||
// ExpireSmoothedMetrics causes the smoothed_rtt to be increased to the latest_rtt if the latest_rtt
|
||||
// is larger. The mean deviation is increased to the most recent deviation if
|
||||
// it's larger.
|
||||
func (r *RTTStats) ExpireSmoothedMetrics() {
|
||||
r.meanDeviation = MaxDuration(r.meanDeviation, AbsDuration(r.smoothedRTT-r.latestRTT))
|
||||
r.smoothedRTT = MaxDuration(r.smoothedRTT, r.latestRTT)
|
||||
}
|
||||
159
internal/utils/rtt_stats_test.go
Normal file
159
internal/utils/rtt_stats_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("RTT stats", func() {
|
||||
var (
|
||||
rttStats *RTTStats
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
rttStats = NewRTTStats()
|
||||
})
|
||||
|
||||
It("DefaultsBeforeUpdate", func() {
|
||||
Expect(rttStats.MinRTT()).To(Equal(time.Duration(0)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(0)))
|
||||
})
|
||||
|
||||
It("SmoothedRTT", func() {
|
||||
// Verify that ack_delay is ignored in the first measurement.
|
||||
rttStats.UpdateRTT((300 * time.Millisecond), (100 * time.Millisecond), time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal((300 * time.Millisecond)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal((300 * time.Millisecond)))
|
||||
// Verify that Smoothed RTT includes max ack delay if it's reasonable.
|
||||
rttStats.UpdateRTT((350 * time.Millisecond), (50 * time.Millisecond), time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal((300 * time.Millisecond)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal((300 * time.Millisecond)))
|
||||
// Verify that large erroneous ack_delay does not change Smoothed RTT.
|
||||
rttStats.UpdateRTT((200 * time.Millisecond), (300 * time.Millisecond), time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal((287500 * time.Microsecond)))
|
||||
})
|
||||
|
||||
It("MinRTT", func() {
|
||||
rttStats.UpdateRTT((200 * time.Millisecond), 0, time.Time{})
|
||||
Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))
|
||||
rttStats.UpdateRTT((10 * time.Millisecond), 0, time.Time{}.Add((10 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
|
||||
rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((20 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
|
||||
rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((30 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
|
||||
rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((40 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
|
||||
// Verify that ack_delay does not go into recording of MinRTT_.
|
||||
rttStats.UpdateRTT((7 * time.Millisecond), (2 * time.Millisecond), time.Time{}.Add((50 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((7 * time.Millisecond)))
|
||||
})
|
||||
|
||||
It("MaxAckDelay", func() {
|
||||
rttStats.SetMaxAckDelay(42 * time.Minute)
|
||||
Expect(rttStats.MaxAckDelay()).To(Equal(42 * time.Minute))
|
||||
})
|
||||
|
||||
It("computes the PTO", func() {
|
||||
maxAckDelay := 42 * time.Minute
|
||||
rttStats.SetMaxAckDelay(maxAckDelay)
|
||||
rtt := time.Second
|
||||
rttStats.UpdateRTT(rtt, 0, time.Time{})
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(rtt))
|
||||
Expect(rttStats.MeanDeviation()).To(Equal(rtt / 2))
|
||||
Expect(rttStats.PTO(false)).To(Equal(rtt + 4*(rtt/2)))
|
||||
Expect(rttStats.PTO(true)).To(Equal(rtt + 4*(rtt/2) + maxAckDelay))
|
||||
})
|
||||
|
||||
It("uses the granularity for computing the PTO for short RTTs", func() {
|
||||
rtt := time.Microsecond
|
||||
rttStats.UpdateRTT(rtt, 0, time.Time{})
|
||||
Expect(rttStats.PTO(true)).To(Equal(rtt + protocol.TimerGranularity))
|
||||
})
|
||||
|
||||
It("ExpireSmoothedMetrics", func() {
|
||||
initialRtt := (10 * time.Millisecond)
|
||||
rttStats.UpdateRTT(initialRtt, 0, time.Time{})
|
||||
Expect(rttStats.MinRTT()).To(Equal(initialRtt))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))
|
||||
|
||||
Expect(rttStats.MeanDeviation()).To(Equal(initialRtt / 2))
|
||||
|
||||
// Update once with a 20ms RTT.
|
||||
doubledRtt := initialRtt * (2)
|
||||
rttStats.UpdateRTT(doubledRtt, 0, time.Time{})
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(float32(initialRtt) * 1.125)))
|
||||
|
||||
// Expire the smoothed metrics, increasing smoothed rtt and mean deviation.
|
||||
rttStats.ExpireSmoothedMetrics()
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(doubledRtt))
|
||||
Expect(rttStats.MeanDeviation()).To(Equal(time.Duration(float32(initialRtt) * 0.875)))
|
||||
|
||||
// Now go back down to 5ms and expire the smoothed metrics, and ensure the
|
||||
// mean deviation increases to 15ms.
|
||||
halfRtt := initialRtt / 2
|
||||
rttStats.UpdateRTT(halfRtt, 0, time.Time{})
|
||||
Expect(doubledRtt).To(BeNumerically(">", rttStats.SmoothedRTT()))
|
||||
Expect(initialRtt).To(BeNumerically("<", rttStats.MeanDeviation()))
|
||||
})
|
||||
|
||||
It("UpdateRTTWithBadSendDeltas", func() {
|
||||
// Make sure we ignore bad RTTs.
|
||||
// base::test::MockLog log;
|
||||
|
||||
initialRtt := (10 * time.Millisecond)
|
||||
rttStats.UpdateRTT(initialRtt, 0, time.Time{})
|
||||
Expect(rttStats.MinRTT()).To(Equal(initialRtt))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))
|
||||
|
||||
badSendDeltas := []time.Duration{
|
||||
0,
|
||||
InfDuration,
|
||||
-1000 * time.Microsecond,
|
||||
}
|
||||
// log.StartCapturingLogs();
|
||||
|
||||
for _, badSendDelta := range badSendDeltas {
|
||||
// SCOPED_TRACE(Message() << "bad_send_delta = "
|
||||
// << bad_send_delta.ToMicroseconds());
|
||||
// EXPECT_CALL(log, Log(LOG_WARNING, _, _, _, HasSubstr("Ignoring")));
|
||||
rttStats.UpdateRTT(badSendDelta, 0, time.Time{})
|
||||
Expect(rttStats.MinRTT()).To(Equal(initialRtt))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))
|
||||
}
|
||||
})
|
||||
|
||||
It("ResetAfterConnectionMigrations", func() {
|
||||
rttStats.UpdateRTT(200*time.Millisecond, 0, time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal((200 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))
|
||||
rttStats.UpdateRTT((300 * time.Millisecond), (100 * time.Millisecond), time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal((200 * time.Millisecond)))
|
||||
Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))
|
||||
|
||||
// Reset rtt stats on connection migrations.
|
||||
rttStats.OnConnectionMigration()
|
||||
Expect(rttStats.LatestRTT()).To(Equal(time.Duration(0)))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(0)))
|
||||
Expect(rttStats.MinRTT()).To(Equal(time.Duration(0)))
|
||||
})
|
||||
|
||||
It("restores the RTT", func() {
|
||||
rttStats.SetInitialRTT(10 * time.Second)
|
||||
Expect(rttStats.LatestRTT()).To(Equal(10 * time.Second))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(10 * time.Second))
|
||||
Expect(rttStats.MeanDeviation()).To(BeZero())
|
||||
// update the RTT and make sure that the initial value is immediately forgotten
|
||||
rttStats.UpdateRTT(200*time.Millisecond, 0, time.Time{})
|
||||
Expect(rttStats.LatestRTT()).To(Equal(200 * time.Millisecond))
|
||||
Expect(rttStats.SmoothedRTT()).To(Equal(200 * time.Millisecond))
|
||||
Expect(rttStats.MeanDeviation()).To(Equal(100 * time.Millisecond))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user