Fix bug with counter reset header

Signed-off-by: Carrie Edwards <edwrdscarrie@gmail.com>
This commit is contained in:
Carrie Edwards 2026-06-01 11:18:34 -07:00
parent 3c2620823b
commit 28e9e3f237
6 changed files with 154 additions and 16 deletions

View file

@ -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

View file

@ -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,

View file

@ -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())
}

View file

@ -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

View file

@ -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,

View file

@ -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())
}