github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/counters/counter.go (about) 1 // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package counters 4 5 import ( 6 "github.com/TeaOSLab/EdgeNode/internal/goman" 7 "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" 8 memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 9 syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync" 10 "github.com/cespare/xxhash/v2" 11 "sync" 12 "time" 13 ) 14 15 const maxItemsPerGroup = 50_000 16 17 var SharedCounter = NewCounter[uint32]().WithGC() 18 19 type SupportedUIntType interface { 20 uint32 | uint64 21 } 22 23 type Counter[T SupportedUIntType] struct { 24 countMaps uint64 25 locker *syncutils.RWMutex 26 itemMaps []map[uint64]Item[T] 27 28 gcTicker *time.Ticker 29 gcIndex int 30 gcLocker sync.Mutex 31 } 32 33 // NewCounter create new counter 34 func NewCounter[T SupportedUIntType]() *Counter[T] { 35 var count = memutils.SystemMemoryGB() * 8 36 if count < 8 { 37 count = 8 38 } 39 40 var itemMaps = []map[uint64]Item[T]{} 41 for i := 0; i < count; i++ { 42 itemMaps = append(itemMaps, map[uint64]Item[T]{}) 43 } 44 45 var counter = &Counter[T]{ 46 countMaps: uint64(count), 47 locker: syncutils.NewRWMutex(count), 48 itemMaps: itemMaps, 49 } 50 51 return counter 52 } 53 54 // WithGC start the counter with gc automatically 55 func (this *Counter[T]) WithGC() *Counter[T] { 56 if this.gcTicker != nil { 57 return this 58 } 59 this.gcTicker = time.NewTicker(1 * time.Second) 60 goman.New(func() { 61 for range this.gcTicker.C { 62 this.GC() 63 } 64 }) 65 66 return this 67 } 68 69 // Increase key 70 func (this *Counter[T]) Increase(key uint64, lifeSeconds int) T { 71 var index = int(key % this.countMaps) 72 this.locker.RLock(index) 73 var item = this.itemMaps[index][key] // item MUST NOT be pointer 74 this.locker.RUnlock(index) 75 if !item.IsOk() { 76 // no need to care about duplication 77 // always insert new item even when itemMap is full 78 item = NewItem[T](lifeSeconds) 79 var result = item.Increase() 80 this.locker.Lock(index) 81 this.itemMaps[index][key] = item 82 this.locker.Unlock(index) 83 return result 84 } 85 86 this.locker.Lock(index) 87 var result = item.Increase() 88 this.itemMaps[index][key] = item // overwrite 89 this.locker.Unlock(index) 90 return result 91 } 92 93 // IncreaseKey increase string key 94 func (this *Counter[T]) IncreaseKey(key string, lifeSeconds int) T { 95 return this.Increase(this.hash(key), lifeSeconds) 96 } 97 98 // Get value of key 99 func (this *Counter[T]) Get(key uint64) T { 100 var index = int(key % this.countMaps) 101 this.locker.RLock(index) 102 defer this.locker.RUnlock(index) 103 var item = this.itemMaps[index][key] 104 if item.IsOk() { 105 return item.Sum() 106 } 107 return 0 108 } 109 110 // GetKey get value of string key 111 func (this *Counter[T]) GetKey(key string) T { 112 return this.Get(this.hash(key)) 113 } 114 115 // Reset key 116 func (this *Counter[T]) Reset(key uint64) { 117 var index = int(key % this.countMaps) 118 this.locker.RLock(index) 119 var item = this.itemMaps[index][key] 120 this.locker.RUnlock(index) 121 122 if item.IsOk() { 123 this.locker.Lock(index) 124 delete(this.itemMaps[index], key) 125 this.locker.Unlock(index) 126 } 127 } 128 129 // ResetKey string key 130 func (this *Counter[T]) ResetKey(key string) { 131 this.Reset(this.hash(key)) 132 } 133 134 // TotalItems get items count 135 func (this *Counter[T]) TotalItems() int { 136 var total = 0 137 138 for i := 0; i < int(this.countMaps); i++ { 139 this.locker.RLock(i) 140 total += len(this.itemMaps[i]) 141 this.locker.RUnlock(i) 142 } 143 144 return total 145 } 146 147 // GC garbage expired items 148 func (this *Counter[T]) GC() { 149 this.gcLocker.Lock() 150 var gcIndex = this.gcIndex 151 152 this.gcIndex++ 153 if this.gcIndex >= int(this.countMaps) { 154 this.gcIndex = 0 155 } 156 157 this.gcLocker.Unlock() 158 159 var currentTime = fasttime.Now().Unix() 160 161 this.locker.RLock(gcIndex) 162 var itemMap = this.itemMaps[gcIndex] 163 var expiredKeys = []uint64{} 164 for key, item := range itemMap { 165 if item.IsExpired(currentTime) { 166 expiredKeys = append(expiredKeys, key) 167 } 168 } 169 var tooManyItems = len(itemMap) > maxItemsPerGroup // prevent too many items 170 this.locker.RUnlock(gcIndex) 171 172 if len(expiredKeys) > 0 { 173 this.locker.Lock(gcIndex) 174 for _, key := range expiredKeys { 175 delete(itemMap, key) 176 } 177 this.locker.Unlock(gcIndex) 178 } 179 180 if tooManyItems { 181 this.locker.Lock(gcIndex) 182 var count = len(itemMap) - maxItemsPerGroup 183 if count > 0 { 184 itemMap = this.itemMaps[gcIndex] 185 for key := range itemMap { 186 delete(itemMap, key) 187 count-- 188 if count < 0 { 189 break 190 } 191 } 192 } 193 this.locker.Unlock(gcIndex) 194 } 195 } 196 197 func (this *Counter[T]) CountMaps() int { 198 return int(this.countMaps) 199 } 200 201 // calculate hash of the key 202 func (this *Counter[T]) hash(key string) uint64 { 203 return xxhash.Sum64String(key) 204 }