mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Merge pull request #14585 from fatsheep9146/covert-TestNativeHistogram_Sum_Count_Add_AvgOperator-to-framework
convert TestNativeHistogram_Sum_Count_Add_AvgOperator into testing framework
This commit is contained in:
commit
849215d90c
|
@ -3097,217 +3097,6 @@ func TestInstantQueryWithRangeVectorSelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNativeHistogram_Sum_Count_Add_AvgOperator(t *testing.T) {
|
||||
// TODO(codesome): Integrate histograms into the PromQL testing framework
|
||||
// and write more tests there.
|
||||
cases := []struct {
|
||||
histograms []histogram.Histogram
|
||||
expected histogram.FloatHistogram
|
||||
expectedAvg histogram.FloatHistogram
|
||||
}{
|
||||
{
|
||||
histograms: []histogram.Histogram{
|
||||
{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 0,
|
||||
Count: 25,
|
||||
Sum: 1234.5,
|
||||
ZeroThreshold: 0.001,
|
||||
ZeroCount: 4,
|
||||
PositiveSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 2},
|
||||
{Offset: 1, Length: 2},
|
||||
},
|
||||
PositiveBuckets: []int64{1, 1, -1, 0},
|
||||
NegativeSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 2},
|
||||
{Offset: 2, Length: 2},
|
||||
},
|
||||
NegativeBuckets: []int64{2, 2, -3, 8},
|
||||
},
|
||||
{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 0,
|
||||
Count: 41,
|
||||
Sum: 2345.6,
|
||||
ZeroThreshold: 0.001,
|
||||
ZeroCount: 5,
|
||||
PositiveSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 4},
|
||||
{Offset: 0, Length: 0},
|
||||
{Offset: 0, Length: 3},
|
||||
},
|
||||
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
|
||||
NegativeSpans: []histogram.Span{
|
||||
{Offset: 1, Length: 4},
|
||||
{Offset: 2, Length: 0},
|
||||
{Offset: 2, Length: 3},
|
||||
},
|
||||
NegativeBuckets: []int64{1, 3, -2, 5, -2, 0, -3},
|
||||
},
|
||||
{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 0,
|
||||
Count: 41,
|
||||
Sum: 1111.1,
|
||||
ZeroThreshold: 0.001,
|
||||
ZeroCount: 5,
|
||||
PositiveSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 4},
|
||||
{Offset: 0, Length: 0},
|
||||
{Offset: 0, Length: 3},
|
||||
},
|
||||
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
|
||||
NegativeSpans: []histogram.Span{
|
||||
{Offset: 1, Length: 4},
|
||||
{Offset: 2, Length: 0},
|
||||
{Offset: 2, Length: 3},
|
||||
},
|
||||
NegativeBuckets: []int64{1, 3, -2, 5, -2, 0, -3},
|
||||
},
|
||||
{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 1, // Everything is 0 just to make the count 4 so avg has nicer numbers.
|
||||
},
|
||||
},
|
||||
expected: histogram.FloatHistogram{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 0,
|
||||
ZeroThreshold: 0.001,
|
||||
ZeroCount: 14,
|
||||
Count: 107,
|
||||
Sum: 4691.2,
|
||||
PositiveSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 7},
|
||||
},
|
||||
PositiveBuckets: []float64{3, 8, 2, 5, 3, 2, 2},
|
||||
NegativeSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 6},
|
||||
{Offset: 3, Length: 3},
|
||||
},
|
||||
NegativeBuckets: []float64{2, 6, 8, 4, 15, 9, 10, 10, 4},
|
||||
},
|
||||
expectedAvg: histogram.FloatHistogram{
|
||||
CounterResetHint: histogram.GaugeType,
|
||||
Schema: 0,
|
||||
ZeroThreshold: 0.001,
|
||||
ZeroCount: 3.5,
|
||||
Count: 26.75,
|
||||
Sum: 1172.8,
|
||||
PositiveSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 7},
|
||||
},
|
||||
PositiveBuckets: []float64{0.75, 2, 0.5, 1.25, 0.75, 0.5, 0.5},
|
||||
NegativeSpans: []histogram.Span{
|
||||
{Offset: 0, Length: 6},
|
||||
{Offset: 3, Length: 3},
|
||||
},
|
||||
NegativeBuckets: []float64{0.5, 1.5, 2, 1, 3.75, 2.25, 2.5, 2.5, 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
idx0 := int64(0)
|
||||
for _, c := range cases {
|
||||
for _, floatHisto := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("floatHistogram=%t %d", floatHisto, idx0), func(t *testing.T) {
|
||||
storage := teststorage.New(t)
|
||||
t.Cleanup(func() { storage.Close() })
|
||||
|
||||
seriesName := "sparse_histogram_series"
|
||||
seriesNameOverTime := "sparse_histogram_series_over_time"
|
||||
|
||||
engine := newTestEngine()
|
||||
|
||||
ts := idx0 * int64(10*time.Minute/time.Millisecond)
|
||||
app := storage.Appender(context.Background())
|
||||
_, err := app.Append(0, labels.FromStrings("__name__", "float_series", "idx", "0"), ts, 42)
|
||||
require.NoError(t, err)
|
||||
for idx1, h := range c.histograms {
|
||||
lbls := labels.FromStrings("__name__", seriesName, "idx", strconv.Itoa(idx1))
|
||||
// Since we mutate h later, we need to create a copy here.
|
||||
var err error
|
||||
if floatHisto {
|
||||
_, err = app.AppendHistogram(0, lbls, ts, nil, h.Copy().ToFloat(nil))
|
||||
} else {
|
||||
_, err = app.AppendHistogram(0, lbls, ts, h.Copy(), nil)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
lbls = labels.FromStrings("__name__", seriesNameOverTime)
|
||||
newTs := ts + int64(idx1)*int64(time.Minute/time.Millisecond)
|
||||
// Since we mutate h later, we need to create a copy here.
|
||||
if floatHisto {
|
||||
_, err = app.AppendHistogram(0, lbls, newTs, nil, h.Copy().ToFloat(nil))
|
||||
} else {
|
||||
_, err = app.AppendHistogram(0, lbls, newTs, h.Copy(), nil)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, app.Commit())
|
||||
|
||||
queryAndCheck := func(queryString string, ts int64, exp promql.Vector) {
|
||||
qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts))
|
||||
require.NoError(t, err)
|
||||
|
||||
res := qry.Exec(context.Background())
|
||||
require.NoError(t, res.Err)
|
||||
require.Empty(t, res.Warnings)
|
||||
|
||||
vector, err := res.Vector()
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.RequireEqual(t, exp, vector)
|
||||
}
|
||||
queryAndCheckAnnotations := func(queryString string, ts int64, expWarnings annotations.Annotations) {
|
||||
qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts))
|
||||
require.NoError(t, err)
|
||||
|
||||
res := qry.Exec(context.Background())
|
||||
require.NoError(t, res.Err)
|
||||
require.Equal(t, expWarnings, res.Warnings)
|
||||
}
|
||||
|
||||
// sum().
|
||||
queryString := fmt.Sprintf("sum(%s)", seriesName)
|
||||
queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expected, Metric: labels.EmptyLabels()}})
|
||||
|
||||
queryString = `sum({idx="0"})`
|
||||
var annos annotations.Annotations
|
||||
annos.Add(annotations.NewMixedFloatsHistogramsAggWarning(posrange.PositionRange{Start: 4, End: 13}))
|
||||
queryAndCheckAnnotations(queryString, ts, annos)
|
||||
|
||||
// + operator.
|
||||
queryString = fmt.Sprintf(`%s{idx="0"}`, seriesName)
|
||||
for idx := 1; idx < len(c.histograms); idx++ {
|
||||
queryString += fmt.Sprintf(` + ignoring(idx) %s{idx="%d"}`, seriesName, idx)
|
||||
}
|
||||
queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expected, Metric: labels.EmptyLabels()}})
|
||||
|
||||
// count().
|
||||
queryString = fmt.Sprintf("count(%s)", seriesName)
|
||||
queryAndCheck(queryString, ts, []promql.Sample{{T: ts, F: 4, Metric: labels.EmptyLabels()}})
|
||||
|
||||
// avg().
|
||||
queryString = fmt.Sprintf("avg(%s)", seriesName)
|
||||
queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expectedAvg, Metric: labels.EmptyLabels()}})
|
||||
|
||||
offset := int64(len(c.histograms) - 1)
|
||||
newTs := ts + offset*int64(time.Minute/time.Millisecond)
|
||||
|
||||
// sum_over_time().
|
||||
queryString = fmt.Sprintf("sum_over_time(%s[%dm:1m])", seriesNameOverTime, offset)
|
||||
queryAndCheck(queryString, newTs, []promql.Sample{{T: newTs, H: &c.expected, Metric: labels.EmptyLabels()}})
|
||||
|
||||
// avg_over_time().
|
||||
queryString = fmt.Sprintf("avg_over_time(%s[%dm:1m])", seriesNameOverTime, offset)
|
||||
queryAndCheck(queryString, newTs, []promql.Sample{{T: newTs, H: &c.expectedAvg, Metric: labels.EmptyLabels()}})
|
||||
})
|
||||
idx0++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeHistogram_SubOperator(t *testing.T) {
|
||||
// TODO(codesome): Integrate histograms into the PromQL testing framework
|
||||
// and write more tests there.
|
||||
|
|
|
@ -769,7 +769,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
|
|||
return fmt.Errorf("expected histogram value at index %v for %s to have timestamp %v, but it had timestamp %v (result has %s)", i, ev.metrics[hash], expected.T, actual.T, formatSeriesResult(s))
|
||||
}
|
||||
|
||||
if !actual.H.Equals(expected.H.Compact(0)) {
|
||||
if !compareNativeHistogram(expected.H.Compact(0), actual.H.Compact(0)) {
|
||||
return fmt.Errorf("expected histogram value at index %v (t=%v) for %s to be %v, but got %v (result has %s)", i, actual.T, ev.metrics[hash], expected.H, actual.H, formatSeriesResult(s))
|
||||
}
|
||||
}
|
||||
|
@ -804,7 +804,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
|
|||
if expH != nil && v.H == nil {
|
||||
return fmt.Errorf("expected histogram %s for %s but got float value %v", HistogramTestExpression(expH), v.Metric, v.F)
|
||||
}
|
||||
if expH != nil && !expH.Compact(0).Equals(v.H) {
|
||||
if expH != nil && !compareNativeHistogram(expH.Compact(0), v.H.Compact(0)) {
|
||||
return fmt.Errorf("expected %v for %s but got %s", HistogramTestExpression(expH), v.Metric, HistogramTestExpression(v.H))
|
||||
}
|
||||
if !almost.Equal(exp0.Value, v.F, defaultEpsilon) {
|
||||
|
@ -837,6 +837,121 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// compareNativeHistogram is helper function to compare two native histograms
|
||||
// which can tolerate some differ in the field of float type, such as Count, Sum.
|
||||
func compareNativeHistogram(exp, cur *histogram.FloatHistogram) bool {
|
||||
if exp == nil || cur == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if exp.Schema != cur.Schema ||
|
||||
!almost.Equal(exp.Count, cur.Count, defaultEpsilon) ||
|
||||
!almost.Equal(exp.Sum, cur.Sum, defaultEpsilon) {
|
||||
return false
|
||||
}
|
||||
|
||||
if exp.UsesCustomBuckets() {
|
||||
if !histogram.FloatBucketsMatch(exp.CustomValues, cur.CustomValues) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if exp.ZeroThreshold != cur.ZeroThreshold ||
|
||||
!almost.Equal(exp.ZeroCount, cur.ZeroCount, defaultEpsilon) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !spansMatch(exp.NegativeSpans, cur.NegativeSpans) {
|
||||
return false
|
||||
}
|
||||
if !floatBucketsMatch(exp.NegativeBuckets, cur.NegativeBuckets) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !spansMatch(exp.PositiveSpans, cur.PositiveSpans) {
|
||||
return false
|
||||
}
|
||||
if !floatBucketsMatch(exp.PositiveBuckets, cur.PositiveBuckets) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func floatBucketsMatch(b1, b2 []float64) bool {
|
||||
if len(b1) != len(b2) {
|
||||
return false
|
||||
}
|
||||
for i, b := range b1 {
|
||||
if !almost.Equal(b, b2[i], defaultEpsilon) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func spansMatch(s1, s2 []histogram.Span) bool {
|
||||
if len(s1) == 0 && len(s2) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
s1idx, s2idx := 0, 0
|
||||
for {
|
||||
if s1idx >= len(s1) {
|
||||
return allEmptySpans(s2[s2idx:])
|
||||
}
|
||||
if s2idx >= len(s2) {
|
||||
return allEmptySpans(s1[s1idx:])
|
||||
}
|
||||
|
||||
currS1, currS2 := s1[s1idx], s2[s2idx]
|
||||
s1idx++
|
||||
s2idx++
|
||||
if currS1.Length == 0 {
|
||||
// This span is zero length, so we add consecutive such spans
|
||||
// until we find a non-zero span.
|
||||
for ; s1idx < len(s1) && s1[s1idx].Length == 0; s1idx++ {
|
||||
currS1.Offset += s1[s1idx].Offset
|
||||
}
|
||||
if s1idx < len(s1) {
|
||||
currS1.Offset += s1[s1idx].Offset
|
||||
currS1.Length = s1[s1idx].Length
|
||||
s1idx++
|
||||
}
|
||||
}
|
||||
if currS2.Length == 0 {
|
||||
// This span is zero length, so we add consecutive such spans
|
||||
// until we find a non-zero span.
|
||||
for ; s2idx < len(s2) && s2[s2idx].Length == 0; s2idx++ {
|
||||
currS2.Offset += s2[s2idx].Offset
|
||||
}
|
||||
if s2idx < len(s2) {
|
||||
currS2.Offset += s2[s2idx].Offset
|
||||
currS2.Length = s2[s2idx].Length
|
||||
s2idx++
|
||||
}
|
||||
}
|
||||
|
||||
if currS1.Length == 0 && currS2.Length == 0 {
|
||||
// The last spans of both set are zero length. Previous spans match.
|
||||
return true
|
||||
}
|
||||
|
||||
if currS1.Offset != currS2.Offset || currS1.Length != currS2.Length {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func allEmptySpans(s []histogram.Span) bool {
|
||||
for _, ss := range s {
|
||||
if ss.Length > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ev *evalCmd) checkExpectedFailure(actual error) error {
|
||||
if ev.expectedFailMessage != "" {
|
||||
if ev.expectedFailMessage != actual.Error() {
|
||||
|
|
|
@ -886,3 +886,39 @@ eval_warn instant at 0 sum by (group) (metric)
|
|||
{group="just-floats"} 5
|
||||
{group="just-exponential-histograms"} {{sum:5 count:7 buckets:[2 3 2]}}
|
||||
{group="just-custom-histograms"} {{schema:-53 sum:4 count:5 custom_values:[2] buckets:[8]}}
|
||||
|
||||
clear
|
||||
|
||||
# Test native histograms with sum, count, avg.
|
||||
load 10m
|
||||
histogram_sum{idx="0"} {{schema:0 count:25 sum:1234.5 z_bucket:4 z_bucket_w:0.001 buckets:[1 2 0 1 1] n_buckets:[2 4 0 0 1 9]}}x1
|
||||
histogram_sum{idx="1"} {{schema:0 count:41 sum:2345.6 z_bucket:5 z_bucket_w:0.001 buckets:[1 3 1 2 1 1 1] n_buckets:[0 1 4 2 7 0 0 0 0 5 5 2]}}x1
|
||||
histogram_sum{idx="2"} {{schema:0 count:41 sum:1111.1 z_bucket:5 z_bucket_w:0.001 buckets:[1 3 1 2 1 1 1] n_buckets:[0 1 4 2 7 0 0 0 0 5 5 2]}}x1
|
||||
histogram_sum{idx="3"} {{schema:1 count:0}}x1
|
||||
histogram_sum_float{idx="0"} 42.0x1
|
||||
|
||||
eval instant at 10m sum(histogram_sum)
|
||||
{} {{schema:0 count:107 sum:4691.2 z_bucket:14 z_bucket_w:0.001 buckets:[3 8 2 5 3 2 2] n_buckets:[2 6 8 4 15 9 0 0 0 10 10 4]}}
|
||||
|
||||
eval_warn instant at 10m sum({idx="0"})
|
||||
|
||||
eval instant at 10m sum(histogram_sum{idx="0"} + ignoring(idx) histogram_sum{idx="1"} + ignoring(idx) histogram_sum{idx="2"} + ignoring(idx) histogram_sum{idx="3"})
|
||||
{} {{schema:0 count:107 sum:4691.2 z_bucket:14 z_bucket_w:0.001 buckets:[3 8 2 5 3 2 2] n_buckets:[2 6 8 4 15 9 0 0 0 10 10 4]}}
|
||||
|
||||
eval instant at 10m count(histogram_sum)
|
||||
{} 4
|
||||
|
||||
eval instant at 10m avg(histogram_sum)
|
||||
{} {{schema:0 count:26.75 sum:1172.8 z_bucket:3.5 z_bucket_w:0.001 buckets:[0.75 2 0.5 1.25 0.75 0.5 0.5] n_buckets:[0.5 1.5 2 1 3.75 2.25 0 0 0 2.5 2.5 1]}}
|
||||
|
||||
clear
|
||||
|
||||
# Test native histograms with sum_over_time, avg_over_time.
|
||||
load 1m
|
||||
histogram_sum_over_time {{schema:0 count:25 sum:1234.5 z_bucket:4 z_bucket_w:0.001 buckets:[1 2 0 1 1] n_buckets:[2 4 0 0 1 9]}} {{schema:0 count:41 sum:2345.6 z_bucket:5 z_bucket_w:0.001 buckets:[1 3 1 2 1 1 1] n_buckets:[0 1 4 2 7 0 0 0 0 5 5 2]}} {{schema:0 count:41 sum:1111.1 z_bucket:5 z_bucket_w:0.001 buckets:[1 3 1 2 1 1 1] n_buckets:[0 1 4 2 7 0 0 0 0 5 5 2]}} {{schema:1 count:0}}
|
||||
|
||||
eval instant at 3m sum_over_time(histogram_sum_over_time[3m:1m])
|
||||
{} {{schema:0 count:107 sum:4691.2 z_bucket:14 z_bucket_w:0.001 buckets:[3 8 2 5 3 2 2] n_buckets:[2 6 8 4 15 9 0 0 0 10 10 4]}}
|
||||
|
||||
eval instant at 3m avg_over_time(histogram_sum_over_time[3m:1m])
|
||||
{} {{schema:0 count:26.75 sum:1172.8 z_bucket:3.5 z_bucket_w:0.001 buckets:[0.75 2 0.5 1.25 0.75 0.5 0.5] n_buckets:[0.5 1.5 2 1 3.75 2.25 0 0 0 2.5 2.5 1]}}
|
||||
|
|
|
@ -13,13 +13,23 @@
|
|||
|
||||
package almost
|
||||
|
||||
import "math"
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/prometheus/prometheus/model/value"
|
||||
)
|
||||
|
||||
var minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64.
|
||||
|
||||
// Equal returns true if a and b differ by less than their sum
|
||||
// multiplied by epsilon.
|
||||
func Equal(a, b, epsilon float64) bool {
|
||||
// StaleNaN is a special value that is used as staleness maker, so
|
||||
// the two values are equal when both are exactly equals to stale NaN.
|
||||
if value.IsStaleNaN(a) || value.IsStaleNaN(b) {
|
||||
return value.IsStaleNaN(a) && value.IsStaleNaN(b)
|
||||
}
|
||||
|
||||
// NaN has no equality but for testing we still want to know whether both values
|
||||
// are NaN.
|
||||
if math.IsNaN(a) && math.IsNaN(b) {
|
||||
|
|
Loading…
Reference in a new issue