对WAF正则缓存增加命中率检查

This commit is contained in:
刘祥超
2023-10-12 20:10:30 +08:00
parent adb0069c59
commit 3aa68b5ffc
4 changed files with 309 additions and 5 deletions

View File

@@ -0,0 +1,161 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cachehits
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"sync"
"sync/atomic"
"time"
)
const countSamples = 10_000
type Item struct {
countHits uint64
countCached uint64
timestamp int64
isGood bool
isBad bool
}
type Stat struct {
goodRatio uint64
maxItems int
itemMap map[string]*Item // category => *Item
mu *sync.RWMutex
ticker *time.Ticker
}
func NewStat(goodRatio uint64) *Stat {
if goodRatio == 0 {
goodRatio = 5
}
var maxItems = utils.SystemMemoryGB() * 10_000
if maxItems <= 0 {
maxItems = 100_000
}
var stat = &Stat{
goodRatio: goodRatio,
itemMap: map[string]*Item{},
mu: &sync.RWMutex{},
ticker: time.NewTicker(24 * time.Hour),
maxItems: maxItems,
}
goman.New(func() {
stat.init()
})
return stat
}
func (this *Stat) init() {
for range this.ticker.C {
var currentTime = fasttime.Now().Unix()
this.mu.RLock()
for _, item := range this.itemMap {
if item.timestamp < currentTime-7*24*86400 {
// reset
item.countHits = 0
item.countCached = 1
item.timestamp = currentTime
item.isGood = false
item.isBad = false
}
}
this.mu.RUnlock()
}
}
func (this *Stat) IncreaseCached(category string) {
this.mu.RLock()
var item = this.itemMap[category]
if item != nil {
if item.isGood || item.isBad {
this.mu.RUnlock()
return
}
atomic.AddUint64(&item.countCached, 1)
this.mu.RUnlock()
return
}
this.mu.RUnlock()
this.mu.Lock()
if len(this.itemMap) > this.maxItems {
// remove one randomly
for k := range this.itemMap {
delete(this.itemMap, k)
break
}
}
this.itemMap[category] = &Item{
countHits: 0,
countCached: 1,
timestamp: fasttime.Now().Unix(),
}
this.mu.Unlock()
}
func (this *Stat) IncreaseHit(category string) {
this.mu.RLock()
defer this.mu.RUnlock()
var item = this.itemMap[category]
if item != nil {
if item.isGood || item.isBad {
return
}
atomic.AddUint64(&item.countHits, 1)
return
}
}
func (this *Stat) IsGood(category string) bool {
this.mu.RLock()
defer func() {
this.mu.RUnlock()
}()
var item = this.itemMap[category]
if item != nil {
if item.isBad {
return false
}
if item.isGood {
return true
}
if item.countCached > countSamples {
var isGood = item.countHits*100/item.countCached >= this.goodRatio
if isGood {
item.isGood = true
} else {
item.isBad = true
}
return isGood
}
}
return true
}
func (this *Stat) Len() int {
this.mu.RLock()
defer this.mu.RUnlock()
return len(this.itemMap)
}

View File

@@ -0,0 +1,107 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cachehits_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/cachehits"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"runtime"
"strconv"
"testing"
"time"
)
func TestNewStat(t *testing.T) {
var a = assert.NewAssertion(t)
{
var stat = cachehits.NewStat(20)
for i := 0; i < 1000; i++ {
stat.IncreaseCached("a")
}
a.IsTrue(stat.IsGood("a"))
}
{
var stat = cachehits.NewStat(5)
for i := 0; i < 10000; i++ {
stat.IncreaseCached("a")
}
for i := 0; i < 500; i++ {
stat.IncreaseHit("a")
}
stat.IncreaseHit("b") // empty
a.IsTrue(stat.IsGood("a"))
a.IsTrue(stat.IsGood("b"))
}
{
var stat = cachehits.NewStat(10)
for i := 0; i < 10000; i++ {
stat.IncreaseCached("a")
}
for i := 0; i < 1000; i++ {
stat.IncreaseHit("a")
}
stat.IncreaseHit("b") // empty
a.IsTrue(stat.IsGood("a"))
a.IsTrue(stat.IsGood("b"))
}
{
var stat = cachehits.NewStat(5)
for i := 0; i < 10001; i++ {
stat.IncreaseCached("a")
}
for i := 0; i < 499; i++ {
stat.IncreaseHit("a")
}
a.IsFalse(stat.IsGood("a"))
}
}
func TestNewStat_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var stat = cachehits.NewStat(20)
for i := 0; i < 10_000_000; i++ {
stat.IncreaseCached("a" + types.String(i))
}
time.Sleep(60 * time.Second)
t.Log(stat.Len())
}
func BenchmarkStat(b *testing.B) {
runtime.GOMAXPROCS(4)
var stat = cachehits.NewStat(5)
for i := 0; i < 1_000_000; i++ {
stat.IncreaseCached("a" + types.String(i))
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var key = strconv.Itoa(rands.Int(0, 100_000))
stat.IncreaseCached(key)
if rands.Int(0, 3) == 0 {
stat.IncreaseHit(key)
}
_ = stat.IsGood(key)
}
})
}