@ -14,12 +14,17 @@
package storage
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/value"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks"
"github.com/prometheus/prometheus/tsdb/tsdbutil"
)
@ -119,3 +124,284 @@ func TestChunkSeriesSetToSeriesSet(t *testing.T) {
}
}
}
type histogramTest struct {
samples [ ] tsdbutil . Sample
expectedChunks int
expectedCounterReset bool
}
func TestHistogramSeriesToChunks ( t * testing . T ) {
h1 := & histogram . Histogram {
Count : 3 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
} ,
PositiveBuckets : [ ] int64 { 2 , 1 } , // Abs: 2, 3
}
// Appendable to h1.
h2 := & histogram . Histogram {
Count : 12 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
{ Offset : 1 , Length : 2 } ,
} ,
PositiveBuckets : [ ] int64 { 2 , 1 , - 2 , 3 } , // Abs: 2, 3, 1, 4
}
// Implicit counter reset by reduction in buckets, not appendable.
h2down := & histogram . Histogram {
Count : 8 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
{ Offset : 1 , Length : 2 } ,
} ,
PositiveBuckets : [ ] int64 { 1 , 1 , - 1 , 3 } , // Abs: 1, 2, 1, 4
}
fh1 := & histogram . FloatHistogram {
Count : 4 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
} ,
PositiveBuckets : [ ] float64 { 3 , 1 } ,
}
// Appendable to fh1.
fh2 := & histogram . FloatHistogram {
Count : 15 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
{ Offset : 1 , Length : 2 } ,
} ,
PositiveBuckets : [ ] float64 { 4 , 2 , 7 , 2 } ,
}
// Implicit counter reset by reduction in buckets, not appendable.
fh2down := & histogram . FloatHistogram {
Count : 13 ,
ZeroCount : 2 ,
ZeroThreshold : 0.001 ,
Sum : 100 ,
Schema : 0 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : 0 , Length : 2 } ,
{ Offset : 1 , Length : 2 } ,
} ,
PositiveBuckets : [ ] float64 { 2 , 2 , 7 , 2 } ,
}
staleHistogram := & histogram . Histogram {
Sum : math . Float64frombits ( value . StaleNaN ) ,
}
staleFloatHistogram := & histogram . FloatHistogram {
Sum : math . Float64frombits ( value . StaleNaN ) ,
}
tests := map [ string ] histogramTest {
"single histogram to single chunk" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h1 } ,
} ,
expectedChunks : 1 ,
} ,
"two histograms encoded to a single chunk" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h1 } ,
hSample { t : 2 , h : h2 } ,
} ,
expectedChunks : 1 ,
} ,
"two histograms encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h2 } ,
hSample { t : 2 , h : h1 } ,
} ,
expectedChunks : 2 ,
expectedCounterReset : true ,
} ,
"histogram and stale sample encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : staleHistogram } ,
hSample { t : 2 , h : h1 } ,
} ,
expectedChunks : 2 ,
} ,
"histogram and reduction in bucket encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h1 } ,
hSample { t : 2 , h : h2down } ,
} ,
expectedChunks : 2 ,
expectedCounterReset : true ,
} ,
// Float histograms.
"single float histogram to single chunk" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : fh1 } ,
} ,
expectedChunks : 1 ,
} ,
"two float histograms encoded to a single chunk" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : fh1 } ,
fhSample { t : 2 , fh : fh2 } ,
} ,
expectedChunks : 1 ,
} ,
"two float histograms encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : fh2 } ,
fhSample { t : 2 , fh : fh1 } ,
} ,
expectedChunks : 2 ,
expectedCounterReset : true ,
} ,
"float histogram and stale sample encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : staleFloatHistogram } ,
fhSample { t : 2 , fh : fh1 } ,
} ,
expectedChunks : 2 ,
} ,
"float histogram and reduction in bucket encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : fh1 } ,
fhSample { t : 2 , fh : fh2down } ,
} ,
expectedChunks : 2 ,
expectedCounterReset : true ,
} ,
// Mixed.
"histogram and float histogram encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h1 } ,
fhSample { t : 2 , fh : fh2 } ,
} ,
expectedChunks : 2 ,
} ,
"float histogram and histogram encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
fhSample { t : 1 , fh : fh1 } ,
hSample { t : 2 , h : h2 } ,
} ,
expectedChunks : 2 ,
} ,
"histogram and stale float histogram encoded to two chunks" : {
samples : [ ] tsdbutil . Sample {
hSample { t : 1 , h : h1 } ,
fhSample { t : 2 , fh : staleFloatHistogram } ,
} ,
expectedChunks : 2 ,
} ,
}
for testName , test := range tests {
t . Run ( testName , func ( t * testing . T ) {
testHistogramsSeriesToChunks ( t , test )
} )
}
}
func testHistogramsSeriesToChunks ( t * testing . T , test histogramTest ) {
lbs := labels . FromStrings ( "__name__" , "up" , "instance" , "localhost:8080" )
series := NewListSeries ( lbs , test . samples )
encoder := NewSeriesToChunkEncoder ( series )
require . EqualValues ( t , lbs , encoder . Labels ( ) )
chks , err := ExpandChunks ( encoder . Iterator ( nil ) )
require . NoError ( t , err )
require . Equal ( t , test . expectedChunks , len ( chks ) )
// Decode all encoded samples and assert they are equal to the original ones.
encodedSamples := expandHistogramSamples ( chks )
require . Equal ( t , len ( test . samples ) , len ( encodedSamples ) )
for i , s := range test . samples {
switch expectedSample := s . ( type ) {
case hSample :
encodedSample , ok := encodedSamples [ i ] . ( hSample )
require . True ( t , ok , "expect histogram" , fmt . Sprintf ( "at idx %d" , i ) )
// Ignore counter reset here, will check on chunk level.
encodedSample . h . CounterResetHint = histogram . UnknownCounterReset
if value . IsStaleNaN ( expectedSample . h . Sum ) {
require . True ( t , value . IsStaleNaN ( encodedSample . h . Sum ) , fmt . Sprintf ( "at idx %d" , i ) )
continue
}
require . Equal ( t , * expectedSample . h , * encodedSample . h . Compact ( 0 ) , fmt . Sprintf ( "at idx %d" , i ) )
case fhSample :
encodedSample , ok := encodedSamples [ i ] . ( fhSample )
require . True ( t , ok , "expect float histogram" , fmt . Sprintf ( "at idx %d" , i ) )
// Ignore counter reset here, will check on chunk level.
encodedSample . fh . CounterResetHint = histogram . UnknownCounterReset
if value . IsStaleNaN ( expectedSample . fh . Sum ) {
require . True ( t , value . IsStaleNaN ( encodedSample . fh . Sum ) , fmt . Sprintf ( "at idx %d" , i ) )
continue
}
require . Equal ( t , * expectedSample . fh , * encodedSample . fh . Compact ( 0 ) , fmt . Sprintf ( "at idx %d" , i ) )
default :
t . Error ( "internal error, unexpected type" )
}
}
// If a counter reset hint is expected, it can only be found in the second chunk.
// Otherwise, we assert an unknown counter reset hint in all chunks.
if test . expectedCounterReset {
require . Equal ( t , chunkenc . UnknownCounterReset , getCounterResetHint ( chks [ 0 ] ) )
require . Equal ( t , chunkenc . CounterReset , getCounterResetHint ( chks [ 1 ] ) )
} else {
for _ , chk := range chks {
require . Equal ( t , chunkenc . UnknownCounterReset , getCounterResetHint ( chk ) )
}
}
}
func expandHistogramSamples ( chunks [ ] chunks . Meta ) ( result [ ] tsdbutil . Sample ) {
if len ( chunks ) == 0 {
return
}
for _ , chunk := range chunks {
it := chunk . Chunk . Iterator ( nil )
for vt := it . Next ( ) ; vt != chunkenc . ValNone ; vt = it . Next ( ) {
switch vt {
case chunkenc . ValHistogram :
t , h := it . AtHistogram ( )
result = append ( result , hSample { t : t , h : h } )
case chunkenc . ValFloatHistogram :
t , fh := it . AtFloatHistogram ( )
result = append ( result , fhSample { t : t , fh : fh } )
default :
panic ( "unexpected value type" )
}
}
}
return
}
func getCounterResetHint ( chunk chunks . Meta ) chunkenc . CounterResetHeader {
switch chk := chunk . Chunk . ( type ) {
case * chunkenc . HistogramChunk :
return chk . GetCounterResetHeader ( )
case * chunkenc . FloatHistogramChunk :
return chk . GetCounterResetHeader ( )
}
return chunkenc . UnknownCounterReset
}