2024-03-03 19:43:05 +00:00
// Copyright 2017 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 tsdb
import (
"testing"
2024-09-17 09:19:06 +00:00
"github.com/prometheus/prometheus/tsdb/tsdbutil"
2024-03-04 15:58:29 +00:00
prom_testutil "github.com/prometheus/client_golang/prometheus/testutil"
2024-03-03 19:43:05 +00:00
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunks"
)
const (
2024-09-17 09:19:06 +00:00
float = "float"
intHistogram = "integer histogram"
floatHistogram = "float histogram"
gaugeIntHistogram = "gauge int histogram"
gaugeFloatHistogram = "gauge float histogram"
2024-03-03 19:43:05 +00:00
)
2024-07-03 15:28:31 +00:00
type testValue struct {
2024-03-03 19:43:05 +00:00
Ts int64
V int64
CounterResetHeader histogram . CounterResetHint
}
type sampleTypeScenario struct {
sampleType string
appendFunc func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error )
sampleFunc func ( ts , value int64 ) sample
}
var sampleTypeScenarios = map [ string ] sampleTypeScenario {
float : {
sampleType : sampleMetricTypeFloat ,
appendFunc : func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error ) {
s := sample { t : ts , f : float64 ( value ) }
ref , err := appender . Append ( 0 , lbls , ts , s . f )
return ref , s , err
} ,
sampleFunc : func ( ts , value int64 ) sample {
return sample { t : ts , f : float64 ( value ) }
} ,
} ,
2024-09-17 09:19:06 +00:00
intHistogram : {
sampleType : sampleMetricTypeHistogram ,
appendFunc : func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error ) {
2024-12-15 22:53:36 +00:00
s := sample { t : ts , h : tsdbutil . GenerateTestHistogram ( value ) }
2024-09-17 09:19:06 +00:00
ref , err := appender . AppendHistogram ( 0 , lbls , ts , s . h , nil )
return ref , s , err
} ,
sampleFunc : func ( ts , value int64 ) sample {
2024-12-15 22:53:36 +00:00
return sample { t : ts , h : tsdbutil . GenerateTestHistogram ( value ) }
2024-09-17 09:19:06 +00:00
} ,
} ,
floatHistogram : {
sampleType : sampleMetricTypeHistogram ,
appendFunc : func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error ) {
2024-12-15 22:53:36 +00:00
s := sample { t : ts , fh : tsdbutil . GenerateTestFloatHistogram ( value ) }
2024-09-17 09:19:06 +00:00
ref , err := appender . AppendHistogram ( 0 , lbls , ts , nil , s . fh )
return ref , s , err
} ,
sampleFunc : func ( ts , value int64 ) sample {
2024-12-15 22:53:36 +00:00
return sample { t : ts , fh : tsdbutil . GenerateTestFloatHistogram ( value ) }
2024-09-17 09:19:06 +00:00
} ,
} ,
gaugeIntHistogram : {
sampleType : sampleMetricTypeHistogram ,
appendFunc : func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error ) {
2024-12-15 22:53:36 +00:00
s := sample { t : ts , h : tsdbutil . GenerateTestGaugeHistogram ( value ) }
2024-09-17 09:19:06 +00:00
ref , err := appender . AppendHistogram ( 0 , lbls , ts , s . h , nil )
return ref , s , err
} ,
sampleFunc : func ( ts , value int64 ) sample {
2024-12-15 22:53:36 +00:00
return sample { t : ts , h : tsdbutil . GenerateTestGaugeHistogram ( value ) }
2024-09-17 09:19:06 +00:00
} ,
} ,
gaugeFloatHistogram : {
sampleType : sampleMetricTypeHistogram ,
appendFunc : func ( appender storage . Appender , lbls labels . Labels , ts , value int64 ) ( storage . SeriesRef , sample , error ) {
2024-12-15 22:53:36 +00:00
s := sample { t : ts , fh : tsdbutil . GenerateTestGaugeFloatHistogram ( value ) }
2024-09-17 09:19:06 +00:00
ref , err := appender . AppendHistogram ( 0 , lbls , ts , nil , s . fh )
return ref , s , err
} ,
sampleFunc : func ( ts , value int64 ) sample {
2024-12-15 22:53:36 +00:00
return sample { t : ts , fh : tsdbutil . GenerateTestGaugeFloatHistogram ( value ) }
2024-09-17 09:19:06 +00:00
} ,
} ,
2024-03-03 19:43:05 +00:00
}
2024-03-07 17:41:03 +00:00
// requireEqualSeries checks that the actual series are equal to the expected ones. It ignores the counter reset hints for histograms.
func requireEqualSeries ( t * testing . T , expected , actual map [ string ] [ ] chunks . Sample , ignoreCounterResets bool ) {
2024-03-03 19:43:05 +00:00
for name , expectedItem := range expected {
actualItem , ok := actual [ name ]
require . True ( t , ok , "Expected series %s not found" , name )
2024-11-12 14:14:06 +00:00
if ignoreCounterResets {
requireEqualSamples ( t , name , expectedItem , actualItem , requireEqualSamplesIgnoreCounterResets )
} else {
requireEqualSamples ( t , name , expectedItem , actualItem )
}
2024-03-03 19:43:05 +00:00
}
for name := range actual {
_ , ok := expected [ name ]
require . True ( t , ok , "Unexpected series %s" , name )
}
}
func requireEqualOOOSamples ( t * testing . T , expectedSamples int , db * DB ) {
2024-07-03 15:28:31 +00:00
require . Equal ( t , float64 ( expectedSamples ) ,
2024-03-03 19:43:05 +00:00
prom_testutil . ToFloat64 ( db . head . metrics . outOfOrderSamplesAppended . WithLabelValues ( sampleMetricTypeFloat ) ) +
prom_testutil . ToFloat64 ( db . head . metrics . outOfOrderSamplesAppended . WithLabelValues ( sampleMetricTypeHistogram ) ) ,
"number of ooo appended samples mismatch" )
}
2024-10-18 06:54:37 +00:00
type requireEqualSamplesOption int
const (
requireEqualSamplesNoOption requireEqualSamplesOption = iota
requireEqualSamplesIgnoreCounterResets
requireEqualSamplesInUseBucketCompare
)
func requireEqualSamples ( t * testing . T , name string , expected , actual [ ] chunks . Sample , options ... requireEqualSamplesOption ) {
var (
ignoreCounterResets bool
inUseBucketCompare bool
)
for _ , option := range options {
switch option {
case requireEqualSamplesIgnoreCounterResets :
ignoreCounterResets = true
case requireEqualSamplesInUseBucketCompare :
inUseBucketCompare = true
}
}
2024-03-07 17:41:03 +00:00
require . Equal ( t , len ( expected ) , len ( actual ) , "Length not equal to expected for %s" , name )
2024-03-03 19:43:05 +00:00
for i , s := range expected {
expectedSample := s
actualSample := actual [ i ]
2024-03-07 17:41:03 +00:00
require . Equal ( t , expectedSample . T ( ) , actualSample . T ( ) , "Different timestamps for %s[%d]" , name , i )
2024-03-03 19:43:05 +00:00
require . Equal ( t , expectedSample . Type ( ) . String ( ) , actualSample . Type ( ) . String ( ) , "Different types for %s[%d] at ts %d" , name , i , expectedSample . T ( ) )
switch {
case s . H ( ) != nil :
{
expectedHist := expectedSample . H ( )
actualHist := actualSample . H ( )
if ignoreCounterResets && expectedHist . CounterResetHint != histogram . GaugeType {
expectedHist . CounterResetHint = histogram . UnknownCounterReset
actualHist . CounterResetHint = histogram . UnknownCounterReset
} else {
require . Equal ( t , expectedHist . CounterResetHint , actualHist . CounterResetHint , "Sample header doesn't match for %s[%d] at ts %d, expected: %s, actual: %s" , name , i , expectedSample . T ( ) , counterResetAsString ( expectedHist . CounterResetHint ) , counterResetAsString ( actualHist . CounterResetHint ) )
}
2024-10-18 06:54:37 +00:00
if inUseBucketCompare {
expectedSample . H ( ) . Compact ( 0 )
actualSample . H ( ) . Compact ( 0 )
}
2024-03-03 19:43:05 +00:00
require . Equal ( t , expectedHist , actualHist , "Sample doesn't match for %s[%d] at ts %d" , name , i , expectedSample . T ( ) )
}
case s . FH ( ) != nil :
{
expectedHist := expectedSample . FH ( )
actualHist := actualSample . FH ( )
if ignoreCounterResets {
expectedHist . CounterResetHint = histogram . UnknownCounterReset
actualHist . CounterResetHint = histogram . UnknownCounterReset
} else {
require . Equal ( t , expectedHist . CounterResetHint , actualHist . CounterResetHint , "Sample header doesn't match for %s[%d] at ts %d, expected: %s, actual: %s" , name , i , expectedSample . T ( ) , counterResetAsString ( expectedHist . CounterResetHint ) , counterResetAsString ( actualHist . CounterResetHint ) )
}
2024-10-18 06:54:37 +00:00
if inUseBucketCompare {
expectedSample . FH ( ) . Compact ( 0 )
actualSample . FH ( ) . Compact ( 0 )
}
2024-03-03 19:43:05 +00:00
require . Equal ( t , expectedHist , actualHist , "Sample doesn't match for %s[%d] at ts %d" , name , i , expectedSample . T ( ) )
}
default :
2024-06-25 18:01:50 +00:00
expectedFloat := expectedSample . F ( )
actualFloat := actualSample . F ( )
require . Equal ( t , expectedFloat , actualFloat , "Sample doesn't match for %s[%d] at ts %d" , name , i , expectedSample . T ( ) )
2024-03-03 19:43:05 +00:00
}
}
}
func counterResetAsString ( h histogram . CounterResetHint ) string {
switch h {
case histogram . UnknownCounterReset :
return "UnknownCounterReset"
case histogram . CounterReset :
return "CounterReset"
case histogram . NotCounterReset :
return "NotCounterReset"
case histogram . GaugeType :
return "GaugeType"
}
panic ( "Unexpected counter reset type" )
}