mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
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:
+105
@@ -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:
|
||||
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- |----------------------------------------------------------|--------|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -76,7 +76,7 @@ type metric struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (m *metrics) Info() Info {
|
||||
func (*metrics) Info() Info {
|
||||
return Info{
|
||||
Name: "<built-in>",
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Generated
Vendored
+65
@@ -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)
|
||||
}
|
||||
|
||||
Vendored
+2
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user