diff --git a/staging/src/k8s.io/component-base/metrics/BUILD b/staging/src/k8s.io/component-base/metrics/BUILD index c1d7c0d28be..1602d395668 100644 --- a/staging/src/k8s.io/component-base/metrics/BUILD +++ b/staging/src/k8s.io/component-base/metrics/BUILD @@ -48,6 +48,7 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/github.com/prometheus/client_golang/prometheus/testutil:go_default_library", "//vendor/github.com/prometheus/common/expfmt:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", ], diff --git a/staging/src/k8s.io/component-base/metrics/gauge.go b/staging/src/k8s.io/component-base/metrics/gauge.go index 665a24c4135..5c997443b73 100644 --- a/staging/src/k8s.io/component-base/metrics/gauge.go +++ b/staging/src/k8s.io/component-base/metrics/gauge.go @@ -19,6 +19,8 @@ package metrics import ( "github.com/blang/semver" "github.com/prometheus/client_golang/prometheus" + + "k8s.io/component-base/version" ) // Gauge is our internal representation for our wrapping struct around prometheus @@ -158,3 +160,25 @@ func (v *GaugeVec) Delete(labels map[string]string) bool { } return v.GaugeVec.Delete(labels) } + +func newGaugeFunc(opts GaugeOpts, function func() float64, v semver.Version) GaugeFunc { + g := NewGauge(&opts) + + if !g.Create(&v) { + return nil + } + + return prometheus.NewGaugeFunc(g.GaugeOpts.toPromGaugeOpts(), function) +} + +// NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The +// value reported is determined by calling the given function from within the +// Write method. Take into account that metric collection may happen +// concurrently. If that results in concurrent calls to Write, like in the case +// where a GaugeFunc is directly registered with Prometheus, the provided +// function must be concurrency-safe. +func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc { + v := parseVersion(version.Get()) + + return newGaugeFunc(opts, function, v) +} diff --git a/staging/src/k8s.io/component-base/metrics/gauge_test.go b/staging/src/k8s.io/component-base/metrics/gauge_test.go index 16f8ca5e2de..6af9920c65d 100644 --- a/staging/src/k8s.io/component-base/metrics/gauge_test.go +++ b/staging/src/k8s.io/component-base/metrics/gauge_test.go @@ -17,9 +17,11 @@ limitations under the License. package metrics import ( + "strings" "testing" "github.com/blang/semver" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" apimachineryversion "k8s.io/apimachinery/pkg/version" @@ -191,3 +193,78 @@ func TestGaugeVec(t *testing.T) { }) } } + +func TestGaugeFunc(t *testing.T) { + currentVersion := apimachineryversion.Info{ + Major: "1", + Minor: "17", + GitVersion: "v1.17.0-alpha-1.12345", + } + + var function = func() float64 { + return 1 + } + + var tests = []struct { + desc string + GaugeOpts + expectedMetrics string + }{ + { + desc: "Test non deprecated", + GaugeOpts: GaugeOpts{ + Namespace: "namespace", + Subsystem: "subsystem", + Name: "metric_non_deprecated", + Help: "gauge help", + }, + expectedMetrics: ` +# HELP namespace_subsystem_metric_non_deprecated [ALPHA] gauge help +# TYPE namespace_subsystem_metric_non_deprecated gauge +namespace_subsystem_metric_non_deprecated 1 + `, + }, + { + desc: "Test deprecated", + GaugeOpts: GaugeOpts{ + Namespace: "namespace", + Subsystem: "subsystem", + Name: "metric_deprecated", + Help: "gauge help", + DeprecatedVersion: "1.17.0", + }, + expectedMetrics: ` +# HELP namespace_subsystem_metric_deprecated [ALPHA] (Deprecated since 1.17.0) gauge help +# TYPE namespace_subsystem_metric_deprecated gauge +namespace_subsystem_metric_deprecated 1 +`, + }, + { + desc: "Test hidden", + GaugeOpts: GaugeOpts{ + Namespace: "namespace", + Subsystem: "subsystem", + Name: "metric_hidden", + Help: "gauge help", + DeprecatedVersion: "1.16.0", + }, + expectedMetrics: "", + }, + } + + for _, test := range tests { + tc := test + t.Run(test.desc, func(t *testing.T) { + registry := newKubeRegistry(currentVersion) + gauge := newGaugeFunc(tc.GaugeOpts, function, parseVersion(currentVersion)) + if gauge != nil { // hidden metrics will not be initialize, register is not allowed + registry.RawMustRegister(gauge) + } + + metricName := BuildFQName(tc.GaugeOpts.Namespace, tc.GaugeOpts.Subsystem, tc.GaugeOpts.Name) + if err := testutil.GatherAndCompare(registry, strings.NewReader(tc.expectedMetrics), metricName); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/staging/src/k8s.io/component-base/metrics/opts.go b/staging/src/k8s.io/component-base/metrics/opts.go index 02d4aa18992..6b4a290d27d 100644 --- a/staging/src/k8s.io/component-base/metrics/opts.go +++ b/staging/src/k8s.io/component-base/metrics/opts.go @@ -42,6 +42,17 @@ type KubeOpts struct { StabilityLevel StabilityLevel } +// BuildFQName joins the given three name components by "_". Empty name +// components are ignored. If the name parameter itself is empty, an empty +// string is returned, no matter what. Metric implementations included in this +// library use this function internally to generate the fully-qualified metric +// name from the name component in their Opts. Users of the library will only +// need this function if they implement their own Metric or instantiate a Desc +// (with NewDesc) directly. +func BuildFQName(namespace, subsystem, name string) string { + return prometheus.BuildFQName(namespace, subsystem, name) +} + // StabilityLevel represents the API guarantees for a given defined metric. type StabilityLevel string diff --git a/staging/src/k8s.io/component-base/metrics/wrappers.go b/staging/src/k8s.io/component-base/metrics/wrappers.go index c860cb0af30..6ae8a458acb 100644 --- a/staging/src/k8s.io/component-base/metrics/wrappers.go +++ b/staging/src/k8s.io/component-base/metrics/wrappers.go @@ -84,3 +84,12 @@ type PromRegistry interface { type Gatherer interface { prometheus.Gatherer } + +// GaugeFunc is a Gauge whose value is determined at collect time by calling a +// provided function. +// +// To create GaugeFunc instances, use NewGaugeFunc. +type GaugeFunc interface { + Metric + Collector +}