diff --git a/tsdb/chunkenc/benchmark_test.go b/tsdb/chunkenc/benchmark_test.go new file mode 100644 index 0000000000..0bdcd8f2db --- /dev/null +++ b/tsdb/chunkenc/benchmark_test.go @@ -0,0 +1,228 @@ +package chunkenc + +import ( + "errors" + "io" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/prometheus/prometheus/model/histogram" +) + +/* + export bench=chunkenc && go test \ + -run '^$' -bench '^BenchmarkChunkEnc' \ + -benchtime 5s -count 6 -cpu 2 -timeout 999m \ + | tee ${bench}.txt +*/ +func BenchmarkChunkEnc(b *testing.B) { + r := rand.New(rand.NewSource(1)) + + for _, data := range []struct { + name string + deltas func() (int64, float64) + }{ + { + name: "constant", + deltas: func() (int64, float64) { + return 1000, 0 + }, + }, + { + name: "random steps", + deltas: func() (int64, float64) { + return int64(r.Intn(100) - 50 + 15000), // 15 seconds +- up to 100ms of jitter. + float64(r.Intn(100) - 50) // Varying from -50 to +50 in 100 discrete steps. + }, + }, + { + name: "random 0-1", + deltas: func() (int64, float64) { + return int64(r.Intn(100) - 50 + 15000), // 15 seconds +- up to 100ms of jitter. + r.Float64() // Random between 0 and 1.0. + }, + }, + } { + b.Run("data="+data.name, func(b *testing.B) { + b.Run("format=XOR", func(b *testing.B) { + benchmarkChunkEnc(b, data.deltas, func() Chunk { return NewXORChunk() }) + }) + }) + } +} + +func benchmarkChunkEnc(b *testing.B, deltas func() (int64, float64), newChunk func() Chunk) { + var ( + t = int64(1234123324) + v = 1243535.123 + ) + const nSamples = 120 // Same as tsdb.DefaultSamplesPerChunk. + var samples []pair + for range nSamples { + dt, dv := deltas() + t += dt + v += dv + samples = append(samples, pair{t: t, v: v}) + } + + // Measure encoding efficiency. + b.Run("op=Append", func(b *testing.B) { + b.ReportAllocs() + + for b.Loop() { + c := newChunk() + + a, err := c.Appender() + if err != nil { + b.Fatalf("get appender: %s", err) + } + for _, p := range samples { + a.Append(p.t, p.v) + } + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + } + }) + + // Create a chunk for decoding and compact benchmarks. + c := newChunk() + a, err := c.Appender() + if err != nil { + b.Fatalf("get appender: %s", err) + } + for _, p := range samples { + a.Append(p.t, p.v) + } + + // Measure decoding efficiency. + b.Run("op=Iterate", func(b *testing.B) { + b.ReportAllocs() + + var ( + sink float64 + it Iterator + ) + for i := 0; b.Loop(); { + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + + it := c.Iterator(it) + for it.Next() == ValFloat { + _, v := it.At() + sink = v + i++ + } + if err := it.Err(); err != nil && !errors.Is(err, io.EOF) { + require.NoError(b, err) + } + _ = sink + } + }) + + // Measure size before and after compaction and it's efficiency. + b.Run("op=Compact", func(b *testing.B) { + for b.Loop() { + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + c.Compact() + b.ReportMetric(float64(len(c.Bytes())), "B/compacted_chunk") + } + }) +} + +/* + export bench=histchunkenc && go test \ + -run '^$' -bench '^BenchmarkHistogramChunkEnc' \ + -benchtime 5s -count 6 -cpu 2 -timeout 999m \ + | tee ${bench}.txt +*/ +func BenchmarkHistogramChunkEnc(b *testing.B) { + // Create a histogram with a bunch of spans and buckets. + const ( + numSpans = 1000 + spanLength = 10 + ) + h := &histogram.Histogram{ + Schema: 0, + Count: 100, + Sum: 1000, + ZeroThreshold: 0.001, + ZeroCount: 5, + } + for range numSpans { + h.PositiveSpans = append(h.PositiveSpans, histogram.Span{Offset: 5, Length: spanLength}) + h.NegativeSpans = append(h.NegativeSpans, histogram.Span{Offset: 5, Length: spanLength}) + for j := range spanLength { + h.PositiveBuckets = append(h.PositiveBuckets, int64(j)) + h.NegativeBuckets = append(h.NegativeBuckets, int64(j)) + } + } + + // Measure encoding efficiency. + b.Run("op=Append", func(b *testing.B) { + b.ReportAllocs() + + for b.Loop() { + c := Chunk(NewHistogramChunk()) + + a, err := c.Appender() + if err != nil { + b.Fatalf("get appender: %s", err) + } + var stop bool + for i := int64(0); !stop; i++ { + _, stop, _, err = a.AppendHistogram(nil, i, h, true) + if err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + } + }) + + // Create a chunk for decoding and compact benchmarks. + c := Chunk(NewHistogramChunk()) + a, err := c.Appender() + if err != nil { + b.Fatalf("get appender: %s", err) + } + var stop bool + for i := int64(0); !stop; i++ { + _, stop, _, err = a.AppendHistogram(nil, i, h, true) + if err != nil { + b.Fatal(err) + } + } + + // Measure decoding efficiency. + b.Run("op=Iterate", func(b *testing.B) { + b.ReportAllocs() + + var ( + sink *histogram.Histogram + it Iterator + ) + for i := 0; b.Loop(); { + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + + it := c.Iterator(it) + for it.Next() == ValFloat { + _, got := it.AtHistogram(h) + sink = got + i++ + } + if err := it.Err(); err != nil && !errors.Is(err, io.EOF) { + require.NoError(b, err) + } + _ = sink + } + }) + + // Measure size before and after compaction and it's efficiency. + b.Run("op=Compact", func(b *testing.B) { + for b.Loop() { + b.ReportMetric(float64(len(c.Bytes())), "B/chunk") + c.Compact() + b.ReportMetric(float64(len(c.Bytes())), "B/compacted_chunk") + } + }) +} diff --git a/tsdb/chunkenc/chunk_test.go b/tsdb/chunkenc/chunk_test.go index eac9e12b29..cfd304a482 100644 --- a/tsdb/chunkenc/chunk_test.go +++ b/tsdb/chunkenc/chunk_test.go @@ -16,7 +16,6 @@ package chunkenc import ( "errors" "fmt" - "io" "math/rand" "testing" @@ -199,111 +198,3 @@ func (c fakeChunk) Encoding() Encoding { func (c fakeChunk) Reset([]byte) { c.t.Fatal("Reset should not be called") } - -func benchmarkIterator(b *testing.B, newChunk func() Chunk) { - const samplesPerChunk = 250 - var ( - t = int64(1234123324) - v = 1243535.123 - exp []pair - ) - for range samplesPerChunk { - // t += int64(rand.Intn(10000) + 1) - t += int64(1000) - // v = rand.Float64() - v += float64(100) - exp = append(exp, pair{t: t, v: v}) - } - - chunk := newChunk() - { - a, err := chunk.Appender() - if err != nil { - b.Fatalf("get appender: %s", err) - } - j := 0 - for _, p := range exp { - if j > 250 { - break - } - a.Append(p.t, p.v) - j++ - } - } - - b.ReportAllocs() - - var res float64 - var it Iterator - for i := 0; b.Loop(); { - it := chunk.Iterator(it) - - for it.Next() == ValFloat { - _, v := it.At() - res = v - i++ - } - if err := it.Err(); err != nil && !errors.Is(err, io.EOF) { - require.NoError(b, err) - } - _ = res - } -} - -func newXORChunk() Chunk { - return NewXORChunk() -} - -func BenchmarkXORIterator(b *testing.B) { - benchmarkIterator(b, newXORChunk) -} - -func BenchmarkXORAppender(b *testing.B) { - r := rand.New(rand.NewSource(1)) - b.Run("constant", func(b *testing.B) { - benchmarkAppender(b, func() (int64, float64) { - return 1000, 0 - }, newXORChunk) - }) - b.Run("random steps", func(b *testing.B) { - benchmarkAppender(b, func() (int64, float64) { - return int64(r.Intn(100) - 50 + 15000), // 15 seconds +- up to 100ms of jitter. - float64(r.Intn(100) - 50) // Varying from -50 to +50 in 100 discrete steps. - }, newXORChunk) - }) - b.Run("random 0-1", func(b *testing.B) { - benchmarkAppender(b, func() (int64, float64) { - return int64(r.Intn(100) - 50 + 15000), // 15 seconds +- up to 100ms of jitter. - r.Float64() // Random between 0 and 1.0. - }, newXORChunk) - }) -} - -func benchmarkAppender(b *testing.B, deltas func() (int64, float64), newChunk func() Chunk) { - var ( - t = int64(1234123324) - v = 1243535.123 - ) - const nSamples = 120 // Same as tsdb.DefaultSamplesPerChunk. - var exp []pair - for range nSamples { - dt, dv := deltas() - t += dt - v += dv - exp = append(exp, pair{t: t, v: v}) - } - - b.ReportAllocs() - - for b.Loop() { - c := newChunk() - - a, err := c.Appender() - if err != nil { - b.Fatalf("get appender: %s", err) - } - for _, p := range exp { - a.Append(p.t, p.v) - } - } -}