-
Notifications
You must be signed in to change notification settings - Fork 4
/
stat.go
163 lines (134 loc) · 2.91 KB
/
stat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// 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/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/iwind/TeaGo/Tea"
"sync"
"sync/atomic"
"time"
)
const countSamples = 100_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 = memutils.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() {
idles.RunTicker(this.ticker, func() {
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 && (Tea.IsTesting() || item.timestamp < fasttime.Now().Unix()-600) /** 10 minutes ago **/ {
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)
}