prometheus/tsdb/chunkenc/st_helper_test.go
György Krajcsovits 07c543f02d
test(chunkenc): test that appender can contonue chunks
Test that initializing a chunk appender from an existing chunk
works correctly.

Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
2026-02-06 15:35:20 +01:00

156 lines
4.6 KiB
Go

// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package chunkenc
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
)
// testChunkSTHandling tests handling of start times in chunks.
// It uses 0-4 samples with timestamp 1000,2000,3000,4000 and monotonically
// increasing start times that are chosen from 0-(ts-500) for each sample.
// All combinations of start times are tested for each number of samples.
func testChunkSTHandling(t *testing.T, vt ValueType, chunkFactory func() Chunk) {
sampleAppend := func(app Appender, vt ValueType, st, ts int64, v float64) {
switch vt {
case ValFloat:
app.Append(st, ts, v)
case ValHistogram:
_, recoded, _, err := app.AppendHistogram(nil, st, ts, &histogram.Histogram{Sum: v, Count: uint64(v * 10)}, false)
require.NoError(t, err)
require.False(t, recoded)
case ValFloatHistogram:
_, recoded, _, err := app.AppendFloatHistogram(nil, st, ts, &histogram.FloatHistogram{Sum: v, Count: v * 10}, false)
require.NoError(t, err)
require.False(t, recoded)
default:
t.Fatalf("unsupported value type %v", vt)
}
}
get := func(it Iterator, vt ValueType) (int64, int64, float64) {
switch vt {
case ValFloat:
ts, v := it.At()
return it.AtST(), ts, v
case ValHistogram:
ts, h := it.AtHistogram(nil)
return it.AtST(), ts, float64(h.Sum)
case ValFloatHistogram:
ts, fh := it.AtFloatHistogram(nil)
return it.AtST(), ts, fh.Sum
default:
t.Fatalf("unsupported value type %v", vt)
return 0, 0, 0
}
}
runCase := func(t *testing.T, samples []triple) {
chunk := chunkFactory()
app, err := chunk.Appender()
require.NoError(t, err)
var clone []byte
for i, s := range samples {
if i == len(samples)-1 {
clone = append(clone, chunk.Bytes()...)
}
sampleAppend(app, vt, s.st, s.t, s.v)
}
chunksToTest := []Chunk{chunk}
if len(samples) > 0 {
// If there are samples, also test that appending to a chunk cloned from the original chunk works correctly.
// This tests resuming the appender from a previous chunk.
cloneChunk := chunkFactory()
cloneChunk.Reset(clone)
cloneApp, err := cloneChunk.Appender()
require.NoError(t, err)
sampleAppend(cloneApp, vt, samples[len(samples)-1].st, samples[len(samples)-1].t, samples[len(samples)-1].v)
chunksToTest = append(chunksToTest, cloneChunk)
}
printChunkName := func(i int) string {
if i == 0 {
return "original"
}
return "cloned"
}
for ci, chk := range chunksToTest {
require.Equal(t, len(samples), chk.NumSamples(), "%s chunk: number of samples mismatch", printChunkName(ci))
it := chk.Iterator(nil)
for i, s := range samples {
require.Equal(t, vt, it.Next(), "%s[%d]: value type mismatch", printChunkName(ci), i)
st, ts, f := get(it, vt)
require.Equal(t, s.t, ts, "%s[%d]: timestamp mismatch", printChunkName(ci), i)
require.Equal(t, s.st, st, "%s[%d]: start time mismatch", printChunkName(ci), i)
require.InDelta(t, s.v, f, 1e-9, "%s[%d]: value mismatch", printChunkName(ci), i)
}
require.Equal(t, ValNone, it.Next())
require.NoError(t, it.Err())
}
}
t.Run("manual for debugging", func(t *testing.T) {
samples := []triple{
{st: 0, t: 1000, v: 1.5},
{st: 0, t: 2000, v: 2.5},
{st: 0, t: 3000, v: 3.5},
{st: 0, t: 4000, v: 4.5},
}
runCase(t, samples)
})
stTimes := []int64{0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000}
ts := func(j int) int64 {
return int64(1000 * (j + 1))
}
for numberOfSamples := range 5 {
samples := make([]triple, numberOfSamples)
sampleSTidx := make([]int, numberOfSamples)
for {
for j := range numberOfSamples {
samples[j] = triple{
st: stTimes[sampleSTidx[j]],
t: ts(j),
v: float64(j) + 0.5,
}
}
t.Run(fmt.Sprintf("%v", samples), func(t *testing.T) {
runCase(t, samples)
})
exhausted := true
for j := numberOfSamples - 1; j >= 0; j-- {
if stTimes[sampleSTidx[j]] < ts(j) {
sampleSTidx[j]++
exhausted = false
break
}
sampleSTidx[j] = 0
}
if exhausted {
break
}
}
}
}