Skip to content
Permalink
Browse files
Merge pull request #109729 from MikeSpreitzer/wrap-weighted-histograms
Wrap weighted histograms
  • Loading branch information
k8s-ci-robot committed May 12, 2022
2 parents dd1a789 + d23254b commit 6e04ca634dca6d1d7c4337bbb777682492617426
Showing 12 changed files with 963 additions and 41 deletions.
@@ -66,6 +66,7 @@ allowed_prometheus_importers=(
./staging/src/k8s.io/component-base/metrics/testutil/metrics_test.go
./staging/src/k8s.io/component-base/metrics/testutil/promlint.go
./staging/src/k8s.io/component-base/metrics/testutil/testutil.go
./staging/src/k8s.io/component-base/metrics/timing_histogram_test.go
./staging/src/k8s.io/component-base/metrics/value.go
./staging/src/k8s.io/component-base/metrics/wrappers.go
./test/e2e/apimachinery/flowcontrol.go
@@ -18,6 +18,7 @@ package metrics

import (
"context"

"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
@@ -106,9 +107,14 @@ type CounterVec struct {
originalLabels []string
}

// NewCounterVec returns an object which satisfies the kubeCollector and CounterVecMetric interfaces.
var _ kubeCollector = &CounterVec{}

// TODO: make this true: var _ CounterVecMetric = &CounterVec{}

// NewCounterVec returns an object which satisfies the kubeCollector and (almost) CounterVecMetric interfaces.
// 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, and only members extracted after
// registration will actually measure anything.
func NewCounterVec(opts *CounterOpts, labels []string) *CounterVec {
opts.StabilityLevel.setDefaults()

@@ -149,13 +155,16 @@ func (v *CounterVec) initializeDeprecatedMetric() {
v.initializeMetric()
}

// Default Prometheus behavior actually results in the creation of a new metric
// if a metric with the unique label values is not found in the underlying stored metricMap.
// Default Prometheus Vec behavior is that member extraction results in creation of a new element
// if one with the unique label values is not found in the underlying stored metricMap.
// This means that if this function is called but the underlying metric is not registered
// (which means it will never be exposed externally nor consumed), the metric will exist in memory
// for perpetuity (i.e. throughout application lifecycle).
//
// For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/counter.go#L179-L197
//
// In contrast, the Vec behavior in this package is that member extraction before registration
// returns a permanent noop object.

// WithLabelValues returns the Counter for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of
@@ -18,6 +18,7 @@ package metrics

import (
"context"

"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus"

@@ -33,7 +34,11 @@ type Gauge struct {
selfCollector
}

// NewGauge returns an object which satisfies the kubeCollector and KubeGauge interfaces.
var _ GaugeMetric = &Gauge{}
var _ Registerable = &Gauge{}
var _ kubeCollector = &Gauge{}

// NewGauge returns an object which satisfies the kubeCollector, Registerable, and Gauge interfaces.
// However, the object returned will not measure anything unless the collector is first
// registered, since the metric is lazily instantiated.
func NewGauge(opts *GaugeOpts) *Gauge {
@@ -88,9 +93,14 @@ type GaugeVec struct {
originalLabels []string
}

// NewGaugeVec returns an object which satisfies the kubeCollector and KubeGaugeVec interfaces.
var _ GaugeVecMetric = &GaugeVec{}
var _ Registerable = &GaugeVec{}
var _ kubeCollector = &GaugeVec{}

// NewGaugeVec returns an object which satisfies the kubeCollector, Registerable, and GaugeVecMetric interfaces.
// 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, and only members extracted after
// registration will actually measure anything.
func NewGaugeVec(opts *GaugeOpts, labels []string) *GaugeVec {
opts.StabilityLevel.setDefaults()

@@ -130,40 +140,67 @@ func (v *GaugeVec) initializeDeprecatedMetric() {
v.initializeMetric()
}

// Default Prometheus behavior actually results in the creation of a new metric
// if a metric with the unique label values is not found in the underlying stored metricMap.
func (v *GaugeVec) WithLabelValuesChecked(lvs ...string) (GaugeMetric, error) {
if !v.IsCreated() {
if v.IsHidden() {
return noop, nil
}
return noop, errNotRegistered // return no-op gauge
}
if v.LabelValueAllowLists != nil {
v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs)
}
elt, err := v.GaugeVec.GetMetricWithLabelValues(lvs...)
return elt, err
}

// Default Prometheus Vec behavior is that member extraction results in creation of a new element
// if one with the unique label values is not found in the underlying stored metricMap.
// This means that if this function is called but the underlying metric is not registered
// (which means it will never be exposed externally nor consumed), the metric will exist in memory
// for perpetuity (i.e. throughout application lifecycle).
//
// For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/gauge.go#L190-L208
//
// In contrast, the Vec behavior in this package is that member extraction before registration
// returns a permanent noop object.

// WithLabelValues returns the GaugeMetric 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 GaugeMetric is created IFF the gaugeVec
// has been registered to a metrics registry.
func (v *GaugeVec) WithLabelValues(lvs ...string) GaugeMetric {
ans, err := v.WithLabelValuesChecked(lvs...)
if err == nil || ErrIsNotRegistered(err) {
return ans
}
panic(err)
}

func (v *GaugeVec) WithChecked(labels map[string]string) (GaugeMetric, error) {
if !v.IsCreated() {
return noop // return no-op gauge
if v.IsHidden() {
return noop, nil
}
return noop, errNotRegistered // return no-op gauge
}
if v.LabelValueAllowLists != nil {
v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs)
v.LabelValueAllowLists.ConstrainLabelMap(labels)
}
return v.GaugeVec.WithLabelValues(lvs...)
elt, err := v.GaugeVec.GetMetricWith(labels)
return elt, err
}

// With returns the GaugeMetric 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 GaugeMetric is created IFF the gaugeVec has
// been registered to a metrics registry.
func (v *GaugeVec) With(labels map[string]string) GaugeMetric {
if !v.IsCreated() {
return noop // return no-op gauge
ans, err := v.WithChecked(labels)
if err == nil || ErrIsNotRegistered(err) {
return ans
}
if v.LabelValueAllowLists != nil {
v.LabelValueAllowLists.ConstrainLabelMap(labels)
}
return v.GaugeVec.With(labels)
panic(err)
}

// Delete deletes the metric where the variable labels are the same as those
@@ -219,6 +256,10 @@ func (v *GaugeVec) WithContext(ctx context.Context) *GaugeVecWithContext {
}
}

func (v *GaugeVec) InterfaceWithContext(ctx context.Context) GaugeVecMetric {
return v.WithContext(ctx)
}

// GaugeVecWithContext is the wrapper of GaugeVec with context.
type GaugeVecWithContext struct {
*GaugeVec
@@ -18,6 +18,7 @@ package metrics

import (
"context"

"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus"
)
@@ -100,7 +101,10 @@ type HistogramVec struct {

// NewHistogramVec returns an object which satisfies kubeCollector and wraps the
// prometheus.HistogramVec object. However, the object returned will not measure
// anything unless the collector is first registered, since the metric is lazily instantiated.
// anything unless the collector is first registered, since the metric is lazily instantiated,
// and only members extracted after
// registration will actually measure anything.

func NewHistogramVec(opts *HistogramOpts, labels []string) *HistogramVec {
opts.StabilityLevel.setDefaults()

@@ -136,13 +140,16 @@ func (v *HistogramVec) initializeDeprecatedMetric() {
v.initializeMetric()
}

// Default Prometheus behavior actually results in the creation of a new metric
// if a metric with the unique label values is not found in the underlying stored metricMap.
// Default Prometheus Vec behavior is that member extraction results in creation of a new element
// if one with the unique label values is not found in the underlying stored metricMap.
// This means that if this function is called but the underlying metric is not registered
// (which means it will never be exposed externally nor consumed), the metric will exist in memory
// for perpetuity (i.e. throughout application lifecycle).
//
// For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/histogram.go#L460-L470
//
// In contrast, the Vec behavior in this package is that member extraction before registration
// returns a permanent noop object.

// WithLabelValues returns the ObserverMetric for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of
@@ -87,6 +87,19 @@ func TestHistogram(t *testing.T) {
})
c := NewHistogram(test.HistogramOpts)
registry.MustRegister(c)
cm := c.ObserverMetric.(prometheus.Metric)

metricChan := make(chan prometheus.Metric, 2)
c.Collect(metricChan)
close(metricChan)
m1 := <-metricChan
if m1 != cm {
t.Error("Unexpected metric", m1, cm)
}
m2, ok := <-metricChan
if ok {
t.Error("Unexpected second metric", m2)
}

ms, err := registry.Gather()
assert.Equalf(t, test.expectedMetricCount, len(ms), "Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
@@ -179,7 +192,24 @@ func TestHistogramVec(t *testing.T) {
})
c := NewHistogramVec(test.HistogramOpts, test.labels)
registry.MustRegister(c)
c.WithLabelValues("1", "2").Observe(1.0)
ov12 := c.WithLabelValues("1", "2")
cm1 := ov12.(prometheus.Metric)
ov12.Observe(1.0)

if test.expectedMetricCount > 0 {
metricChan := make(chan prometheus.Metric, 2)
c.Collect(metricChan)
close(metricChan)
m1 := <-metricChan
if m1 != cm1 {
t.Error("Unexpected metric", m1, cm1)
}
m2, ok := <-metricChan
if ok {
t.Error("Unexpected second metric", m2)
}
}

ms, err := registry.Gather()
assert.Equalf(t, test.expectedMetricCount, len(ms), "Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
assert.Nil(t, err, "Gather failed %v", err)
@@ -218,20 +248,20 @@ func TestHistogramWithLabelValueAllowList(t *testing.T) {
var tests = []struct {
desc string
labelValues [][]string
expectMetricValues map[string]int
expectMetricValues map[string]uint64
}{
{
desc: "Test no unexpected input",
labelValues: [][]string{{"allowed", "b1"}, {"allowed", "b2"}},
expectMetricValues: map[string]int{
expectMetricValues: map[string]uint64{
"allowed b1": 1.0,
"allowed b2": 1.0,
},
},
{
desc: "Test unexpected input",
labelValues: [][]string{{"allowed", "b1"}, {"not_allowed", "b1"}},
expectMetricValues: map[string]int{
expectMetricValues: map[string]uint64{
"allowed b1": 1.0,
"unexpected b1": 1.0,
},
@@ -274,7 +304,7 @@ func TestHistogramWithLabelValueAllowList(t *testing.T) {
labelValuePair := aValue + " " + bValue
expectedValue, ok := test.expectMetricValues[labelValuePair]
assert.True(t, ok, "Got unexpected label values, lable_a is %v, label_b is %v", aValue, bValue)
actualValue := int(m.GetHistogram().GetSampleCount())
actualValue := m.GetHistogram().GetSampleCount()
assert.Equalf(t, expectedValue, actualValue, "Got %v, wanted %v as the count while setting label_a to %v and label b to %v", actualValue, expectedValue, aValue, bValue)
}
}
@@ -22,6 +22,7 @@ import (
"github.com/blang/semver/v4"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
promext "k8s.io/component-base/metrics/prometheusextension"

"k8s.io/klog/v2"
)
@@ -203,6 +204,7 @@ func (c *selfCollector) Collect(ch chan<- prometheus.Metric) {
// no-op vecs for convenience
var noopCounterVec = &prometheus.CounterVec{}
var noopHistogramVec = &prometheus.HistogramVec{}
var noopTimingHistogramVec = &promext.TimingHistogramVec{}
var noopGaugeVec = &prometheus.GaugeVec{}
var noopObserverVec = &noopObserverVector{}

@@ -211,17 +213,18 @@ var noop = &noopMetric{}

type noopMetric struct{}

func (noopMetric) Inc() {}
func (noopMetric) Add(float64) {}
func (noopMetric) Dec() {}
func (noopMetric) Set(float64) {}
func (noopMetric) Sub(float64) {}
func (noopMetric) Observe(float64) {}
func (noopMetric) SetToCurrentTime() {}
func (noopMetric) Desc() *prometheus.Desc { return nil }
func (noopMetric) Write(*dto.Metric) error { return nil }
func (noopMetric) Describe(chan<- *prometheus.Desc) {}
func (noopMetric) Collect(chan<- prometheus.Metric) {}
func (noopMetric) Inc() {}
func (noopMetric) Add(float64) {}
func (noopMetric) Dec() {}
func (noopMetric) Set(float64) {}
func (noopMetric) Sub(float64) {}
func (noopMetric) Observe(float64) {}
func (noopMetric) ObserveWithWeight(float64, uint64) {}
func (noopMetric) SetToCurrentTime() {}
func (noopMetric) Desc() *prometheus.Desc { return nil }
func (noopMetric) Write(*dto.Metric) error { return nil }
func (noopMetric) Describe(chan<- *prometheus.Desc) {}
func (noopMetric) Collect(chan<- prometheus.Metric) {}

type noopObserverVector struct{}

0 comments on commit 6e04ca6

Please sign in to comment.