Status API: use jsonpb for json marshalling of prometheus metrics (#4324)

* Wrap the prometheus portion of our metrics in such a way that they use jsonpb for
   encoding to JSON, as prescribed by the protobuf library.

   Note: We're using jsonpb, not protojson, because there is no protobuf V2 version of
   github.com/prometheus/client_golang

* build(deps): bump github.com/prometheus/client_golang (#4307)

 This reverts commit 2f298db68c.

* CHANGELOG.md: add note re: JSON encoding of Status API payloads

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
Stephan Renatus
2022-02-22 09:25:19 +01:00
committed by GitHub
parent 1dfa6bd723
commit 9afdad7919
13 changed files with 495 additions and 101 deletions
+105
View File
@@ -5,6 +5,111 @@ project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
### Backwards incompatible changes
The JSON representation of the Status API's payloads -- both for `GET /v1/status`
responses and the metrics sent to a remote Status API endpoint -- have changed:
Previously, they had been serialized into JSON using the standard library "encoding/json"
methods. However, the metrics coming from the Prometheus integration are only available
in Golang structs generated from Protobuf definitions. For serializing these into JSON,
the standard library functions are unsuited:
- enums would be converted into numbers,
- field names would be `snake_case`, not `camelCase`,
- and NaNs would cause the encoder to panic.
Now, we're using the protobuf ecosystem's `jsonpb` package, to serialize the Prometheus
metrics into JSON in a way that is compliant with the Protobuf specification.
Concretely, what would before be
```
"metrics": {
"prometheus": {
"go_gc_duration_seconds": {
"help": "A summary of the GC invocation durations.",
"metric": [
{
"summary": {
"quantile": [
{
"quantile": 0,
"value": 0.000011799
},
{
"quantile": 0.25,
"value": 0.000011905
},
{
"quantile": 0.5,
"value": 0.000040002
},
{
"quantile": 0.75,
"value": 0.000065238
},
{
"quantile": 1,
"value": 0.000104897
}
],
"sample_count": 7,
"sample_sum": 0.000309117
}
}
],
"name": "go_gc_duration_seconds",
"type": 2
},
```
is *now*:
```
"metrics": {
"prometheus": {
"go_gc_duration_seconds": {
"name": "go_gc_duration_seconds",
"help": "A summary of the pause duration of garbage collection cycles.",
"type": "SUMMARY",
"metric": [
{
"summary": {
"sampleCount": "1",
"sampleSum": 4.1765e-05,
"quantile": [
{
"quantile": 0,
"value": 4.1765e-05
},
{
"quantile": 0.25,
"value": 4.1765e-05
},
{
"quantile": 0.5,
"value": 4.1765e-05
},
{
"quantile": 0.75,
"value": 4.1765e-05
},
{
"quantile": 1,
"value": 4.1765e-05
}
]
}
}
]
},
```
Note that `sample_count` is now `sampleCount`, and the `type` is using the enum's
string representation, `"SUMMARY"`, not `2`.
Note: For compatibility reasons (the Prometheus golang client doesn't use the V2
protobuf API), this change uses `jsonpb` and not `protojson`.
## 0.37.2
This is a bugfix release addressing two bugs:
+98 -66
View File
@@ -70,96 +70,84 @@ on the agent, updates will be sent to `/status`.
},
"metrics": {
"prometheus": {
"go_gc_cycles_automatic_gc_cycles_total": {
"name": "go_gc_cycles_automatic_gc_cycles_total",
"help": "Count of completed GC cycles generated by the Go runtime.",
"type": "COUNTER",
"metric": [
{
"counter": {
"value": 1
}
}
]
},
"go_gc_cycles_forced_gc_cycles_total": {
"name": "go_gc_cycles_forced_gc_cycles_total",
"help": "Count of completed GC cycles forced by the application.",
"type": "COUNTER",
"metric": [
{
"counter": {
"value": 0
}
}
]
},
"go_gc_cycles_total_gc_cycles_total": {
"name": "go_gc_cycles_total_gc_cycles_total",
"help": "Count of all completed GC cycles.",
"type": "COUNTER",
"metric": [
{
"counter": {
"value": 1
}
}
]
},
"go_gc_duration_seconds": {
"help": "A summary of the GC invocation durations.",
"name": "go_gc_duration_seconds",
"help": "A summary of the pause duration of garbage collection cycles.",
"type": "SUMMARY",
"metric": [
{
"summary": {
"sampleCount": "1",
"sampleSum": 4.1765e-05,
"quantile": [
{
"quantile": 0,
"value": 0.000011799
"value": 4.1765e-05
},
{
"quantile": 0.25,
"value": 0.000011905
"value": 4.1765e-05
},
{
"quantile": 0.5,
"value": 0.000040002
"value": 4.1765e-05
},
{
"quantile": 0.75,
"value": 0.000065238
"value": 4.1765e-05
},
{
"quantile": 1,
"value": 0.000104897
"value": 4.1765e-05
}
],
"sample_count": 7,
"sample_sum": 0.000309117
]
}
}
],
"name": "go_gc_duration_seconds",
"type": 2
]
},
------------------------------8< SNIP 8<------------------------------
"http_request_duration_seconds": {
"name": "http_request_duration_seconds",
"help": "A histogram of duration for requests.",
"type": "HISTOGRAM",
"metric": [
{
"histogram": {
"bucket": [
{
"cumulative_count": 1,
"upper_bound": 0.005
},
{
"cumulative_count": 1,
"upper_bound": 0.01
},
{
"cumulative_count": 1,
"upper_bound": 0.025
},
{
"cumulative_count": 1,
"upper_bound": 0.05
},
{
"cumulative_count": 1,
"upper_bound": 0.1
},
{
"cumulative_count": 1,
"upper_bound": 0.25
},
{
"cumulative_count": 1,
"upper_bound": 0.5
},
{
"cumulative_count": 1,
"upper_bound": 1
},
{
"cumulative_count": 1,
"upper_bound": 2.5
},
{
"cumulative_count": 1,
"upper_bound": 5
},
{
"cumulative_count": 1,
"upper_bound": 10
}
],
"sample_count": 1,
"sample_sum": 0.003157399
},
"label": [
{
"name": "code",
@@ -173,11 +161,55 @@ on the agent, updates will be sent to `/status`.
"name": "method",
"value": "get"
}
]
],
"histogram": {
"sampleCount": "2",
"sampleSum": 0.00060022,
"bucket": [
{
"cumulativeCount": "0",
"upperBound": 1e-06
},
{
"cumulativeCount": "0",
"upperBound": 5e-06
},
{
"cumulativeCount": "0",
"upperBound": 1e-05
},
{
"cumulativeCount": "0",
"upperBound": 5e-05
},
{
"cumulativeCount": "0",
"upperBound": 0.0001
},
{
"cumulativeCount": "2",
"upperBound": 0.0005
},
{
"cumulativeCount": "2",
"upperBound": 0.001
},
{
"cumulativeCount": "2",
"upperBound": 0.01
},
{
"cumulativeCount": "2",
"upperBound": 0.1
},
{
"cumulativeCount": "2",
"upperBound": 1
}
]
}
}
],
"name": "http_request_duration_seconds",
"type": 4
]
}
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ The Prometheus endpoint exports Go runtime metrics as well as HTTP request laten
### Status Metrics
When Prometheus is enabled in the status plugin (see [Configuration](../configuration/#status), the OPA instance's Prometheus endpoint also exposes these metrics:
When Prometheus is enabled in the status plugin (see [Configuration](../configuration/#status)), the OPA instance's Prometheus endpoint also exposes these metrics:
| Metric name | Metric type | Description | Status |
| --- | --- |----------------------------------------------------------|--------|
+2 -1
View File
@@ -15,6 +15,7 @@ require (
github.com/go-logr/logr v1.2.2
github.com/gobwas/glob v0.2.3
github.com/golang/glog v1.0.0 // indirect
github.com/golang/protobuf v1.5.2
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/mux v1.8.0
github.com/klauspost/compress v1.13.5 // indirect
@@ -24,7 +25,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.0
github.com/prometheus/client_golang v1.12.1
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.3.0
+2 -2
View File
@@ -371,8 +371,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+18 -3
View File
@@ -11,6 +11,10 @@ import (
"net/http"
"strconv"
// Need to keep deprecated package for compatibility with prometheus/client_golang
"github.com/golang/protobuf/jsonpb" // nolint:staticcheck
"github.com/golang/protobuf/proto" // nolint:staticcheck
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -25,11 +29,13 @@ type Provider struct {
durationHistogram *prometheus.HistogramVec
cancellationCounters *prometheus.CounterVec
inner metrics.Metrics
logger func(attrs map[string]interface{}, f string, a ...interface{})
logger loggerFunc
}
type loggerFunc func(attrs map[string]interface{}, f string, a ...interface{})
// New returns a new Provider object.
func New(inner metrics.Metrics, logger func(attrs map[string]interface{}, f string, a ...interface{})) *Provider {
func New(inner metrics.Metrics, logger loggerFunc) *Provider {
registry := prometheus.NewRegistry()
registry.MustRegister(collectors.NewGoCollector())
durationHistogram := prometheus.NewHistogramVec(
@@ -116,12 +122,21 @@ func (p *Provider) All() map[string]interface{} {
}
for _, f := range families {
all[f.GetName()] = f
all[f.GetName()] = wrap{family: f}
}
return all
}
type wrap struct{ family proto.Message }
var marshaler = jsonpb.Marshaler{}
func (w wrap) MarshalJSON() ([]byte, error) {
s, err := marshaler.MarshalToString(w.family)
return []byte(s), err
}
// MarshalJSON returns a JSON representation of the unioned metrics.
func (p *Provider) MarshalJSON() ([]byte, error) {
return json.Marshal(p.All())
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2022 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package prometheus
import (
"encoding/json"
"testing"
"github.com/open-policy-agent/opa/logging"
"github.com/open-policy-agent/opa/metrics"
)
func TestJSONSerialization(t *testing.T) {
inner := metrics.New()
logger := func(logger logging.Logger) loggerFunc {
return func(attrs map[string]interface{}, f string, a ...interface{}) {
logger.WithFields(map[string]interface{}(attrs)).Error(f, a...)
}
}(logging.NewNoOpLogger())
prom := New(inner, logger)
m := prom.All()
bs, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
act := make(map[string]map[string]interface{}, len(m))
err = json.Unmarshal(bs, &act)
if err != nil {
t.Fatal(err)
}
// NOTE(sr): "http_request_duration_seconds" only shows up after there has been a request
exp := map[string][]string{
"GAUGE": {
"go_gc_heap_goal_bytes",
"go_gc_heap_objects_objects",
"go_goroutines",
"go_info",
"go_memory_classes_heap_free_bytes",
"go_memory_classes_heap_objects_bytes",
"go_memory_classes_heap_released_bytes",
"go_memory_classes_heap_stacks_bytes",
"go_memory_classes_heap_unused_bytes",
"go_memory_classes_metadata_mcache_free_bytes",
"go_memory_classes_metadata_mcache_inuse_bytes",
"go_memory_classes_metadata_mspan_free_bytes",
"go_memory_classes_metadata_mspan_inuse_bytes",
"go_memory_classes_metadata_other_bytes",
"go_memory_classes_os_stacks_bytes",
"go_memory_classes_other_bytes",
"go_memory_classes_profiling_buckets_bytes",
"go_memory_classes_total_bytes",
"go_memstats_alloc_bytes",
"go_memstats_buck_hash_sys_bytes",
"go_memstats_gc_cpu_fraction",
"go_memstats_gc_sys_bytes",
"go_memstats_heap_alloc_bytes",
"go_memstats_heap_idle_bytes",
"go_memstats_heap_inuse_bytes",
"go_memstats_heap_objects",
"go_memstats_heap_released_bytes",
"go_memstats_heap_sys_bytes",
"go_memstats_last_gc_time_seconds",
"go_memstats_mcache_inuse_bytes",
"go_memstats_mcache_sys_bytes",
"go_memstats_mspan_inuse_bytes",
"go_memstats_mspan_sys_bytes",
"go_memstats_next_gc_bytes",
"go_memstats_other_sys_bytes",
"go_memstats_stack_inuse_bytes",
"go_memstats_stack_sys_bytes",
"go_memstats_sys_bytes",
"go_sched_goroutines_goroutines",
"go_threads",
},
"COUNTER": {
"go_gc_cycles_automatic_gc_cycles_total",
"go_gc_cycles_forced_gc_cycles_total",
"go_gc_cycles_total_gc_cycles_total",
"go_gc_heap_allocs_bytes_total",
"go_gc_heap_allocs_objects_total",
"go_gc_heap_tiny_allocs_objects_total",
"go_gc_heap_frees_bytes_total",
"go_gc_heap_frees_objects_total",
"go_memstats_alloc_bytes_total",
"go_memstats_lookups_total",
"go_memstats_mallocs_total",
"go_memstats_frees_total",
},
"SUMMARY": {
"go_gc_duration_seconds",
},
"HISTOGRAM": {
"go_gc_pauses_seconds_total",
"go_gc_heap_allocs_by_size_bytes_total",
"go_gc_heap_frees_by_size_bytes_total",
"go_sched_latencies_seconds",
},
}
found := 0
for typ, es := range exp {
for _, e := range es {
a, ok := act[e]
if !ok {
t.Errorf("%v: metric missing", e)
continue
}
if act, ok := a["type"].(string); !ok || act != typ {
t.Errorf("%v: unexpected type: %v (expected %v)", e, act, typ)
continue
}
found++
}
}
if len(act) != found {
t.Errorf("unexpected extra metrics, expected %d, got %d", found, len(act))
}
}
+1 -1
View File
@@ -76,7 +76,7 @@ type metric struct {
Value interface{}
}
func (m *metrics) Info() Info {
func (*metrics) Info() Info {
return Info{
Name: "<built-in>",
}
+1 -1
View File
@@ -411,7 +411,7 @@ func (p *Plugin) oneShot(ctx context.Context) error {
Do(ctx, "POST", fmt.Sprintf("/status/%v", p.config.PartitionName))
if err != nil {
return errors.Wrap(err, "Status update failed")
return fmt.Errorf("Status update failed: %w", err)
}
defer util.Close(resp)
+8
View File
@@ -118,3 +118,11 @@ func (c *selfCollector) Describe(ch chan<- *Desc) {
func (c *selfCollector) Collect(ch chan<- Metric) {
ch <- c.self
}
// collectorMetric is a metric that is also a collector.
// Because of selfCollector, most (if not all) Metrics in
// this package are also collectors.
type collectorMetric interface {
Metric
Collector
}
+69 -25
View File
@@ -20,6 +20,7 @@ import (
"math"
"runtime"
"runtime/metrics"
"strings"
"sync"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
@@ -31,10 +32,14 @@ import (
type goCollector struct {
base baseGoCollector
// mu protects updates to all fields ensuring a consistent
// snapshot is always produced by Collect.
mu sync.Mutex
// rm... fields all pertain to the runtime/metrics package.
rmSampleBuf []metrics.Sample
rmSampleMap map[string]*metrics.Sample
rmMetrics []Metric
rmMetrics []collectorMetric
// With Go 1.17, the runtime/metrics package was introduced.
// From that point on, metric names produced by the runtime/metrics
@@ -52,13 +57,24 @@ type goCollector struct {
// Deprecated: Use collectors.NewGoCollector instead.
func NewGoCollector() Collector {
descriptions := metrics.All()
descMap := make(map[string]*metrics.Description)
for i := range descriptions {
descMap[descriptions[i].Name] = &descriptions[i]
// Collect all histogram samples so that we can get their buckets.
// The API guarantees that the buckets are always fixed for the lifetime
// of the process.
var histograms []metrics.Sample
for _, d := range descriptions {
if d.Kind == metrics.KindFloat64Histogram {
histograms = append(histograms, metrics.Sample{Name: d.Name})
}
}
metrics.Read(histograms)
bucketsMap := make(map[string][]float64)
for i := range histograms {
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
}
// Generate a Desc and ValueType for each runtime/metrics metric.
metricSet := make([]Metric, 0, len(descriptions))
metricSet := make([]collectorMetric, 0, len(descriptions))
sampleBuf := make([]metrics.Sample, 0, len(descriptions))
sampleMap := make(map[string]*metrics.Sample, len(descriptions))
for i := range descriptions {
@@ -76,9 +92,10 @@ func NewGoCollector() Collector {
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
var m Metric
var m collectorMetric
if d.Kind == metrics.KindFloat64Histogram {
_, hasSum := rmExactSumMap[d.Name]
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
m = newBatchHistogram(
NewDesc(
BuildFQName(namespace, subsystem, name),
@@ -86,6 +103,7 @@ func NewGoCollector() Collector {
nil,
nil,
),
internal.RuntimeMetricsBucketsForUnit(bucketsMap[d.Name], unit),
hasSum,
)
} else if d.Cumulative {
@@ -130,9 +148,25 @@ func (c *goCollector) Collect(ch chan<- Metric) {
// Collect base non-memory metrics.
c.base.Collect(ch)
// Collect must be thread-safe, so prevent concurrent use of
// rmSampleBuf. Just read into rmSampleBuf but write all the data
// we get into our Metrics or MemStats.
//
// This lock also ensures that the Metrics we send out are all from
// the same updates, ensuring their mutual consistency insofar as
// is guaranteed by the runtime/metrics package.
//
// N.B. This locking is heavy-handed, but Collect is expected to be called
// relatively infrequently. Also the core operation here, metrics.Read,
// is fast (O(tens of microseconds)) so contention should certainly be
// low, though channel operations and any allocations may add to that.
c.mu.Lock()
defer c.mu.Unlock()
// Populate runtime/metrics sample buffer.
metrics.Read(c.rmSampleBuf)
// Update all our metrics from rmSampleBuf.
for i, sample := range c.rmSampleBuf {
// N.B. switch on concrete type because it's significantly more efficient
// than checking for the Counter and Gauge interface implementations. In
@@ -157,7 +191,6 @@ func (c *goCollector) Collect(ch chan<- Metric) {
panic("unexpected metric type")
}
}
// ms is a dummy MemStats that we populate ourselves so that we can
// populate the old metrics from it.
var ms runtime.MemStats
@@ -280,13 +313,27 @@ type batchHistogram struct {
// but Write calls may operate concurrently with updates.
// Contention between these two sources should be rare.
mu sync.Mutex
buckets []float64 // Inclusive lower bounds.
buckets []float64 // Inclusive lower bounds, like runtime/metrics.
counts []uint64
sum float64 // Used if hasSum is true.
}
func newBatchHistogram(desc *Desc, hasSum bool) *batchHistogram {
h := &batchHistogram{desc: desc, hasSum: hasSum}
// newBatchHistogram creates a new batch histogram value with the given
// Desc, buckets, and whether or not it has an exact sum available.
//
// buckets must always be from the runtime/metrics package, following
// the same conventions.
func newBatchHistogram(desc *Desc, buckets []float64, hasSum bool) *batchHistogram {
h := &batchHistogram{
desc: desc,
buckets: buckets,
// Because buckets follows runtime/metrics conventions, there's
// 1 more value in the buckets list than there are buckets represented,
// because in runtime/metrics, the bucket values represent *boundaries*,
// and non-Inf boundaries are inclusive lower bounds for that bucket.
counts: make([]uint64, len(buckets)-1),
hasSum: hasSum,
}
h.init(h)
return h
}
@@ -294,28 +341,25 @@ func newBatchHistogram(desc *Desc, hasSum bool) *batchHistogram {
// update updates the batchHistogram from a runtime/metrics histogram.
//
// sum must be provided if the batchHistogram was created to have an exact sum.
// h.buckets must be a strict subset of his.Buckets.
func (h *batchHistogram) update(his *metrics.Float64Histogram, sum float64) {
counts, buckets := his.Counts, his.Buckets
// Skip a -Inf bucket altogether. It's not clear how to represent that.
if math.IsInf(buckets[0], -1) {
buckets = buckets[1:]
counts = counts[1:]
}
h.mu.Lock()
defer h.mu.Unlock()
// Check if we're initialized.
if h.buckets == nil {
// Make copies of counts and buckets. It's really important
// that we don't retain his.Counts or his.Buckets anywhere since
// it's going to get reused.
h.buckets = make([]float64, len(buckets))
copy(h.buckets, buckets)
h.counts = make([]uint64, len(counts))
// Clear buckets.
for i := range h.counts {
h.counts[i] = 0
}
// Copy and reduce buckets.
var j int
for i, count := range counts {
h.counts[j] += count
if buckets[i+1] == h.buckets[j+1] {
j++
}
}
copy(h.counts, counts)
if h.hasSum {
h.sum = sum
}
@@ -17,6 +17,7 @@
package internal
import (
"math"
"path"
"runtime/metrics"
"strings"
@@ -75,3 +76,67 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
}
return namespace, subsystem, name, valid
}
// RuntimeMetricsBucketsForUnit takes a set of buckets obtained for a runtime/metrics histogram
// type (so, lower-bound inclusive) and a unit from a runtime/metrics name, and produces
// a reduced set of buckets. This function always removes any -Inf bucket as it's represented
// as the bottom-most upper-bound inclusive bucket in Prometheus.
func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
switch unit {
case "bytes":
// Rebucket as powers of 2.
return rebucketExp(buckets, 2)
case "seconds":
// Rebucket as powers of 10 and then merge all buckets greater
// than 1 second into the +Inf bucket.
b := rebucketExp(buckets, 10)
for i := range b {
if b[i] <= 1 {
continue
}
b[i] = math.Inf(1)
b = b[:i+1]
break
}
return b
}
return buckets
}
// rebucketExp takes a list of bucket boundaries (lower bound inclusive) and
// downsamples the buckets to those a multiple of base apart. The end result
// is a roughly exponential (in many cases, perfectly exponential) bucketing
// scheme.
func rebucketExp(buckets []float64, base float64) []float64 {
bucket := buckets[0]
var newBuckets []float64
// We may see a -Inf here, in which case, add it and skip it
// since we risk producing NaNs otherwise.
//
// We need to preserve -Inf values to maintain runtime/metrics
// conventions. We'll strip it out later.
if bucket == math.Inf(-1) {
newBuckets = append(newBuckets, bucket)
buckets = buckets[1:]
bucket = buckets[0]
}
// From now on, bucket should always have a non-Inf value because
// Infs are only ever at the ends of the bucket lists, so
// arithmetic operations on it are non-NaN.
for i := 1; i < len(buckets); i++ {
if bucket >= 0 && buckets[i] < bucket*base {
// The next bucket we want to include is at least bucket*base.
continue
} else if bucket < 0 && buckets[i] < bucket/base {
// In this case the bucket we're targeting is negative, and since
// we're ascending through buckets here, we need to divide to get
// closer to zero exponentially.
continue
}
// The +Inf bucket will always be the last one, and we'll always
// end up including it here because bucket
newBuckets = append(newBuckets, bucket)
bucket = buckets[i]
}
return append(newBuckets, bucket)
}
+2 -1
View File
@@ -82,6 +82,7 @@ github.com/golang/glog
# github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golang/groupcache/lru
# github.com/golang/protobuf v1.5.2
## explicit
github.com/golang/protobuf/descriptor
github.com/golang/protobuf/jsonpb
github.com/golang/protobuf/proto
@@ -135,7 +136,7 @@ github.com/peterh/liner
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/prometheus/client_golang v1.12.0
# github.com/prometheus/client_golang v1.12.1
## explicit
github.com/prometheus/client_golang/prometheus
github.com/prometheus/client_golang/prometheus/collectors