cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.34.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/cache.go

191lines · modecode

1package promapi
2
3import (
4 "container/list"
5 "sync"
6 "time"
7
8 "github.com/prometheus/client_golang/prometheus"
9)
10
11type cacheEntry struct {
12 lst *list.Element
13 data queryResult
14 expiresAt time.Time
15 cost int
16}
17
18type endpointStats struct {
19 hits int
20 misses int
21}
22
23func (e *endpointStats) hit() { e.hits++ }
24func (e *endpointStats) miss() { e.misses++ }
25
26func newQueryCache(maxSize int) *queryCache {
27 return &queryCache{
28 entries: map[uint64]*cacheEntry{},
29 stats: map[string]*endpointStats{},
30 maxCost: maxSize,
31 useList: list.New(),
32 }
33}
34
35type queryCache struct {
36 mu sync.Mutex
37 entries map[uint64]*cacheEntry
38 stats map[string]*endpointStats
39 cost int
40 maxCost int
41 evictions int
42 useList *list.List
43}
44
45func (c *queryCache) endpointStats(endpoint string) *endpointStats {
46 e, ok := c.stats[endpoint]
47 if ok {
48 return e
49 }
50
51 e = &endpointStats{}
52 c.stats[endpoint] = e
53 return e
54}
55
56func (c *queryCache) get(key uint64, endpoint string) (v queryResult, ok bool) {
57 c.mu.Lock()
58 defer c.mu.Unlock()
59
60 var ce *cacheEntry
61 ce, ok = c.entries[key]
62 if !ok {
63 c.endpointStats(endpoint).miss()
64 return v, ok
65 }
66
67 c.useList.MoveToFront(ce.lst)
68 c.endpointStats(endpoint).hit()
69
70 return ce.data, true
71}
72
73// Cache results if it was requested at least twice EVER - which means it's either
74// popular and requested multiple times within a loop OR this cache key survives between loops.
75func (c *queryCache) set(key uint64, val queryResult, ttl time.Duration, cost int, endpoint string) {
76 c.mu.Lock()
77 defer c.mu.Unlock()
78
79 var lst *list.Element
80 oe, ok := c.entries[key]
81 if ok {
82 c.cost -= oe.cost
83 lst = oe.lst
84 } else {
85 lst = c.useList.PushFront(key)
86 }
87
88 // If we're not updating in-place then we need to make room for this entry
89 if !ok && c.cost+cost > c.maxCost {
90 c.makeRoom(cost)
91 }
92
93 c.cost += cost
94 c.entries[key] = &cacheEntry{
95 data: val,
96 cost: cost,
97 lst: lst,
98 }
99 if ttl > 0 {
100 c.entries[key].expiresAt = time.Now().Add(ttl)
101 }
102}
103
104func (c *queryCache) makeRoom(needed int) {
105 for c.useList.Len() > 0 && needed > 0 {
106 if lst := c.useList.Back(); lst != nil {
107 key := lst.Value.(uint64)
108 c.cost -= c.entries[key].cost
109 needed -= c.entries[key].cost
110 delete(c.entries, key)
111 c.useList.Remove(lst)
112 c.evictions++
113 }
114 }
115}
116
117func (c *queryCache) gc() {
118 c.mu.Lock()
119 defer c.mu.Unlock()
120
121 entries := map[uint64]*cacheEntry{}
122
123 now := time.Now()
124 for key, ce := range c.entries {
125 if !ce.expiresAt.IsZero() && ce.expiresAt.Before(now) {
126 c.useList.Remove(ce.lst)
127 c.cost -= ce.cost
128 c.evictions++
129 continue
130 }
131 entries[key] = ce
132 }
133 c.entries = entries
134}
135
136type cacheCollector struct {
137 cache *queryCache
138 entries *prometheus.Desc
139 hits *prometheus.Desc
140 misses *prometheus.Desc
141 evictions *prometheus.Desc
142}
143
144func newCacheCollector(cache *queryCache, name string) *cacheCollector {
145 return &cacheCollector{
146 cache: cache,
147 entries: prometheus.NewDesc(
148 "pint_prometheus_cache_size",
149 "Total number of entries currently stored in Prometheus query cache",
150 nil,
151 prometheus.Labels{"name": name},
152 ),
153 hits: prometheus.NewDesc(
154 "pint_prometheus_cache_hits_total",
155 "Total number of query cache hits",
156 []string{"endpoint"},
157 prometheus.Labels{"name": name},
158 ),
159 misses: prometheus.NewDesc(
160 "pint_prometheus_cache_miss_total",
161 "Total number of query cache misses",
162 []string{"endpoint"},
163 prometheus.Labels{"name": name},
164 ),
165 evictions: prometheus.NewDesc(
166 "pint_prometheus_cache_evictions_total",
167 "Total number of times an entry was evicted from query cache due to size limit or TTL",
168 nil,
169 prometheus.Labels{"name": name},
170 ),
171 }
172}
173
174func (c *cacheCollector) Describe(ch chan<- *prometheus.Desc) {
175 ch <- c.entries
176 ch <- c.hits
177 ch <- c.misses
178 ch <- c.evictions
179}
180
181func (c *cacheCollector) Collect(ch chan<- prometheus.Metric) {
182 c.cache.mu.Lock()
183 defer c.cache.mu.Unlock()
184 ch <- prometheus.MustNewConstMetric(c.entries, prometheus.GaugeValue, float64(c.cache.cost))
185
186 for endpoint, stats := range c.cache.stats {
187 ch <- prometheus.MustNewConstMetric(c.hits, prometheus.CounterValue, float64(stats.hits), endpoint)
188 ch <- prometheus.MustNewConstMetric(c.misses, prometheus.CounterValue, float64(stats.misses), endpoint)
189 }
190 ch <- prometheus.MustNewConstMetric(c.evictions, prometheus.CounterValue, float64(c.cache.evictions))
191}
192