package pool import ( "code.squareroundforest.org/arpio/times" "math" "time" ) const ( // arbitrary values to be most likely out of sync with anything else: minNightshiftTime = 729 * time.Millisecond // ~1sec maxNightshiftTime = 59049 * time.Second // ~2/3day ) type adaptive struct { clock times.Clock activeTime time.Time nsTO time.Duration idle bool average float64 deviation float64 } func makeAdaptiveAlgo() *adaptive { return &adaptive{idle: true} } 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) nightshift(s Stats) time.Duration { if a.idle && s.Active == 0 { return a.nsTO } if !a.idle && s.Active > 0 { return 0 } now := a.clock.Now() a.idle = !a.idle if !a.idle { a.activeTime = now return 0 } a.nsTO = now.Sub(a.activeTime) a.nsTO = (3 * a.nsTO) / 8 if a.nsTO < minNightshiftTime { a.nsTO = minNightshiftTime } if a.nsTO > maxNightshiftTime { a.nsTO = maxNightshiftTime } return a.nsTO } func (a *adaptive) Target(s Stats) (int, time.Duration) { t := a.target(s) 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) }