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  }