Skip to content

Commit 0326c96

Browse files
committed
Adds benchmarking
1 parent 0c83267 commit 0326c96

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

pkg/beholder/auth_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,151 @@ func TestRotatingAuth(t *testing.T) {
194194
mockSigner.AssertExpectations(t)
195195
})
196196
}
197+
198+
// BenchmarkRotatingAuth_Headers_CachedPath benchmarks the fast path where headers are cached and within TTL.
199+
// This is the most common case in production.
200+
func BenchmarkRotatingAuth_Headers_CachedPath(b *testing.B) {
201+
202+
pubKey, privKey, err := ed25519.GenerateKey(nil)
203+
require.NoError(b, err)
204+
205+
mockSigner := &MockSigner{}
206+
dummySignature := ed25519.Sign(privKey, []byte("test data"))
207+
208+
mockSigner.
209+
On("Sign", mock.Anything, mock.Anything, mock.Anything).
210+
Return(dummySignature, nil).
211+
Maybe()
212+
213+
// Use a long TTL so headers don't expire during the benchmark
214+
ttl := 1 * time.Hour
215+
auth := beholder.NewRotatingAuth(pubKey, mockSigner, ttl, false)
216+
217+
// Prime the cache by calling Headers once
218+
ctx := b.Context()
219+
_, err = auth.Headers(ctx)
220+
require.NoError(b, err)
221+
222+
b.ReportAllocs()
223+
224+
for b.Loop() {
225+
headers, err := auth.Headers(ctx)
226+
if err != nil {
227+
b.Fatal(err)
228+
}
229+
if len(headers) == 0 {
230+
b.Fatal("expected non-empty headers")
231+
}
232+
}
233+
}
234+
235+
// BenchmarkRotatingAuth_Headers_ExpiredPath benchmarks the slow path where headers need to be regenerated.
236+
// This happens when TTL expires.
237+
func BenchmarkRotatingAuth_Headers_ExpiredPath(b *testing.B) {
238+
239+
pubKey, privKey, err := ed25519.GenerateKey(nil)
240+
require.NoError(b, err)
241+
242+
mockSigner := &MockSigner{}
243+
dummySignature := ed25519.Sign(privKey, []byte("test data"))
244+
245+
mockSigner.
246+
On("Sign", mock.Anything, mock.Anything, mock.Anything).
247+
Return(dummySignature, nil).
248+
Maybe()
249+
250+
// Use a TTL of 0 to force regeneration on every call
251+
ttl := 0 * time.Second
252+
auth := beholder.NewRotatingAuth(pubKey, mockSigner, ttl, false)
253+
254+
ctx := b.Context()
255+
256+
b.ReportAllocs()
257+
258+
for b.Loop() {
259+
headers, err := auth.Headers(ctx)
260+
if err != nil {
261+
b.Fatal(err)
262+
}
263+
if len(headers) == 0 {
264+
b.Fatal("expected non-empty headers")
265+
}
266+
}
267+
}
268+
269+
// BenchmarkRotatingAuth_Headers_ParallelCached benchmarks concurrent access when headers are cached.
270+
// This simulates multiple goroutines making concurrent requests with valid cached headers.
271+
func BenchmarkRotatingAuth_Headers_ParallelCached(b *testing.B) {
272+
273+
pubKey, privKey, err := ed25519.GenerateKey(nil)
274+
require.NoError(b, err)
275+
276+
mockSigner := &MockSigner{}
277+
dummySignature := ed25519.Sign(privKey, []byte("test data"))
278+
279+
mockSigner.
280+
On("Sign", mock.Anything, mock.Anything, mock.Anything).
281+
Return(dummySignature, nil).
282+
Maybe()
283+
284+
// Use a long TTL so headers don't expire during the benchmark
285+
ttl := 1 * time.Hour
286+
auth := beholder.NewRotatingAuth(pubKey, mockSigner, ttl, false)
287+
288+
// Prime the cache
289+
ctx := b.Context()
290+
_, err = auth.Headers(ctx)
291+
require.NoError(b, err)
292+
293+
b.ResetTimer()
294+
b.ReportAllocs()
295+
296+
b.RunParallel(func(pb *testing.PB) {
297+
for pb.Next() {
298+
headers, err := auth.Headers(ctx)
299+
if err != nil {
300+
b.Fatal(err)
301+
}
302+
if len(headers) == 0 {
303+
b.Fatal("expected non-empty headers")
304+
}
305+
}
306+
})
307+
}
308+
309+
// BenchmarkRotatingAuth_Headers_ParallelExpired benchmarks concurrent access when headers expire.
310+
// This tests contention on the mutex when multiple goroutines race to regenerate headers.
311+
func BenchmarkRotatingAuth_Headers_ParallelExpired(b *testing.B) {
312+
313+
pubKey, privKey, err := ed25519.GenerateKey(nil)
314+
require.NoError(b, err)
315+
316+
mockSigner := &MockSigner{}
317+
dummySignature := ed25519.Sign(privKey, []byte("test data"))
318+
319+
mockSigner.
320+
On("Sign", mock.Anything, mock.Anything, mock.Anything).
321+
Return(dummySignature, nil).
322+
Maybe()
323+
324+
// Use a short TTL to cause periodic regeneration
325+
ttl := 10 * time.Millisecond
326+
auth := beholder.NewRotatingAuth(pubKey, mockSigner, ttl, false)
327+
328+
ctx := b.Context()
329+
330+
b.ResetTimer()
331+
b.ReportAllocs()
332+
333+
b.RunParallel(func(pb *testing.PB) {
334+
for pb.Next() {
335+
headers, err := auth.Headers(ctx)
336+
if err != nil {
337+
b.Fatal(err)
338+
}
339+
if len(headers) == 0 {
340+
b.Fatal("expected non-empty headers")
341+
}
342+
}
343+
})
344+
}

0 commit comments

Comments
 (0)