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
|
package gcc
import (
"math"
"time"
)
const (
maxDeltas = 60
)
type adaptiveThresholdOption func(*adaptiveThreshold)
func setInitialThreshold(t time.Duration) adaptiveThresholdOption {
return func(at *adaptiveThreshold) {
at.thresh = t
}
}
// adaptiveThreshold implements a threshold that continuously adapts depending on
// the current measurements/estimates. This is necessary to avoid starving GCC
// in the presence of concurrent TCP flows by allowing larger Queueing delays,
// when measurements/estimates increase. overuseCoefficientU and
// overuseCoefficientD define by how much the threshold adapts. We basically
// want the threshold to increase fast, if the measurement is outside [-thresh,
// thresh] and decrease slowly if it is within.
//
// See https://datatracker.ietf.org/doc/html/draft-ietf-rmcat-gcc-02#section-5.4
// or [Analysis and Design of the Google Congestion Control for Web Real-time
// Communication (WebRTC)](https://c3lab.poliba.it/images/6/65/Gcc-analysis.pdf)
// for a more detailed description
type adaptiveThreshold struct {
thresh time.Duration
overuseCoefficientUp float64
overuseCoefficientDown float64
min time.Duration
max time.Duration
lastUpdate time.Time
numDeltas int
}
// newAdaptiveThreshold initializes a new adaptiveThreshold with default
// values taken from draft-ietf-rmcat-gcc-02
func newAdaptiveThreshold(opts ...adaptiveThresholdOption) *adaptiveThreshold {
at := &adaptiveThreshold{
thresh: time.Duration(12500 * float64(time.Microsecond)),
overuseCoefficientUp: 0.01,
overuseCoefficientDown: 0.00018,
min: 6 * time.Millisecond,
max: 600 * time.Millisecond,
lastUpdate: time.Time{},
numDeltas: 0,
}
for _, opt := range opts {
opt(at)
}
return at
}
func (a *adaptiveThreshold) compare(estimate, dt time.Duration) (usage, time.Duration, time.Duration) {
a.numDeltas++
if a.numDeltas < 2 {
return usageNormal, estimate, a.max
}
t := time.Duration(minInt(a.numDeltas, maxDeltas)) * estimate
use := usageNormal
if t > a.thresh {
use = usageOver
} else if t < -a.thresh {
use = usageUnder
}
thresh := a.thresh
a.update(t)
return use, t, thresh
}
func (a *adaptiveThreshold) update(estimate time.Duration) {
now := time.Now()
if a.lastUpdate.IsZero() {
a.lastUpdate = now
}
absEstimate := time.Duration(math.Abs(float64(estimate.Microseconds()))) * time.Microsecond
if absEstimate > a.thresh+15*time.Millisecond {
a.lastUpdate = now
return
}
k := a.overuseCoefficientUp
if absEstimate < a.thresh {
k = a.overuseCoefficientDown
}
maxTimeDelta := 100 * time.Millisecond
timeDelta := time.Duration(minInt(int(now.Sub(a.lastUpdate).Milliseconds()), int(maxTimeDelta.Milliseconds()))) * time.Millisecond
d := absEstimate - a.thresh
add := k * float64(d.Milliseconds()) * float64(timeDelta.Milliseconds())
a.thresh += time.Duration(add) * 1000 * time.Microsecond
a.thresh = clampDuration(a.thresh, a.min, a.max)
a.lastUpdate = now
}
|