diff --git a/prometheus/histogram.go b/prometheus/histogram.go index a0e4b4e..1e3c4e1 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -238,11 +238,11 @@ var DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} // DefSparseBucketsZeroThreshold is the default value for // SparseBucketsZeroThreshold in the HistogramOpts. +// +// The value is 2^-128 (or 0.5*2^-127 in the actual IEEE 754 representation), +// which is a bucket boundary at all possible resolutions. const DefSparseBucketsZeroThreshold = 2.938735877055719e-39 -// This is 2^-128 (or 0.5*2^-127 in the actual IEEE 754 representation), which -// is a bucket boundary at all possible resolutions. - var errBucketLabelNotAllowed = fmt.Errorf( "%q is not allowed as label name in histograms", bucketLabel, ) diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 3514e81..61d8047 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -456,3 +456,93 @@ func TestHistogramExemplar(t *testing.T) { } } } + +func TestSparseHistogram(t *testing.T) { + + scenarios := []struct { + name string + observations []float64 + factor float64 + zeroThreshold float64 + want string // String representation of protobuf. + }{ + { + name: "no sparse buckets", + observations: []float64{1, 2, 3}, + factor: 1, + want: `sample_count:3 sample_sum:6 bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: sb_schema:0 sb_zero_threshold:0 `, // Has conventional buckets because there are no sparse buckets. + }, + { + name: "factor 1.1 results in schema 3", + observations: []float64{0, 1, 2, 3}, + factor: 1.1, + want: `sample_count:4 sample_sum:6 sb_schema:3 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_positive: span: span: delta:1 delta:0 delta:0 > `, + }, + { + name: "factor 1.2 results in schema 2", + observations: []float64{0, 1, 1.2, 1.4, 1.8, 2}, + factor: 1.2, + want: `sample_count:6 sample_sum:7.4 sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_positive: delta:1 delta:-1 delta:2 delta:-2 delta:2 > `, + }, + { + name: "negative buckets", + observations: []float64{0, -1, -1.2, -1.4, -1.8, -2}, + factor: 1.2, + want: `sample_count:6 sample_sum:-7.4 sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_negative: delta:1 delta:-1 delta:2 delta:-2 delta:2 > `, + }, + { + name: "negative and positive buckets", + observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2}, + factor: 1.2, + want: `sample_count:11 sample_sum:0 sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_negative: delta:1 delta:-1 delta:2 delta:-2 delta:2 > sb_positive: delta:1 delta:-1 delta:2 delta:-2 delta:2 > `, + }, + { + name: "wide zero bucket", + observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2}, + factor: 1.2, + zeroThreshold: 1.4, + want: `sample_count:11 sample_sum:0 sb_schema:2 sb_zero_threshold:1.4 sb_zero_count:7 sb_negative: delta:2 > sb_positive: delta:2 > `, + }, + { + name: "NaN observation", + observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.NaN()}, + factor: 1.2, + want: `sample_count:7 sample_sum:nan sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_positive: delta:1 delta:-1 delta:2 delta:-2 delta:2 > `, + }, + { + name: "+Inf observation", + observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(+1)}, + factor: 1.2, + want: `sample_count:7 sample_sum:inf sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_positive: span: delta:1 delta:-1 delta:2 delta:-2 delta:2 delta:-1 > `, + }, + { + name: "-Inf observation", + observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(-1)}, + factor: 1.2, + want: `sample_count:7 sample_sum:-inf sb_schema:2 sb_zero_threshold:2.938735877055719e-39 sb_zero_count:1 sb_negative: delta:1 > sb_positive: delta:1 delta:-1 delta:2 delta:-2 delta:2 > `, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + his := NewHistogram(HistogramOpts{ + Name: "name", + Help: "help", + SparseBucketsFactor: s.factor, + SparseBucketsZeroThreshold: s.zeroThreshold, + }) + for _, o := range s.observations { + his.Observe(o) + } + m := &dto.Metric{} + if err := his.Write(m); err != nil { + t.Fatal("unexpected error writing metric", err) + } + got := m.Histogram.String() + if s.want != got { + t.Errorf("want histogram %q, got %q", s.want, got) + } + }) + } + +}