diff --git a/pkg/util/metrics/framework/counter.go b/pkg/util/metrics/framework/counter.go index d02b7fd8369..a2e3ab5455c 100644 --- a/pkg/util/metrics/framework/counter.go +++ b/pkg/util/metrics/framework/counter.go @@ -21,24 +21,24 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -// kubeCounter is our internal representation for our wrapping struct around prometheus -// counters. kubeCounter implements both KubeCollector and KubeCounter. -type kubeCounter struct { - KubeCounter +// Counter is our internal representation for our wrapping struct around prometheus +// counters. Counter implements both KubeCollector and CounterMetric. +type Counter struct { + CounterMetric *CounterOpts lazyMetric selfCollector } -// NewCounter returns an object which satisfies the KubeCollector and KubeCounter interfaces. +// NewCounter returns an object which satisfies the KubeCollector and CounterMetric interfaces. // However, the object returned will not measure anything unless the collector is first // registered, since the metric is lazily instantiated. -func NewCounter(opts *CounterOpts) *kubeCounter { +func NewCounter(opts *CounterOpts) *Counter { // todo: handle defaulting better if opts.StabilityLevel == "" { opts.StabilityLevel = ALPHA } - kc := &kubeCounter{ + kc := &Counter{ CounterOpts: opts, lazyMetric: lazyMetric{}, } @@ -47,20 +47,20 @@ func NewCounter(opts *CounterOpts) *kubeCounter { return kc } -// setPrometheusCounter sets the underlying KubeCounter object, i.e. the thing that does the measurement. -func (c *kubeCounter) setPrometheusCounter(counter prometheus.Counter) { - c.KubeCounter = counter +// setPrometheusCounter sets the underlying CounterMetric object, i.e. the thing that does the measurement. +func (c *Counter) setPrometheusCounter(counter prometheus.Counter) { + c.CounterMetric = counter c.initSelfCollection(counter) } // DeprecatedVersion returns a pointer to the Version or nil -func (c *kubeCounter) DeprecatedVersion() *semver.Version { +func (c *Counter) DeprecatedVersion() *semver.Version { return c.CounterOpts.DeprecatedVersion } // initializeMetric invocation creates the actual underlying Counter. Until this method is called // the underlying counter is a no-op. -func (c *kubeCounter) initializeMetric() { +func (c *Counter) initializeMetric() { c.CounterOpts.annotateStabilityLevel() // this actually creates the underlying prometheus counter. c.setPrometheusCounter(prometheus.NewCounter(c.CounterOpts.toPromCounterOpts())) @@ -68,25 +68,25 @@ func (c *kubeCounter) initializeMetric() { // initializeDeprecatedMetric invocation creates the actual (but deprecated) Counter. Until this method // is called the underlying counter is a no-op. -func (c *kubeCounter) initializeDeprecatedMetric() { +func (c *Counter) initializeDeprecatedMetric() { c.CounterOpts.markDeprecated() c.initializeMetric() } -// kubeCounterVec is the internal representation of our wrapping struct around prometheus -// counterVecs. kubeCounterVec implements both KubeCollector and KubeCounterVec. -type kubeCounterVec struct { +// CounterVec is the internal representation of our wrapping struct around prometheus +// counterVecs. CounterVec implements both KubeCollector and CounterVecMetric. +type CounterVec struct { *prometheus.CounterVec *CounterOpts lazyMetric originalLabels []string } -// NewCounterVec returns an object which satisfies the KubeCollector and KubeCounterVec interfaces. +// NewCounterVec returns an object which satisfies the KubeCollector and CounterVecMetric interfaces. // However, the object returned will not measure anything unless the collector is first // registered, since the metric is lazily instantiated. -func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec { - cv := &kubeCounterVec{ +func NewCounterVec(opts *CounterOpts, labels []string) *CounterVec { + cv := &CounterVec{ CounterVec: noopCounterVec, CounterOpts: opts, originalLabels: labels, @@ -97,19 +97,19 @@ func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec { } // DeprecatedVersion returns a pointer to the Version or nil -func (v *kubeCounterVec) DeprecatedVersion() *semver.Version { +func (v *CounterVec) DeprecatedVersion() *semver.Version { return v.CounterOpts.DeprecatedVersion } // initializeMetric invocation creates the actual underlying CounterVec. Until this method is called // the underlying counterVec is a no-op. -func (v *kubeCounterVec) initializeMetric() { +func (v *CounterVec) initializeMetric() { v.CounterVec = prometheus.NewCounterVec(v.CounterOpts.toPromCounterOpts(), v.originalLabels) } // initializeDeprecatedMetric invocation creates the actual (but deprecated) CounterVec. Until this method is called // the underlying counterVec is a no-op. -func (v *kubeCounterVec) initializeDeprecatedMetric() { +func (v *CounterVec) initializeDeprecatedMetric() { v.CounterOpts.markDeprecated() v.initializeMetric() } @@ -121,17 +121,23 @@ func (v *kubeCounterVec) initializeDeprecatedMetric() { // for perpetuity (i.e. throughout application lifecycle). // // For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/counter.go#L179-L197 -// -// This method returns a no-op metric if the metric is not actually created/registered, avoiding that -// memory leak. -func (v *kubeCounterVec) WithLabelValues(lvs ...string) KubeCounter { + +// WithLabelValues returns the Counter for the given slice of label +// values (same order as the VariableLabels in Desc). If that combination of +// label values is accessed for the first time, a new Counter is created IFF the counterVec +// has been registered to a metrics registry. +func (v *CounterVec) WithLabelValues(lvs ...string) CounterMetric { if !v.IsCreated() { return noop // return no-op counter } return v.CounterVec.WithLabelValues(lvs...) } -func (v *kubeCounterVec) With(labels prometheus.Labels) KubeCounter { +// With returns the Counter for the given Labels map (the label names +// must match those of the VariableLabels in Desc). If that label map is +// accessed for the first time, a new Counter is created IFF the counterVec has +// been registered to a metrics registry. +func (v *CounterVec) With(labels prometheus.Labels) CounterMetric { if !v.IsCreated() { return noop // return no-op counter } diff --git a/pkg/util/metrics/framework/metric.go b/pkg/util/metrics/framework/metric.go index 6f41041e09e..e9733dbf1ed 100644 --- a/pkg/util/metrics/framework/metric.go +++ b/pkg/util/metrics/framework/metric.go @@ -25,7 +25,7 @@ import ( ) /* -This extends the prometheus.Collector interface to allow customization of the metric +KubeCollector extends the prometheus.Collector interface to allow customization of the metric registration process. Defer metric initialization until Create() is called, which then delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric method call depending on whether the metric is deprecated or not. diff --git a/pkg/util/metrics/framework/opts.go b/pkg/util/metrics/framework/opts.go index 44597601697..315e8055eee 100644 --- a/pkg/util/metrics/framework/opts.go +++ b/pkg/util/metrics/framework/opts.go @@ -41,13 +41,19 @@ type KubeOpts struct { StabilityLevel StabilityLevel } +// StabilityLevel represents the API guarantees for a given defined metric. type StabilityLevel string const ( - ALPHA StabilityLevel = "ALPHA" + // ALPHA metrics have no stability guarantees, as such, labels may + // be arbitrarily added/removed and the metric may be deleted at any time. + ALPHA StabilityLevel = "ALPHA" + // STABLE metrics are guaranteed not be mutated and removal is governed by + // the deprecation policy outlined in by the control plane metrics stability KEP. STABLE StabilityLevel = "STABLE" ) +// CounterOpts is an alias for Opts. See there for doc comments. type CounterOpts KubeOpts // Modify help description on the metric description. diff --git a/pkg/util/metrics/framework/registry.go b/pkg/util/metrics/framework/registry.go index 9b42b935e0d..2f31aa4f167 100644 --- a/pkg/util/metrics/framework/registry.go +++ b/pkg/util/metrics/framework/registry.go @@ -24,8 +24,13 @@ import ( "k8s.io/kubernetes/pkg/version" ) +// DefaultGlobalRegistry is a stub for the global registry which prometheus client +// currently uses. var DefaultGlobalRegistry = NewKubeRegistry() +// KubeRegistry is a wrapper around a prometheus registry-type object. Upon initialization +// the kubernetes binary version information is loaded into the registry object, so that +// automatic behavior can be configured for metric versioning. type KubeRegistry struct { PromRegistry version semver.Version @@ -43,6 +48,11 @@ func MustRegister(cs ...KubeCollector) { DefaultGlobalRegistry.MustRegister(cs...) } +// Register registers a new Collector to be included in metrics +// collection. It returns an error if the descriptors provided by the +// Collector are invalid or if they — in combination with descriptors of +// already registered Collectors — do not fulfill the consistency and +// uniqueness criteria described in the documentation of metric.Desc. func (kr *KubeRegistry) Register(c KubeCollector) error { if c.Create(&kr.version) { return kr.PromRegistry.Register(c) @@ -50,6 +60,9 @@ func (kr *KubeRegistry) Register(c KubeCollector) error { return nil } +// MustRegister works like Register but registers any number of +// Collectors and panics upon the first registration that causes an +// error. func (kr *KubeRegistry) MustRegister(cs ...KubeCollector) { metrics := make([]prometheus.Collector, 0, len(cs)) for _, c := range cs { @@ -60,14 +73,29 @@ func (kr *KubeRegistry) MustRegister(cs ...KubeCollector) { kr.PromRegistry.MustRegister(metrics...) } +// Unregister unregisters the Collector that equals the Collector passed +// in as an argument. (Two Collectors are considered equal if their +// Describe method yields the same set of descriptors.) The function +// returns whether a Collector was unregistered. Note that an unchecked +// Collector cannot be unregistered (as its Describe method does not +// yield any descriptor). func (kr *KubeRegistry) Unregister(collector KubeCollector) bool { return kr.PromRegistry.Unregister(collector) } +// Gather calls the Collect method of the registered Collectors and then +// gathers the collected metrics into a lexicographically sorted slice +// of uniquely named MetricFamily protobufs. Gather ensures that the +// returned slice is valid and self-consistent so that it can be used +// for valid exposition. As an exception to the strict consistency +// requirements described for metric.Desc, Gather will tolerate +// different sets of label names for metrics of the same metric family. func (kr *KubeRegistry) Gather() ([]*dto.MetricFamily, error) { return kr.PromRegistry.Gather() } +// NewKubeRegistry creates a new kubernetes metric registry, loading in the kubernetes +// version information available to the binary. func NewKubeRegistry() *KubeRegistry { v, err := parseVersion(version.Get()) if err != nil { diff --git a/pkg/util/metrics/framework/registry_test.go b/pkg/util/metrics/framework/registry_test.go index 7d72e78aadf..7b4d09a5395 100644 --- a/pkg/util/metrics/framework/registry_test.go +++ b/pkg/util/metrics/framework/registry_test.go @@ -69,7 +69,7 @@ var ( func TestRegister(t *testing.T) { var tests = []struct { desc string - metrics []*kubeCounter + metrics []*Counter registryVersion *semver.Version expectedErrors []error expectedIsCreatedValues []bool @@ -78,7 +78,7 @@ func TestRegister(t *testing.T) { }{ { desc: "test alpha metric", - metrics: []*kubeCounter{alphaCounter}, + metrics: []*Counter{alphaCounter}, registryVersion: &v115, expectedErrors: []error{nil}, expectedIsCreatedValues: []bool{true}, @@ -87,7 +87,7 @@ func TestRegister(t *testing.T) { }, { desc: "test registering same metric multiple times", - metrics: []*kubeCounter{alphaCounter, alphaCounter}, + metrics: []*Counter{alphaCounter, alphaCounter}, registryVersion: &v115, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedIsCreatedValues: []bool{true, true}, @@ -96,7 +96,7 @@ func TestRegister(t *testing.T) { }, { desc: "test alpha deprecated metric", - metrics: []*kubeCounter{alphaDeprecatedCounter}, + metrics: []*Counter{alphaDeprecatedCounter}, registryVersion: &v115, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedIsCreatedValues: []bool{true}, @@ -105,7 +105,7 @@ func TestRegister(t *testing.T) { }, { desc: "test alpha hidden metric", - metrics: []*kubeCounter{alphaHiddenCounter}, + metrics: []*Counter{alphaHiddenCounter}, registryVersion: &v115, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedIsCreatedValues: []bool{false}, @@ -139,43 +139,43 @@ func TestRegister(t *testing.T) { func TestMustRegister(t *testing.T) { var tests = []struct { desc string - metrics []*kubeCounter + metrics []*Counter registryVersion *semver.Version expectedPanics []bool }{ { desc: "test alpha metric", - metrics: []*kubeCounter{alphaCounter}, + metrics: []*Counter{alphaCounter}, registryVersion: &v115, expectedPanics: []bool{false}, }, { desc: "test registering same metric multiple times", - metrics: []*kubeCounter{alphaCounter, alphaCounter}, + metrics: []*Counter{alphaCounter, alphaCounter}, registryVersion: &v115, expectedPanics: []bool{false, true}, }, { desc: "test alpha deprecated metric", - metrics: []*kubeCounter{alphaDeprecatedCounter}, + metrics: []*Counter{alphaDeprecatedCounter}, registryVersion: &v115, expectedPanics: []bool{false}, }, { desc: "test must registering same deprecated metric", - metrics: []*kubeCounter{alphaDeprecatedCounter, alphaDeprecatedCounter}, + metrics: []*Counter{alphaDeprecatedCounter, alphaDeprecatedCounter}, registryVersion: &v115, expectedPanics: []bool{false, true}, }, { desc: "test alpha hidden metric", - metrics: []*kubeCounter{alphaHiddenCounter}, + metrics: []*Counter{alphaHiddenCounter}, registryVersion: &v115, expectedPanics: []bool{false}, }, { desc: "test must registering same hidden metric", - metrics: []*kubeCounter{alphaHiddenCounter, alphaHiddenCounter}, + metrics: []*Counter{alphaHiddenCounter, alphaHiddenCounter}, registryVersion: &v115, expectedPanics: []bool{false, false}, // hidden metrics no-opt }, diff --git a/pkg/util/metrics/framework/wrappers.go b/pkg/util/metrics/framework/wrappers.go index c085bed8e1a..fa790b090c5 100644 --- a/pkg/util/metrics/framework/wrappers.go +++ b/pkg/util/metrics/framework/wrappers.go @@ -27,38 +27,37 @@ import ( // so that we can prevent breakage if methods are ever added to prometheus // variants of them. -/** - * Collector defines a subset of prometheus.Collector interface methods - */ +// Collector defines a subset of prometheus.Collector interface methods type Collector interface { Describe(chan<- *prometheus.Desc) Collect(chan<- prometheus.Metric) } -/** - * Metric defines a subset of prometheus.Metric interface methods - */ +// Metric defines a subset of prometheus.Metric interface methods type Metric interface { Desc() *prometheus.Desc Write(*dto.Metric) error } -// Counter is a Metric that represents a single numerical value that only ever +// CounterMetric is a Metric that represents a single numerical value that only ever // goes up. That implies that it cannot be used to count items whose number can // also go down, e.g. the number of currently running goroutines. Those // "counters" are represented by Gauges. -// -// This interface defines a subset of the interface provided by prometheus.Counter -type KubeCounter interface { + +// CounterMetric is an interface which defines a subset of the interface provided by prometheus.Counter +type CounterMetric interface { Inc() Add(float64) } -type KubeCounterVec interface { - WithLabelValues(...string) KubeCounter - With(prometheus.Labels) KubeCounter +// CounterVecMetric is an interface which prometheus.CounterVec satisfies. +type CounterVecMetric interface { + WithLabelValues(...string) CounterMetric + With(prometheus.Labels) CounterMetric } +// PromRegistry is an interface which implements a subset of prometheus.Registerer and +// prometheus.Gatherer interfaces type PromRegistry interface { Register(prometheus.Collector) error MustRegister(...prometheus.Collector)