2026-03-04 15:03:29 +01:00
|
|
|
package pool
|
|
|
|
|
|
2026-03-05 12:34:35 +01:00
|
|
|
import (
|
|
|
|
|
"code.squareroundforest.org/arpio/times"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
2026-03-04 15:03:29 +01:00
|
|
|
|
|
|
|
|
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 {
|
2026-03-05 12:34:35 +01:00
|
|
|
clock times.Clock
|
2026-03-04 15:03:29 +01:00
|
|
|
activeTime time.Time
|
|
|
|
|
nsTO time.Duration
|
|
|
|
|
idle bool
|
|
|
|
|
average int
|
|
|
|
|
deviation int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeAdaptiveAlgo() *adaptive {
|
|
|
|
|
return &adaptive{idle: true}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 12:34:35 +01:00
|
|
|
func (a *adaptive) setClock(c times.Clock) {
|
|
|
|
|
a.clock = c
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:03:29 +01:00
|
|
|
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.nsTO
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !a.idle && s.Active > 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 12:34:35 +01:00
|
|
|
now := a.clock.Now()
|
2026-03-04 15:03:29 +01:00
|
|
|
a.idle = !a.idle
|
|
|
|
|
if !a.idle {
|
|
|
|
|
a.activeTime = now
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.nsTO = now.Sub(a.activeTime)
|
|
|
|
|
a.nsTO = (3 * a.nsTO) >> 3
|
|
|
|
|
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
|
|
|
|
|
}
|