2026-03-14 19:03:18 +01:00
|
|
|
package pool_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"code.squareroundforest.org/arpio/pool"
|
|
|
|
|
"code.squareroundforest.org/arpio/times"
|
|
|
|
|
"math"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type scenarioOptions struct {
|
|
|
|
|
algo pool.Algo
|
|
|
|
|
initial int
|
|
|
|
|
ops int
|
|
|
|
|
deviation int
|
|
|
|
|
changeRate int
|
|
|
|
|
minDelay time.Duration
|
|
|
|
|
maxDelay time.Duration
|
|
|
|
|
concurrency int
|
|
|
|
|
plot bool
|
|
|
|
|
exclude []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
|
resource[T any] chan T
|
|
|
|
|
active resource[[][]byte]
|
|
|
|
|
stats resource[[]pool.Stats]
|
|
|
|
|
scenarioStep func(scenarioOptions, *rand.Rand, int, int, func(), func())
|
|
|
|
|
verifyScenario func(*testing.T, scenarioOptions, []pool.Stats)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func initResource[T any]() resource[T] {
|
|
|
|
|
var zero T
|
|
|
|
|
r := make(resource[T], 1)
|
|
|
|
|
r <- zero
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r resource[T]) apply(f func(T) T) {
|
|
|
|
|
v := <-r
|
|
|
|
|
defer func() {
|
|
|
|
|
r <- v
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
v = f(v)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a active) count() int {
|
|
|
|
|
var c int
|
|
|
|
|
resource[[][]byte](a).apply(func(a [][]byte) [][]byte {
|
|
|
|
|
c = len(a)
|
|
|
|
|
return a
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a active) shift() []byte {
|
|
|
|
|
var b []byte
|
|
|
|
|
resource[[][]byte](a).apply(func(a [][]byte) [][]byte {
|
|
|
|
|
if len(a) == 0 {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b, a = a[len(a)-1], a[:len(a)-1]
|
|
|
|
|
return a
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a active) push(b []byte) {
|
|
|
|
|
resource[[][]byte](a).apply(func(a [][]byte) [][]byte {
|
|
|
|
|
return append(a, b)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stats) get() []pool.Stats {
|
|
|
|
|
var a []pool.Stats
|
|
|
|
|
resource[[]pool.Stats](s).apply(func(r []pool.Stats) []pool.Stats {
|
|
|
|
|
a = r
|
|
|
|
|
return r
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stats) push(a pool.Stats) {
|
|
|
|
|
resource[[]pool.Stats](s).apply(func(r []pool.Stats) []pool.Stats {
|
|
|
|
|
return append(r, a)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testScenario(t *testing.T, o scenarioOptions, step scenarioStep, verify verifyScenario) {
|
|
|
|
|
for _, n := range o.exclude {
|
|
|
|
|
if strings.HasSuffix(t.Name(), n) {
|
|
|
|
|
t.Skip()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
testClock times.TestClock
|
|
|
|
|
clock times.Clock
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if o.concurrency <= 0 {
|
|
|
|
|
o.concurrency = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o.initial *= o.concurrency
|
|
|
|
|
if o.minDelay > 0 {
|
|
|
|
|
if o.maxDelay < o.minDelay {
|
|
|
|
|
o.maxDelay = o.minDelay
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testClock = times.Test()
|
|
|
|
|
clock = testClock
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
po := pool.Options{
|
|
|
|
|
Algo: o.algo,
|
|
|
|
|
Clock: clock,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := pool.Make(alloc, nil, po)
|
|
|
|
|
active := active(initResource[[][]byte]())
|
|
|
|
|
stats := stats(initResource[[]pool.Stats]())
|
|
|
|
|
get := func() {
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
active.push(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
put := func() {
|
|
|
|
|
b := active.shift()
|
|
|
|
|
if len(b) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.Put(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < o.initial; i++ {
|
|
|
|
|
get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stats.push(p.Stats())
|
|
|
|
|
rnd := rand.New(rand.NewSource(0))
|
|
|
|
|
c := make(chan struct{}, o.concurrency)
|
|
|
|
|
iter := func(i int, localClock times.TestClock) {
|
|
|
|
|
if o.minDelay > 0 {
|
|
|
|
|
d := o.minDelay
|
|
|
|
|
if o.maxDelay > o.minDelay {
|
|
|
|
|
diff := o.maxDelay - o.minDelay
|
|
|
|
|
rdiff := rand.Intn(int(diff))
|
|
|
|
|
d += time.Duration(rdiff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localClock.Pass(d)
|
|
|
|
|
testClock.Jump(localClock.Now())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
step(o, rnd, i, active.count(), get, put)
|
|
|
|
|
stats.push(p.Stats())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < o.concurrency; i++ {
|
|
|
|
|
go func() {
|
|
|
|
|
var localClock times.TestClock
|
|
|
|
|
if o.minDelay > 0 {
|
|
|
|
|
localClock = times.Test()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < o.ops; i++ {
|
|
|
|
|
iter(i, localClock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c <- struct{}{}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < o.concurrency; i++ {
|
|
|
|
|
<-c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.plot {
|
|
|
|
|
for i, s := range stats.get() {
|
|
|
|
|
t.Log(i, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if verify != nil {
|
|
|
|
|
verify(t, o, stats.get())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicScenario(t *testing.T, o scenarioOptions, step scenarioStep, verify verifyScenario) {
|
|
|
|
|
cyclicStep := func(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
o.ops = o.ops / 4
|
|
|
|
|
i = i % o.ops
|
|
|
|
|
step(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testScenario(t, o, cyclicStep, verify)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func steadyStep(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func(), target int) {
|
|
|
|
|
switch {
|
|
|
|
|
case active > target:
|
|
|
|
|
put()
|
|
|
|
|
case active < target-o.deviation:
|
|
|
|
|
get()
|
|
|
|
|
default:
|
|
|
|
|
if rnd.Intn(2) == 1 {
|
|
|
|
|
get()
|
|
|
|
|
} else {
|
|
|
|
|
put()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func steady(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jumpStep(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dropStep(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
put()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func steadyStepUp(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
switch {
|
|
|
|
|
case i < o.ops/3:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial)
|
|
|
|
|
case i >= o.ops/3 && i < 2*o.ops/3:
|
|
|
|
|
jumpStep(o, rnd, i, active, get, put)
|
|
|
|
|
default:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial/3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func steadyStepDown(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
switch {
|
|
|
|
|
case i < o.ops/3:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial)
|
|
|
|
|
case i >= o.ops/3 && i < 2*o.ops/3:
|
|
|
|
|
dropStep(o, rnd, i, active, get, put)
|
|
|
|
|
default:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial/3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func steadyStepDownToZero(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
switch {
|
|
|
|
|
case i < o.ops/3:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, o.initial)
|
|
|
|
|
case i >= o.ops/3 && i < 2*o.ops/3:
|
|
|
|
|
dropStep(o, rnd, i, active, get, put)
|
|
|
|
|
default:
|
|
|
|
|
steadyStep(o, rnd, i, active, get, put, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func change(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
cr := o.changeRate
|
|
|
|
|
inc := get
|
|
|
|
|
dec := put
|
|
|
|
|
if cr < 0 {
|
|
|
|
|
cr = 0 - cr
|
|
|
|
|
inc, dec = put, get
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := rnd.Intn(cr + 2)
|
|
|
|
|
if v == 0 {
|
|
|
|
|
dec()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inc()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func changeAndJump(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 3*i < 2*o.ops:
|
|
|
|
|
f = change
|
|
|
|
|
default:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cyclicSpikes(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 3*i < o.ops:
|
|
|
|
|
o.initial = active
|
|
|
|
|
f = steady
|
|
|
|
|
case 3*i < 2*o.ops:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
default:
|
|
|
|
|
f = dropStep
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cyclicDrops(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 3*i < o.ops:
|
|
|
|
|
o.initial = active
|
|
|
|
|
f = steady
|
|
|
|
|
case 3*i < 2*o.ops:
|
|
|
|
|
f = dropStep
|
|
|
|
|
default:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sigmaSteps(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 6*i < o.ops:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
case 2*i < o.ops:
|
|
|
|
|
o.initial = active
|
|
|
|
|
f = steady
|
|
|
|
|
case 3*i < 2*o.ops:
|
|
|
|
|
f = dropStep
|
|
|
|
|
default:
|
|
|
|
|
o.initial = active
|
|
|
|
|
f = steady
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func chainSaw(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 3*i < o.ops:
|
|
|
|
|
f = change
|
|
|
|
|
case 2*i < o.ops:
|
|
|
|
|
f = dropStep
|
|
|
|
|
case 6*i < 5*o.ops:
|
|
|
|
|
f = change
|
|
|
|
|
default:
|
|
|
|
|
f = dropStep
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func inverseChainSaw(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
var f scenarioStep
|
|
|
|
|
switch {
|
|
|
|
|
case 6*i < o.ops:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
case 2*i < o.ops:
|
|
|
|
|
f = change
|
|
|
|
|
case 3*i < 2*o.ops:
|
|
|
|
|
f = jumpStep
|
|
|
|
|
default:
|
|
|
|
|
f = change
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(o, rnd, i, active, get, put)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sinusStep(o scenarioOptions, rnd *rand.Rand, i, active int, get, put func()) {
|
|
|
|
|
cycle := float64(o.ops)
|
|
|
|
|
amp := cycle / 4
|
|
|
|
|
x := float64(i)
|
|
|
|
|
sin := amp * math.Sin(x*2*math.Pi/cycle)
|
|
|
|
|
target := o.initial + int(sin)
|
|
|
|
|
if target < 0 {
|
|
|
|
|
target = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delta := target - active
|
|
|
|
|
absDelta := delta
|
|
|
|
|
if absDelta < 0 {
|
|
|
|
|
absDelta = 0 - absDelta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absR := rnd.Intn(o.deviation + absDelta)
|
|
|
|
|
r := absR - o.deviation
|
|
|
|
|
|
|
|
|
|
var grow bool
|
|
|
|
|
switch {
|
|
|
|
|
case delta >= 0 && r >= 0:
|
|
|
|
|
grow = true
|
|
|
|
|
case delta <= 0 && r < 0:
|
|
|
|
|
grow = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f := put
|
|
|
|
|
if grow {
|
|
|
|
|
f = get
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func noopVerify(*testing.T, scenarioOptions, []pool.Stats) {}
|
|
|
|
|
|
|
|
|
|
func verifySteady(t *testing.T, o scenarioOptions, s []pool.Stats) {
|
|
|
|
|
for i, stats := range s {
|
2026-03-15 17:18:20 +01:00
|
|
|
if 2*stats.Idle > 3*(o.deviation+1)*o.concurrency {
|
2026-03-14 19:03:18 +01:00
|
|
|
t.Fatal(i, stats)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyAllocRate(t *testing.T, o scenarioOptions, s []pool.Stats) {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
t.Fatal("no stats")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last := s[len(s)-1]
|
|
|
|
|
if (last.Alloc-o.initial)*3 > last.Get*2 {
|
|
|
|
|
t.Fatal("too many allocations", last)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyAllocRateLax(t *testing.T, o scenarioOptions, s []pool.Stats) {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
t.Fatal("no stats")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last := s[len(s)-1]
|
|
|
|
|
if (last.Alloc-o.initial)*10 > last.Get*9 {
|
|
|
|
|
t.Fatal("too many allocations", last)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyDealloc(t *testing.T, o scenarioOptions, s []pool.Stats) {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
t.Fatal("no stats")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last := s[len(s)-1]
|
|
|
|
|
|
|
|
|
|
if last.Free > o.deviation*o.concurrency {
|
|
|
|
|
t.Fatal("too many deallocations", last)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testSteadyUsage(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testScenario(t, o, steady, verifySteady)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testSteadyStepUp(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testScenario(t, o, steadyStepUp, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testSteadyStepDown(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testScenario(t, o, steadyStepDown, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testSteadyStepDownToZero(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testScenario(t, o, steadyStepDownToZero, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testChange(t *testing.T, o scenarioOptions) {
|
|
|
|
|
verify := verifyDealloc
|
|
|
|
|
if o.changeRate < 0 {
|
|
|
|
|
verify = verifyAllocRate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testScenario(t, o, change, verify)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testChangeAndJump(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testScenario(t, o, changeAndJump, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicSpikes(t *testing.T, o scenarioOptions) {
|
|
|
|
|
verify := noopVerify
|
|
|
|
|
if o.initial > 0 {
|
|
|
|
|
verify = verifyAllocRate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testCyclicScenario(t, o, cyclicSpikes, verify)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicDrops(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testCyclicScenario(t, o, cyclicDrops, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicSigmaSteps(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testCyclicScenario(t, o, sigmaSteps, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicChainSaw(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testCyclicScenario(t, o, chainSaw, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicInverseChainSaw(t *testing.T, o scenarioOptions) {
|
|
|
|
|
testCyclicScenario(t, o, inverseChainSaw, verifyAllocRateLax)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicSinus(t *testing.T, o scenarioOptions) {
|
|
|
|
|
o.initial += o.ops / 16 // a single cycle is o.ops / 4, the amp in sinusStep is cycle ops / 4
|
|
|
|
|
testCyclicScenario(t, o, sinusStep, verifyAllocRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testBasicSet(t *testing.T, base scenarioOptions) {
|
2026-03-15 17:18:20 +01:00
|
|
|
t.Run("steady minimal", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 1
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.deviation = 1
|
|
|
|
|
testSteadyUsage(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-14 19:03:18 +01:00
|
|
|
t.Run("steady small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 8
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.deviation = 2
|
|
|
|
|
testSteadyUsage(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testSteadyUsage(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step up small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 8
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.deviation = 2
|
|
|
|
|
testSteadyStepUp(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step up large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testSteadyStepUp(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step down small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 20
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.deviation = 2
|
|
|
|
|
testSteadyStepDown(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step down large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 450
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testSteadyStepDown(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step down small to zero", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 20
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.deviation = 2
|
|
|
|
|
testSteadyStepDownToZero(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady step down large to zero", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 450
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testSteadyStepDownToZero(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow rise from zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.changeRate = 1
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testChange(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow rise from zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.changeRate = 1
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testChange(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow decrease to zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 15
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.changeRate = -1
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testChange(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow decrease to zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 300
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.changeRate = -1
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testChange(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow decrease and jump small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 15
|
|
|
|
|
o.ops = 60
|
|
|
|
|
o.changeRate = -2
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testChangeAndJump(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slow decrease and jump large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 300
|
|
|
|
|
o.ops = 1200
|
|
|
|
|
o.changeRate = -2
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testChangeAndJump(t, o)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCyclicSet(t *testing.T, base scenarioOptions) {
|
|
|
|
|
t.Run("cyclic spikes from zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSpikes(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("cyclic spikes from zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSpikes(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady and cyclic spikes small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 30
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSpikes(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady and cyclic spikes large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 300
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSpikes(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady and cyclic drops to zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 30
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicDrops(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("steady and cyclic drops to zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 420
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicDrops(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sigma steps from zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSigmaSteps(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sigma steps from zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSigmaSteps(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sigma steps small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSigmaSteps(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sigma steps large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 600
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSigmaSteps(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("chain saw from zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = 2
|
|
|
|
|
testCyclicChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("chain saw from zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = 2
|
|
|
|
|
testCyclicChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("chain saw small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = 2
|
|
|
|
|
testCyclicChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("chain saw large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 600
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = 2
|
|
|
|
|
testCyclicChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("inverse chain saw from zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = -3
|
|
|
|
|
testCyclicInverseChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("inverse chain saw from zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = -3
|
|
|
|
|
testCyclicInverseChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("inverse chain saw small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = -3
|
|
|
|
|
testCyclicInverseChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("inverse chain saw large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 600
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
o.changeRate = -3
|
|
|
|
|
testCyclicInverseChainSaw(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sinus to zero small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSinus(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sinus to zero large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSinus(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sinus small", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 15
|
|
|
|
|
o.ops = 300
|
|
|
|
|
o.deviation = 3
|
|
|
|
|
testCyclicSinus(t, o)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("sinus large", func(t *testing.T) {
|
|
|
|
|
o := base
|
|
|
|
|
o.initial = 60
|
|
|
|
|
o.ops = 6000
|
|
|
|
|
o.deviation = 10
|
|
|
|
|
testCyclicSinus(t, o)
|
|
|
|
|
})
|
|
|
|
|
}
|