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:
Marten Seemann
2020-07-22 14:18:57 +07:00
parent ce16603a24
commit 741dc28d74
29 changed files with 129 additions and 139 deletions

127
internal/utils/rtt_stats.go Normal file
View 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)
}

View 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))
})
})