github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/cachehits/stat.go (about) 1 // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package cachehits 4 5 import ( 6 "github.com/TeaOSLab/EdgeNode/internal/goman" 7 "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" 8 "github.com/TeaOSLab/EdgeNode/internal/utils/idles" 9 memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 10 "github.com/iwind/TeaGo/Tea" 11 "sync" 12 "sync/atomic" 13 "time" 14 ) 15 16 const countSamples = 100_000 17 18 type Item struct { 19 countHits uint64 20 countCached uint64 21 timestamp int64 22 23 isGood bool 24 isBad bool 25 } 26 27 type Stat struct { 28 goodRatio uint64 29 maxItems int 30 31 itemMap map[string]*Item // category => *Item 32 mu *sync.RWMutex 33 34 ticker *time.Ticker 35 } 36 37 func NewStat(goodRatio uint64) *Stat { 38 if goodRatio == 0 { 39 goodRatio = 5 40 } 41 42 var maxItems = memutils.SystemMemoryGB() * 10_000 43 if maxItems <= 0 { 44 maxItems = 100_000 45 } 46 47 var stat = &Stat{ 48 goodRatio: goodRatio, 49 itemMap: map[string]*Item{}, 50 mu: &sync.RWMutex{}, 51 ticker: time.NewTicker(24 * time.Hour), 52 maxItems: maxItems, 53 } 54 55 goman.New(func() { 56 stat.init() 57 }) 58 return stat 59 } 60 61 func (this *Stat) init() { 62 idles.RunTicker(this.ticker, func() { 63 var currentTime = fasttime.Now().Unix() 64 65 this.mu.RLock() 66 for _, item := range this.itemMap { 67 if item.timestamp < currentTime-7*24*86400 { 68 // reset 69 item.countHits = 0 70 item.countCached = 1 71 item.timestamp = currentTime 72 item.isGood = false 73 item.isBad = false 74 } 75 } 76 this.mu.RUnlock() 77 }) 78 } 79 80 func (this *Stat) IncreaseCached(category string) { 81 this.mu.RLock() 82 var item = this.itemMap[category] 83 if item != nil { 84 if item.isGood || item.isBad { 85 this.mu.RUnlock() 86 return 87 } 88 89 atomic.AddUint64(&item.countCached, 1) 90 this.mu.RUnlock() 91 return 92 } 93 this.mu.RUnlock() 94 95 this.mu.Lock() 96 97 if len(this.itemMap) > this.maxItems { 98 // remove one randomly 99 for k := range this.itemMap { 100 delete(this.itemMap, k) 101 break 102 } 103 } 104 105 this.itemMap[category] = &Item{ 106 countHits: 0, 107 countCached: 1, 108 timestamp: fasttime.Now().Unix(), 109 } 110 this.mu.Unlock() 111 } 112 113 func (this *Stat) IncreaseHit(category string) { 114 this.mu.RLock() 115 defer this.mu.RUnlock() 116 117 var item = this.itemMap[category] 118 if item != nil { 119 if item.isGood || item.isBad { 120 return 121 } 122 123 atomic.AddUint64(&item.countHits, 1) 124 return 125 } 126 } 127 128 func (this *Stat) IsGood(category string) bool { 129 this.mu.RLock() 130 defer func() { 131 this.mu.RUnlock() 132 }() 133 134 var item = this.itemMap[category] 135 if item != nil { 136 if item.isBad { 137 return false 138 } 139 if item.isGood { 140 return true 141 } 142 143 if item.countCached > countSamples && (Tea.IsTesting() || item.timestamp < fasttime.Now().Unix()-600) /** 10 minutes ago **/ { 144 var isGood = item.countHits*100/item.countCached >= this.goodRatio 145 if isGood { 146 item.isGood = true 147 } else { 148 item.isBad = true 149 } 150 151 return isGood 152 } 153 } 154 155 return true 156 } 157 158 func (this *Stat) Len() int { 159 this.mu.RLock() 160 defer this.mu.RUnlock() 161 162 return len(this.itemMap) 163 }