From 577f3b5e9930e48383a6be378f8ced208b4ffaf4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Sat, 23 Nov 2019 00:46:51 +0200 Subject: [PATCH] Remove rounding error when marshaling histogram ranges --- histogram.go | 21 +++++++++++------ histogram_test.go | 60 +++++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/histogram.go b/histogram.go index 48056ec..16e6989 100644 --- a/histogram.go +++ b/histogram.go @@ -132,12 +132,12 @@ func (h *Histogram) marshalBucket(prefix string, w io.Writer, idx int) { if v == 0 { return } - start := float64(0) + start := "0" if idx > 0 { start = getRangeEndFromBucketIdx(uint(idx - 1)) } end := getRangeEndFromBucketIdx(uint(idx)) - tag := fmt.Sprintf(`vmrange="%g...%g"`, start, end) + tag := fmt.Sprintf(`vmrange="%s...%s"`, start, end) prefix = addTag(prefix, tag) name, filters := splitMetricName(prefix) fmt.Fprintf(w, "%s_vmbucket%s %d\n", name, filters, v) @@ -180,20 +180,27 @@ func getBucketIdx(v float64) uint { return 1 + m + uint(e10-e10Min)*9 } -func getRangeEndFromBucketIdx(idx uint) float64 { +func getRangeEndFromBucketIdx(idx uint) string { if idx == 0 { - return 0 + return "0" } if idx == 1 { - return math.Pow10(e10Min) + return fmt.Sprintf("1e%d", e10Min) } if idx >= bucketsCount-1 { - return math.Inf(1) + return "+Inf" } idx -= 2 e10 := e10Min + int(idx/9) m := 2 + (idx % 9) - return math.Pow10(e10) * float64(m) + if m == 10 { + e10++ + m = 1 + } + if e10 == 0 { + return fmt.Sprintf("%d", m) + } + return fmt.Sprintf("%de%d", m, e10) } // Each range (10^n..10^(n+1)] for e10Min<=n<=e10Max is split into 9 equal sub-ranges, plus 3 additional buckets: diff --git a/histogram_test.go b/histogram_test.go index a4581ff..f512e50 100644 --- a/histogram_test.go +++ b/histogram_test.go @@ -66,37 +66,37 @@ func TestGetBucketIdx(t *testing.T) { } func TestGetRangeEndFromBucketIdx(t *testing.T) { - f := func(idx uint, endExpected float64) { + f := func(idx uint, endExpected string) { t.Helper() end := getRangeEndFromBucketIdx(idx) if end != endExpected { - t.Fatalf("unexpected getRangeEndFromBucketIdx(%d); got %g; want %g", idx, end, endExpected) + t.Fatalf("unexpected getRangeEndFromBucketIdx(%d); got %s; want %s", idx, end, endExpected) } } - f(0, 0) - f(1, math.Pow10(e10Min)) - f(2, 2*math.Pow10(e10Min)) - f(3, 3*math.Pow10(e10Min)) - f(9, 9*math.Pow10(e10Min)) - f(10, math.Pow10(e10Min+1)) - f(11, 2*math.Pow10(e10Min+1)) - f(16, 7*math.Pow10(e10Min+1)) - f(17, 8*math.Pow10(e10Min+1)) - f(18, 9*math.Pow10(e10Min+1)) - f(19, math.Pow10(e10Min+2)) - f(20, 2*math.Pow10(e10Min+2)) - f(21, 3*math.Pow10(e10Min+2)) - f(bucketsCount-21, 9*math.Pow10(e10Max-2)) - f(bucketsCount-20, math.Pow10(e10Max-1)) - f(bucketsCount-16, 5*math.Pow10(e10Max-1)) - f(bucketsCount-13, 8*math.Pow10(e10Max-1)) - f(bucketsCount-12, 9*math.Pow10(e10Max-1)) - f(bucketsCount-11, math.Pow10(e10Max)) - f(bucketsCount-10, 2*math.Pow10(e10Max)) - f(bucketsCount-4, 8*math.Pow10(e10Max)) - f(bucketsCount-3, 9*math.Pow10(e10Max)) - f(bucketsCount-2, math.Pow10(e10Max+1)) - f(bucketsCount-1, math.Inf(1)) + f(0, "0") + f(1, fmt.Sprintf("1e%d", e10Min)) + f(2, fmt.Sprintf("2e%d", e10Min)) + f(3, fmt.Sprintf("3e%d", e10Min)) + f(9, fmt.Sprintf("9e%d", e10Min)) + f(10, fmt.Sprintf("1e%d", e10Min+1)) + f(11, fmt.Sprintf("2e%d", e10Min+1)) + f(16, fmt.Sprintf("7e%d", e10Min+1)) + f(17, fmt.Sprintf("8e%d", e10Min+1)) + f(18, fmt.Sprintf("9e%d", e10Min+1)) + f(19, fmt.Sprintf("1e%d", e10Min+2)) + f(20, fmt.Sprintf("2e%d", e10Min+2)) + f(21, fmt.Sprintf("3e%d", e10Min+2)) + f(bucketsCount-21, fmt.Sprintf("9e%d", e10Max-2)) + f(bucketsCount-20, fmt.Sprintf("1e%d", e10Max-1)) + f(bucketsCount-16, fmt.Sprintf("5e%d", e10Max-1)) + f(bucketsCount-13, fmt.Sprintf("8e%d", e10Max-1)) + f(bucketsCount-12, fmt.Sprintf("9e%d", e10Max-1)) + f(bucketsCount-11, fmt.Sprintf("1e%d", e10Max)) + f(bucketsCount-10, fmt.Sprintf("2e%d", e10Max)) + f(bucketsCount-4, fmt.Sprintf("8e%d", e10Max)) + f(bucketsCount-3, fmt.Sprintf("9e%d", e10Max)) + f(bucketsCount-2, fmt.Sprintf("1e%d", e10Max+1)) + f(bucketsCount-1, "+Inf") } func TestHistogramSerial(t *testing.T) { @@ -117,8 +117,8 @@ func TestHistogramSerial(t *testing.T) { } // Make sure the histogram prints _xbucket on marshalTo call - testMarshalTo(t, h, "prefix", "prefix_vmbucket{vmrange=\"80...90\"} 7\nprefix_vmbucket{vmrange=\"90...100\"} 10\nprefix_vmbucket{vmrange=\"100...200\"} 100\nprefix_vmbucket{vmrange=\"200...300\"} 100\nprefix_vmbucket{vmrange=\"300...400\"} 23\nprefix_sum 48840\nprefix_count 240\n") - testMarshalTo(t, h, ` m{foo="bar"}`, "\t m_vmbucket{foo=\"bar\",vmrange=\"80...90\"} 7\n\t m_vmbucket{foo=\"bar\",vmrange=\"90...100\"} 10\n\t m_vmbucket{foo=\"bar\",vmrange=\"100...200\"} 100\n\t m_vmbucket{foo=\"bar\",vmrange=\"200...300\"} 100\n\t m_vmbucket{foo=\"bar\",vmrange=\"300...400\"} 23\n\t m_sum{foo=\"bar\"} 48840\n\t m_count{foo=\"bar\"} 240\n") + testMarshalTo(t, h, "prefix", "prefix_vmbucket{vmrange=\"8e1...9e1\"} 7\nprefix_vmbucket{vmrange=\"9e1...1e2\"} 10\nprefix_vmbucket{vmrange=\"1e2...2e2\"} 100\nprefix_vmbucket{vmrange=\"2e2...3e2\"} 100\nprefix_vmbucket{vmrange=\"3e2...4e2\"} 23\nprefix_sum 48840\nprefix_count 240\n") + testMarshalTo(t, h, ` m{foo="bar"}`, "\t m_vmbucket{foo=\"bar\",vmrange=\"8e1...9e1\"} 7\n\t m_vmbucket{foo=\"bar\",vmrange=\"9e1...1e2\"} 10\n\t m_vmbucket{foo=\"bar\",vmrange=\"1e2...2e2\"} 100\n\t m_vmbucket{foo=\"bar\",vmrange=\"2e2...3e2\"} 100\n\t m_vmbucket{foo=\"bar\",vmrange=\"3e2...4e2\"} 23\n\t m_sum{foo=\"bar\"} 48840\n\t m_count{foo=\"bar\"} 240\n") // Verify supported ranges for i := -100; i < 100; i++ { @@ -148,7 +148,7 @@ func TestHistogramConcurrent(t *testing.T) { if err != nil { t.Fatal(err) } - testMarshalTo(t, h, "prefix", "prefix_vmbucket{vmrange=\"0...0\"} 5\nprefix_vmbucket{vmrange=\"0.9...1\"} 5\nprefix_vmbucket{vmrange=\"1...2\"} 5\nprefix_vmbucket{vmrange=\"2...3\"} 5\nprefix_vmbucket{vmrange=\"3...4\"} 5\nprefix_vmbucket{vmrange=\"4...5\"} 5\nprefix_vmbucket{vmrange=\"5...6\"} 5\nprefix_vmbucket{vmrange=\"6...7\"} 5\nprefix_vmbucket{vmrange=\"7...8\"} 5\nprefix_vmbucket{vmrange=\"8...9\"} 5\nprefix_sum 225\nprefix_count 50\n") + testMarshalTo(t, h, "prefix", "prefix_vmbucket{vmrange=\"0...0\"} 5\nprefix_vmbucket{vmrange=\"9e-1...1\"} 5\nprefix_vmbucket{vmrange=\"1...2\"} 5\nprefix_vmbucket{vmrange=\"2...3\"} 5\nprefix_vmbucket{vmrange=\"3...4\"} 5\nprefix_vmbucket{vmrange=\"4...5\"} 5\nprefix_vmbucket{vmrange=\"5...6\"} 5\nprefix_vmbucket{vmrange=\"6...7\"} 5\nprefix_vmbucket{vmrange=\"7...8\"} 5\nprefix_vmbucket{vmrange=\"8...9\"} 5\nprefix_sum 225\nprefix_count 50\n") } func TestHistogramWithTags(t *testing.T) { @@ -159,7 +159,7 @@ func TestHistogramWithTags(t *testing.T) { var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() - namePrefixWithTag := `TestHistogram_vmbucket{tag="foo",vmrange="100...200"} 1` + "\n" + namePrefixWithTag := `TestHistogram_vmbucket{tag="foo",vmrange="1e2...2e2"} 1` + "\n" if !strings.Contains(result, namePrefixWithTag) { t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", namePrefixWithTag, result) }