cloudflare/pint

Public

mirrored fromhttps://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.75.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/cache_test.go

344lines · modecode

1package promapi
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/prometheus/client_golang/prometheus/testutil"
11 "github.com/stretchr/testify/require"
12)
13
14func TestQueryCacheOnlySet(t *testing.T) {
15 mockErr := errors.New("Fake Error")
16 cache := newQueryCache(time.Minute, time.Now)
17
18 var i uint64
19 for i = 1; i <= 100; i++ {
20 cache.set(i, mockErr, 0)
21 }
22
23 require.Len(t, cache.entries, 100)
24 require.Equal(t, 0, cache.evictions)
25}
26
27func TestQueryCacheReplace(t *testing.T) {
28 mockErr := errors.New("Fake Error")
29 cache := newQueryCache(time.Minute, time.Now)
30
31 cache.set(6, mockErr, 0)
32 cache.set(6, mockErr, 0)
33 cache.set(6, mockErr, 0)
34
35 require.Len(t, cache.entries, 1)
36 require.Equal(t, 0, cache.evictions)
37}
38
39func TestQueryCacheGetAndSet(t *testing.T) {
40 mockErr := errors.New("Fake Error")
41 cache := newQueryCache(time.Minute, time.Now)
42
43 var i uint64
44 for i = 1; i <= 100; i++ {
45 // first get
46 v, ok := cache.get(i, "/foo")
47 require.False(t, ok, "should be missing from cache on first get")
48 require.Zero(t, v)
49
50 // first set
51 cache.set(i, mockErr, time.Minute)
52
53 // second get, should be in cache now
54 v, ok = cache.get(i, "/foo")
55 require.True(t, ok, "should be present in cache on third get")
56 require.NotZero(t, v)
57 require.Equal(t, mockErr, v)
58 }
59
60 require.Len(t, cache.entries, 100)
61 require.Equal(t, 100, cache.stats["/foo"].hits)
62 require.Equal(t, 100, cache.stats["/foo"].misses)
63 require.Equal(t, 0, cache.evictions)
64
65 cache.gc()
66 require.Len(t, cache.entries, 100)
67 require.Equal(t, 100, cache.stats["/foo"].hits)
68 require.Equal(t, 100, cache.stats["/foo"].misses)
69 require.Equal(t, 0, cache.evictions)
70}
71
72func TestQueryCachePurgeZeroTTL(t *testing.T) {
73 const maxSize = 100
74 mockErr := errors.New("Fake Error")
75 cache := newQueryCache(time.Minute, time.Now)
76
77 var i uint64
78 for i = 1; i <= maxSize; i++ {
79 cache.set(i, mockErr, 0)
80 _, _ = cache.get(i, "/foo")
81 }
82 require.Len(t, cache.entries, 100)
83 require.Equal(t, 0, cache.evictions)
84
85 time.Sleep(time.Second)
86
87 cache.gc()
88 require.Len(t, cache.entries, 100)
89 require.Equal(t, 0, cache.evictions)
90}
91
92func TestQueryCachePurgeExpired(t *testing.T) {
93 const maxSize = 100
94 mockErr := errors.New("Fake Error")
95 cache := newQueryCache(time.Minute, time.Now)
96
97 var i uint64
98 for i = 1; i <= maxSize; i++ {
99 _, _ = cache.get(i, "/foo")
100 _, _ = cache.get(i, "/foo")
101 cache.set(i, mockErr, time.Second)
102 _, _ = cache.get(i, "/foo")
103 }
104 require.Len(t, cache.entries, 100)
105 require.Equal(t, 0, cache.evictions)
106
107 for i = 1; i <= maxSize/2; i++ {
108 cache.entries[i].expiresAt = time.Now().Add(time.Second * -1)
109 }
110
111 cache.gc()
112 require.Len(t, cache.entries, 50)
113 require.Equal(t, 50, cache.evictions)
114}
115
116func TestQueryCacheEvictMaxStale(t *testing.T) {
117 mockErr := errors.New("Fake Error")
118 cache := newQueryCache(time.Second, time.Now)
119
120 var i, j uint64
121 for i = 1; i <= 100; i++ {
122 cache.set(i, mockErr, time.Minute)
123 for j = 1; j <= i; j++ {
124 _, _ = cache.get(i, "/foo")
125 }
126 }
127 require.Len(t, cache.entries, 100)
128 require.Equal(t, 0, cache.evictions)
129
130 cache.gc()
131 require.Len(t, cache.entries, 100)
132 require.Equal(t, 0, cache.evictions)
133
134 time.Sleep(time.Second + time.Millisecond*100)
135 for i = 1; i <= 50; i++ {
136 _, _ = cache.get(i, "/foo")
137 }
138 cache.gc()
139 require.Len(t, cache.entries, 50)
140 require.Equal(t, 50, cache.evictions)
141
142 var ok bool
143 for i = 1; i <= 50; i++ {
144 _, ok = cache.get(i, "/foo")
145 require.True(t, ok)
146 }
147 for i = 51; i <= 100; i++ {
148 _, ok = cache.get(i, "/foo")
149 require.False(t, ok)
150 }
151}
152
153func TestCacheCollector(t *testing.T) {
154 cache := newQueryCache(time.Minute, time.Now)
155
156 names := []string{
157 "pint_prometheus_cache_size",
158 "pint_prometheus_cache_hits_total",
159 "pint_prometheus_cache_miss_total",
160 "pint_prometheus_cache_evictions_total",
161 }
162
163 collector := newCacheCollector(cache, "prom")
164 require.NoError(t, testutil.CollectAndCompare(
165 collector, strings.NewReader(`
166# HELP pint_prometheus_cache_evictions_total Total number of times an entry was evicted from query cache due to size limit or TTL
167# TYPE pint_prometheus_cache_evictions_total counter
168pint_prometheus_cache_evictions_total{name="prom"} 0
169# HELP pint_prometheus_cache_size Total number of entries currently stored in Prometheus query cache
170# TYPE pint_prometheus_cache_size gauge
171pint_prometheus_cache_size{name="prom"} 0
172`),
173 "pint_prometheus_cache_size", "pint_prometheus_cache_evictions_total",
174 ))
175
176 var i uint64
177 for i = 1; i <= 100; i++ {
178 endpoint := fmt.Sprintf("/foo/%d", i%10)
179 _, _ = cache.get(i, endpoint)
180 _, _ = cache.get(i, endpoint)
181 cache.set(i, queryResult{}, time.Minute)
182 _, _ = cache.get(i, endpoint)
183 cache.set(i, queryResult{}, time.Minute)
184 _, _ = cache.get(i, endpoint)
185 }
186
187 require.NoError(t, testutil.CollectAndCompare(
188 collector, strings.NewReader(`
189# HELP pint_prometheus_cache_evictions_total Total number of times an entry was evicted from query cache due to size limit or TTL
190# TYPE pint_prometheus_cache_evictions_total counter
191pint_prometheus_cache_evictions_total{name="prom"} 0
192# HELP pint_prometheus_cache_hits_total Total number of query cache hits
193# TYPE pint_prometheus_cache_hits_total counter
194pint_prometheus_cache_hits_total{endpoint="/foo/0",name="prom"} 20
195pint_prometheus_cache_hits_total{endpoint="/foo/1",name="prom"} 20
196pint_prometheus_cache_hits_total{endpoint="/foo/2",name="prom"} 20
197pint_prometheus_cache_hits_total{endpoint="/foo/3",name="prom"} 20
198pint_prometheus_cache_hits_total{endpoint="/foo/4",name="prom"} 20
199pint_prometheus_cache_hits_total{endpoint="/foo/5",name="prom"} 20
200pint_prometheus_cache_hits_total{endpoint="/foo/6",name="prom"} 20
201pint_prometheus_cache_hits_total{endpoint="/foo/7",name="prom"} 20
202pint_prometheus_cache_hits_total{endpoint="/foo/8",name="prom"} 20
203pint_prometheus_cache_hits_total{endpoint="/foo/9",name="prom"} 20
204# HELP pint_prometheus_cache_miss_total Total number of query cache misses
205# TYPE pint_prometheus_cache_miss_total counter
206pint_prometheus_cache_miss_total{endpoint="/foo/0",name="prom"} 20
207pint_prometheus_cache_miss_total{endpoint="/foo/1",name="prom"} 20
208pint_prometheus_cache_miss_total{endpoint="/foo/2",name="prom"} 20
209pint_prometheus_cache_miss_total{endpoint="/foo/3",name="prom"} 20
210pint_prometheus_cache_miss_total{endpoint="/foo/4",name="prom"} 20
211pint_prometheus_cache_miss_total{endpoint="/foo/5",name="prom"} 20
212pint_prometheus_cache_miss_total{endpoint="/foo/6",name="prom"} 20
213pint_prometheus_cache_miss_total{endpoint="/foo/7",name="prom"} 20
214pint_prometheus_cache_miss_total{endpoint="/foo/8",name="prom"} 20
215pint_prometheus_cache_miss_total{endpoint="/foo/9",name="prom"} 20
216# HELP pint_prometheus_cache_size Total number of entries currently stored in Prometheus query cache
217# TYPE pint_prometheus_cache_size gauge
218pint_prometheus_cache_size{name="prom"} 100
219`),
220 names...,
221 ))
222
223 for i = 101; i <= 110; i++ {
224 endpoint := fmt.Sprintf("/foo/%d", i%10)
225 _, _ = cache.get(i, endpoint)
226 _, _ = cache.get(i, endpoint)
227 cache.set(i, queryResult{}, time.Minute)
228 }
229
230 require.NoError(t, testutil.CollectAndCompare(
231 collector, strings.NewReader(`
232# HELP pint_prometheus_cache_evictions_total Total number of times an entry was evicted from query cache due to size limit or TTL
233# TYPE pint_prometheus_cache_evictions_total counter
234pint_prometheus_cache_evictions_total{name="prom"} 0
235# HELP pint_prometheus_cache_hits_total Total number of query cache hits
236# TYPE pint_prometheus_cache_hits_total counter
237pint_prometheus_cache_hits_total{endpoint="/foo/0",name="prom"} 20
238pint_prometheus_cache_hits_total{endpoint="/foo/1",name="prom"} 20
239pint_prometheus_cache_hits_total{endpoint="/foo/2",name="prom"} 20
240pint_prometheus_cache_hits_total{endpoint="/foo/3",name="prom"} 20
241pint_prometheus_cache_hits_total{endpoint="/foo/4",name="prom"} 20
242pint_prometheus_cache_hits_total{endpoint="/foo/5",name="prom"} 20
243pint_prometheus_cache_hits_total{endpoint="/foo/6",name="prom"} 20
244pint_prometheus_cache_hits_total{endpoint="/foo/7",name="prom"} 20
245pint_prometheus_cache_hits_total{endpoint="/foo/8",name="prom"} 20
246pint_prometheus_cache_hits_total{endpoint="/foo/9",name="prom"} 20
247# HELP pint_prometheus_cache_miss_total Total number of query cache misses
248# TYPE pint_prometheus_cache_miss_total counter
249pint_prometheus_cache_miss_total{endpoint="/foo/0",name="prom"} 22
250pint_prometheus_cache_miss_total{endpoint="/foo/1",name="prom"} 22
251pint_prometheus_cache_miss_total{endpoint="/foo/2",name="prom"} 22
252pint_prometheus_cache_miss_total{endpoint="/foo/3",name="prom"} 22
253pint_prometheus_cache_miss_total{endpoint="/foo/4",name="prom"} 22
254pint_prometheus_cache_miss_total{endpoint="/foo/5",name="prom"} 22
255pint_prometheus_cache_miss_total{endpoint="/foo/6",name="prom"} 22
256pint_prometheus_cache_miss_total{endpoint="/foo/7",name="prom"} 22
257pint_prometheus_cache_miss_total{endpoint="/foo/8",name="prom"} 22
258pint_prometheus_cache_miss_total{endpoint="/foo/9",name="prom"} 22
259# HELP pint_prometheus_cache_size Total number of entries currently stored in Prometheus query cache
260# TYPE pint_prometheus_cache_size gauge
261pint_prometheus_cache_size{name="prom"} 110
262`),
263 names...,
264 ))
265}
266
267func BenchmarkQueryCacheOnlySet(b *testing.B) {
268 mockErr := errors.New("Fake Error")
269 cache := newQueryCache(time.Minute, time.Now)
270
271 b.ResetTimer()
272 for b.Loop() {
273 cache.set(1, mockErr, 0)
274 }
275}
276
277func BenchmarkQueryCacheSetGrow(b *testing.B) {
278 const maxSize = 1000
279 mockErr := errors.New("Fake Error")
280 cache := newQueryCache(time.Minute, time.Now)
281
282 var i uint64
283 for i = 1; i <= maxSize; i++ {
284 cache.set(i, mockErr, 0)
285 }
286
287 b.ResetTimer()
288 for n := 1; n <= b.N; n++ {
289 cache.set(uint64(maxSize+n), mockErr, 0)
290 }
291}
292
293func BenchmarkQueryCacheGetMiss(b *testing.B) {
294 cache := newQueryCache(time.Minute, time.Now)
295
296 b.ResetTimer()
297 for n := 0; b.Loop(); n++ {
298 cache.get(uint64(n), "/foo")
299 }
300}
301
302func BenchmarkQueryCacheGC(b *testing.B) {
303 mockErr := errors.New("Fake Error")
304 var now time.Time
305 cache := newQueryCache(time.Minute, func() time.Time {
306 return now
307 })
308
309 var i uint64
310 var ttl time.Duration
311
312 b.ResetTimer()
313 for n := 0; b.Loop(); n++ {
314 b.StopTimer()
315 if n%2 == 0 {
316 ttl = 0
317 } else {
318 ttl = time.Millisecond
319 }
320 for i = 1; i <= 1000; i++ {
321 cache.set(i, mockErr, ttl)
322 }
323 now = now.Add(time.Millisecond * 2)
324 b.StartTimer()
325 cache.gc()
326 }
327}
328
329func BenchmarkQueryCacheGCNoop(b *testing.B) {
330 var now time.Time
331 cache := newQueryCache(time.Minute, func() time.Time {
332 return now
333 })
334 mockErr := errors.New("Fake Error")
335 var i uint64
336 for i = 1; i <= 1000; i++ {
337 cache.set(i, mockErr, time.Hour)
338 }
339
340 b.ResetTimer()
341 for b.Loop() {
342 cache.gc()
343 }
344}
345