package pool import "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 { lastTurnedActive time.Time nightshiftTime time.Duration idle bool average int deviation int } type maxTimeout struct { max int to time.Duration items []time.Time } func makeAdaptiveAlgo() *adaptive { return &adaptive{idle: true} } func abs(v int) int { if v >= 0 { return v } return 0 - v } func divE(v int) int { return (3 * v) >> 3 // 1 / 2.72 => 3 / 8 } func mulE(v int) int { return (11 * v) >> 2 // 2.72 => 11 / 4 } func movingAverage(prev, currv int) int { return prev + divE(currv-prev) } func movingAbsoluteDeviation(prev, currv, currav int) int { return prev + divE(abs(currv-currav)-prev) } func targetCapacity(av, dev int) int { return av + mulE(dev) } func (a *adaptive) target(s Stats) int { av := movingAverage(a.average, s.Active) dev := movingAbsoluteDeviation(a.deviation, s.Active, av) a.average = av a.deviation = dev return targetCapacity(av, dev) } func (a *adaptive) nightshift(s Stats) time.Duration { if a.idle && s.Active == 0 { return a.nightshiftTime } if !a.idle && s.Active > 0 { return 0 } now := time.Now() a.idle = !a.idle if !a.idle { a.lastTurnedActive = now return 0 } a.nightshiftTime = now.Sub(a.lastTurnedActive) a.nightshiftTime = (3 * a.nightshiftTime) >> 3 if a.nightshiftTime < minNightshiftTime { a.nightshiftTime = minNightshiftTime } if a.nightshiftTime > maxNightshiftTime { a.nightshiftTime = maxNightshiftTime } return a.nightshiftTime } func (a *adaptive) Target(s Stats) (int, time.Duration) { t := a.target(s) ns := a.nightshift(s) return t, ns } func makeMaxTimeout(max int, to time.Duration) *maxTimeout { return &maxTimeout{ max: max, to: to, } } func (a *maxTimeout) Target(s Stats) (int, time.Duration) { if a.max <= 0 && a.to <= 0 { return s.Idle, 0 } if a.max > 0 && a.to <= 0 { t := s.Idle if t > a.max { t = a.max } return t, 0 } var zero time.Time if len(a.items) > s.Idle { for i := s.Idle; i < len(a.items); i++ { a.items[i] = zero } a.items = a.items[:s.Idle] if len(a.items) == 0 { a.items = nil } } now := time.Now() for len(a.items) < s.Idle { a.items = append(a.items, now) } var drop int for drop < len(a.items) && a.items[drop].Add(a.to).Before(now) { a.items[drop] = zero drop++ } if drop > 0 { for i := 0; i < drop; i++ { a.items[i] = zero } a.items = a.items[drop:] } if len(a.items) == 0 { a.items = nil return 0, 0 } t := len(a.items) if a.max > 0 && t > a.max { t = a.max } return t, a.items[0].Add(a.to).Sub(now) }