diff --git a/tsdb/chunkenc/float_histogram.go b/tsdb/chunkenc/float_histogram.go index da2b22dc5e..ba8e2ce00f 100644 --- a/tsdb/chunkenc/float_histogram.go +++ b/tsdb/chunkenc/float_histogram.go @@ -170,6 +170,8 @@ func (c *FloatHistogramChunk) Iterator(it Iterator) Iterator { type FloatHistogramAppender struct { b *bstream + headerLayout histogramHeaderLayout + // Layout: schema int32 zThreshold float64 @@ -181,16 +183,32 @@ type FloatHistogramAppender struct { pBuckets, nBuckets []xorValue } +func (a *FloatHistogramAppender) counterResetHeaderPos() int { + if a.headerLayout == histogramHeaderST { + return 0 + } + return histogramFlagPos +} + +func (a *FloatHistogramAppender) sampleCountMask() uint16 { + if a.headerLayout == histogramHeaderST { + return 0x3FFF + } + return 0xFFFF +} + func (a *FloatHistogramAppender) GetCounterResetHeader() CounterResetHeader { - return CounterResetHeader(a.b.bytes()[histogramFlagPos] & CounterResetHeaderMask) + return CounterResetHeader(a.b.bytes()[a.counterResetHeaderPos()] & CounterResetHeaderMask) } func (a *FloatHistogramAppender) setCounterResetHeader(cr CounterResetHeader) { - a.b.bytes()[histogramFlagPos] = (a.b.bytes()[histogramFlagPos] & (^CounterResetHeaderMask)) | (byte(cr) & CounterResetHeaderMask) + b := a.b.bytes() + pos := a.counterResetHeaderPos() + b[pos] = (b[pos] &^ CounterResetHeaderMask) | (byte(cr) & CounterResetHeaderMask) } func (a *FloatHistogramAppender) NumSamples() int { - return int(binary.BigEndian.Uint16(a.b.bytes())) + return int(binary.BigEndian.Uint16(a.b.bytes()) & a.sampleCountMask()) } // Append implements Appender. This implementation panics because normal float diff --git a/tsdb/chunkenc/float_histogram_st.go b/tsdb/chunkenc/float_histogram_st.go index d30ffd98d9..90ef55290a 100644 --- a/tsdb/chunkenc/float_histogram_st.go +++ b/tsdb/chunkenc/float_histogram_st.go @@ -79,11 +79,12 @@ func (c *FloatHistogramSTChunk) Appender() (Appender, error) { if len(c.b.stream) == histogramHeaderSize { return &FloatHistogramSTAppender{ FloatHistogramAppender: FloatHistogramAppender{ - b: &c.b, - t: math.MinInt64, - sum: xorValue{leading: 0xff}, - cnt: xorValue{leading: 0xff}, - zCnt: xorValue{leading: 0xff}, + b: &c.b, + headerLayout: histogramHeaderST, + t: math.MinInt64, + sum: xorValue{leading: 0xff}, + cnt: xorValue{leading: 0xff}, + zCnt: xorValue{leading: 0xff}, }, }, nil } @@ -118,7 +119,8 @@ func (c *FloatHistogramSTChunk) Appender() (Appender, error) { a := &FloatHistogramSTAppender{ FloatHistogramAppender: FloatHistogramAppender{ - b: &c.b, + b: &c.b, + headerLayout: histogramHeaderST, schema: it.schema, zThreshold: it.zThreshold, diff --git a/tsdb/chunkenc/float_histogram_st_test.go b/tsdb/chunkenc/float_histogram_st_test.go index 6b0d82cee5..dccfea4e75 100644 --- a/tsdb/chunkenc/float_histogram_st_test.go +++ b/tsdb/chunkenc/float_histogram_st_test.go @@ -531,3 +531,49 @@ func TestFloatHistogramSTChunkGaugeRecodePreservesST(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, stChunk.NumSamples()) } + +func TestFloatHistogramSTChunk_CounterResetHeader(t *testing.T) { + c := NewFloatHistogramSTChunk() + require.Len(t, c.Bytes(), 3) + + app, err := c.Appender() + require.NoError(t, err) + happ := app.(*FloatHistogramSTAppender) + + fh := tsdbutil.GenerateTestFloatHistograms(1)[0] + _, _, _, err = happ.AppendFloatHistogram(nil, 100, 1000, fh, false) + require.NoError(t, err) + + byte2Before := c.Bytes()[2] + for _, cr := range []CounterResetHeader{UnknownCounterReset, CounterReset, NotCounterReset, GaugeType} { + happ.setCounterResetHeader(cr) + require.Equal(t, cr, c.GetCounterResetHeader()) + require.Equal(t, cr, happ.FloatHistogramAppender.GetCounterResetHeader()) + require.Equal(t, 1, c.NumSamples(), "NumSamples must not include CR bits") + require.Equal(t, 1, happ.FloatHistogramAppender.NumSamples(), "NumSamples must not include CR bits") + require.Equal(t, byte2Before, c.Bytes()[2], "setting CR must not disturb byte 2 (ST header)") + } +} + +func TestFloatHistogramSTAppenderPreviousEmbeddedAppenderUsesSTHeader(t *testing.T) { + prevChunk := NewFloatHistogramSTChunk() + prevApp, err := prevChunk.Appender() + require.NoError(t, err) + + fh1 := tsdbutil.GenerateTestFloatHistogram(10) + _, _, prevApp, err = prevApp.AppendFloatHistogram(nil, 100, 1000, fh1, false) + require.NoError(t, err) + + prevSTApp := prevApp.(*FloatHistogramSTAppender) + prevSTApp.setCounterResetHeader(NotCounterReset) + prevChunk.Bytes()[2] = byte(GaugeType) + + nextChunk := NewFloatHistogramSTChunk() + nextApp, err := nextChunk.Appender() + require.NoError(t, err) + + fh2 := tsdbutil.GenerateTestFloatHistogram(0) + _, _, _, err = nextApp.AppendFloatHistogram(&prevSTApp.FloatHistogramAppender, 200, 2000, fh2, false) + require.NoError(t, err) + require.Equal(t, CounterReset, nextChunk.GetCounterResetHeader()) +} diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go index 9b6f28e9f3..a2a9d00360 100644 --- a/tsdb/chunkenc/histogram.go +++ b/tsdb/chunkenc/histogram.go @@ -178,10 +178,18 @@ func (c *HistogramChunk) Iterator(it Iterator) Iterator { return c.iterator(it) } +type histogramHeaderLayout uint8 + +const ( + histogramHeaderST histogramHeaderLayout = 1 +) + // HistogramAppender is an Appender implementation for sparse histograms. type HistogramAppender struct { b *bstream + headerLayout histogramHeaderLayout + // Layout: schema int32 zThreshold float64 @@ -205,16 +213,32 @@ type HistogramAppender struct { trailing uint8 } +func (a *HistogramAppender) counterResetHeaderPos() int { + if a.headerLayout == histogramHeaderST { + return 0 + } + return histogramFlagPos +} + +func (a *HistogramAppender) sampleCountMask() uint16 { + if a.headerLayout == histogramHeaderST { + return 0x3FFF + } + return 0xFFFF +} + func (a *HistogramAppender) GetCounterResetHeader() CounterResetHeader { - return CounterResetHeader(a.b.bytes()[histogramFlagPos] & CounterResetHeaderMask) + return CounterResetHeader(a.b.bytes()[a.counterResetHeaderPos()] & CounterResetHeaderMask) } func (a *HistogramAppender) setCounterResetHeader(cr CounterResetHeader) { - a.b.bytes()[histogramFlagPos] = (a.b.bytes()[histogramFlagPos] & (^CounterResetHeaderMask)) | (byte(cr) & CounterResetHeaderMask) + b := a.b.bytes() + pos := a.counterResetHeaderPos() + b[pos] = (b[pos] &^ CounterResetHeaderMask) | (byte(cr) & CounterResetHeaderMask) } func (a *HistogramAppender) NumSamples() int { - return int(binary.BigEndian.Uint16(a.b.bytes())) + return int(binary.BigEndian.Uint16(a.b.bytes()) & a.sampleCountMask()) } // Append implements Appender. This implementation panics because normal float diff --git a/tsdb/chunkenc/histogram_st.go b/tsdb/chunkenc/histogram_st.go index c5c0988794..fec4463340 100644 --- a/tsdb/chunkenc/histogram_st.go +++ b/tsdb/chunkenc/histogram_st.go @@ -78,9 +78,10 @@ func (c *HistogramSTChunk) Appender() (Appender, error) { if len(c.b.stream) == histogramHeaderSize { return &HistogramSTAppender{ HistogramAppender: HistogramAppender{ - b: &c.b, - t: math.MinInt64, - leading: 0xff, + b: &c.b, + headerLayout: histogramHeaderST, + t: math.MinInt64, + leading: 0xff, }, }, nil } @@ -99,7 +100,8 @@ func (c *HistogramSTChunk) Appender() (Appender, error) { a := &HistogramSTAppender{ HistogramAppender: HistogramAppender{ - b: &c.b, + b: &c.b, + headerLayout: histogramHeaderST, schema: it.schema, zThreshold: it.zThreshold, diff --git a/tsdb/chunkenc/histogram_st_test.go b/tsdb/chunkenc/histogram_st_test.go index 4853fcb647..f5b3d3eeb6 100644 --- a/tsdb/chunkenc/histogram_st_test.go +++ b/tsdb/chunkenc/histogram_st_test.go @@ -528,3 +528,49 @@ func TestHistogramSTChunkGauge(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, stChunk.NumSamples()) } + +func TestHistogramSTChunk_CounterResetHeader(t *testing.T) { + c := NewHistogramSTChunk() + require.Len(t, c.Bytes(), 3) + + app, err := c.Appender() + require.NoError(t, err) + happ := app.(*HistogramSTAppender) + + h := tsdbutil.GenerateTestHistograms(1)[0] + _, _, _, err = happ.AppendHistogram(nil, 100, 1000, h, false) + require.NoError(t, err) + + byte2Before := c.Bytes()[2] + for _, cr := range []CounterResetHeader{UnknownCounterReset, CounterReset, NotCounterReset, GaugeType} { + happ.setCounterResetHeader(cr) + require.Equal(t, cr, c.GetCounterResetHeader()) + require.Equal(t, cr, happ.HistogramAppender.GetCounterResetHeader()) + require.Equal(t, 1, c.NumSamples()) + require.Equal(t, 1, happ.HistogramAppender.NumSamples()) + require.Equal(t, byte2Before, c.Bytes()[2]) + } +} + +func TestHistogramSTAppenderPreviousEmbeddedAppenderUsesSTHeader(t *testing.T) { + prevChunk := NewHistogramSTChunk() + prevApp, err := prevChunk.Appender() + require.NoError(t, err) + + h1 := tsdbutil.GenerateTestHistogram(10) + _, _, prevApp, err = prevApp.AppendHistogram(nil, 100, 1000, h1, false) + require.NoError(t, err) + + prevSTApp := prevApp.(*HistogramSTAppender) + prevSTApp.setCounterResetHeader(NotCounterReset) + prevChunk.Bytes()[2] = byte(GaugeType) + + nextChunk := NewHistogramSTChunk() + nextApp, err := nextChunk.Appender() + require.NoError(t, err) + + h2 := tsdbutil.GenerateTestHistogram(0) + _, _, _, err = nextApp.AppendHistogram(&prevSTApp.HistogramAppender, 200, 2000, h2, false) + require.NoError(t, err) + require.Equal(t, CounterReset, nextChunk.GetCounterResetHeader()) +}