Add Histogram.VisitNonZeroBuckets
This commit is contained in:
parent
eab0e32ed4
commit
839018719c
62
histogram.go
62
histogram.go
@ -91,6 +91,10 @@ func GetOrCreateHistogram(name string) *Histogram {
|
||||
//
|
||||
// v cannot be negative.
|
||||
func (h *Histogram) Update(v float64) {
|
||||
if math.IsNaN(v) || v < 0 {
|
||||
// Skip NaNs and negative values.
|
||||
return
|
||||
}
|
||||
idx := getBucketIdx(v)
|
||||
if idx >= uint(len(h.buckets)) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
count := atomic.LoadUint64(&h.count)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
for i := range h.buckets[:] {
|
||||
h.marshalBucket(prefix, w, i)
|
||||
h.marshalBucket(prefix, w, uint(i))
|
||||
}
|
||||
// Marshal `_sum` and `_count` metrics.
|
||||
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)
|
||||
}
|
||||
|
||||
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]
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
start := "0"
|
||||
if idx > 0 {
|
||||
start = getRangeEndFromBucketIdx(uint(idx - 1))
|
||||
}
|
||||
end := getRangeEndFromBucketIdx(uint(idx))
|
||||
tag := fmt.Sprintf(`vmrange="%s...%s"`, start, end)
|
||||
tag := getTagForBucketIdx(idx)
|
||||
prefix = addTag(prefix, tag)
|
||||
name, filters := splitMetricName(prefix)
|
||||
fmt.Fprintf(w, "%s_bucket%s %d\n", name, filters, v)
|
||||
|
@ -4,18 +4,12 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHistogramUpdateNegativeValue(t *testing.T) {
|
||||
h := NewHistogram("TestHisogramUpdateNegativeValue")
|
||||
expectPanic(t, "negative value", func() {
|
||||
h.Update(-123)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetBucketIdx(t *testing.T) {
|
||||
f := func(v float64, idxExpected uint) {
|
||||
t.Helper()
|
||||
@ -126,6 +120,11 @@ func TestHistogramSerial(t *testing.T) {
|
||||
}
|
||||
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,
|
||||
// since now it contains values.
|
||||
bb.Reset()
|
||||
@ -149,6 +148,21 @@ func TestHistogramConcurrent(t *testing.T) {
|
||||
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")
|
||||
|
||||
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) {
|
||||
|
@ -106,6 +106,7 @@ func testWritePrometheus() error {
|
||||
func expectPanic(t *testing.T, context string, f func()) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
t.Helper()
|
||||
if r := recover(); r == nil {
|
||||
t.Fatalf("expecting panic in %s", context)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user