mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Merge pull request #77618 from logicalhan/other-metric-wrappers
add wrappers around gauge, histogram & summary
This commit is contained in:
commit
33d763ff3a
@ -10,9 +10,12 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"counter.go",
|
"counter.go",
|
||||||
|
"gauge.go",
|
||||||
|
"histogram.go",
|
||||||
"metric.go",
|
"metric.go",
|
||||||
"opts.go",
|
"opts.go",
|
||||||
"registry.go",
|
"registry.go",
|
||||||
|
"summary.go",
|
||||||
"version_parser.go",
|
"version_parser.go",
|
||||||
"wrappers.go",
|
"wrappers.go",
|
||||||
],
|
],
|
||||||
@ -31,7 +34,10 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"counter_test.go",
|
"counter_test.go",
|
||||||
|
"gauge_test.go",
|
||||||
|
"histogram_test.go",
|
||||||
"registry_test.go",
|
"registry_test.go",
|
||||||
|
"summary_test.go",
|
||||||
"version_parser_test.go",
|
"version_parser_test.go",
|
||||||
],
|
],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
|
150
staging/src/k8s.io/component-base/metrics/gauge.go
Normal file
150
staging/src/k8s.io/component-base/metrics/gauge.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gauge is our internal representation for our wrapping struct around prometheus
|
||||||
|
// gauges. kubeGauge implements both KubeCollector and KubeGauge.
|
||||||
|
type Gauge struct {
|
||||||
|
GaugeMetric
|
||||||
|
*GaugeOpts
|
||||||
|
lazyMetric
|
||||||
|
selfCollector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGauge returns an object which satisfies the KubeCollector and KubeGauge 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 {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
kc := &Gauge{
|
||||||
|
GaugeOpts: opts,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
kc.setPrometheusGauge(noop)
|
||||||
|
kc.lazyInit(kc)
|
||||||
|
return kc
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPrometheusGauge sets the underlying KubeGauge object, i.e. the thing that does the measurement.
|
||||||
|
func (g *Gauge) setPrometheusGauge(gauge prometheus.Gauge) {
|
||||||
|
g.GaugeMetric = gauge
|
||||||
|
g.initSelfCollection(gauge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (g *Gauge) DeprecatedVersion() *semver.Version {
|
||||||
|
return g.GaugeOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeMetric invocation creates the actual underlying Gauge. Until this method is called
|
||||||
|
// the underlying gauge is a no-op.
|
||||||
|
func (g *Gauge) initializeMetric() {
|
||||||
|
g.GaugeOpts.annotateStabilityLevel()
|
||||||
|
// this actually creates the underlying prometheus gauge.
|
||||||
|
g.setPrometheusGauge(prometheus.NewGauge(g.GaugeOpts.toPromGaugeOpts()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeDeprecatedMetric invocation creates the actual (but deprecated) Gauge. Until this method
|
||||||
|
// is called the underlying gauge is a no-op.
|
||||||
|
func (g *Gauge) initializeDeprecatedMetric() {
|
||||||
|
g.GaugeOpts.markDeprecated()
|
||||||
|
g.initializeMetric()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GaugeVec is the internal representation of our wrapping struct around prometheus
|
||||||
|
// gaugeVecs. kubeGaugeVec implements both KubeCollector and KubeGaugeVec.
|
||||||
|
type GaugeVec struct {
|
||||||
|
*prometheus.GaugeVec
|
||||||
|
*GaugeOpts
|
||||||
|
lazyMetric
|
||||||
|
originalLabels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGaugeVec returns an object which satisfies the KubeCollector and KubeGaugeVec interfaces.
|
||||||
|
// However, the object returned will not measure anything unless the collector is first
|
||||||
|
// registered, since the metric is lazily instantiated.
|
||||||
|
func NewGaugeVec(opts *GaugeOpts, labels []string) *GaugeVec {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
cv := &GaugeVec{
|
||||||
|
GaugeVec: noopGaugeVec,
|
||||||
|
GaugeOpts: opts,
|
||||||
|
originalLabels: labels,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
cv.lazyInit(cv)
|
||||||
|
return cv
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (v *GaugeVec) DeprecatedVersion() *semver.Version {
|
||||||
|
return v.GaugeOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeMetric invocation creates the actual underlying GaugeVec. Until this method is called
|
||||||
|
// the underlying gaugeVec is a no-op.
|
||||||
|
func (v *GaugeVec) initializeMetric() {
|
||||||
|
v.GaugeOpts.annotateStabilityLevel()
|
||||||
|
v.GaugeVec = prometheus.NewGaugeVec(v.GaugeOpts.toPromGaugeOpts(), v.originalLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeDeprecatedMetric invocation creates the actual (but deprecated) GaugeVec. Until this method is called
|
||||||
|
// the underlying gaugeVec is a no-op.
|
||||||
|
func (v *GaugeVec) initializeDeprecatedMetric() {
|
||||||
|
v.GaugeOpts.markDeprecated()
|
||||||
|
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.
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop // return no-op gauge
|
||||||
|
}
|
||||||
|
return v.GaugeVec.WithLabelValues(lvs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 prometheus.Labels) GaugeMetric {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop // return no-op gauge
|
||||||
|
}
|
||||||
|
return v.GaugeVec.With(labels)
|
||||||
|
}
|
210
staging/src/k8s.io/component-base/metrics/gauge_test.go
Normal file
210
staging/src/k8s.io/component-base/metrics/gauge_test.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGauge(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
GaugeOpts
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] gauge help",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) gauge help",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "gauge help",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewGauge(&test.GaugeOpts)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.Set(100)
|
||||||
|
c.Set(101)
|
||||||
|
expected := 101
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if int(m.GetGauge().GetValue()) != expected {
|
||||||
|
t.Errorf("Got %v, wanted %v as the count", m.GetGauge().GetValue(), expected)
|
||||||
|
}
|
||||||
|
t.Logf("%v\n", m.GetGauge().GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGaugeVec(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
GaugeOpts
|
||||||
|
labels []string
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] gauge help",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) gauge help",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
GaugeOpts: GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "gauge help",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "gauge help",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewGaugeVec(&test.GaugeOpts, test.labels)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
c.WithLabelValues("1", "2").Set(1.0)
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.WithLabelValues("1", "3").Set(1.0)
|
||||||
|
c.WithLabelValues("2", "3").Set(1.0)
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
if len(mf.GetMetric()) != 3 {
|
||||||
|
t.Errorf("Got %v metrics, wanted 2 as the count", len(mf.GetMetric()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
148
staging/src/k8s.io/component-base/metrics/histogram.go
Normal file
148
staging/src/k8s.io/component-base/metrics/histogram.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Histogram is our internal representation for our wrapping struct around prometheus
|
||||||
|
// histograms. Summary implements both KubeCollector and ObserverMetric
|
||||||
|
type Histogram struct {
|
||||||
|
ObserverMetric
|
||||||
|
*HistogramOpts
|
||||||
|
lazyMetric
|
||||||
|
selfCollector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistogram returns an object which is Histogram-like. However, nothing
|
||||||
|
// will be measured until the histogram is registered somewhere.
|
||||||
|
func NewHistogram(opts *HistogramOpts) *Histogram {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
h := &Histogram{
|
||||||
|
HistogramOpts: opts,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
h.setPrometheusHistogram(noopMetric{})
|
||||||
|
h.lazyInit(h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPrometheusHistogram sets the underlying KubeGauge object, i.e. the thing that does the measurement.
|
||||||
|
func (h *Histogram) setPrometheusHistogram(histogram prometheus.Histogram) {
|
||||||
|
h.ObserverMetric = histogram
|
||||||
|
h.initSelfCollection(histogram)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (h *Histogram) DeprecatedVersion() *semver.Version {
|
||||||
|
return h.HistogramOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeMetric invokes the actual prometheus.Histogram object instantiation
|
||||||
|
// and stores a reference to it
|
||||||
|
func (h *Histogram) initializeMetric() {
|
||||||
|
h.HistogramOpts.annotateStabilityLevel()
|
||||||
|
// this actually creates the underlying prometheus gauge.
|
||||||
|
h.setPrometheusHistogram(prometheus.NewHistogram(h.HistogramOpts.toPromHistogramOpts()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeDeprecatedMetric invokes the actual prometheus.Histogram object instantiation
|
||||||
|
// but modifies the Help description prior to object instantiation.
|
||||||
|
func (h *Histogram) initializeDeprecatedMetric() {
|
||||||
|
h.HistogramOpts.markDeprecated()
|
||||||
|
h.initializeMetric()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistogramVec is the internal representation of our wrapping struct around prometheus
|
||||||
|
// histogramVecs.
|
||||||
|
type HistogramVec struct {
|
||||||
|
*prometheus.HistogramVec
|
||||||
|
*HistogramOpts
|
||||||
|
lazyMetric
|
||||||
|
originalLabels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func NewHistogramVec(opts *HistogramOpts, labels []string) *HistogramVec {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
klog.Errorf("---%v---\n", opts)
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
klog.Errorf("---%v---\n", opts)
|
||||||
|
v := &HistogramVec{
|
||||||
|
HistogramVec: noopHistogramVec,
|
||||||
|
HistogramOpts: opts,
|
||||||
|
originalLabels: labels,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
v.lazyInit(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (v *HistogramVec) DeprecatedVersion() *semver.Version {
|
||||||
|
return v.HistogramOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *HistogramVec) initializeMetric() {
|
||||||
|
v.HistogramOpts.annotateStabilityLevel()
|
||||||
|
v.HistogramVec = prometheus.NewHistogramVec(v.HistogramOpts.toPromHistogramOpts(), v.originalLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *HistogramVec) initializeDeprecatedMetric() {
|
||||||
|
v.HistogramOpts.markDeprecated()
|
||||||
|
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.
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// WithLabelValues returns the ObserverMetric 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 ObserverMetric is created IFF the HistogramVec
|
||||||
|
// has been registered to a metrics registry.
|
||||||
|
func (v *HistogramVec) WithLabelValues(lvs ...string) ObserverMetric {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
return v.HistogramVec.WithLabelValues(lvs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With returns the ObserverMetric 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 ObserverMetric is created IFF the HistogramVec has
|
||||||
|
// been registered to a metrics registry.
|
||||||
|
func (v *HistogramVec) With(labels prometheus.Labels) ObserverMetric {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
return v.HistogramVec.With(labels)
|
||||||
|
}
|
225
staging/src/k8s.io/component-base/metrics/histogram_test.go
Normal file
225
staging/src/k8s.io/component-base/metrics/histogram_test.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHistogram(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
HistogramOpts
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] histogram help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) histogram help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "histogram help message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewHistogram(&test.HistogramOpts)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.Observe(1)
|
||||||
|
c.Observe(2)
|
||||||
|
c.Observe(3)
|
||||||
|
c.Observe(1.5)
|
||||||
|
expected := 4
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if int(m.GetHistogram().GetSampleCount()) != expected {
|
||||||
|
t.Errorf("Got %v, want %v as the sample count", m.GetHistogram().GetSampleCount(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHistogramVec(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
HistogramOpts
|
||||||
|
labels []string
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] histogram help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) histogram help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
HistogramOpts: HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "histogram help message",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "histogram help message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewHistogramVec(&test.HistogramOpts, test.labels)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
c.WithLabelValues("1", "2").Observe(1.0)
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.WithLabelValues("1", "3").Observe(1.0)
|
||||||
|
c.WithLabelValues("2", "3").Observe(1.0)
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
if len(mf.GetMetric()) != 3 {
|
||||||
|
t.Errorf("Got %v metrics, wanted 2 as the count", len(mf.GetMetric()))
|
||||||
|
}
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if m.GetHistogram().GetSampleCount() != 1 {
|
||||||
|
t.Errorf(
|
||||||
|
"Got %v metrics, expected histogram sample count to equal 1",
|
||||||
|
m.GetHistogram().GetSampleCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KubeOpts is superset struct for prometheus.Opts. The prometheus Opts structure
|
// KubeOpts is superset struct for prometheus.Opts. The prometheus Opts structure
|
||||||
@ -82,3 +83,130 @@ func (o *CounterOpts) toPromCounterOpts() prometheus.CounterOpts {
|
|||||||
ConstLabels: o.ConstLabels,
|
ConstLabels: o.ConstLabels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GaugeOpts is an alias for Opts. See there for doc comments.
|
||||||
|
type GaugeOpts KubeOpts
|
||||||
|
|
||||||
|
// Modify help description on the metric description.
|
||||||
|
func (o *GaugeOpts) markDeprecated() {
|
||||||
|
o.deprecateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotateStabilityLevel annotates help description on the metric description with the stability level
|
||||||
|
// of the metric
|
||||||
|
func (o *GaugeOpts) annotateStabilityLevel() {
|
||||||
|
o.annotateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience function to allow easy transformation to the prometheus
|
||||||
|
// counterpart. This will do more once we have a proper label abstraction
|
||||||
|
func (o GaugeOpts) toPromGaugeOpts() prometheus.GaugeOpts {
|
||||||
|
return prometheus.GaugeOpts{
|
||||||
|
Namespace: o.Namespace,
|
||||||
|
Subsystem: o.Subsystem,
|
||||||
|
Name: o.Name,
|
||||||
|
Help: o.Help,
|
||||||
|
ConstLabels: o.ConstLabels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistogramOpts bundles the options for creating a Histogram metric. It is
|
||||||
|
// mandatory to set Name to a non-empty string. All other fields are optional
|
||||||
|
// and can safely be left at their zero value, although it is strongly
|
||||||
|
// encouraged to set a Help string.
|
||||||
|
type HistogramOpts struct {
|
||||||
|
Namespace string
|
||||||
|
Subsystem string
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
ConstLabels prometheus.Labels
|
||||||
|
Buckets []float64
|
||||||
|
DeprecatedVersion *semver.Version
|
||||||
|
deprecateOnce sync.Once
|
||||||
|
annotateOnce sync.Once
|
||||||
|
StabilityLevel StabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify help description on the metric description.
|
||||||
|
func (o *HistogramOpts) markDeprecated() {
|
||||||
|
o.deprecateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotateStabilityLevel annotates help description on the metric description with the stability level
|
||||||
|
// of the metric
|
||||||
|
func (o *HistogramOpts) annotateStabilityLevel() {
|
||||||
|
o.annotateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience function to allow easy transformation to the prometheus
|
||||||
|
// counterpart. This will do more once we have a proper label abstraction
|
||||||
|
func (o HistogramOpts) toPromHistogramOpts() prometheus.HistogramOpts {
|
||||||
|
return prometheus.HistogramOpts{
|
||||||
|
Namespace: o.Namespace,
|
||||||
|
Subsystem: o.Subsystem,
|
||||||
|
Name: o.Name,
|
||||||
|
Help: o.Help,
|
||||||
|
ConstLabels: o.ConstLabels,
|
||||||
|
Buckets: o.Buckets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SummaryOpts bundles the options for creating a Summary metric. It is
|
||||||
|
// mandatory to set Name to a non-empty string. While all other fields are
|
||||||
|
// optional and can safely be left at their zero value, it is recommended to set
|
||||||
|
// a help string and to explicitly set the Objectives field to the desired value
|
||||||
|
// as the default value will change in the upcoming v0.10 of the library.
|
||||||
|
type SummaryOpts struct {
|
||||||
|
Namespace string
|
||||||
|
Subsystem string
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
ConstLabels prometheus.Labels
|
||||||
|
Objectives map[float64]float64
|
||||||
|
MaxAge time.Duration
|
||||||
|
AgeBuckets uint32
|
||||||
|
BufCap uint32
|
||||||
|
DeprecatedVersion *semver.Version
|
||||||
|
deprecateOnce sync.Once
|
||||||
|
annotateOnce sync.Once
|
||||||
|
StabilityLevel StabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify help description on the metric description.
|
||||||
|
func (o *SummaryOpts) markDeprecated() {
|
||||||
|
o.deprecateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotateStabilityLevel annotates help description on the metric description with the stability level
|
||||||
|
// of the metric
|
||||||
|
func (o *SummaryOpts) annotateStabilityLevel() {
|
||||||
|
o.annotateOnce.Do(func() {
|
||||||
|
o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience function to allow easy transformation to the prometheus
|
||||||
|
// counterpart. This will do more once we have a proper label abstraction
|
||||||
|
func (o SummaryOpts) toPromSummaryOpts() prometheus.SummaryOpts {
|
||||||
|
return prometheus.SummaryOpts{
|
||||||
|
Namespace: o.Namespace,
|
||||||
|
Subsystem: o.Subsystem,
|
||||||
|
Name: o.Name,
|
||||||
|
Help: o.Help,
|
||||||
|
ConstLabels: o.ConstLabels,
|
||||||
|
Objectives: o.Objectives,
|
||||||
|
MaxAge: o.MaxAge,
|
||||||
|
AgeBuckets: o.AgeBuckets,
|
||||||
|
BufCap: o.BufCap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
152
staging/src/k8s.io/component-base/metrics/summary.go
Normal file
152
staging/src/k8s.io/component-base/metrics/summary.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Summary is our internal representation for our wrapping struct around prometheus
|
||||||
|
// summaries. Summary implements both KubeCollector and ObserverMetric
|
||||||
|
//
|
||||||
|
// DEPRECATED: as per the metrics overhaul KEP
|
||||||
|
type Summary struct {
|
||||||
|
ObserverMetric
|
||||||
|
*SummaryOpts
|
||||||
|
lazyMetric
|
||||||
|
selfCollector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSummary returns an object which is Summary-like. However, nothing
|
||||||
|
// will be measured until the summary is registered somewhere.
|
||||||
|
//
|
||||||
|
// DEPRECATED: as per the metrics overhaul KEP
|
||||||
|
func NewSummary(opts *SummaryOpts) *Summary {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
s := &Summary{
|
||||||
|
SummaryOpts: opts,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
s.setPrometheusSummary(noopMetric{})
|
||||||
|
s.lazyInit(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPrometheusSummary sets the underlying KubeGauge object, i.e. the thing that does the measurement.
|
||||||
|
func (s *Summary) setPrometheusSummary(summary prometheus.Summary) {
|
||||||
|
s.ObserverMetric = summary
|
||||||
|
s.initSelfCollection(summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (s *Summary) DeprecatedVersion() *semver.Version {
|
||||||
|
return s.SummaryOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeMetric invokes the actual prometheus.Summary object instantiation
|
||||||
|
// and stores a reference to it
|
||||||
|
func (s *Summary) initializeMetric() {
|
||||||
|
s.SummaryOpts.annotateStabilityLevel()
|
||||||
|
// this actually creates the underlying prometheus gauge.
|
||||||
|
s.setPrometheusSummary(prometheus.NewSummary(s.SummaryOpts.toPromSummaryOpts()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeDeprecatedMetric invokes the actual prometheus.Summary object instantiation
|
||||||
|
// but modifies the Help description prior to object instantiation.
|
||||||
|
func (s *Summary) initializeDeprecatedMetric() {
|
||||||
|
s.SummaryOpts.markDeprecated()
|
||||||
|
s.initializeMetric()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SummaryVec is the internal representation of our wrapping struct around prometheus
|
||||||
|
// summaryVecs.
|
||||||
|
//
|
||||||
|
// DEPRECATED: as per the metrics overhaul KEP
|
||||||
|
type SummaryVec struct {
|
||||||
|
*prometheus.SummaryVec
|
||||||
|
*SummaryOpts
|
||||||
|
lazyMetric
|
||||||
|
originalLabels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSummaryVec returns an object which satisfies KubeCollector and wraps the
|
||||||
|
// prometheus.SummaryVec object. However, the object returned will not measure
|
||||||
|
// anything unless the collector is first registered, since the metric is lazily instantiated.
|
||||||
|
//
|
||||||
|
// DEPRECATED: as per the metrics overhaul KEP
|
||||||
|
func NewSummaryVec(opts *SummaryOpts, labels []string) *SummaryVec {
|
||||||
|
// todo: handle defaulting better
|
||||||
|
if opts.StabilityLevel == "" {
|
||||||
|
opts.StabilityLevel = ALPHA
|
||||||
|
}
|
||||||
|
v := &SummaryVec{
|
||||||
|
SummaryOpts: opts,
|
||||||
|
originalLabels: labels,
|
||||||
|
lazyMetric: lazyMetric{},
|
||||||
|
}
|
||||||
|
v.lazyInit(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeprecatedVersion returns a pointer to the Version or nil
|
||||||
|
func (v *SummaryVec) DeprecatedVersion() *semver.Version {
|
||||||
|
return v.SummaryOpts.DeprecatedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *SummaryVec) initializeMetric() {
|
||||||
|
v.SummaryOpts.annotateStabilityLevel()
|
||||||
|
v.SummaryVec = prometheus.NewSummaryVec(v.SummaryOpts.toPromSummaryOpts(), v.originalLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *SummaryVec) initializeDeprecatedMetric() {
|
||||||
|
v.SummaryOpts.markDeprecated()
|
||||||
|
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.
|
||||||
|
// 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/summary.go#L485-L495
|
||||||
|
|
||||||
|
// WithLabelValues returns the ObserverMetric 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 ObserverMetric is created IFF the summaryVec
|
||||||
|
// has been registered to a metrics registry.
|
||||||
|
func (v *SummaryVec) WithLabelValues(lvs ...string) ObserverMetric {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
return v.SummaryVec.WithLabelValues(lvs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With returns the ObserverMetric 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 ObserverMetric is created IFF the summaryVec has
|
||||||
|
// been registered to a metrics registry.
|
||||||
|
func (v *SummaryVec) With(labels prometheus.Labels) ObserverMetric {
|
||||||
|
if !v.IsCreated() {
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
return v.SummaryVec.With(labels)
|
||||||
|
}
|
220
staging/src/k8s.io/component-base/metrics/summary_test.go
Normal file
220
staging/src/k8s.io/component-base/metrics/summary_test.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSummary(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
SummaryOpts
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
StabilityLevel: ALPHA,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] summary help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
StabilityLevel: ALPHA,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) summary help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "summary help message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewSummary(&test.SummaryOpts)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.Observe(1)
|
||||||
|
c.Observe(2)
|
||||||
|
c.Observe(3)
|
||||||
|
c.Observe(1.5)
|
||||||
|
expected := 4
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if int(m.GetSummary().GetSampleCount()) != expected {
|
||||||
|
t.Errorf("Got %v, want %v as the sample count", m.GetHistogram().GetSampleCount(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSummaryVec(t *testing.T) {
|
||||||
|
v115 := semver.MustParse("1.15.0")
|
||||||
|
v114 := semver.MustParse("1.14.0")
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
SummaryOpts
|
||||||
|
labels []string
|
||||||
|
registryVersion *semver.Version
|
||||||
|
expectedMetricCount int
|
||||||
|
expectedHelp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test non deprecated",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] summary help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test deprecated",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
DeprecatedVersion: &v115,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 1,
|
||||||
|
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) summary help message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test hidden",
|
||||||
|
SummaryOpts: SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_test_name",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "summary help message",
|
||||||
|
DeprecatedVersion: &v114,
|
||||||
|
},
|
||||||
|
labels: []string{"label_a", "label_b"},
|
||||||
|
registryVersion: &v115,
|
||||||
|
expectedMetricCount: 0,
|
||||||
|
expectedHelp: "summary help message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
registry := NewKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewSummaryVec(&test.SummaryOpts, test.labels)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
c.WithLabelValues("1", "2").Observe(1.0)
|
||||||
|
ms, err := registry.Gather()
|
||||||
|
|
||||||
|
if len(ms) != test.expectedMetricCount {
|
||||||
|
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, metric := range ms {
|
||||||
|
if metric.GetHelp() != test.expectedHelp {
|
||||||
|
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's increment the counter and verify that the metric still works
|
||||||
|
c.WithLabelValues("1", "3").Observe(1.0)
|
||||||
|
c.WithLabelValues("2", "3").Observe(1.0)
|
||||||
|
ms, err = registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather failed %v", err)
|
||||||
|
}
|
||||||
|
for _, mf := range ms {
|
||||||
|
if len(mf.GetMetric()) != 3 {
|
||||||
|
t.Errorf("Got %v metrics, wanted 2 as the count", len(mf.GetMetric()))
|
||||||
|
}
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if m.GetSummary().GetSampleCount() != 1 {
|
||||||
|
t.Errorf(
|
||||||
|
"Got %v metrics, wanted 2 as the summary sample count",
|
||||||
|
m.GetSummary().GetSampleCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,16 @@ type CounterVecMetric interface {
|
|||||||
With(prometheus.Labels) CounterMetric
|
With(prometheus.Labels) CounterMetric
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GaugeMetric is an interface which defines a subset of the interface provided by prometheus.Gauge
|
||||||
|
type GaugeMetric interface {
|
||||||
|
Set(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserverMetric captures individual observations.
|
||||||
|
type ObserverMetric interface {
|
||||||
|
Observe(float64)
|
||||||
|
}
|
||||||
|
|
||||||
// PromRegistry is an interface which implements a subset of prometheus.Registerer and
|
// PromRegistry is an interface which implements a subset of prometheus.Registerer and
|
||||||
// prometheus.Gatherer interfaces
|
// prometheus.Gatherer interfaces
|
||||||
type PromRegistry interface {
|
type PromRegistry interface {
|
||||||
|
Loading…
Reference in New Issue
Block a user