cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.33.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/promapi/cache.go

251lines · modecode

1package promapi
2
3import (
4 "sort"
5 "sync"
6 "time"
7
8 "github.com/prometheus/client_golang/prometheus"
9)
10
11type cacheEntry struct {
12 data queryResult
13 expiresAt time.Time
14 cost int
15 gets int
16}
17
18type cacheLine struct {
19 key uint64
20 val *cacheEntry
21 ttl time.Duration
22 isExpired bool
23}
24
25type cacheLines []cacheLine
26
27func (cl cacheLines) Len() int {
28 return len(cl)
29}
30
31func (cl cacheLines) Swap(i, j int) {
32 cl[i], cl[j] = cl[j], cl[i]
33}
34
35// [expired, costly, cheap, high ttl]
36func (cl cacheLines) Less(i, j int) bool {
37 if cl[i].isExpired != cl[j].isExpired {
38 return cl[i].isExpired
39 }
40
41 if cl[i].val.gets == 0 {
42 return true
43 }
44 if cl[j].val.gets == 0 {
45 return false
46 }
47
48 if cl[i].val.cost != cl[j].val.cost {
49 ca := float64(cl[i].val.cost) / float64(cl[i].val.gets)
50 cb := float64(cl[j].val.cost) / float64(cl[j].val.gets)
51 if ca != cb {
52 return ca >= cb
53 }
54 }
55
56 if cl[i].val.gets != cl[j].val.gets {
57 return cl[i].val.gets < cl[j].val.gets
58 }
59
60 return cl[i].ttl < cl[j].ttl
61}
62
63type endpointStats struct {
64 hits int
65 misses int
66}
67
68func (e *endpointStats) hit() { e.hits++ }
69func (e *endpointStats) miss() { e.misses++ }
70
71func newQueryCache(maxSize int) *queryCache {
72 return &queryCache{
73 entries: map[uint64]*cacheEntry{},
74 stats: map[string]*endpointStats{},
75 maxCost: maxSize,
76 }
77}
78
79type queryCache struct {
80 mu sync.Mutex
81 entries map[uint64]*cacheEntry
82 stats map[string]*endpointStats
83 cost int
84 maxCost int
85 evictions int
86}
87
88func (c *queryCache) endpointStats(endpoint string) *endpointStats {
89 e, ok := c.stats[endpoint]
90 if ok {
91 return e
92 }
93
94 e = &endpointStats{}
95 c.stats[endpoint] = e
96 return e
97}
98
99func (c *queryCache) get(key uint64, endpoint string) (v queryResult, ok bool) {
100 c.mu.Lock()
101 defer c.mu.Unlock()
102
103 var ce *cacheEntry
104 ce, ok = c.entries[key]
105 if !ok {
106 c.endpointStats(endpoint).miss()
107 return v, ok
108 }
109
110 ce.gets++
111 c.endpointStats(endpoint).hit()
112
113 return ce.data, true
114}
115
116// Cache results if it was requested at least twice EVER - which means it's either
117// popular and requested multiple times within a loop OR this cache key survives between loops.
118func (c *queryCache) set(key uint64, val queryResult, ttl time.Duration, cost int, endpoint string) {
119 c.mu.Lock()
120 defer c.mu.Unlock()
121
122 oe, ok := c.entries[key]
123 if ok {
124 c.cost -= oe.cost
125 }
126
127 // If we're not updating in-place then we need to make room for this entry
128 if !ok && c.cost+cost > c.maxCost {
129 c.makeRoom(cost)
130 }
131
132 c.cost += cost
133 c.entries[key] = &cacheEntry{
134 data: val,
135 cost: cost,
136 }
137 if ttl > 0 {
138 c.entries[key].expiresAt = time.Now().Add(ttl)
139 }
140}
141
142func (c *queryCache) makeRoom(needed int) {
143 now := time.Now()
144 for key, ce := range c.entries {
145 if !ce.expiresAt.IsZero() && ce.expiresAt.Before(now) {
146 c.cost -= ce.cost
147 needed -= ce.cost
148 delete(c.entries, key)
149 c.evictions++
150 }
151 }
152 if needed <= 0 {
153 return
154 }
155
156 entries := make(cacheLines, 0, len(c.entries))
157 for key, ce := range c.entries {
158 entries = append(entries, cacheLine{
159 key: key,
160 val: ce,
161 ttl: ce.expiresAt.Sub(now).Round(time.Second),
162 isExpired: ce.expiresAt.Before(now),
163 })
164 }
165 sort.Stable(entries)
166
167 for i := len(entries) - 1; i >= 0; i-- {
168 c.cost -= entries[i].val.cost
169 needed -= entries[i].val.cost
170 delete(c.entries, entries[i].key)
171 c.evictions++
172 if needed <= 0 {
173 return
174 }
175 }
176}
177
178func (c *queryCache) gc() {
179 c.mu.Lock()
180 defer c.mu.Unlock()
181
182 entries := map[uint64]*cacheEntry{}
183
184 now := time.Now()
185 for key, ce := range c.entries {
186 if !ce.expiresAt.IsZero() && ce.expiresAt.Before(now) {
187 c.cost -= ce.cost
188 c.evictions++
189 continue
190 }
191 entries[key] = ce
192 }
193 c.entries = entries
194}
195
196type cacheCollector struct {
197 cache *queryCache
198 entries *prometheus.Desc
199 hits *prometheus.Desc
200 misses *prometheus.Desc
201 evictions *prometheus.Desc
202}
203
204func newCacheCollector(cache *queryCache, name string) *cacheCollector {
205 return &cacheCollector{
206 cache: cache,
207 entries: prometheus.NewDesc(
208 "pint_prometheus_cache_size",
209 "Total number of entries currently stored in Prometheus query cache",
210 nil,
211 prometheus.Labels{"name": name},
212 ),
213 hits: prometheus.NewDesc(
214 "pint_prometheus_cache_hits_total",
215 "Total number of query cache hits",
216 []string{"endpoint"},
217 prometheus.Labels{"name": name},
218 ),
219 misses: prometheus.NewDesc(
220 "pint_prometheus_cache_miss_total",
221 "Total number of query cache misses",
222 []string{"endpoint"},
223 prometheus.Labels{"name": name},
224 ),
225 evictions: prometheus.NewDesc(
226 "pint_prometheus_cache_evictions_total",
227 "Total number of times an entry was evicted from query cache due to size limit or TTL",
228 nil,
229 prometheus.Labels{"name": name},
230 ),
231 }
232}
233
234func (c *cacheCollector) Describe(ch chan<- *prometheus.Desc) {
235 ch <- c.entries
236 ch <- c.hits
237 ch <- c.misses
238 ch <- c.evictions
239}
240
241func (c *cacheCollector) Collect(ch chan<- prometheus.Metric) {
242 c.cache.mu.Lock()
243 defer c.cache.mu.Unlock()
244 ch <- prometheus.MustNewConstMetric(c.entries, prometheus.GaugeValue, float64(c.cache.cost))
245
246 for endpoint, stats := range c.cache.stats {
247 ch <- prometheus.MustNewConstMetric(c.hits, prometheus.CounterValue, float64(stats.hits), endpoint)
248 ch <- prometheus.MustNewConstMetric(c.misses, prometheus.CounterValue, float64(stats.misses), endpoint)
249 }
250 ch <- prometheus.MustNewConstMetric(c.evictions, prometheus.CounterValue, float64(c.cache.evictions))
251}
252