File: rtt_stats.go

package info (click to toggle)
golang-github-lucas-clemente-quic-go 0.55.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,376 kB
  • sloc: sh: 54; makefile: 14
file content (146 lines) | stat: -rw-r--r-- 4,472 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
}