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.
|
// 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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user