add helper function to compare native histograms in testing

Signed-off-by: Ziqi Zhao <zhaoziqi9146@gmail.com>
This commit is contained in:
Ziqi Zhao 2024-08-23 09:28:31 +08:00
parent 8f828d45c1
commit c7c4a5c347
3 changed files with 123 additions and 12 deletions

View file

@ -17,12 +17,6 @@ import (
"fmt" "fmt"
"math" "math"
"strings" "strings"
"github.com/prometheus/prometheus/util/almost"
)
const (
defaultEpsilon = 0.000001
) )
// FloatHistogram is similar to Histogram but uses float64 for all // FloatHistogram is similar to Histogram but uses float64 for all
@ -462,8 +456,8 @@ func (h *FloatHistogram) Equals(h2 *FloatHistogram) bool {
} }
if h.Schema != h2.Schema || if h.Schema != h2.Schema ||
!almost.Equal(h.Count, h2.Count, defaultEpsilon) || math.Float64bits(h.Count) != math.Float64bits(h2.Count) ||
!almost.Equal(h.Sum, h2.Sum, defaultEpsilon) { math.Float64bits(h.Sum) != math.Float64bits(h2.Sum) {
return false return false
} }
@ -474,7 +468,7 @@ func (h *FloatHistogram) Equals(h2 *FloatHistogram) bool {
} }
if h.ZeroThreshold != h2.ZeroThreshold || if h.ZeroThreshold != h2.ZeroThreshold ||
!almost.Equal(h.ZeroCount, h2.ZeroCount, defaultEpsilon) { math.Float64bits(h.ZeroCount) != math.Float64bits(h2.ZeroCount) {
return false return false
} }
@ -1316,7 +1310,7 @@ func FloatBucketsMatch(b1, b2 []float64) bool {
return false return false
} }
for i, b := range b1 { for i, b := range b1 {
if !almost.Equal(b, b2[i], defaultEpsilon) { if math.Float64bits(b) != math.Float64bits(b2[i]) {
return false return false
} }
} }

View file

@ -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)) 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.Compact(0).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)) 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 { 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) 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.Compact(0)) { 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)) 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) { if !almost.Equal(exp0.Value, v.F, defaultEpsilon) {
@ -837,6 +837,121 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
return nil 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 { func (ev *evalCmd) checkExpectedFailure(actual error) error {
if ev.expectedFailMessage != "" { if ev.expectedFailMessage != "" {
if ev.expectedFailMessage != actual.Error() { if ev.expectedFailMessage != actual.Error() {

View file

@ -24,6 +24,8 @@ var minNormal = math.Float64frombits(0x0010000000000000) // The smallest positiv
// Equal returns true if a and b differ by less than their sum // Equal returns true if a and b differ by less than their sum
// multiplied by epsilon. // multiplied by epsilon.
func Equal(a, b, epsilon float64) bool { 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) { if value.IsStaleNaN(a) || value.IsStaleNaN(b) {
return value.IsStaleNaN(a) && value.IsStaleNaN(b) return value.IsStaleNaN(a) && value.IsStaleNaN(b)
} }