From 0a78d1ce907e0678b7747c8de51f503d19cf5afb Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Tue, 17 Feb 2026 03:25:12 -0500 Subject: [PATCH] test: add comprehensive benchmark suite (#18) Adds benchmarks for all log levels (Trace, Debug, Info, Warn, Error), formatted logging, multiple client fan-out, parallel writes, namespace filtering, and component-level benchmarks (fileInfo, entry creation). Uses isolated namespaces to avoid interference with the stderr client. Fixes #6 --- log/bench_test.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 log/bench_test.go diff --git a/log/bench_test.go b/log/bench_test.go new file mode 100644 index 0000000..49e3187 --- /dev/null +++ b/log/bench_test.go @@ -0,0 +1,171 @@ +package log + +import ( + "fmt" + "testing" +) + +const benchNS = "bench-isolated-ns" + +// benchClient creates a client with a continuous drain goroutine. +// Returns the client and a stop function to call after b.StopTimer(). +func benchClient(ns string) (*Client, func()) { + c := CreateClient(ns) + c.SetLogLevel(LTrace) + done := make(chan struct{}) + go func() { + for { + select { + case <-done: + return + case <-c.writer: + } + } + }() + return c, func() { + close(done) + c.Destroy() + } +} + +// BenchmarkCreateClient measures client creation overhead. +func BenchmarkCreateClient(b *testing.B) { + for i := 0; i < b.N; i++ { + c := CreateClient("bench") + c.Destroy() + } +} + +// BenchmarkTrace benchmarks Logger.Trace. +func BenchmarkTrace(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Trace("benchmark trace message") + } +} + +// BenchmarkTracef benchmarks formatted Logger.Tracef. +func BenchmarkTracef(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Tracef("benchmark trace message %d", i) + } +} + +// BenchmarkDebug benchmarks Logger.Debug. +func BenchmarkDebug(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Debug("benchmark debug message") + } +} + +// BenchmarkInfo benchmarks Logger.Info. +func BenchmarkInfo(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Info("benchmark info message") + } +} + +// BenchmarkInfof benchmarks Logger.Infof with formatting. +func BenchmarkInfof(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Infof("user %s performed action %d", "testuser", i) + } +} + +// BenchmarkWarn benchmarks Logger.Warn. +func BenchmarkWarn(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Warn("benchmark warn message") + } +} + +// BenchmarkError benchmarks Logger.Error. +func BenchmarkError(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Error("benchmark error message") + } +} + +// BenchmarkMultipleClients measures fan-out to multiple consumers. +func BenchmarkMultipleClients(b *testing.B) { + const numClients = 5 + l := NewLogger(benchNS) + stops := make([]func(), numClients) + for i := range stops { + _, stops[i] = benchClient(benchNS) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Info("benchmark multi-client") + } + b.StopTimer() + for _, stop := range stops { + stop() + } +} + +// BenchmarkParallelInfo benchmarks concurrent Info calls from multiple goroutines. +func BenchmarkParallelInfo(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient(benchNS) + defer stop() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + l.Info("parallel benchmark info") + } + }) +} + +// BenchmarkNamespaceFiltering benchmarks logging when the consumer +// filters by a different namespace (messages are not delivered). +func BenchmarkNamespaceFiltering(b *testing.B) { + l := NewLogger(benchNS) + _, stop := benchClient("completely-different-ns") + defer stop() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Info("filtered out message") + } +} + +// BenchmarkFileInfo measures the cost of runtime.Caller for file info. +func BenchmarkFileInfo(b *testing.B) { + for i := 0; i < b.N; i++ { + fileInfo(1) + } +} + +// BenchmarkEntryCreation measures raw fmt.Sprint overhead (baseline). +func BenchmarkEntryCreation(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = fmt.Sprint("benchmark message ", i) + } +}