Promql: Initial rate implementation for sparse histograms (#9926)
Signed-off-by: beorn7 <beorn@grafana.com>
This commit is contained in:
parent
e8e9155a11
commit
0e1b9dd308
|
@ -115,6 +115,295 @@ func (h *FloatHistogram) ZeroBucket() FloatBucket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scale scales the FloatHistogram by the provided factor, i.e. it scales all
|
||||||
|
// bucket counts including the zero bucket and the count and the sum of
|
||||||
|
// observations. The bucket layout stays the same. This method changes the
|
||||||
|
// receiving histogram directly (rather than acting on a copy). It returns a
|
||||||
|
// pointer to the receiving histogram for convenience.
|
||||||
|
func (h *FloatHistogram) Scale(factor float64) *FloatHistogram {
|
||||||
|
h.ZeroCount *= factor
|
||||||
|
h.Count *= factor
|
||||||
|
h.Sum *= factor
|
||||||
|
for i := range h.PositiveBuckets {
|
||||||
|
h.PositiveBuckets[i] *= factor
|
||||||
|
}
|
||||||
|
for i := range h.NegativeBuckets {
|
||||||
|
h.NegativeBuckets[i] *= factor
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the provided other histogram to the receiving histogram. Count, Sum,
|
||||||
|
// and buckets from the other histogram are added to the corresponding
|
||||||
|
// components of the receiving histogram. Buckets in the other histogram that do
|
||||||
|
// not exist in the receiving histogram are inserted into the latter. The
|
||||||
|
// resulting histogram might have buckets with a population of zero or directly
|
||||||
|
// adjacent spans (offset=0). To normalize those, call the Compact method.
|
||||||
|
//
|
||||||
|
// This method returns a pointer to the receiving histogram for convenience.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This method requires the Schema and the ZeroThreshold to be the
|
||||||
|
// same in both histograms. Otherwise, its behavior is undefined.
|
||||||
|
// TODO(beorn7): Change that!
|
||||||
|
func (h *FloatHistogram) Add(other *FloatHistogram) *FloatHistogram {
|
||||||
|
h.ZeroCount += other.ZeroCount
|
||||||
|
h.Count += other.Count
|
||||||
|
h.Sum += other.Sum
|
||||||
|
|
||||||
|
// TODO(beorn7): If needed, this can be optimized by inspecting the
|
||||||
|
// spans in other and create missing buckets in h in batches.
|
||||||
|
iSpan, iBucket := -1, -1
|
||||||
|
var iInSpan, index int32
|
||||||
|
for it := other.PositiveBucketIterator(); it.Next(); {
|
||||||
|
b := it.At()
|
||||||
|
h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
|
||||||
|
b, h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan, index,
|
||||||
|
)
|
||||||
|
index = b.Index
|
||||||
|
}
|
||||||
|
iSpan, iBucket = -1, -1
|
||||||
|
for it := other.NegativeBucketIterator(); it.Next(); {
|
||||||
|
b := it.At()
|
||||||
|
h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
|
||||||
|
b, h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan, index,
|
||||||
|
)
|
||||||
|
index = b.Index
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub works like Add but subtracts the other histogram.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This method requires the Schema and the ZeroThreshold to be the
|
||||||
|
// same in both histograms. Otherwise, its behavior is undefined.
|
||||||
|
// TODO(beorn7): Change that!
|
||||||
|
func (h *FloatHistogram) Sub(other *FloatHistogram) *FloatHistogram {
|
||||||
|
h.ZeroCount -= other.ZeroCount
|
||||||
|
h.Count -= other.Count
|
||||||
|
h.Sum -= other.Sum
|
||||||
|
|
||||||
|
// TODO(beorn7): If needed, this can be optimized by inspecting the
|
||||||
|
// spans in other and create missing buckets in h in batches.
|
||||||
|
iSpan, iBucket := -1, -1
|
||||||
|
var iInSpan, index int32
|
||||||
|
for it := other.PositiveBucketIterator(); it.Next(); {
|
||||||
|
b := it.At()
|
||||||
|
b.Count *= -1
|
||||||
|
h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
|
||||||
|
b, h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan, index,
|
||||||
|
)
|
||||||
|
index = b.Index
|
||||||
|
}
|
||||||
|
iSpan, iBucket = -1, -1
|
||||||
|
for it := other.NegativeBucketIterator(); it.Next(); {
|
||||||
|
b := it.At()
|
||||||
|
b.Count *= -1
|
||||||
|
h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
|
||||||
|
b, h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan, index,
|
||||||
|
)
|
||||||
|
index = b.Index
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// addBucket takes the "coordinates" of the last bucket that was handled and
|
||||||
|
// adds the provided bucket after it. If a corresponding bucket exists, the
|
||||||
|
// count is added. If not, the bucket is inserted. The updated slices and the
|
||||||
|
// coordinates of the inserted or added-to bucket are returned.
|
||||||
|
func addBucket(
|
||||||
|
b FloatBucket,
|
||||||
|
spans []Span, buckets []float64,
|
||||||
|
iSpan, iBucket int,
|
||||||
|
iInSpan, index int32,
|
||||||
|
) (
|
||||||
|
newSpans []Span, newBuckets []float64,
|
||||||
|
newISpan, newIBucket int, newIInSpan int32,
|
||||||
|
) {
|
||||||
|
if iSpan == -1 {
|
||||||
|
// First add, check if it is before all spans.
|
||||||
|
if len(spans) == 0 || spans[0].Offset > b.Index {
|
||||||
|
// Add bucket before all others.
|
||||||
|
buckets = append(buckets, 0)
|
||||||
|
copy(buckets[1:], buckets)
|
||||||
|
buckets[0] = b.Count
|
||||||
|
if spans[0].Offset == b.Index+1 {
|
||||||
|
spans[0].Length++
|
||||||
|
spans[0].Offset--
|
||||||
|
return spans, buckets, 0, 0, 0
|
||||||
|
}
|
||||||
|
spans = append(spans, Span{})
|
||||||
|
copy(spans[1:], spans)
|
||||||
|
spans[0] = Span{Offset: b.Index, Length: 1}
|
||||||
|
if len(spans) > 1 {
|
||||||
|
// Convert the absolute offset in the formerly
|
||||||
|
// first span to a relative offset.
|
||||||
|
spans[1].Offset -= b.Index + 1
|
||||||
|
}
|
||||||
|
return spans, buckets, 0, 0, 0
|
||||||
|
}
|
||||||
|
if spans[0].Offset == b.Index {
|
||||||
|
// Just add to first bucket.
|
||||||
|
buckets[0] += b.Count
|
||||||
|
return spans, buckets, 0, 0, 0
|
||||||
|
}
|
||||||
|
// We are behind the first bucket, so set everything to the
|
||||||
|
// first bucket and continue normally.
|
||||||
|
iSpan, iBucket, iInSpan = 0, 0, 0
|
||||||
|
index = spans[0].Offset
|
||||||
|
}
|
||||||
|
deltaIndex := b.Index - index
|
||||||
|
for {
|
||||||
|
remainingInSpan := int32(spans[iSpan].Length) - iInSpan
|
||||||
|
if deltaIndex < remainingInSpan {
|
||||||
|
// Bucket is in current span.
|
||||||
|
iBucket += int(deltaIndex)
|
||||||
|
iInSpan += deltaIndex
|
||||||
|
buckets[iBucket] += b.Count
|
||||||
|
return spans, buckets, iSpan, iBucket, iInSpan
|
||||||
|
}
|
||||||
|
deltaIndex -= remainingInSpan
|
||||||
|
iBucket += int(remainingInSpan)
|
||||||
|
iSpan++
|
||||||
|
if iSpan == len(spans) || deltaIndex < spans[iSpan].Offset {
|
||||||
|
// Bucket is in gap behind previous span (or there are no further spans).
|
||||||
|
buckets = append(buckets, 0)
|
||||||
|
copy(buckets[iBucket+1:], buckets[iBucket:])
|
||||||
|
buckets[iBucket] = b.Count
|
||||||
|
if deltaIndex == 0 {
|
||||||
|
// Directly after previous span, extend previous span.
|
||||||
|
if iSpan < len(spans) {
|
||||||
|
spans[iSpan].Offset--
|
||||||
|
}
|
||||||
|
iSpan--
|
||||||
|
iInSpan = int32(spans[iSpan].Length)
|
||||||
|
spans[iSpan].Length++
|
||||||
|
return spans, buckets, iSpan, iBucket, iInSpan
|
||||||
|
}
|
||||||
|
if iSpan < len(spans) && deltaIndex == spans[iSpan].Offset-1 {
|
||||||
|
// Directly before next span, extend next span.
|
||||||
|
iInSpan = 0
|
||||||
|
spans[iSpan].Offset--
|
||||||
|
spans[iSpan].Length++
|
||||||
|
return spans, buckets, iSpan, iBucket, iInSpan
|
||||||
|
}
|
||||||
|
// No next span, or next span is not directly adjacent to new bucket.
|
||||||
|
// Add new span.
|
||||||
|
iInSpan = 0
|
||||||
|
if iSpan < len(spans) {
|
||||||
|
spans[iSpan].Offset -= deltaIndex + 1
|
||||||
|
}
|
||||||
|
spans = append(spans, Span{})
|
||||||
|
copy(spans[iSpan+1:], spans[iSpan:])
|
||||||
|
spans[iSpan] = Span{Length: 1, Offset: deltaIndex}
|
||||||
|
return spans, buckets, iSpan, iBucket, iInSpan
|
||||||
|
}
|
||||||
|
// Try start of next span.
|
||||||
|
deltaIndex -= spans[iSpan].Offset
|
||||||
|
iInSpan = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact eliminates empty buckets at the beginning and end of each span, then
|
||||||
|
// merges spans that are consecutive or at most maxEmptyBuckets apart, and
|
||||||
|
// finally splits spans that contain more than maxEmptyBuckets. The compaction
|
||||||
|
// happens "in place" in the receiving histogram, but a pointer to it is
|
||||||
|
// returned for convenience.
|
||||||
|
func (h *FloatHistogram) Compact(maxEmptyBuckets int) *FloatHistogram {
|
||||||
|
// TODO(beorn7): Implement.
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectReset returns true if the receiving histogram is missing any buckets
|
||||||
|
// that have a non-zero population in the provided previous histogram. It also
|
||||||
|
// returns true if any count (in any bucket, in the zero count, or in the count
|
||||||
|
// of observations, but NOT the sum of observations) is smaller in the receiving
|
||||||
|
// histogram compared to the previous histogram. Otherwise, it returns false.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This method requires the Schema and the ZeroThreshold to be the
|
||||||
|
// same in both histograms. Otherwise, its behavior is undefined.
|
||||||
|
// TODO(beorn7): Change that!
|
||||||
|
//
|
||||||
|
// Note that this kind of reset detection is quite expensive. Ideally, resets
|
||||||
|
// are detected at ingest time and stored in the TSDB, so that the reset
|
||||||
|
// information can be read directly from there rather than be detected each time
|
||||||
|
// again.
|
||||||
|
func (h *FloatHistogram) DetectReset(previous *FloatHistogram) bool {
|
||||||
|
if h.Count < previous.Count {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if h.ZeroCount < previous.ZeroCount {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
currIt := h.PositiveBucketIterator()
|
||||||
|
prevIt := previous.PositiveBucketIterator()
|
||||||
|
if detectReset(currIt, prevIt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
currIt = h.NegativeBucketIterator()
|
||||||
|
prevIt = previous.NegativeBucketIterator()
|
||||||
|
return detectReset(currIt, prevIt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectReset(currIt, prevIt FloatBucketIterator) bool {
|
||||||
|
if !prevIt.Next() {
|
||||||
|
return false // If no buckets in previous histogram, nothing can be reset.
|
||||||
|
}
|
||||||
|
prevBucket := prevIt.At()
|
||||||
|
if !currIt.Next() {
|
||||||
|
// No bucket in current, but at least one in previous
|
||||||
|
// histogram. Check if any of those are non-zero, in which case
|
||||||
|
// this is a reset.
|
||||||
|
for {
|
||||||
|
if prevBucket.Count != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !prevIt.Next() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currBucket := currIt.At()
|
||||||
|
for {
|
||||||
|
// Forward currIt until we find the bucket corresponding to prevBucket.
|
||||||
|
for currBucket.Index < prevBucket.Index {
|
||||||
|
if !currIt.Next() {
|
||||||
|
// Reached end of currIt early, therefore
|
||||||
|
// previous histogram has a bucket that the
|
||||||
|
// current one does not have. Unlass all
|
||||||
|
// remaining buckets in the previous histogram
|
||||||
|
// are unpopulated, this is a reset.
|
||||||
|
for {
|
||||||
|
if prevBucket.Count != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !prevIt.Next() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currBucket = currIt.At()
|
||||||
|
}
|
||||||
|
if currBucket.Index > prevBucket.Index {
|
||||||
|
// Previous histogram has a bucket the current one does
|
||||||
|
// not have. If it's populated, it's a reset.
|
||||||
|
if prevBucket.Count != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We have reached corresponding buckets in both iterators.
|
||||||
|
// We can finally compare the counts.
|
||||||
|
if currBucket.Count < prevBucket.Count {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !prevIt.Next() {
|
||||||
|
// Reached end of prevIt without finding offending buckets.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prevBucket = prevIt.At()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PositiveBucketIterator returns a FloatBucketIterator to iterate over all
|
// PositiveBucketIterator returns a FloatBucketIterator to iterate over all
|
||||||
// positive buckets in ascending order (starting next to the zero bucket and
|
// positive buckets in ascending order (starting next to the zero bucket and
|
||||||
// going up).
|
// going up).
|
||||||
|
|
|
@ -0,0 +1,772 @@
|
||||||
|
// Copyright 2021 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 histogram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloatHistogramScale(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in *FloatHistogram
|
||||||
|
scale float64
|
||||||
|
expected *FloatHistogram
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"zero value",
|
||||||
|
&FloatHistogram{},
|
||||||
|
3.1415,
|
||||||
|
&FloatHistogram{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no-op",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"double",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 3493.3 * 2,
|
||||||
|
Sum: 2349209.324 * 2,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{2, 6.6, 8.4, 0.2},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{6.2, 6, 1.234e5 * 2, 2000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.expected, c.in.Scale(c.scale))
|
||||||
|
// Has it also happened in-place?
|
||||||
|
require.Equal(t, c.expected, c.in)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatHistogramDetectReset(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
previous, current *FloatHistogram
|
||||||
|
resetExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"zero values",
|
||||||
|
&FloatHistogram{},
|
||||||
|
&FloatHistogram{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no buckets to some buckets",
|
||||||
|
&FloatHistogram{},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"some buckets to no buckets",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one bucket appears, nothing else changes",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 1.23, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one bucket disappears, nothing else changes",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 1.23, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"an unpopulated bucket disappears, nothing else changes",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"an unpopulated bucket at the end disappears, nothing else changes",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2, 0},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 1}, {1, 2}},
|
||||||
|
PositiveBuckets: []float64{1, 3.3, 4.2},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"an unpopulated bucket disappears in a histogram with nothing else",
|
||||||
|
&FloatHistogram{
|
||||||
|
PositiveSpans: []Span{{23, 1}},
|
||||||
|
PositiveBuckets: []float64{0},
|
||||||
|
},
|
||||||
|
&FloatHistogram{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero count goes up",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.6,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero count goes down",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.4,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count goes up",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.4,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count goes down",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.2,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sum goes up",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349210,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sum goes down",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349200,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one positive bucket goes up",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.3, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one positive bucket goes down",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.1, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one negative bucket goes up",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3.1, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"one negative bucket goes down",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 5.5,
|
||||||
|
Count: 3493.3,
|
||||||
|
Sum: 2349209.324,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3.3, 4.2, 0.1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3.1, 2.9, 1.234e5, 1000},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.resetExpected, c.current.DetectReset(c.previous))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatHistogramCompact(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in *FloatHistogram
|
||||||
|
maxEmptyBuckets int
|
||||||
|
expected *FloatHistogram
|
||||||
|
}{
|
||||||
|
// TODO(beorn7): Add test cases.
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.expected, c.in.Compact(c.maxEmptyBuckets))
|
||||||
|
// Has it also happened in-place?
|
||||||
|
require.Equal(t, c.expected, c.in)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatHistogramAdd(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in1, in2, expected *FloatHistogram
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"same bucket layout",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{0, 0, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 5, 7, 13},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{4, 2, 9, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"same bucket layout, defined differently",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 1}, {0, 2}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 2}, {0, 1}},
|
||||||
|
PositiveBuckets: []float64{0, 0, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{3, 7}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 0, 0, 0, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 1}, {0, 2}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 5, 7, 13},
|
||||||
|
NegativeSpans: []Span{{3, 5}, {0, 2}},
|
||||||
|
NegativeBuckets: []float64{4, 2, 0, 0, 0, 9, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-overlapping spans",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{0, 2}, {3, 3}},
|
||||||
|
PositiveBuckets: []float64{5, 4, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{-9, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 4}, {0, 6}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{-9, 2}, {3, 2}, {5, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-overlapping inverted order",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{0, 2}, {3, 3}},
|
||||||
|
PositiveBuckets: []float64{5, 4, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{-9, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {0, 5}, {0, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{-9, 2}, {3, 2}, {5, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overlapping spans",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{-1, 4}, {0, 3}},
|
||||||
|
PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5},
|
||||||
|
NegativeSpans: []Span{{4, 2}, {1, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 4}, {0, 4}},
|
||||||
|
PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5},
|
||||||
|
NegativeSpans: []Span{{3, 3}, {1, 3}},
|
||||||
|
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overlapping spans inverted order",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 1.234,
|
||||||
|
PositiveSpans: []Span{{-1, 4}, {0, 3}},
|
||||||
|
PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5},
|
||||||
|
NegativeSpans: []Span{{4, 2}, {1, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 2.345,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {2, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 19,
|
||||||
|
Count: 51,
|
||||||
|
Sum: 3.579,
|
||||||
|
PositiveSpans: []Span{{-2, 5}, {0, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5},
|
||||||
|
NegativeSpans: []Span{{3, 3}, {1, 3}},
|
||||||
|
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.expected, c.in1.Add(c.in2))
|
||||||
|
// Has it also happened in-place?
|
||||||
|
require.Equal(t, c.expected, c.in1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloatHistogramSub(t *testing.T) {
|
||||||
|
// This has fewer test cases than TestFloatHistogramAdd because Add and
|
||||||
|
// Sub share most of the trickier code.
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in1, in2, expected *FloatHistogram
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"same bucket layout",
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 11,
|
||||||
|
Count: 30,
|
||||||
|
Sum: 23,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 3, 4, 7},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{3, 1, 5, 6},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 8,
|
||||||
|
Count: 21,
|
||||||
|
Sum: 12,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{0, 0, 2, 3, 6},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{1, 1, 4, 4},
|
||||||
|
},
|
||||||
|
&FloatHistogram{
|
||||||
|
ZeroThreshold: 0.01,
|
||||||
|
ZeroCount: 3,
|
||||||
|
Count: 9,
|
||||||
|
Sum: 11,
|
||||||
|
PositiveSpans: []Span{{-2, 2}, {1, 3}},
|
||||||
|
PositiveBuckets: []float64{1, 0, 1, 1, 1},
|
||||||
|
NegativeSpans: []Span{{3, 2}, {3, 2}},
|
||||||
|
NegativeBuckets: []float64{2, 0, 1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.expected, c.in1.Sub(c.in2))
|
||||||
|
// Has it also happened in-place?
|
||||||
|
require.Equal(t, c.expected, c.in1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/goleak"
|
"go.uber.org/goleak"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/model/histogram"
|
||||||
"github.com/prometheus/prometheus/model/labels"
|
"github.com/prometheus/prometheus/model/labels"
|
||||||
"github.com/prometheus/prometheus/model/timestamp"
|
"github.com/prometheus/prometheus/model/timestamp"
|
||||||
"github.com/prometheus/prometheus/promql/parser"
|
"github.com/prometheus/prometheus/promql/parser"
|
||||||
|
@ -2619,11 +2620,8 @@ func TestRangeQuery(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSparseHistogramRate(t *testing.T) {
|
func TestSparseHistogramRate(t *testing.T) {
|
||||||
// Currently, this test it to only find panics or errors in the engine execution path.
|
// TODO(beorn7): Integrate histograms into the PromQL testing framework
|
||||||
// The panic stack trace will mostly tell you what code path is breaking and needs fixing for
|
// and write more tests there.
|
||||||
// fetching the raw histograms and passing it rightly upto the rate() function implementation.
|
|
||||||
// TODO: Check the result for correctness once implementation is ready.
|
|
||||||
|
|
||||||
test, err := NewTest(t, "")
|
test, err := NewTest(t, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer test.Close()
|
defer test.Close()
|
||||||
|
@ -2646,4 +2644,21 @@ func TestSparseHistogramRate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res := qry.Exec(test.Context())
|
res := qry.Exec(test.Context())
|
||||||
require.NoError(t, res.Err)
|
require.NoError(t, res.Err)
|
||||||
|
vector, err := res.Vector()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, vector, 1)
|
||||||
|
actualHistogram := vector[0].H
|
||||||
|
expectedHistogram := &histogram.FloatHistogram{
|
||||||
|
Schema: 1,
|
||||||
|
ZeroThreshold: 0.001,
|
||||||
|
ZeroCount: 1. / 15.,
|
||||||
|
Count: 4. / 15.,
|
||||||
|
Sum: 1.226666666666667,
|
||||||
|
PositiveSpans: []histogram.Span{
|
||||||
|
{Offset: 0, Length: 2},
|
||||||
|
{Offset: 1, Length: 2},
|
||||||
|
},
|
||||||
|
PositiveBuckets: []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedHistogram, actualHistogram)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/model/histogram"
|
||||||
"github.com/prometheus/prometheus/model/labels"
|
"github.com/prometheus/prometheus/model/labels"
|
||||||
"github.com/prometheus/prometheus/promql/parser"
|
"github.com/prometheus/prometheus/promql/parser"
|
||||||
)
|
)
|
||||||
|
@ -60,9 +61,11 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
|
||||||
ms := args[0].(*parser.MatrixSelector)
|
ms := args[0].(*parser.MatrixSelector)
|
||||||
vs := ms.VectorSelector.(*parser.VectorSelector)
|
vs := ms.VectorSelector.(*parser.VectorSelector)
|
||||||
var (
|
var (
|
||||||
samples = vals[0].(Matrix)[0]
|
samples = vals[0].(Matrix)[0]
|
||||||
rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset)
|
rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset)
|
||||||
rangeEnd = enh.Ts - durationMilliseconds(vs.Offset)
|
rangeEnd = enh.Ts - durationMilliseconds(vs.Offset)
|
||||||
|
resultValue float64
|
||||||
|
resultHistogram *histogram.FloatHistogram
|
||||||
)
|
)
|
||||||
|
|
||||||
// No sense in trying to compute a rate without at least two points. Drop
|
// No sense in trying to compute a rate without at least two points. Drop
|
||||||
|
@ -71,14 +74,32 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
|
||||||
return enh.Out
|
return enh.Out
|
||||||
}
|
}
|
||||||
|
|
||||||
resultValue := samples.Points[len(samples.Points)-1].V - samples.Points[0].V
|
if samples.Points[0].H != nil {
|
||||||
if isCounter {
|
resultHistogram = histogramRate(samples.Points, isCounter)
|
||||||
var lastValue float64
|
if resultHistogram == nil {
|
||||||
for _, sample := range samples.Points {
|
// Points are a mix of floats and histograms, or the histograms
|
||||||
if sample.V < lastValue {
|
// are not compatible with each other.
|
||||||
resultValue += lastValue
|
// TODO(beorn7): find a way of communicating the exact reason
|
||||||
|
return enh.Out
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultValue = samples.Points[len(samples.Points)-1].V - samples.Points[0].V
|
||||||
|
prevValue := samples.Points[0].V
|
||||||
|
// We have to iterate through everything even in the non-counter
|
||||||
|
// case because we have to check that everything is a float.
|
||||||
|
// TODO(beorn7): Find a way to check that earlier, e.g. by
|
||||||
|
// handing in a []FloatPoint and a []HistogramPoint separately.
|
||||||
|
for _, currPoint := range samples.Points[1:] {
|
||||||
|
if currPoint.H != nil {
|
||||||
|
return nil // Range contains a mix of histograms and floats.
|
||||||
}
|
}
|
||||||
lastValue = sample.V
|
if !isCounter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if currPoint.V < prevValue {
|
||||||
|
resultValue += prevValue
|
||||||
|
}
|
||||||
|
prevValue = currPoint.V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +110,7 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
|
||||||
sampledInterval := float64(samples.Points[len(samples.Points)-1].T-samples.Points[0].T) / 1000
|
sampledInterval := float64(samples.Points[len(samples.Points)-1].T-samples.Points[0].T) / 1000
|
||||||
averageDurationBetweenSamples := sampledInterval / float64(len(samples.Points)-1)
|
averageDurationBetweenSamples := sampledInterval / float64(len(samples.Points)-1)
|
||||||
|
|
||||||
|
// TODO(beorn7): Do this for histograms, too.
|
||||||
if isCounter && resultValue > 0 && samples.Points[0].V >= 0 {
|
if isCounter && resultValue > 0 && samples.Points[0].V >= 0 {
|
||||||
// Counters cannot be negative. If we have any slope at
|
// Counters cannot be negative. If we have any slope at
|
||||||
// all (i.e. resultValue went up), we can extrapolate
|
// all (i.e. resultValue went up), we can extrapolate
|
||||||
|
@ -120,16 +142,60 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
|
||||||
} else {
|
} else {
|
||||||
extrapolateToInterval += averageDurationBetweenSamples / 2
|
extrapolateToInterval += averageDurationBetweenSamples / 2
|
||||||
}
|
}
|
||||||
resultValue = resultValue * (extrapolateToInterval / sampledInterval)
|
factor := extrapolateToInterval / sampledInterval
|
||||||
if isRate {
|
if isRate {
|
||||||
resultValue = resultValue / ms.Range.Seconds()
|
factor /= ms.Range.Seconds()
|
||||||
|
}
|
||||||
|
if resultHistogram == nil {
|
||||||
|
resultValue *= factor
|
||||||
|
} else {
|
||||||
|
resultHistogram.Scale(factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(enh.Out, Sample{
|
return append(enh.Out, Sample{
|
||||||
Point: Point{V: resultValue},
|
Point: Point{V: resultValue, H: resultHistogram},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// histogramRate is a helper function for extrapolatedRate. It requires
|
||||||
|
// points[0] to be a histogram. It returns nil if any other Point in points is
|
||||||
|
// not a histogram. Currently, it also returns nil on mixed schemas or zero
|
||||||
|
// thresholds in the histograms, because it cannot handle those schema changes
|
||||||
|
// yet.
|
||||||
|
func histogramRate(points []Point, isCounter bool) *histogram.FloatHistogram {
|
||||||
|
prev := points[0].H // We already know that this is a histogram.
|
||||||
|
last := points[len(points)-1].H
|
||||||
|
if last == nil {
|
||||||
|
return nil // Last point in range is not a histogram.
|
||||||
|
}
|
||||||
|
if last.Schema != prev.Schema || last.ZeroThreshold != prev.ZeroThreshold {
|
||||||
|
return nil // TODO(beorn7): Handle schema changes properly.
|
||||||
|
}
|
||||||
|
h := last.Copy()
|
||||||
|
h.Sub(prev)
|
||||||
|
// We have to iterate through everything even in the non-counter case
|
||||||
|
// because we have to check that everything is a histogram.
|
||||||
|
// TODO(beorn7): Find a way to check that earlier, e.g. by handing in a
|
||||||
|
// []FloatPoint and a []HistogramPoint separately.
|
||||||
|
for _, currPoint := range points[1:] {
|
||||||
|
curr := currPoint.H
|
||||||
|
if curr == nil {
|
||||||
|
return nil // Range contains a mix of histograms and floats.
|
||||||
|
}
|
||||||
|
if !isCounter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if curr.Schema != prev.Schema || curr.ZeroThreshold != prev.ZeroThreshold {
|
||||||
|
return nil // TODO(beorn7): Handle schema changes properly.
|
||||||
|
}
|
||||||
|
if curr.DetectReset(prev) {
|
||||||
|
h.Add(prev)
|
||||||
|
}
|
||||||
|
prev = curr
|
||||||
|
}
|
||||||
|
return h.Compact(3)
|
||||||
|
}
|
||||||
|
|
||||||
// === delta(Matrix parser.ValueTypeMatrix) Vector ===
|
// === delta(Matrix parser.ValueTypeMatrix) Vector ===
|
||||||
func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
|
func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
|
||||||
return extrapolatedRate(vals, args, enh, false, false)
|
return extrapolatedRate(vals, args, enh, false, false)
|
||||||
|
|
Loading…
Reference in New Issue