1
0
pool/adapative.go
2026-03-15 17:44:03 +01:00

126 lines
2.5 KiB
Go

package pool
import (
"code.squareroundforest.org/arpio/times"
"math"
"time"
)
const (
// arbitrary values to be most likely out of sync with anything else:
minNightshiftTime = 6561 * time.Millisecond // ~6sec
maxNightshiftTime = 59049 * time.Second // ~2/3day
)
type adaptive struct {
clock times.Clock
prevState Stats
initialized bool
activeStart time.Time
activeEnd time.Time
average float64
deviation float64
}
func makeAdaptiveAlgo() *adaptive {
return &adaptive{}
}
func (a *adaptive) setClock(c times.Clock) {
a.clock = c
}
func movingAverage(prev, currv float64) float64 {
return prev + (currv-prev)/math.E
}
func movingAbsoluteDeviation(prev, currv, currav float64) float64 {
return prev + (math.Abs(currv-currav)-prev)/math.E
}
func targetCapacity(av, dev float64) float64 {
return av + dev*math.E
}
func (a *adaptive) target(s Stats) int {
av := movingAverage(a.average, float64(s.Active))
dev := movingAbsoluteDeviation(a.deviation, float64(s.Active), av)
a.average = av
a.deviation = dev
return int(targetCapacity(av, dev))
}
func (a *adaptive) nightshiftTO() time.Duration {
to := a.activeEnd.Sub(a.activeStart)
to = (3 * to) / 8
if to < minNightshiftTime {
to = minNightshiftTime
}
if to > maxNightshiftTime {
to = maxNightshiftTime
}
return to
}
func (a *adaptive) nightshift(s Stats) time.Duration {
pactive := a.prevState.Active > 0
empty := s.Idle == 0
active := s.Active > 0
a.prevState = s
// on the first put we need to set an active start time:
if !a.initialized {
a.initialized = true
a.activeStart = a.clock.Now()
if active {
return 0
}
return a.nightshiftTO()
}
if !pactive && !active && !empty {
return a.nightshiftTO()
}
if !pactive && active {
a.activeStart = a.clock.Now()
return 0
}
if pactive && !active {
a.activeEnd = a.clock.Now()
if empty {
return 0
}
return a.nightshiftTO()
}
return 0
}
func (a *adaptive) Target(s Stats) (int, time.Duration) {
t := a.target(s)
// magic number 2: we allow max 2 idle items to be collected only by the nightshift, to provide better
// support for sporadic requests, when it's active or just going inactive:
if t < 2 && a.activeStart.After(a.activeEnd) {
t = 2
}
ns := a.nightshift(s)
return t, ns
}
func (a *adaptive) Load(n int) {
// we lie to the algorithm when adding the additional idle count to the average active count. This way
// we can adjust the calculated target capacity:
a.average += float64(n)
s := a.prevState
s.Idle += n
a.nightshift(s)
}