Files
EdgeNode/internal/utils/counters/counter.go

206 lines
4.4 KiB
Go
Raw Normal View History

2024-07-27 15:42:50 +08:00
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cloud .
package counters
import (
2024-07-27 15:42:50 +08:00
"sync"
"time"
2023-07-15 11:08:25 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
2024-05-11 09:23:54 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
2024-03-28 17:17:34 +08:00
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
2024-04-18 18:25:33 +08:00
"github.com/cespare/xxhash/v2"
)
2023-10-05 13:36:55 +08:00
const maxItemsPerGroup = 50_000
2023-11-15 15:57:41 +08:00
var SharedCounter = NewCounter[uint32]().WithGC()
2023-10-04 16:53:39 +08:00
2023-11-15 15:57:41 +08:00
type SupportedUIntType interface {
uint32 | uint64
}
type Counter[T SupportedUIntType] struct {
countMaps uint64
locker *syncutils.RWMutex
2023-12-25 16:41:07 +08:00
itemMaps []map[uint64]Item[T]
gcTicker *time.Ticker
gcIndex int
2023-07-15 11:08:25 +08:00
gcLocker sync.Mutex
}
// NewCounter create new counter
2023-11-15 15:57:41 +08:00
func NewCounter[T SupportedUIntType]() *Counter[T] {
2024-03-28 17:17:34 +08:00
var count = memutils.SystemMemoryGB() * 8
if count < 8 {
count = 8
}
2023-12-25 16:41:07 +08:00
var itemMaps = []map[uint64]Item[T]{}
for i := 0; i < count; i++ {
2023-12-25 16:41:07 +08:00
itemMaps = append(itemMaps, map[uint64]Item[T]{})
}
2023-11-15 15:57:41 +08:00
var counter = &Counter[T]{
countMaps: uint64(count),
locker: syncutils.NewRWMutex(count),
itemMaps: itemMaps,
}
return counter
}
// WithGC start the counter with gc automatically
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) WithGC() *Counter[T] {
if this.gcTicker != nil {
return this
}
2023-07-15 11:08:25 +08:00
this.gcTicker = time.NewTicker(1 * time.Second)
goman.New(func() {
for range this.gcTicker.C {
this.GC()
}
})
return this
}
// Increase key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) Increase(key uint64, lifeSeconds int) T {
var index = int(key % this.countMaps)
this.locker.RLock(index)
2023-12-25 16:41:07 +08:00
var item = this.itemMaps[index][key] // item MUST NOT be pointer
this.locker.RUnlock(index)
2023-12-25 16:41:07 +08:00
if !item.IsOk() {
2023-11-15 15:17:03 +08:00
// no need to care about duplication
// always insert new item even when itemMap is full
2023-11-15 15:57:41 +08:00
item = NewItem[T](lifeSeconds)
2023-12-25 16:41:07 +08:00
var result = item.Increase()
2023-11-13 15:11:11 +08:00
this.locker.Lock(index)
this.itemMaps[index][key] = item
this.locker.Unlock(index)
2023-12-25 16:41:07 +08:00
return result
}
this.locker.Lock(index)
var result = item.Increase()
2023-12-25 16:41:07 +08:00
this.itemMaps[index][key] = item // overwrite
this.locker.Unlock(index)
return result
}
// IncreaseKey increase string key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) IncreaseKey(key string, lifeSeconds int) T {
return this.Increase(this.hash(key), lifeSeconds)
}
// Get value of key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) Get(key uint64) T {
var index = int(key % this.countMaps)
this.locker.RLock(index)
defer this.locker.RUnlock(index)
var item = this.itemMaps[index][key]
2023-12-25 16:41:07 +08:00
if item.IsOk() {
return item.Sum()
}
return 0
}
// GetKey get value of string key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) GetKey(key string) T {
return this.Get(this.hash(key))
}
// Reset key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) Reset(key uint64) {
var index = int(key % this.countMaps)
this.locker.RLock(index)
var item = this.itemMaps[index][key]
this.locker.RUnlock(index)
2023-12-25 16:41:07 +08:00
if item.IsOk() {
this.locker.Lock(index)
delete(this.itemMaps[index], key)
this.locker.Unlock(index)
}
}
// ResetKey string key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) ResetKey(key string) {
this.Reset(this.hash(key))
}
// TotalItems get items count
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) TotalItems() int {
var total = 0
for i := 0; i < int(this.countMaps); i++ {
this.locker.RLock(i)
total += len(this.itemMaps[i])
this.locker.RUnlock(i)
}
return total
}
// GC garbage expired items
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) GC() {
2023-07-15 11:08:25 +08:00
this.gcLocker.Lock()
var gcIndex = this.gcIndex
this.gcIndex++
if this.gcIndex >= int(this.countMaps) {
this.gcIndex = 0
}
2023-07-15 11:08:25 +08:00
this.gcLocker.Unlock()
var currentTime = fasttime.Now().Unix()
this.locker.RLock(gcIndex)
var itemMap = this.itemMaps[gcIndex]
var expiredKeys = []uint64{}
for key, item := range itemMap {
2023-07-15 11:08:25 +08:00
if item.IsExpired(currentTime) {
expiredKeys = append(expiredKeys, key)
}
}
2023-10-04 16:53:39 +08:00
var tooManyItems = len(itemMap) > maxItemsPerGroup // prevent too many items
this.locker.RUnlock(gcIndex)
if len(expiredKeys) > 0 {
2023-10-04 16:53:39 +08:00
this.locker.Lock(gcIndex)
for _, key := range expiredKeys {
delete(itemMap, key)
}
2023-10-04 16:53:39 +08:00
this.locker.Unlock(gcIndex)
}
if tooManyItems {
this.locker.Lock(gcIndex)
var count = len(itemMap) - maxItemsPerGroup
if count > 0 {
itemMap = this.itemMaps[gcIndex]
for key := range itemMap {
delete(itemMap, key)
count--
if count < 0 {
break
}
}
}
this.locker.Unlock(gcIndex)
}
}
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) CountMaps() int {
2023-10-04 16:53:39 +08:00
return int(this.countMaps)
}
// calculate hash of the key
2023-11-15 15:57:41 +08:00
func (this *Counter[T]) hash(key string) uint64 {
return xxhash.Sum64String(key)
}