cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/cache.go

186lines · modecode

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