forked from VictoriaMetrics/metricsql
-
Notifications
You must be signed in to change notification settings - Fork 0
/
regexp_cache.go
142 lines (120 loc) · 3.28 KB
/
regexp_cache.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
package metricsql
import (
"regexp"
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
)
// CompileRegexpAnchored returns compiled regexp `^re$`.
func CompileRegexpAnchored(re string) (*regexp.Regexp, error) {
reAnchored := "^(?:" + re + ")$"
return CompileRegexp(reAnchored)
}
// CompileRegexp returns compile regexp re.
func CompileRegexp(re string) (*regexp.Regexp, error) {
rcv := regexpCacheV.Get(re)
if rcv != nil {
return rcv.r, rcv.err
}
r, err := regexp.Compile(re)
rcv = ®expCacheValue{
r: r,
err: err,
}
regexpCacheV.Put(re, rcv)
return rcv.r, rcv.err
}
// regexpCacheCharsMax limits the max number of chars stored in regexp cache across all entries.
//
// We limit by number of chars since calculating the exact size of each regexp is problematic,
// while using chars seems like universal approach for short and long regexps.
const regexpCacheCharsMax = 1e6
var regexpCacheV = func() *regexpCache {
rc := newRegexpCache(regexpCacheCharsMax)
metrics.NewGauge(`vm_cache_requests_total{type="promql/regexp"}`, func() float64 {
return float64(rc.Requests())
})
metrics.NewGauge(`vm_cache_misses_total{type="promql/regexp"}`, func() float64 {
return float64(rc.Misses())
})
metrics.NewGauge(`vm_cache_entries{type="promql/regexp"}`, func() float64 {
return float64(rc.Len())
})
metrics.NewGauge(`vm_cache_chars_current{type="promql/regexp"}`, func() float64 {
return float64(rc.CharsCurrent())
})
metrics.NewGauge(`vm_cache_chars_max{type="promql/regexp"}`, func() float64 {
return float64(rc.charsLimit)
})
return rc
}()
type regexpCacheValue struct {
r *regexp.Regexp
err error
}
type regexpCache struct {
// Move atomic counters to the top of struct for 8-byte alignment on 32-bit arch.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/212
requests uint64
misses uint64
// charsCurrent stores the total number of characters used in stored regexps.
// is used for memory usage estimation.
charsCurrent int
// charsLimit is the maximum number of chars the regexpCache can store.
charsLimit int
m map[string]*regexpCacheValue
mu sync.RWMutex
}
func newRegexpCache(charsLimit int) *regexpCache {
return ®expCache{
m: make(map[string]*regexpCacheValue),
charsLimit: charsLimit,
}
}
func (rc *regexpCache) Requests() uint64 {
return atomic.LoadUint64(&rc.requests)
}
func (rc *regexpCache) Misses() uint64 {
return atomic.LoadUint64(&rc.misses)
}
func (rc *regexpCache) Len() int {
rc.mu.RLock()
n := len(rc.m)
rc.mu.RUnlock()
return n
}
func (rc *regexpCache) CharsCurrent() int {
rc.mu.RLock()
n := rc.charsCurrent
rc.mu.RUnlock()
return int(n)
}
func (rc *regexpCache) Get(regexp string) *regexpCacheValue {
atomic.AddUint64(&rc.requests, 1)
rc.mu.RLock()
rcv := rc.m[regexp]
rc.mu.RUnlock()
if rcv == nil {
atomic.AddUint64(&rc.misses, 1)
}
return rcv
}
func (rc *regexpCache) Put(regexp string, rcv *regexpCacheValue) {
rc.mu.Lock()
if rc.charsCurrent > rc.charsLimit {
// Remove items accounting for 10% chars from the cache.
overflow := int(float64(rc.charsLimit) * 0.1)
for k := range rc.m {
delete(rc.m, k)
size := len(k)
overflow -= size
rc.charsCurrent -= size
if overflow <= 0 {
break
}
}
}
rc.m[regexp] = rcv
rc.charsCurrent += len(regexp)
rc.mu.Unlock()
}