Add Histogram.VisitNonZeroBuckets

This commit is contained in:
Aliaksandr Valialkin 2019-11-23 23:58:18 +02:00
parent eab0e32ed4
commit 839018719c
3 changed files with 76 additions and 15 deletions

View File

@ -91,6 +91,10 @@ func GetOrCreateHistogram(name string) *Histogram {
// //
// v cannot be negative. // v cannot be negative.
func (h *Histogram) Update(v float64) { func (h *Histogram) Update(v float64) {
if math.IsNaN(v) || v < 0 {
// Skip NaNs and negative values.
return
}
idx := getBucketIdx(v) idx := getBucketIdx(v)
if idx >= uint(len(h.buckets)) { if idx >= uint(len(h.buckets)) {
panic(fmt.Errorf("BUG: idx cannot exceed %d; got %d", len(h.buckets), idx)) panic(fmt.Errorf("BUG: idx cannot exceed %d; got %d", len(h.buckets), idx))
@ -108,13 +112,60 @@ func (h *Histogram) UpdateDuration(startTime time.Time) {
h.Update(d) h.Update(d)
} }
// VisitNonZeroBuckets calls f for all buckets with non-zero counters.
func (h *Histogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {
for i, v := range h.buckets[:] {
if v == 0 {
continue
}
vmrange := getRangeForBucketIdx(uint(i))
f(vmrange, v)
}
}
func getRangeForBucketIdx(idx uint) string {
bucketRangesOnce.Do(initBucketRanges)
return bucketRanges[idx]
}
func initBucketRanges() {
start := "0"
for i := 0; i < bucketsCount; i++ {
end := getRangeEndFromBucketIdx(uint(i))
bucketRanges[i] = start + "..." + end
start = end
}
}
var (
bucketRanges [bucketsCount]string
bucketRangesOnce sync.Once
)
func getTagForBucketIdx(idx uint) string {
bucketTagsOnce.Do(initBucketTags)
return bucketTags[idx]
}
func initBucketTags() {
for i := 0; i < bucketsCount; i++ {
vmrange := getRangeForBucketIdx(uint(i))
bucketTags[i] = fmt.Sprintf(`vmrange=%q`, vmrange)
}
}
var (
bucketTags [bucketsCount]string
bucketTagsOnce sync.Once
)
func (h *Histogram) marshalTo(prefix string, w io.Writer) { func (h *Histogram) marshalTo(prefix string, w io.Writer) {
count := atomic.LoadUint64(&h.count) count := atomic.LoadUint64(&h.count)
if count == 0 { if count == 0 {
return return
} }
for i := range h.buckets[:] { for i := range h.buckets[:] {
h.marshalBucket(prefix, w, i) h.marshalBucket(prefix, w, uint(i))
} }
// Marshal `_sum` and `_count` metrics. // Marshal `_sum` and `_count` metrics.
name, filters := splitMetricName(prefix) name, filters := splitMetricName(prefix)
@ -129,17 +180,12 @@ func (h *Histogram) marshalTo(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s_count%s %d\n", name, filters, count) fmt.Fprintf(w, "%s_count%s %d\n", name, filters, count)
} }
func (h *Histogram) marshalBucket(prefix string, w io.Writer, idx int) { func (h *Histogram) marshalBucket(prefix string, w io.Writer, idx uint) {
v := h.buckets[idx] v := h.buckets[idx]
if v == 0 { if v == 0 {
return return
} }
start := "0" tag := getTagForBucketIdx(idx)
if idx > 0 {
start = getRangeEndFromBucketIdx(uint(idx - 1))
}
end := getRangeEndFromBucketIdx(uint(idx))
tag := fmt.Sprintf(`vmrange="%s...%s"`, start, end)
prefix = addTag(prefix, tag) prefix = addTag(prefix, tag)
name, filters := splitMetricName(prefix) name, filters := splitMetricName(prefix)
fmt.Fprintf(w, "%s_bucket%s %d\n", name, filters, v) fmt.Fprintf(w, "%s_bucket%s %d\n", name, filters, v)

View File

@ -4,18 +4,12 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"math" "math"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
) )
func TestHistogramUpdateNegativeValue(t *testing.T) {
h := NewHistogram("TestHisogramUpdateNegativeValue")
expectPanic(t, "negative value", func() {
h.Update(-123)
})
}
func TestGetBucketIdx(t *testing.T) { func TestGetBucketIdx(t *testing.T) {
f := func(v float64, idxExpected uint) { f := func(v float64, idxExpected uint) {
t.Helper() t.Helper()
@ -126,6 +120,11 @@ func TestHistogramSerial(t *testing.T) {
} }
h.UpdateDuration(time.Now().Add(-time.Minute)) h.UpdateDuration(time.Now().Add(-time.Minute))
// Verify edge cases
h.Update(math.Inf(1))
h.Update(math.NaN())
h.Update(-123)
// Make sure the histogram becomes visible in the output of WritePrometheus, // Make sure the histogram becomes visible in the output of WritePrometheus,
// since now it contains values. // since now it contains values.
bb.Reset() bb.Reset()
@ -149,6 +148,21 @@ func TestHistogramConcurrent(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
testMarshalTo(t, h, "prefix", "prefix_bucket{vmrange=\"0...0\"} 5\nprefix_bucket{vmrange=\"9e-1...1\"} 5\nprefix_bucket{vmrange=\"1...2\"} 5\nprefix_bucket{vmrange=\"2...3\"} 5\nprefix_bucket{vmrange=\"3...4\"} 5\nprefix_bucket{vmrange=\"4...5\"} 5\nprefix_bucket{vmrange=\"5...6\"} 5\nprefix_bucket{vmrange=\"6...7\"} 5\nprefix_bucket{vmrange=\"7...8\"} 5\nprefix_bucket{vmrange=\"8...9\"} 5\nprefix_sum 225\nprefix_count 50\n") testMarshalTo(t, h, "prefix", "prefix_bucket{vmrange=\"0...0\"} 5\nprefix_bucket{vmrange=\"9e-1...1\"} 5\nprefix_bucket{vmrange=\"1...2\"} 5\nprefix_bucket{vmrange=\"2...3\"} 5\nprefix_bucket{vmrange=\"3...4\"} 5\nprefix_bucket{vmrange=\"4...5\"} 5\nprefix_bucket{vmrange=\"5...6\"} 5\nprefix_bucket{vmrange=\"6...7\"} 5\nprefix_bucket{vmrange=\"7...8\"} 5\nprefix_bucket{vmrange=\"8...9\"} 5\nprefix_sum 225\nprefix_count 50\n")
var labels []string
var values []uint64
h.VisitNonZeroBuckets(func(label string, value uint64) {
labels = append(labels, label)
values = append(values, value)
})
labelsExpected := []string{"0...0", "9e-1...1", "1...2", "2...3", "3...4", "4...5", "5...6", "6...7", "7...8", "8...9"}
if !reflect.DeepEqual(labels, labelsExpected) {
t.Fatalf("unexpected labels; got %v; want %v", labels, labelsExpected)
}
valuesExpected := []uint64{5, 5, 5, 5, 5, 5, 5, 5, 5, 5}
if !reflect.DeepEqual(values, valuesExpected) {
t.Fatalf("unexpected values; got %v; want %v", values, valuesExpected)
}
} }
func TestHistogramWithTags(t *testing.T) { func TestHistogramWithTags(t *testing.T) {

View File

@ -106,6 +106,7 @@ func testWritePrometheus() error {
func expectPanic(t *testing.T, context string, f func()) { func expectPanic(t *testing.T, context string, f func()) {
t.Helper() t.Helper()
defer func() { defer func() {
t.Helper()
if r := recover(); r == nil { if r := recover(); r == nil {
t.Fatalf("expecting panic in %s", context) t.Fatalf("expecting panic in %s", context)
} }