forked from mirror/client_golang
collectors.GoCollector: Added rule support for granular metric configuration. (#1102)
* goCollector: Added rule support for granular metric configuration. Fixes: https://github.com/prometheus/client_golang/issues/1089 Signed-off-by: bwplotka <bwplotka@gmail.com> * Added compatibility mode with old options. (#1107) * Added compatibility mode with old options. Signed-off-by: bwplotka <bwplotka@gmail.com> * Copyright header. Signed-off-by: bwplotka <bwplotka@gmail.com> * Remove bucket option for now. (#1108) Signed-off-by: bwplotka <bwplotka@gmail.com> * collectors/GoCollector: Add tests and examples (#1109) * Add tests and examples Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> * Add docs for the presets Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> Co-authored-by: Kemal Akkoyun <kakkoyun@users.noreply.github.com>
This commit is contained in:
parent
d44fbbefdd
commit
5b7e8b2e67
|
@ -16,77 +16,145 @@
|
||||||
|
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
type goOptions = prometheus.GoCollectorOptions
|
"github.com/prometheus/client_golang/prometheus/internal"
|
||||||
type goOption func(o *goOptions)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// MetricsAll allows all the metrics to be collected from Go runtime.
|
||||||
|
MetricsAll = GoRuntimeMetricsRule{regexp.MustCompile("/.*")}
|
||||||
|
// MetricsGC allows only GC metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_gc_cycles_automatic_gc_cycles_total
|
||||||
|
MetricsGC = GoRuntimeMetricsRule{regexp.MustCompile(`^/gc/.*`)}
|
||||||
|
// MetricsMemory allows only memory metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_memory_classes_heap_free_bytes
|
||||||
|
MetricsMemory = GoRuntimeMetricsRule{regexp.MustCompile(`^/memory/.*`)}
|
||||||
|
// MetricsScheduler allows only scheduler metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_sched_goroutines_goroutines
|
||||||
|
MetricsScheduler = GoRuntimeMetricsRule{regexp.MustCompile(`^/sched/.*`)}
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithGoCollectorMemStatsMetricsDisabled disables metrics that is gathered in runtime.MemStats structure such as:
|
||||||
|
//
|
||||||
|
// go_memstats_alloc_bytes
|
||||||
|
// go_memstats_alloc_bytes_total
|
||||||
|
// go_memstats_sys_bytes
|
||||||
|
// go_memstats_lookups_total
|
||||||
|
// go_memstats_mallocs_total
|
||||||
|
// go_memstats_frees_total
|
||||||
|
// go_memstats_heap_alloc_bytes
|
||||||
|
// go_memstats_heap_sys_bytes
|
||||||
|
// go_memstats_heap_idle_bytes
|
||||||
|
// go_memstats_heap_inuse_bytes
|
||||||
|
// go_memstats_heap_released_bytes
|
||||||
|
// go_memstats_heap_objects
|
||||||
|
// go_memstats_stack_inuse_bytes
|
||||||
|
// go_memstats_stack_sys_bytes
|
||||||
|
// go_memstats_mspan_inuse_bytes
|
||||||
|
// go_memstats_mspan_sys_bytes
|
||||||
|
// go_memstats_mcache_inuse_bytes
|
||||||
|
// go_memstats_mcache_sys_bytes
|
||||||
|
// go_memstats_buck_hash_sys_bytes
|
||||||
|
// go_memstats_gc_sys_bytes
|
||||||
|
// go_memstats_other_sys_bytes
|
||||||
|
// go_memstats_next_gc_bytes
|
||||||
|
//
|
||||||
|
// so the metrics known from pre client_golang v1.12.0,
|
||||||
|
//
|
||||||
|
// NOTE(bwplotka): The above represents runtime.MemStats statistics, but they are
|
||||||
|
// actually implemented using new runtime/metrics package. (except skipped go_memstats_gc_cpu_fraction
|
||||||
|
// -- see https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034 for explanation).
|
||||||
|
//
|
||||||
|
// Some users might want to disable this on collector level (although you can use scrape relabelling on Prometheus),
|
||||||
|
// because similar metrics can be now obtained using WithGoCollectorRuntimeMetrics. Note that the semantics of new
|
||||||
|
// metrics might be different, plus the names can be change over time with different Go version.
|
||||||
|
//
|
||||||
|
// NOTE(bwplotka): Changing metric names can be tedious at times as the alerts, recording rules and dashboards have to be adjusted.
|
||||||
|
// The old metrics are also very useful, with many guides and books written about how to interpret them.
|
||||||
|
//
|
||||||
|
// As a result our recommendation would be to stick with MemStats like metrics and enable other runtime/metrics if you are interested
|
||||||
|
// in advanced insights Go provides. See ExampleGoCollector_WithAdvancedGoMetrics.
|
||||||
|
func WithGoCollectorMemStatsMetricsDisabled() func(options *internal.GoCollectorOptions) {
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.DisableMemStatsLikeMetrics = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoRuntimeMetricsRule allow enabling and configuring particular group of runtime/metrics.
|
||||||
|
// TODO(bwplotka): Consider adding ability to adjust buckets.
|
||||||
|
type GoRuntimeMetricsRule struct {
|
||||||
|
// Matcher represents RE2 expression will match the runtime/metrics from https://golang.bg/src/runtime/metrics/description.go
|
||||||
|
// Use `regexp.MustCompile` or `regexp.Compile` to create this field.
|
||||||
|
Matcher *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGoCollectorRuntimeMetrics allows enabling and configuring particular group of runtime/metrics.
|
||||||
|
// See the list of metrics https://golang.bg/src/runtime/metrics/description.go (pick the Go version you use there!).
|
||||||
|
// You can use this option in repeated manner, which will add new rules. The order of rules is important, the last rule
|
||||||
|
// that matches particular metrics is applied.
|
||||||
|
func WithGoCollectorRuntimeMetrics(rules ...GoRuntimeMetricsRule) func(options *internal.GoCollectorOptions) {
|
||||||
|
rs := make([]internal.GoCollectorRule, len(rules))
|
||||||
|
for i, r := range rules {
|
||||||
|
rs[i] = internal.GoCollectorRule{
|
||||||
|
Matcher: r.Matcher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.RuntimeMetricRules = append(o.RuntimeMetricRules, rs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutGoCollectorRuntimeMetrics allows disabling group of runtime/metrics that you might have added in WithGoCollectorRuntimeMetrics.
|
||||||
|
// It behaves similarly to WithGoCollectorRuntimeMetrics just with deny-list semantics.
|
||||||
|
func WithoutGoCollectorRuntimeMetrics(matchers ...*regexp.Regexp) func(options *internal.GoCollectorOptions) {
|
||||||
|
rs := make([]internal.GoCollectorRule, len(matchers))
|
||||||
|
for i, m := range matchers {
|
||||||
|
rs[i] = internal.GoCollectorRule{
|
||||||
|
Matcher: m,
|
||||||
|
Deny: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.RuntimeMetricRules = append(o.RuntimeMetricRules, rs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoCollectionOption represents Go collection option flag.
|
||||||
|
// Deprecated.
|
||||||
type GoCollectionOption uint32
|
type GoCollectionOption uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// GoRuntimeMemStatsCollection represents the metrics represented by runtime.MemStats structure such as
|
// GoRuntimeMemStatsCollection represents the metrics represented by runtime.MemStats structure.
|
||||||
// go_memstats_alloc_bytes
|
// Deprecated. Use WithGoCollectorMemStatsMetricsDisabled() function to disable those metrics in the collector.
|
||||||
// go_memstats_alloc_bytes_total
|
|
||||||
// go_memstats_sys_bytes
|
|
||||||
// go_memstats_lookups_total
|
|
||||||
// go_memstats_mallocs_total
|
|
||||||
// go_memstats_frees_total
|
|
||||||
// go_memstats_heap_alloc_bytes
|
|
||||||
// go_memstats_heap_sys_bytes
|
|
||||||
// go_memstats_heap_idle_bytes
|
|
||||||
// go_memstats_heap_inuse_bytes
|
|
||||||
// go_memstats_heap_released_bytes
|
|
||||||
// go_memstats_heap_objects
|
|
||||||
// go_memstats_stack_inuse_bytes
|
|
||||||
// go_memstats_stack_sys_bytes
|
|
||||||
// go_memstats_mspan_inuse_bytes
|
|
||||||
// go_memstats_mspan_sys_bytes
|
|
||||||
// go_memstats_mcache_inuse_bytes
|
|
||||||
// go_memstats_mcache_sys_bytes
|
|
||||||
// go_memstats_buck_hash_sys_bytes
|
|
||||||
// go_memstats_gc_sys_bytes
|
|
||||||
// go_memstats_other_sys_bytes
|
|
||||||
// go_memstats_next_gc_bytes
|
|
||||||
// so the metrics known from pre client_golang v1.12.0, except skipped go_memstats_gc_cpu_fraction (see
|
|
||||||
// https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034 for explanation.
|
|
||||||
//
|
|
||||||
// NOTE that this mode represents runtime.MemStats statistics, but they are
|
|
||||||
// actually implemented using new runtime/metrics package.
|
|
||||||
// Deprecated: Use GoRuntimeMetricsCollection instead going forward.
|
|
||||||
GoRuntimeMemStatsCollection GoCollectionOption = 1 << iota
|
GoRuntimeMemStatsCollection GoCollectionOption = 1 << iota
|
||||||
// GoRuntimeMetricsCollection is the new set of metrics represented by runtime/metrics package and follows
|
// GoRuntimeMetricsCollection is the new set of metrics represented by runtime/metrics package.
|
||||||
// consistent naming. The exposed metric set depends on Go version, but it is controlled against
|
// Deprecated. Use WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})
|
||||||
// unexpected cardinality. This set has overlapping information with GoRuntimeMemStatsCollection, just with
|
// function to enable those metrics in the collector.
|
||||||
// new names. GoRuntimeMetricsCollection is what is recommended for using going forward.
|
|
||||||
GoRuntimeMetricsCollection
|
GoRuntimeMetricsCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithGoCollections allows enabling different collections for Go collector on top of base metrics
|
// WithGoCollections allows enabling different collections for Go collector on top of base metrics.
|
||||||
// like go_goroutines, go_threads, go_gc_duration_seconds, go_memstats_last_gc_time_seconds, go_info.
|
// Deprecated. Use WithGoCollectorRuntimeMetrics() and WithGoCollectorMemStatsMetricsDisabled() instead to control metrics.
|
||||||
//
|
func WithGoCollections(flags GoCollectionOption) func(options *internal.GoCollectorOptions) {
|
||||||
// Check GoRuntimeMemStatsCollection and GoRuntimeMetricsCollection for more details. You can use none,
|
return func(options *internal.GoCollectorOptions) {
|
||||||
// one or more collections at once. For example:
|
if flags&GoRuntimeMemStatsCollection == 0 {
|
||||||
// WithGoCollections(GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection) means both GoRuntimeMemStatsCollection
|
WithGoCollectorMemStatsMetricsDisabled()(options)
|
||||||
// metrics and GoRuntimeMetricsCollection will be exposed.
|
}
|
||||||
//
|
|
||||||
// The current default is GoRuntimeMemStatsCollection, so the compatibility mode with
|
if flags&GoRuntimeMetricsCollection != 0 {
|
||||||
// client_golang pre v1.12 (move to runtime/metrics).
|
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})(options)
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
}
|
||||||
func WithGoCollections(flags GoCollectionOption) func(options *prometheus.GoCollectorOptions) {
|
|
||||||
return func(o *goOptions) {
|
|
||||||
o.EnabledCollections = uint32(flags)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoCollector returns a collector that exports metrics about the current Go
|
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||||
// process using debug.GCStats using runtime/metrics.
|
// process using debug.GCStats (base metrics) and runtime/metrics (both in MemStats style and new ones).
|
||||||
func NewGoCollector(opts ...goOption) prometheus.Collector {
|
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) prometheus.Collector {
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||||
promPkgOpts := make([]func(o *prometheus.GoCollectorOptions), len(opts))
|
return prometheus.NewGoCollector(opts...)
|
||||||
for i, opt := range opts {
|
|
||||||
promPkgOpts[i] = opt
|
|
||||||
}
|
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
|
||||||
return prometheus.NewGoCollector(promPkgOpts...)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,31 @@ package collectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var baseMetrics = []string{
|
||||||
|
"go_gc_duration_seconds",
|
||||||
|
"go_goroutines",
|
||||||
|
"go_info",
|
||||||
|
"go_memstats_last_gc_time_seconds",
|
||||||
|
"go_threads",
|
||||||
|
}
|
||||||
|
|
||||||
func TestGoCollectorMarshalling(t *testing.T) {
|
func TestGoCollectorMarshalling(t *testing.T) {
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
reg.MustRegister(NewGoCollector(
|
reg.MustRegister(NewGoCollector(
|
||||||
WithGoCollections(GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection),
|
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("/.*"),
|
||||||
|
}),
|
||||||
))
|
))
|
||||||
result, err := reg.Gather()
|
result, err := reg.Gather()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,3 +53,248 @@ func TestGoCollectorMarshalling(t *testing.T) {
|
||||||
t.Errorf("json marshalling shoud not fail, %v", err)
|
t.Errorf("json marshalling shoud not fail, %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithGoCollectorMemStatsMetricsDisabled(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, baseMetrics) {
|
||||||
|
t.Errorf("got %v, want %v", got, baseMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoCollectorAllowList(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
rules []GoRuntimeMetricsRule
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Without any rules",
|
||||||
|
rules: nil,
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow all",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsAll},
|
||||||
|
expected: withBaseMetrics([]string{
|
||||||
|
"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_by_size_bytes",
|
||||||
|
"go_gc_heap_allocs_bytes_total",
|
||||||
|
"go_gc_heap_allocs_objects_total",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
"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_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow GC",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsGC},
|
||||||
|
expected: withBaseMetrics([]string{
|
||||||
|
"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_by_size_bytes",
|
||||||
|
"go_gc_heap_allocs_bytes_total",
|
||||||
|
"go_gc_heap_allocs_objects_total",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow Memory",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsMemory},
|
||||||
|
expected: withBaseMetrics([]string{
|
||||||
|
"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",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow Scheduler",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsScheduler},
|
||||||
|
expected: []string{
|
||||||
|
"go_gc_duration_seconds",
|
||||||
|
"go_goroutines",
|
||||||
|
"go_info",
|
||||||
|
"go_memstats_last_gc_time_seconds",
|
||||||
|
"go_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
"go_threads",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithGoCollectorRuntimeMetrics(test.rules...),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.expected) {
|
||||||
|
t.Errorf("got %v, want %v", got, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBaseMetrics(metricNames []string) []string {
|
||||||
|
metricNames = append(metricNames, baseMetrics...)
|
||||||
|
sort.Strings(metricNames)
|
||||||
|
return metricNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoCollectorDenyList(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
matchers []*regexp.Regexp
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Without any matchers",
|
||||||
|
matchers: nil,
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny all",
|
||||||
|
matchers: []*regexp.Regexp{regexp.MustCompile("/.*")},
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny gc and scheduler latency",
|
||||||
|
matchers: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile("^/gc/.*"),
|
||||||
|
regexp.MustCompile("^/sched/latencies:.*"),
|
||||||
|
},
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithoutGoCollectorRuntimeMetrics(test.matchers...),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.expected) {
|
||||||
|
t.Errorf("got %v, want %v", got, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector() {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Register the GoCollector with the default options. Only the base metrics will be enabled.
|
||||||
|
reg.MustRegister(NewGoCollector())
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector_WithAdvancedGoMetrics() {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Enable Go metrics with pre-defined rules. Or your custom rules.
|
||||||
|
reg.MustRegister(
|
||||||
|
NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithGoCollectorRuntimeMetrics(
|
||||||
|
MetricsScheduler,
|
||||||
|
MetricsMemory,
|
||||||
|
GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("^/mycustomrule.*"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
WithoutGoCollectorRuntimeMetrics(regexp.MustCompile("^/gc/.*")),
|
||||||
|
))
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector_DefaultRegister() {
|
||||||
|
// Unregister the default GoCollector.
|
||||||
|
prometheus.Unregister(NewGoCollector())
|
||||||
|
|
||||||
|
// Register the default GoCollector with a custom config.
|
||||||
|
prometheus.MustRegister(NewGoCollector(WithGoCollectorRuntimeMetrics(
|
||||||
|
MetricsScheduler,
|
||||||
|
MetricsGC,
|
||||||
|
GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("^/mycustomrule.*"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// goRuntimeMemStats provides the metrics initially provided by runtime.ReadMemStats.
|
||||||
|
// From Go 1.17 those similar (and better) statistics are provided by runtime/metrics, so
|
||||||
|
// while eval closure works on runtime.MemStats, the struct from Go 1.17+ is
|
||||||
|
// populated using runtime/metrics.
|
||||||
func goRuntimeMemStats() memStatsMetrics {
|
func goRuntimeMemStats() memStatsMetrics {
|
||||||
return memStatsMetrics{
|
return memStatsMetrics{
|
||||||
{
|
{
|
||||||
|
@ -224,7 +228,7 @@ func newBaseGoCollector() baseGoCollector {
|
||||||
"A summary of the pause duration of garbage collection cycles.",
|
"A summary of the pause duration of garbage collection cycles.",
|
||||||
nil, nil),
|
nil, nil),
|
||||||
gcLastTimeDesc: NewDesc(
|
gcLastTimeDesc: NewDesc(
|
||||||
memstatNamespace("last_gc_time_seconds"),
|
"go_memstats_last_gc_time_seconds",
|
||||||
"Number of seconds since 1970 of last garbage collection.",
|
"Number of seconds since 1970 of last garbage collection.",
|
||||||
nil, nil),
|
nil, nil),
|
||||||
goInfoDesc: NewDesc(
|
goInfoDesc: NewDesc(
|
||||||
|
@ -270,7 +274,6 @@ func memstatNamespace(s string) string {
|
||||||
|
|
||||||
// memStatsMetrics provide description, evaluator, runtime/metrics name, and
|
// memStatsMetrics provide description, evaluator, runtime/metrics name, and
|
||||||
// value type for memstat metrics.
|
// value type for memstat metrics.
|
||||||
// TODO(bwplotka): Remove with end Go 1.16 EOL and replace with runtime/metrics.Description
|
|
||||||
type memStatsMetrics []struct {
|
type memStatsMetrics []struct {
|
||||||
desc *Desc
|
desc *Desc
|
||||||
eval func(*runtime.MemStats) float64
|
eval func(*runtime.MemStats) float64
|
||||||
|
|
|
@ -31,9 +31,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// constants for strings referenced more than once.
|
||||||
goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
|
goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
|
||||||
goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
|
goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
|
||||||
goGCHeapFreesObjects = "/gc/heap/frees:objects"
|
goGCHeapFreesObjects = "/gc/heap/frees:objects"
|
||||||
|
goGCHeapFreesBytes = "/gc/heap/frees:bytes"
|
||||||
goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
|
goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
|
||||||
goGCHeapObjects = "/gc/heap/objects:objects"
|
goGCHeapObjects = "/gc/heap/objects:objects"
|
||||||
goGCHeapGoalBytes = "/gc/heap/goal:bytes"
|
goGCHeapGoalBytes = "/gc/heap/goal:bytes"
|
||||||
|
@ -53,8 +55,8 @@ const (
|
||||||
goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
|
goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runtime/metrics names required for runtimeMemStats like logic.
|
// rmNamesForMemStatsMetrics represents runtime/metrics names required to populate goRuntimeMemStats from like logic.
|
||||||
var rmForMemStats = []string{
|
var rmNamesForMemStatsMetrics = []string{
|
||||||
goGCHeapTinyAllocsObjects,
|
goGCHeapTinyAllocsObjects,
|
||||||
goGCHeapAllocsObjects,
|
goGCHeapAllocsObjects,
|
||||||
goGCHeapFreesObjects,
|
goGCHeapFreesObjects,
|
||||||
|
@ -90,74 +92,90 @@ func bestEffortLookupRM(lookup []string) []metrics.Description {
|
||||||
}
|
}
|
||||||
|
|
||||||
type goCollector struct {
|
type goCollector struct {
|
||||||
opt GoCollectorOptions
|
|
||||||
base baseGoCollector
|
base baseGoCollector
|
||||||
|
|
||||||
// mu protects updates to all fields ensuring a consistent
|
// mu protects updates to all fields ensuring a consistent
|
||||||
// snapshot is always produced by Collect.
|
// snapshot is always produced by Collect.
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
// rm... fields all pertain to the runtime/metrics package.
|
// Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed).
|
||||||
rmSampleBuf []metrics.Sample
|
sampleBuf []metrics.Sample
|
||||||
rmSampleMap map[string]*metrics.Sample
|
// sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums.
|
||||||
rmMetrics []collectorMetric
|
sampleMap map[string]*metrics.Sample
|
||||||
|
|
||||||
|
// rmExposedMetrics represents all runtime/metrics package metrics
|
||||||
|
// that were configured to be exposed.
|
||||||
|
rmExposedMetrics []collectorMetric
|
||||||
|
rmExactSumMapForHist map[string]string
|
||||||
|
|
||||||
// With Go 1.17, the runtime/metrics package was introduced.
|
// With Go 1.17, the runtime/metrics package was introduced.
|
||||||
// From that point on, metric names produced by the runtime/metrics
|
// From that point on, metric names produced by the runtime/metrics
|
||||||
// package could be generated from runtime/metrics names. However,
|
// package could be generated from runtime/metrics names. However,
|
||||||
// these differ from the old names for the same values.
|
// these differ from the old names for the same values.
|
||||||
//
|
//
|
||||||
// This field exist to export the same values under the old names
|
// This field exists to export the same values under the old names
|
||||||
// as well.
|
// as well.
|
||||||
msMetrics memStatsMetrics
|
msMetrics memStatsMetrics
|
||||||
|
msMetricsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type rmMetricDesc struct {
|
||||||
// Those are not exposed due to need to move Go collector to another package in v2.
|
metrics.Description
|
||||||
// See issue https://github.com/prometheus/client_golang/issues/1030.
|
|
||||||
goRuntimeMemStatsCollection uint32 = 1 << iota
|
|
||||||
goRuntimeMetricsCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoCollectorOptions should not be used be directly by anything, except `collectors` package.
|
|
||||||
// Use it via collectors package instead. See issue
|
|
||||||
// https://github.com/prometheus/client_golang/issues/1030.
|
|
||||||
//
|
|
||||||
// Deprecated: Use collectors.WithGoCollections
|
|
||||||
type GoCollectorOptions struct {
|
|
||||||
// EnabledCollection sets what type of collections collector should expose on top of base collection.
|
|
||||||
// By default it's goMemStatsCollection | goRuntimeMetricsCollection.
|
|
||||||
EnabledCollections uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c GoCollectorOptions) isEnabled(flag uint32) bool {
|
func matchRuntimeMetricsRules(rules []internal.GoCollectorRule) []rmMetricDesc {
|
||||||
return c.EnabledCollections&flag != 0
|
var descs []rmMetricDesc
|
||||||
|
for _, d := range metrics.All() {
|
||||||
|
var (
|
||||||
|
deny = true
|
||||||
|
desc rmMetricDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, r := range rules {
|
||||||
|
if !r.Matcher.MatchString(d.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deny = r.Deny
|
||||||
|
}
|
||||||
|
if deny {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
desc.Description = d
|
||||||
|
descs = append(descs, desc)
|
||||||
|
}
|
||||||
|
return descs
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultGoCollections = goRuntimeMemStatsCollection
|
func defaultGoCollectorOptions() internal.GoCollectorOptions {
|
||||||
|
return internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricSumForHist: map[string]string{
|
||||||
|
"/gc/heap/allocs-by-size:bytes": goGCHeapAllocsBytes,
|
||||||
|
"/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes,
|
||||||
|
},
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
//{Matcher: regexp.MustCompile("")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
||||||
// See there for documentation.
|
// See there for documentation.
|
||||||
//
|
//
|
||||||
// Deprecated: Use collectors.NewGoCollector instead.
|
// Deprecated: Use collectors.NewGoCollector instead.
|
||||||
func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
|
||||||
opt := GoCollectorOptions{EnabledCollections: defaultGoCollections}
|
opt := defaultGoCollectorOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&opt)
|
o(&opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
var descriptions []metrics.Description
|
exposedDescriptions := matchRuntimeMetricsRules(opt.RuntimeMetricRules)
|
||||||
if opt.isEnabled(goRuntimeMetricsCollection) {
|
|
||||||
descriptions = metrics.All()
|
|
||||||
} else if opt.isEnabled(goRuntimeMemStatsCollection) {
|
|
||||||
descriptions = bestEffortLookupRM(rmForMemStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect all histogram samples so that we can get their buckets.
|
// Collect all histogram samples so that we can get their buckets.
|
||||||
// The API guarantees that the buckets are always fixed for the lifetime
|
// The API guarantees that the buckets are always fixed for the lifetime
|
||||||
// of the process.
|
// of the process.
|
||||||
var histograms []metrics.Sample
|
var histograms []metrics.Sample
|
||||||
for _, d := range descriptions {
|
for _, d := range exposedDescriptions {
|
||||||
if d.Kind == metrics.KindFloat64Histogram {
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
||||||
}
|
}
|
||||||
|
@ -172,13 +190,14 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
|
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a Desc and ValueType for each runtime/metrics metric.
|
// Generate a collector for each exposed runtime/metrics metric.
|
||||||
metricSet := make([]collectorMetric, 0, len(descriptions))
|
metricSet := make([]collectorMetric, 0, len(exposedDescriptions))
|
||||||
sampleBuf := make([]metrics.Sample, 0, len(descriptions))
|
// SampleBuf is used for reading from runtime/metrics.
|
||||||
sampleMap := make(map[string]*metrics.Sample, len(descriptions))
|
// We are assuming the largest case to have stable pointers for sampleMap purposes.
|
||||||
for i := range descriptions {
|
sampleBuf := make([]metrics.Sample, 0, len(exposedDescriptions)+len(opt.RuntimeMetricSumForHist)+len(rmNamesForMemStatsMetrics))
|
||||||
d := &descriptions[i]
|
sampleMap := make(map[string]*metrics.Sample, len(exposedDescriptions))
|
||||||
namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(d)
|
for _, d := range exposedDescriptions {
|
||||||
|
namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(&d.Description)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Just ignore this metric; we can't do anything with it here.
|
// Just ignore this metric; we can't do anything with it here.
|
||||||
// If a user decides to use the latest version of Go, we don't want
|
// If a user decides to use the latest version of Go, we don't want
|
||||||
|
@ -186,19 +205,17 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up sample buffer for reading, and a map
|
|
||||||
// for quick lookup of sample values.
|
|
||||||
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
|
||||||
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
|
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
|
||||||
|
|
||||||
var m collectorMetric
|
var m collectorMetric
|
||||||
if d.Kind == metrics.KindFloat64Histogram {
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
_, hasSum := rmExactSumMap[d.Name]
|
_, hasSum := opt.RuntimeMetricSumForHist[d.Name]
|
||||||
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
|
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
|
||||||
m = newBatchHistogram(
|
m = newBatchHistogram(
|
||||||
NewDesc(
|
NewDesc(
|
||||||
BuildFQName(namespace, subsystem, name),
|
BuildFQName(namespace, subsystem, name),
|
||||||
d.Description,
|
d.Description.Description,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
|
@ -210,30 +227,61 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Subsystem: subsystem,
|
Subsystem: subsystem,
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: d.Description,
|
Help: d.Description.Description,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
m = NewGauge(GaugeOpts{
|
m = NewGauge(GaugeOpts{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Subsystem: subsystem,
|
Subsystem: subsystem,
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: d.Description,
|
Help: d.Description.Description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
metricSet = append(metricSet, m)
|
metricSet = append(metricSet, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msMetrics memStatsMetrics
|
// Add exact sum metrics to sampleBuf if not added before.
|
||||||
if opt.isEnabled(goRuntimeMemStatsCollection) {
|
for _, h := range histograms {
|
||||||
msMetrics = goRuntimeMemStats()
|
sumMetric, ok := opt.RuntimeMetricSumForHist[h.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := sampleMap[sumMetric]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: sumMetric})
|
||||||
|
sampleMap[sumMetric] = &sampleBuf[len(sampleBuf)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
msMetrics memStatsMetrics
|
||||||
|
msDescriptions []metrics.Description
|
||||||
|
)
|
||||||
|
|
||||||
|
if !opt.DisableMemStatsLikeMetrics {
|
||||||
|
msMetrics = goRuntimeMemStats()
|
||||||
|
msDescriptions = bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
|
// Check if metric was not exposed before and if not, add to sampleBuf.
|
||||||
|
for _, mdDesc := range msDescriptions {
|
||||||
|
if _, ok := sampleMap[mdDesc.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: mdDesc.Name})
|
||||||
|
sampleMap[mdDesc.Name] = &sampleBuf[len(sampleBuf)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &goCollector{
|
return &goCollector{
|
||||||
opt: opt,
|
|
||||||
base: newBaseGoCollector(),
|
base: newBaseGoCollector(),
|
||||||
rmSampleBuf: sampleBuf,
|
sampleBuf: sampleBuf,
|
||||||
rmSampleMap: sampleMap,
|
sampleMap: sampleMap,
|
||||||
rmMetrics: metricSet,
|
rmExposedMetrics: metricSet,
|
||||||
|
rmExactSumMapForHist: opt.RuntimeMetricSumForHist,
|
||||||
msMetrics: msMetrics,
|
msMetrics: msMetrics,
|
||||||
|
msMetricsEnabled: !opt.DisableMemStatsLikeMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +291,7 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||||
for _, i := range c.msMetrics {
|
for _, i := range c.msMetrics {
|
||||||
ch <- i.desc
|
ch <- i.desc
|
||||||
}
|
}
|
||||||
for _, m := range c.rmMetrics {
|
for _, m := range c.rmExposedMetrics {
|
||||||
ch <- m.Desc()
|
ch <- m.Desc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,8 +301,12 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
// Collect base non-memory metrics.
|
// Collect base non-memory metrics.
|
||||||
c.base.Collect(ch)
|
c.base.Collect(ch)
|
||||||
|
|
||||||
|
if len(c.sampleBuf) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Collect must be thread-safe, so prevent concurrent use of
|
// Collect must be thread-safe, so prevent concurrent use of
|
||||||
// rmSampleBuf. Just read into rmSampleBuf but write all the data
|
// sampleBuf elements. Just read into sampleBuf but write all the data
|
||||||
// we get into our Metrics or MemStats.
|
// we get into our Metrics or MemStats.
|
||||||
//
|
//
|
||||||
// This lock also ensures that the Metrics we send out are all from
|
// This lock also ensures that the Metrics we send out are all from
|
||||||
|
@ -268,18 +320,18 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if len(c.rmSampleBuf) > 0 {
|
|
||||||
// Populate runtime/metrics sample buffer.
|
// Populate runtime/metrics sample buffer.
|
||||||
metrics.Read(c.rmSampleBuf)
|
metrics.Read(c.sampleBuf)
|
||||||
}
|
|
||||||
|
// Collect all our runtime/metrics user chose to expose from sampleBuf (if any).
|
||||||
|
for i, metric := range c.rmExposedMetrics {
|
||||||
|
// We created samples for exposed metrics first in order, so indexes match.
|
||||||
|
sample := c.sampleBuf[i]
|
||||||
|
|
||||||
if c.opt.isEnabled(goRuntimeMetricsCollection) {
|
|
||||||
// Collect all our metrics from rmSampleBuf.
|
|
||||||
for i, sample := range c.rmSampleBuf {
|
|
||||||
// N.B. switch on concrete type because it's significantly more efficient
|
// N.B. switch on concrete type because it's significantly more efficient
|
||||||
// than checking for the Counter and Gauge interface implementations. In
|
// than checking for the Counter and Gauge interface implementations. In
|
||||||
// this case, we control all the types here.
|
// this case, we control all the types here.
|
||||||
switch m := c.rmMetrics[i].(type) {
|
switch m := metric.(type) {
|
||||||
case *counter:
|
case *counter:
|
||||||
// Guard against decreases. This should never happen, but a failure
|
// Guard against decreases. This should never happen, but a failure
|
||||||
// to do so will result in a panic, which is a harsh consequence for
|
// to do so will result in a panic, which is a harsh consequence for
|
||||||
|
@ -299,13 +351,12 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
panic("unexpected metric type")
|
panic("unexpected metric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if c.msMetricsEnabled {
|
||||||
// ms is a dummy MemStats that we populate ourselves so that we can
|
// ms is a dummy MemStats that we populate ourselves so that we can
|
||||||
// populate the old metrics from it if goMemStatsCollection is enabled.
|
// populate the old metrics from it if goMemStatsCollection is enabled.
|
||||||
if c.opt.isEnabled(goRuntimeMemStatsCollection) {
|
|
||||||
var ms runtime.MemStats
|
var ms runtime.MemStats
|
||||||
memStatsFromRM(&ms, c.rmSampleMap)
|
memStatsFromRM(&ms, c.sampleMap)
|
||||||
for _, i := range c.msMetrics {
|
for _, i := range c.msMetrics {
|
||||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
|
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
|
||||||
}
|
}
|
||||||
|
@ -336,11 +387,6 @@ func unwrapScalarRMValue(v metrics.Value) float64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rmExactSumMap = map[string]string{
|
|
||||||
"/gc/heap/allocs-by-size:bytes": "/gc/heap/allocs:bytes",
|
|
||||||
"/gc/heap/frees-by-size:bytes": "/gc/heap/frees:bytes",
|
|
||||||
}
|
|
||||||
|
|
||||||
// exactSumFor takes a runtime/metrics metric name (that is assumed to
|
// exactSumFor takes a runtime/metrics metric name (that is assumed to
|
||||||
// be of kind KindFloat64Histogram) and returns its exact sum and whether
|
// be of kind KindFloat64Histogram) and returns its exact sum and whether
|
||||||
// its exact sum exists.
|
// its exact sum exists.
|
||||||
|
@ -348,11 +394,11 @@ var rmExactSumMap = map[string]string{
|
||||||
// The runtime/metrics API for histograms doesn't currently expose exact
|
// The runtime/metrics API for histograms doesn't currently expose exact
|
||||||
// sums, but some of the other metrics are in fact exact sums of histograms.
|
// sums, but some of the other metrics are in fact exact sums of histograms.
|
||||||
func (c *goCollector) exactSumFor(rmName string) float64 {
|
func (c *goCollector) exactSumFor(rmName string) float64 {
|
||||||
sumName, ok := rmExactSumMap[rmName]
|
sumName, ok := c.rmExactSumMapForHist[rmName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
s, ok := c.rmSampleMap[sumName]
|
s, ok := c.sampleMap[sumName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/metrics"
|
"runtime/metrics"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -30,9 +31,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRmForMemStats(t *testing.T) {
|
func TestRmForMemStats(t *testing.T) {
|
||||||
if got, want := len(bestEffortLookupRM(rmForMemStats)), len(rmForMemStats); got != want {
|
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
|
if got, want := len(descs), len(rmNamesForMemStatsMetrics); got != want {
|
||||||
t.Errorf("got %d, want %d metrics", got, want)
|
t.Errorf("got %d, want %d metrics", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, d := range descs {
|
||||||
|
// We don't expect histograms there.
|
||||||
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
|
t.Errorf("we don't expect to use histograms for MemStats metrics, got %v", d.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectedBaseMetrics() map[string]struct{} {
|
func expectedBaseMetrics() map[string]struct{} {
|
||||||
|
@ -64,30 +74,43 @@ func addExpectedRuntimeMetrics(metrics map[string]struct{}) map[string]struct{}
|
||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoCollector(t *testing.T) {
|
func TestGoCollector_ExposedMetrics(t *testing.T) {
|
||||||
for _, tcase := range []struct {
|
for _, tcase := range []struct {
|
||||||
collections uint32
|
opts internal.GoCollectorOptions
|
||||||
expectedFQNameSet map[string]struct{}
|
expectedFQNameSet map[string]struct{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
collections: 0,
|
opts: internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
},
|
||||||
expectedFQNameSet: expectedBaseMetrics(),
|
expectedFQNameSet: expectedBaseMetrics(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMemStatsCollection,
|
// Default, only MemStats.
|
||||||
expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
|
expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMetricsCollection,
|
// Get all runtime/metrics without MemStats.
|
||||||
|
opts: internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedFQNameSet: addExpectedRuntimeMetrics(expectedBaseMetrics()),
|
expectedFQNameSet: addExpectedRuntimeMetrics(expectedBaseMetrics()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMemStatsCollection | goRuntimeMetricsCollection,
|
// Get all runtime/metrics and MemStats.
|
||||||
|
opts: internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedFQNameSet: addExpectedRuntimeMemStats(addExpectedRuntimeMetrics(expectedBaseMetrics())),
|
expectedFQNameSet: addExpectedRuntimeMemStats(addExpectedRuntimeMetrics(expectedBaseMetrics())),
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if ok := t.Run("", func(t *testing.T) {
|
if ok := t.Run("", func(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, tcase.collections)
|
goMetrics := collectGoMetrics(t, tcase.opts)
|
||||||
goMetricSet := make(map[string]Metric)
|
goMetricSet := make(map[string]Metric)
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
goMetricSet[m.Desc().fqName] = m
|
goMetricSet[m.Desc().fqName] = m
|
||||||
|
@ -118,7 +141,11 @@ func TestGoCollector(t *testing.T) {
|
||||||
var sink interface{}
|
var sink interface{}
|
||||||
|
|
||||||
func TestBatchHistogram(t *testing.T) {
|
func TestBatchHistogram(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, goRuntimeMetricsCollection)
|
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
var mhist Metric
|
var mhist Metric
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
|
@ -145,7 +172,8 @@ func TestBatchHistogram(t *testing.T) {
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
sink = make([]byte, 128)
|
sink = make([]byte, 128)
|
||||||
}
|
}
|
||||||
collectGoMetrics(t, defaultGoCollections)
|
|
||||||
|
collectGoMetrics(t, defaultGoCollectorOptions())
|
||||||
for i, v := range hist.counts {
|
for i, v := range hist.counts {
|
||||||
if v != countsCopy[i] {
|
if v != countsCopy[i] {
|
||||||
t.Error("counts changed during new collection")
|
t.Error("counts changed during new collection")
|
||||||
|
@ -194,11 +222,13 @@ func TestBatchHistogram(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectGoMetrics(t *testing.T, enabledCollections uint32) []Metric {
|
func collectGoMetrics(t *testing.T, opts internal.GoCollectorOptions) []Metric {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
c := NewGoCollector(func(o *GoCollectorOptions) {
|
c := NewGoCollector(func(o *internal.GoCollectorOptions) {
|
||||||
o.EnabledCollections = enabledCollections
|
o.DisableMemStatsLikeMetrics = opts.DisableMemStatsLikeMetrics
|
||||||
|
o.RuntimeMetricSumForHist = opts.RuntimeMetricSumForHist
|
||||||
|
o.RuntimeMetricRules = opts.RuntimeMetricRules
|
||||||
}).(*goCollector)
|
}).(*goCollector)
|
||||||
|
|
||||||
// Collect all metrics.
|
// Collect all metrics.
|
||||||
|
@ -222,7 +252,7 @@ func collectGoMetrics(t *testing.T, enabledCollections uint32) []Metric {
|
||||||
|
|
||||||
func TestMemStatsEquivalence(t *testing.T) {
|
func TestMemStatsEquivalence(t *testing.T) {
|
||||||
var msReal, msFake runtime.MemStats
|
var msReal, msFake runtime.MemStats
|
||||||
descs := bestEffortLookupRM(rmForMemStats)
|
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
samples := make([]metrics.Sample, len(descs))
|
samples := make([]metrics.Sample, len(descs))
|
||||||
samplesMap := make(map[string]*metrics.Sample)
|
samplesMap := make(map[string]*metrics.Sample)
|
||||||
|
@ -269,7 +299,12 @@ func TestMemStatsEquivalence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpectedRuntimeMetrics(t *testing.T) {
|
func TestExpectedRuntimeMetrics(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, goRuntimeMemStatsCollection|goRuntimeMetricsCollection)
|
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
})
|
||||||
goMetricSet := make(map[string]Metric)
|
goMetricSet := make(map[string]Metric)
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
goMetricSet[m.Desc().fqName] = m
|
goMetricSet[m.Desc().fqName] = m
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2021 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
type GoCollectorRule struct {
|
||||||
|
Matcher *regexp.Regexp
|
||||||
|
Deny bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoCollectorOptions should not be used be directly by anything, except `collectors` package.
|
||||||
|
// Use it via collectors package instead. See issue
|
||||||
|
// https://github.com/prometheus/client_golang/issues/1030.
|
||||||
|
//
|
||||||
|
// This is internal, so external users only can use it via `collector.WithGoCollector*` methods
|
||||||
|
type GoCollectorOptions struct {
|
||||||
|
DisableMemStatsLikeMetrics bool
|
||||||
|
RuntimeMetricSumForHist map[string]string
|
||||||
|
RuntimeMetricRules []GoCollectorRule
|
||||||
|
}
|
Loading…
Reference in New Issue