add additional documentation around exposed functionality

This commit is contained in:
Han Kang 2019-05-01 16:28:52 -07:00
parent e6fbb593bb
commit 634ab0be53
6 changed files with 93 additions and 54 deletions

View File

@ -21,24 +21,24 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
// kubeCounter is our internal representation for our wrapping struct around prometheus // Counter is our internal representation for our wrapping struct around prometheus
// counters. kubeCounter implements both KubeCollector and KubeCounter. // counters. Counter implements both KubeCollector and CounterMetric.
type kubeCounter struct { type Counter struct {
KubeCounter CounterMetric
*CounterOpts *CounterOpts
lazyMetric lazyMetric
selfCollector 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 // However, the object returned will not measure anything unless the collector is first
// registered, since the metric is lazily instantiated. // registered, since the metric is lazily instantiated.
func NewCounter(opts *CounterOpts) *kubeCounter { func NewCounter(opts *CounterOpts) *Counter {
// todo: handle defaulting better // todo: handle defaulting better
if opts.StabilityLevel == "" { if opts.StabilityLevel == "" {
opts.StabilityLevel = ALPHA opts.StabilityLevel = ALPHA
} }
kc := &kubeCounter{ kc := &Counter{
CounterOpts: opts, CounterOpts: opts,
lazyMetric: lazyMetric{}, lazyMetric: lazyMetric{},
} }
@ -47,20 +47,20 @@ func NewCounter(opts *CounterOpts) *kubeCounter {
return kc return kc
} }
// setPrometheusCounter sets the underlying KubeCounter object, i.e. the thing that does the measurement. // setPrometheusCounter sets the underlying CounterMetric object, i.e. the thing that does the measurement.
func (c *kubeCounter) setPrometheusCounter(counter prometheus.Counter) { func (c *Counter) setPrometheusCounter(counter prometheus.Counter) {
c.KubeCounter = counter c.CounterMetric = counter
c.initSelfCollection(counter) c.initSelfCollection(counter)
} }
// DeprecatedVersion returns a pointer to the Version or nil // 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 return c.CounterOpts.DeprecatedVersion
} }
// initializeMetric invocation creates the actual underlying Counter. Until this method is called // initializeMetric invocation creates the actual underlying Counter. Until this method is called
// the underlying counter is a no-op. // the underlying counter is a no-op.
func (c *kubeCounter) initializeMetric() { func (c *Counter) initializeMetric() {
c.CounterOpts.annotateStabilityLevel() c.CounterOpts.annotateStabilityLevel()
// this actually creates the underlying prometheus counter. // this actually creates the underlying prometheus counter.
c.setPrometheusCounter(prometheus.NewCounter(c.CounterOpts.toPromCounterOpts())) 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 // initializeDeprecatedMetric invocation creates the actual (but deprecated) Counter. Until this method
// is called the underlying counter is a no-op. // is called the underlying counter is a no-op.
func (c *kubeCounter) initializeDeprecatedMetric() { func (c *Counter) initializeDeprecatedMetric() {
c.CounterOpts.markDeprecated() c.CounterOpts.markDeprecated()
c.initializeMetric() c.initializeMetric()
} }
// kubeCounterVec is the internal representation of our wrapping struct around prometheus // CounterVec is the internal representation of our wrapping struct around prometheus
// counterVecs. kubeCounterVec implements both KubeCollector and KubeCounterVec. // counterVecs. CounterVec implements both KubeCollector and CounterVecMetric.
type kubeCounterVec struct { type CounterVec struct {
*prometheus.CounterVec *prometheus.CounterVec
*CounterOpts *CounterOpts
lazyMetric lazyMetric
originalLabels []string 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 // However, the object returned will not measure anything unless the collector is first
// registered, since the metric is lazily instantiated. // registered, since the metric is lazily instantiated.
func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec { func NewCounterVec(opts *CounterOpts, labels []string) *CounterVec {
cv := &kubeCounterVec{ cv := &CounterVec{
CounterVec: noopCounterVec, CounterVec: noopCounterVec,
CounterOpts: opts, CounterOpts: opts,
originalLabels: labels, originalLabels: labels,
@ -97,19 +97,19 @@ func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec {
} }
// DeprecatedVersion returns a pointer to the Version or nil // 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 return v.CounterOpts.DeprecatedVersion
} }
// initializeMetric invocation creates the actual underlying CounterVec. Until this method is called // initializeMetric invocation creates the actual underlying CounterVec. Until this method is called
// the underlying counterVec is a no-op. // the underlying counterVec is a no-op.
func (v *kubeCounterVec) initializeMetric() { func (v *CounterVec) initializeMetric() {
v.CounterVec = prometheus.NewCounterVec(v.CounterOpts.toPromCounterOpts(), v.originalLabels) v.CounterVec = prometheus.NewCounterVec(v.CounterOpts.toPromCounterOpts(), v.originalLabels)
} }
// initializeDeprecatedMetric invocation creates the actual (but deprecated) CounterVec. Until this method is called // initializeDeprecatedMetric invocation creates the actual (but deprecated) CounterVec. Until this method is called
// the underlying counterVec is a no-op. // the underlying counterVec is a no-op.
func (v *kubeCounterVec) initializeDeprecatedMetric() { func (v *CounterVec) initializeDeprecatedMetric() {
v.CounterOpts.markDeprecated() v.CounterOpts.markDeprecated()
v.initializeMetric() v.initializeMetric()
} }
@ -121,17 +121,23 @@ func (v *kubeCounterVec) initializeDeprecatedMetric() {
// for perpetuity (i.e. throughout application lifecycle). // for perpetuity (i.e. throughout application lifecycle).
// //
// For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/counter.go#L179-L197 // 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 // WithLabelValues returns the Counter for the given slice of label
// memory leak. // values (same order as the VariableLabels in Desc). If that combination of
func (v *kubeCounterVec) WithLabelValues(lvs ...string) KubeCounter { // 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() { if !v.IsCreated() {
return noop // return no-op counter return noop // return no-op counter
} }
return v.CounterVec.WithLabelValues(lvs...) 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() { if !v.IsCreated() {
return noop // return no-op counter return noop // return no-op counter
} }

View File

@ -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 registration process. Defer metric initialization until Create() is called, which then
delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric
method call depending on whether the metric is deprecated or not. method call depending on whether the metric is deprecated or not.

View File

@ -41,13 +41,19 @@ type KubeOpts struct {
StabilityLevel StabilityLevel StabilityLevel StabilityLevel
} }
// StabilityLevel represents the API guarantees for a given defined metric.
type StabilityLevel string type StabilityLevel string
const ( 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" STABLE StabilityLevel = "STABLE"
) )
// CounterOpts is an alias for Opts. See there for doc comments.
type CounterOpts KubeOpts type CounterOpts KubeOpts
// Modify help description on the metric description. // Modify help description on the metric description.

View File

@ -24,8 +24,13 @@ import (
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
) )
// DefaultGlobalRegistry is a stub for the global registry which prometheus client
// currently uses.
var DefaultGlobalRegistry = NewKubeRegistry() 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 { type KubeRegistry struct {
PromRegistry PromRegistry
version semver.Version version semver.Version
@ -43,6 +48,11 @@ func MustRegister(cs ...KubeCollector) {
DefaultGlobalRegistry.MustRegister(cs...) 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 { func (kr *KubeRegistry) Register(c KubeCollector) error {
if c.Create(&kr.version) { if c.Create(&kr.version) {
return kr.PromRegistry.Register(c) return kr.PromRegistry.Register(c)
@ -50,6 +60,9 @@ func (kr *KubeRegistry) Register(c KubeCollector) error {
return nil 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) { func (kr *KubeRegistry) MustRegister(cs ...KubeCollector) {
metrics := make([]prometheus.Collector, 0, len(cs)) metrics := make([]prometheus.Collector, 0, len(cs))
for _, c := range cs { for _, c := range cs {
@ -60,14 +73,29 @@ func (kr *KubeRegistry) MustRegister(cs ...KubeCollector) {
kr.PromRegistry.MustRegister(metrics...) 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 { func (kr *KubeRegistry) Unregister(collector KubeCollector) bool {
return kr.PromRegistry.Unregister(collector) 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) { func (kr *KubeRegistry) Gather() ([]*dto.MetricFamily, error) {
return kr.PromRegistry.Gather() return kr.PromRegistry.Gather()
} }
// NewKubeRegistry creates a new kubernetes metric registry, loading in the kubernetes
// version information available to the binary.
func NewKubeRegistry() *KubeRegistry { func NewKubeRegistry() *KubeRegistry {
v, err := parseVersion(version.Get()) v, err := parseVersion(version.Get())
if err != nil { if err != nil {

View File

@ -69,7 +69,7 @@ var (
func TestRegister(t *testing.T) { func TestRegister(t *testing.T) {
var tests = []struct { var tests = []struct {
desc string desc string
metrics []*kubeCounter metrics []*Counter
registryVersion *semver.Version registryVersion *semver.Version
expectedErrors []error expectedErrors []error
expectedIsCreatedValues []bool expectedIsCreatedValues []bool
@ -78,7 +78,7 @@ func TestRegister(t *testing.T) {
}{ }{
{ {
desc: "test alpha metric", desc: "test alpha metric",
metrics: []*kubeCounter{alphaCounter}, metrics: []*Counter{alphaCounter},
registryVersion: &v115, registryVersion: &v115,
expectedErrors: []error{nil}, expectedErrors: []error{nil},
expectedIsCreatedValues: []bool{true}, expectedIsCreatedValues: []bool{true},
@ -87,7 +87,7 @@ func TestRegister(t *testing.T) {
}, },
{ {
desc: "test registering same metric multiple times", desc: "test registering same metric multiple times",
metrics: []*kubeCounter{alphaCounter, alphaCounter}, metrics: []*Counter{alphaCounter, alphaCounter},
registryVersion: &v115, registryVersion: &v115,
expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}},
expectedIsCreatedValues: []bool{true, true}, expectedIsCreatedValues: []bool{true, true},
@ -96,7 +96,7 @@ func TestRegister(t *testing.T) {
}, },
{ {
desc: "test alpha deprecated metric", desc: "test alpha deprecated metric",
metrics: []*kubeCounter{alphaDeprecatedCounter}, metrics: []*Counter{alphaDeprecatedCounter},
registryVersion: &v115, registryVersion: &v115,
expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}},
expectedIsCreatedValues: []bool{true}, expectedIsCreatedValues: []bool{true},
@ -105,7 +105,7 @@ func TestRegister(t *testing.T) {
}, },
{ {
desc: "test alpha hidden metric", desc: "test alpha hidden metric",
metrics: []*kubeCounter{alphaHiddenCounter}, metrics: []*Counter{alphaHiddenCounter},
registryVersion: &v115, registryVersion: &v115,
expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}},
expectedIsCreatedValues: []bool{false}, expectedIsCreatedValues: []bool{false},
@ -139,43 +139,43 @@ func TestRegister(t *testing.T) {
func TestMustRegister(t *testing.T) { func TestMustRegister(t *testing.T) {
var tests = []struct { var tests = []struct {
desc string desc string
metrics []*kubeCounter metrics []*Counter
registryVersion *semver.Version registryVersion *semver.Version
expectedPanics []bool expectedPanics []bool
}{ }{
{ {
desc: "test alpha metric", desc: "test alpha metric",
metrics: []*kubeCounter{alphaCounter}, metrics: []*Counter{alphaCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false}, expectedPanics: []bool{false},
}, },
{ {
desc: "test registering same metric multiple times", desc: "test registering same metric multiple times",
metrics: []*kubeCounter{alphaCounter, alphaCounter}, metrics: []*Counter{alphaCounter, alphaCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false, true}, expectedPanics: []bool{false, true},
}, },
{ {
desc: "test alpha deprecated metric", desc: "test alpha deprecated metric",
metrics: []*kubeCounter{alphaDeprecatedCounter}, metrics: []*Counter{alphaDeprecatedCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false}, expectedPanics: []bool{false},
}, },
{ {
desc: "test must registering same deprecated metric", desc: "test must registering same deprecated metric",
metrics: []*kubeCounter{alphaDeprecatedCounter, alphaDeprecatedCounter}, metrics: []*Counter{alphaDeprecatedCounter, alphaDeprecatedCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false, true}, expectedPanics: []bool{false, true},
}, },
{ {
desc: "test alpha hidden metric", desc: "test alpha hidden metric",
metrics: []*kubeCounter{alphaHiddenCounter}, metrics: []*Counter{alphaHiddenCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false}, expectedPanics: []bool{false},
}, },
{ {
desc: "test must registering same hidden metric", desc: "test must registering same hidden metric",
metrics: []*kubeCounter{alphaHiddenCounter, alphaHiddenCounter}, metrics: []*Counter{alphaHiddenCounter, alphaHiddenCounter},
registryVersion: &v115, registryVersion: &v115,
expectedPanics: []bool{false, false}, // hidden metrics no-opt expectedPanics: []bool{false, false}, // hidden metrics no-opt
}, },

View File

@ -27,38 +27,37 @@ import (
// so that we can prevent breakage if methods are ever added to prometheus // so that we can prevent breakage if methods are ever added to prometheus
// variants of them. // variants of them.
/** // Collector defines a subset of prometheus.Collector interface methods
* Collector defines a subset of prometheus.Collector interface methods
*/
type Collector interface { type Collector interface {
Describe(chan<- *prometheus.Desc) Describe(chan<- *prometheus.Desc)
Collect(chan<- prometheus.Metric) 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 { type Metric interface {
Desc() *prometheus.Desc Desc() *prometheus.Desc
Write(*dto.Metric) error 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 // 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 // also go down, e.g. the number of currently running goroutines. Those
// "counters" are represented by Gauges. // "counters" are represented by Gauges.
//
// This interface defines a subset of the interface provided by prometheus.Counter // CounterMetric is an interface which defines a subset of the interface provided by prometheus.Counter
type KubeCounter interface { type CounterMetric interface {
Inc() Inc()
Add(float64) Add(float64)
} }
type KubeCounterVec interface { // CounterVecMetric is an interface which prometheus.CounterVec satisfies.
WithLabelValues(...string) KubeCounter type CounterVecMetric interface {
With(prometheus.Labels) KubeCounter 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 { type PromRegistry interface {
Register(prometheus.Collector) error Register(prometheus.Collector) error
MustRegister(...prometheus.Collector) MustRegister(...prometheus.Collector)