1
0
pool/adapative.go

256 lines
7.6 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
}
// ensure that a single client does not get freed if using it
// ensure that eventually gets collected if not using it
// ensure that the background job is not running forever
// handle load as well
func (a *adaptive) nightshift(s Stats, now time.Time) time.Duration {
// idle, inactive, uninitialized => not idle, initialize, 0
// idle, inactive, initialized => to
// idle, active, uninitialized => not idle, initialize, 0
// idle, active, initialized => not idle, initialize, 0
// not idle, inactive, uninitialized => not idle, initialize, 0
// not idle, inactive, initialized => idle, new to
// not idle, active, uninitialized => not idle, initialize, 0
// not idle, active, initialized => 0
// idle := a.idle
// active := s.Active > 0
// initialized := !a.activeTime.IsZero()
// if !initialized {
// a.idle = false
// a.activeTime = now
// return 0
// }
// if idle && !active {
// return a.nsTO
// }
// if idle {
// a.idle = false
// a.nsTO = 0
// a.activeTime = now
// return 0
// }
// if active {
// return 0
// }
// a.idle = true
// a.nsTO = nightshiftTO(a.activeTime, now)
// return a.nsTO
// --
// state flags:
// - initialized
// - pempty
// - pactive
// - empty
// - active
// actions:
// - start active
// - call to
// - update prev state on every call
// states:
// X not initialized, not pempty, not pactive, not empty, not active
// X not initialized, not pempty, not pactive, not empty, active
// X not initialized, not pempty, not pactive, empty, not active
// X not initialized, not pempty, not pactive, empty, active
// X not initialized, not pempty, pactive, not empty, not active
// X not initialized, not pempty, pactive, not empty, active
// X not initialized, not pempty, pactive, empty, not active
// X not initialized, not pempty, pactive, empty, active
// * not initialized, pempty, not pactive, not empty, not active => start active, call to
// * not initialized, pempty, not pactive, not empty, active => start active
// X not initialized, pempty, not pactive, empty, not active
// X not initialized, pempty, not pactive, empty, active
// X not initialized, pempty, pactive, not empty, not active
// X not initialized, pempty, pactive, not empty, active
// X not initialized, pempty, pactive, empty, not active
// X not initialized, pempty, pactive, empty, active
// * initialized, not pempty, not pactive, not empty, not active => end active, call to
// * initialized, not pempty, not pactive, not empty, active => start active
// * initialized, not pempty, not pactive, empty, not active => noop
// * initialized, not pempty, not pactive, empty, active => start active
// * initialized, not pempty, pactive, not empty, not active => end active, call to
// * initialized, not pempty, pactive, not empty, active => noop
// * initialized, not pempty, pactive, empty, not active => end active, noop
// * initialized, not pempty, pactive, empty, active => noop
// * initialized, pempty, not pactive, not empty, not active => call to
// * initialized, pempty, not pactive, not empty, active => start active
// * initialized, pempty, not pactive, empty, not active => noop
// * initialized, pempty, not pactive, empty, active => start active
// * initialized, pempty, pactive, not empty, not active => end active, call to
// * initialized, pempty, pactive, not empty, active => noop
// - initialized, pempty, pactive, empty, not active => end active, noop
// * initialized, pempty, pactive, empty, active => noop
pempty := a.prevState.Idle == 0
pactive := a.prevState.Active > 0
empty := s.Idle == 0
active := s.Active > 0
a.prevState = s
// not initialized, pempty, not pactive, not empty, not active => start active, call to
if !a.initialized && pempty && !pactive && !empty && !active {
a.initialized = true
a.activeStart = now
return a.nightshiftTO()
}
// not initialized, pempty, not pactive, not empty, active => start active
if !a.initialized && pempty && !pactive && !empty && active {
a.initialized = true
a.activeStart = now
return 0
}
if !a.initialized {
return 0
}
// initialized, not pempty, not pactive, not empty, not active => end active, call to
if !pempty && !pactive && !empty && !active {
ns := a.nightshiftTO()
return ns
}
// initialized, not pempty, not pactive, not empty, active => start active
// initialized, not pempty, not pactive, empty, active => start active
if !pempty && !pactive && active {
a.activeStart = now
return 0
}
// initialized, not pempty, pactive, not empty, not active => end active, call to
if !pempty && pactive && !empty && !active {
a.activeEnd = now
ns := a.nightshiftTO()
return ns
}
// initialized, not pempty, pactive, empty, not active => end active, noop
if !pempty && pactive && empty && !active {
a.activeEnd = now
return 0
}
// initialized, pempty, not pactive, not empty, not active => call to
if pempty && !pactive && !empty && !active {
return a.nightshiftTO()
}
// initialized, pempty, not pactive, not empty, active => start active
// initialized, pempty, not pactive, empty, active => start active
if pempty && !pactive && active {
a.activeStart = now
return 0
}
// initialized, pempty, pactive, not empty, not active => end active, call to
if pempty && pactive && !empty && !active {
a.activeEnd = now
return a.nightshiftTO()
}
// initialized, pempty, pactive, empty, not active => end active, noop
if pempty && pactive && empty && !active {
a.activeEnd = now
return 0
}
return 0
}
func (a *adaptive) Target(s Stats) (int, time.Duration) {
t := a.target(s) // handle when t < 2
// 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
}
// TODO: optimize, only take the clock when necessary
ns := a.nightshift(s, a.clock.Now())
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, a.clock.Now())
}