You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-zero/core/load/adaptiveshedder_test.go

271 lines
6.2 KiB
Go

4 years ago
package load
import (
"math/rand"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex"
4 years ago
)
const (
buckets = 10
bucketDuration = time.Millisecond * 50
windowFactor = 0.01
4 years ago
)
func init() {
stat.SetReporter(nil)
}
func TestAdaptiveShedder(t *testing.T) {
DisableLog()
4 years ago
shedder := NewAdaptiveShedder(WithWindow(bucketDuration), WithBuckets(buckets), WithCpuThreshold(100))
var wg sync.WaitGroup
var drop int64
proba := mathx.NewProba()
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 30; i++ {
promise, err := shedder.Allow()
if err != nil {
atomic.AddInt64(&drop, 1)
} else {
count := rand.Intn(5)
time.Sleep(time.Millisecond * time.Duration(count))
if proba.TrueOnProba(0.01) {
promise.Fail()
} else {
promise.Pass()
}
}
}
}()
}
wg.Wait()
}
func TestAdaptiveShedderMaxPass(t *testing.T) {
passCounter := newRollingWindow()
for i := 1; i <= 10; i++ {
passCounter.Add(float64(i * 100))
time.Sleep(bucketDuration)
}
shedder := &adaptiveShedder{
passCounter: passCounter,
droppedRecently: syncx.NewAtomicBool(),
}
assert.Equal(t, int64(1000), shedder.maxPass())
// default max pass is equal to 1.
passCounter = newRollingWindow()
shedder = &adaptiveShedder{
passCounter: passCounter,
droppedRecently: syncx.NewAtomicBool(),
}
assert.Equal(t, int64(1), shedder.maxPass())
}
func TestAdaptiveShedderMinRt(t *testing.T) {
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
rtCounter: rtCounter,
}
assert.Equal(t, float64(6), shedder.minRt())
// default max min rt is equal to maxFloat64.
rtCounter = newRollingWindow()
shedder = &adaptiveShedder{
rtCounter: rtCounter,
droppedRecently: syncx.NewAtomicBool(),
}
assert.Equal(t, defaultMinRt, shedder.minRt())
}
func TestAdaptiveShedderMaxFlight(t *testing.T) {
passCounter := newRollingWindow()
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
passCounter.Add(float64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
passCounter: passCounter,
rtCounter: rtCounter,
windowScale: windowFactor,
4 years ago
droppedRecently: syncx.NewAtomicBool(),
}
assert.Equal(t, float64(54), shedder.maxFlight())
4 years ago
}
func TestAdaptiveShedderShouldDrop(t *testing.T) {
logx.Disable()
passCounter := newRollingWindow()
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
passCounter.Add(float64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
passCounter: passCounter,
rtCounter: rtCounter,
windowScale: windowFactor,
overloadTime: syncx.NewAtomicDuration(),
4 years ago
droppedRecently: syncx.NewAtomicBool(),
}
// cpu >= 800, inflight < maxPass
systemOverloadChecker = func(int64) bool {
return true
}
shedder.avgFlying = 50
assert.False(t, shedder.shouldDrop())
// cpu >= 800, inflight > maxPass
shedder.avgFlying = 80
// because of the overloadFactor, so we need to make sure maxFlight is greater than flying
shedder.flying = int64(shedder.maxFlight()*shedder.overloadFactor()) - 5
4 years ago
assert.False(t, shedder.shouldDrop())
// cpu >= 800, inflight > maxPass
shedder.avgFlying = 80
shedder.flying = 80
assert.True(t, shedder.shouldDrop())
// cpu < 800, inflight > maxPass
systemOverloadChecker = func(int64) bool {
return false
}
shedder.avgFlying = 80
assert.False(t, shedder.shouldDrop())
4 years ago
// cpu >= 800, inflight < maxPass
systemOverloadChecker = func(int64) bool {
return true
}
shedder.avgFlying = 80
shedder.flying = 80
_, err := shedder.Allow()
assert.NotNil(t, err)
}
func TestAdaptiveShedderStillHot(t *testing.T) {
logx.Disable()
passCounter := newRollingWindow()
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
passCounter.Add(float64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
passCounter: passCounter,
rtCounter: rtCounter,
windowScale: windowFactor,
overloadTime: syncx.NewAtomicDuration(),
4 years ago
droppedRecently: syncx.ForAtomicBool(true),
}
assert.False(t, shedder.stillHot())
shedder.overloadTime.Set(-coolOffDuration * 2)
4 years ago
assert.False(t, shedder.stillHot())
shedder.droppedRecently.Set(true)
shedder.overloadTime.Set(timex.Now())
assert.True(t, shedder.stillHot())
4 years ago
}
func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
logx.Disable()
bench := func(b *testing.B) {
shedder := NewAdaptiveShedder()
4 years ago
proba := mathx.NewProba()
for i := 0; i < 6000; i++ {
p, err := shedder.Allow()
if err == nil {
time.Sleep(time.Millisecond)
if proba.TrueOnProba(0.01) {
p.Fail()
} else {
p.Pass()
}
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
p, err := shedder.Allow()
if err == nil {
p.Pass()
}
}
}
systemOverloadChecker = func(int64) bool {
return true
}
b.Run("high load", bench)
systemOverloadChecker = func(int64) bool {
return false
}
b.Run("low load", bench)
}
func BenchmarkMaxFlight(b *testing.B) {
passCounter := newRollingWindow()
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
passCounter.Add(float64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
passCounter: passCounter,
rtCounter: rtCounter,
windowScale: windowFactor,
droppedRecently: syncx.NewAtomicBool(),
}
for i := 0; i < b.N; i++ {
_ = shedder.maxFlight()
}
}
4 years ago
func newRollingWindow() *collection.RollingWindow {
return collection.NewRollingWindow(buckets, bucketDuration, collection.IgnoreCurrentBucket())
}