diff --git a/staging/src/k8s.io/component-base/metrics/BUILD b/staging/src/k8s.io/component-base/metrics/BUILD index 30e6181eb99..b13db6f3536 100644 --- a/staging/src/k8s.io/component-base/metrics/BUILD +++ b/staging/src/k8s.io/component-base/metrics/BUILD @@ -53,6 +53,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/component-base/metrics/legacyregistry:all-srcs", + ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/component-base/metrics/counter_test.go b/staging/src/k8s.io/component-base/metrics/counter_test.go index 79fd30cdbd8..72ddf762a75 100644 --- a/staging/src/k8s.io/component-base/metrics/counter_test.go +++ b/staging/src/k8s.io/component-base/metrics/counter_test.go @@ -74,7 +74,7 @@ func TestCounter(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := newKubeRegistry(&apimachineryversion.Info{ + registry := NewKubeRegistry(&apimachineryversion.Info{ Major: "1", Minor: "15", GitVersion: "v1.15.0-alpha-1.12345", @@ -175,7 +175,7 @@ func TestCounterVec(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := newKubeRegistry(&apimachineryversion.Info{ + registry := NewKubeRegistry(&apimachineryversion.Info{ Major: "1", Minor: "15", GitVersion: "v1.15.0-alpha-1.12345", diff --git a/staging/src/k8s.io/component-base/metrics/legacyregistry/BUILD b/staging/src/k8s.io/component-base/metrics/legacyregistry/BUILD new file mode 100644 index 00000000000..14937d9da42 --- /dev/null +++ b/staging/src/k8s.io/component-base/metrics/legacyregistry/BUILD @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["registry.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/component-base/metrics/legacyregistry", + importpath = "k8s.io/component-base/metrics/legacyregistry", + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", + "//staging/src/k8s.io/component-base/metrics:go_default_library", + "//vendor/github.com/prometheus/client_model/go:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_test", + srcs = ["registry_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", + "//staging/src/k8s.io/component-base/metrics:go_default_library", + "//vendor/github.com/blang/semver:go_default_library", + "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) diff --git a/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go new file mode 100644 index 00000000000..8a941e7dc3c --- /dev/null +++ b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go @@ -0,0 +1,89 @@ +/* +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 legacyregistry + +import ( + apimachineryversion "k8s.io/apimachinery/pkg/version" + "k8s.io/component-base/metrics" + "sync" +) + +var globalRegistryFactory = metricsRegistryFactory{ + registerQueue: make([]metrics.KubeCollector, 0), + mustRegisterQueue: make([]metrics.KubeCollector, 0), +} + +type metricsRegistryFactory struct { + globalRegistry metrics.KubeRegistry + kubeVersion *apimachineryversion.Info + registrationLock sync.Mutex + registerQueue []metrics.KubeCollector + mustRegisterQueue []metrics.KubeCollector +} + +// SetRegistryFactoryVersion sets the kubernetes version information for all +// subsequent metrics registry initializations. Only the first call has an effect. +// If a version is not set, then metrics registry creation will no-opt +func SetRegistryFactoryVersion(ver *apimachineryversion.Info) []error { + globalRegistryFactory.registrationLock.Lock() + defer globalRegistryFactory.registrationLock.Unlock() + if globalRegistryFactory.kubeVersion != nil { + return nil + } + registrationErrs := make([]error, 0) + globalRegistryFactory.globalRegistry = metrics.NewKubeRegistry(ver) + globalRegistryFactory.kubeVersion = ver + for _, c := range globalRegistryFactory.registerQueue { + err := globalRegistryFactory.globalRegistry.Register(c) + if err != nil { + registrationErrs = append(registrationErrs, err) + } + } + for _, c := range globalRegistryFactory.mustRegisterQueue { + globalRegistryFactory.globalRegistry.MustRegister(c) + } + return registrationErrs +} + +// Register registers a collectable metric, but it uses a global registry. Registration is deferred +// until the global registry has a version to use. +func Register(c metrics.KubeCollector) error { + globalRegistryFactory.registrationLock.Lock() + defer globalRegistryFactory.registrationLock.Unlock() + + if globalRegistryFactory.kubeVersion != nil { + return globalRegistryFactory.globalRegistry.Register(c) + } + globalRegistryFactory.registerQueue = append(globalRegistryFactory.registerQueue, c) + return nil +} + +// MustRegister works like Register but registers any number of +// Collectors and panics upon the first registration that causes an +// error. Registration is deferred until the global registry has a version to use. +func MustRegister(cs ...metrics.KubeCollector) { + globalRegistryFactory.registrationLock.Lock() + defer globalRegistryFactory.registrationLock.Unlock() + + if globalRegistryFactory.kubeVersion != nil { + globalRegistryFactory.globalRegistry.MustRegister(cs...) + return + } + for _, c := range cs { + globalRegistryFactory.mustRegisterQueue = append(globalRegistryFactory.mustRegisterQueue, c) + } +} diff --git a/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go new file mode 100644 index 00000000000..a448bf3332d --- /dev/null +++ b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go @@ -0,0 +1,195 @@ +/* +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 legacyregistry + +import ( + "github.com/blang/semver" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "k8s.io/component-base/metrics" + "testing" + + apimachineryversion "k8s.io/apimachinery/pkg/version" +) + +func init() { + SetRegistryFactoryVersion(&apimachineryversion.Info{ + Major: "1", + Minor: "15", + GitVersion: "v1.15.0-alpha-1.12345", + }) +} + +var ( + v115 = semver.MustParse("1.15.0") + v114 = semver.MustParse("1.14.0") + alphaCounter = metrics.NewCounter( + &metrics.CounterOpts{ + Namespace: "some_namespace", + Name: "test_counter_name", + Subsystem: "subsystem", + StabilityLevel: metrics.ALPHA, + Help: "counter help", + }, + ) + alphaDeprecatedCounter = metrics.NewCounter( + &metrics.CounterOpts{ + Namespace: "some_namespace", + Name: "test_alpha_dep_counter", + Subsystem: "subsystem", + StabilityLevel: metrics.ALPHA, + Help: "counter help", + DeprecatedVersion: &v115, + }, + ) + alphaHiddenCounter = metrics.NewCounter( + &metrics.CounterOpts{ + Namespace: "some_namespace", + Name: "test_alpha_hidden_counter", + Subsystem: "subsystem", + StabilityLevel: metrics.ALPHA, + Help: "counter help", + DeprecatedVersion: &v114, + }, + ) +) + +func TestRegister(t *testing.T) { + var tests = []struct { + desc string + metrics []*metrics.Counter + registryVersion *semver.Version + expectedErrors []error + expectedIsCreatedValues []bool + expectedIsDeprecated []bool + expectedIsHidden []bool + }{ + { + desc: "test registering same metric multiple times", + metrics: []*metrics.Counter{alphaCounter, alphaCounter}, + expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, + expectedIsCreatedValues: []bool{true, true}, + expectedIsDeprecated: []bool{false, false}, + expectedIsHidden: []bool{false, false}, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + //t.Errorf("len %v - %v\n", len(test.metrics), len(test.expectedErrors)) + for i, m := range test.metrics { + //t.Errorf("m %v\n", m) + err := Register(m) + if err != test.expectedErrors[i] && err.Error() != test.expectedErrors[i].Error() { + t.Errorf("Got unexpected error %v, wanted %v", err, test.expectedErrors[i]) + } + if m.IsCreated() != test.expectedIsCreatedValues[i] { + t.Errorf("Got isCreated == %v, wanted isCreated to be %v", m.IsCreated(), test.expectedIsCreatedValues[i]) + } + if m.IsDeprecated() != test.expectedIsDeprecated[i] { + t.Errorf("Got IsDeprecated == %v, wanted IsDeprecated to be %v", m.IsDeprecated(), test.expectedIsDeprecated[i]) + } + if m.IsHidden() != test.expectedIsHidden[i] { + t.Errorf("Got IsHidden == %v, wanted IsHidden to be %v", m.IsHidden(), test.expectedIsDeprecated[i]) + } + } + }) + } +} + +func TestMustRegister(t *testing.T) { + var tests = []struct { + desc string + metrics []*metrics.Counter + registryVersion *semver.Version + expectedPanics []bool + }{ + { + desc: "test must registering same deprecated metric", + metrics: []*metrics.Counter{alphaDeprecatedCounter, alphaDeprecatedCounter}, + expectedPanics: []bool{false, true}, + }, + { + desc: "test alpha hidden metric", + metrics: []*metrics.Counter{alphaHiddenCounter}, + expectedPanics: []bool{false}, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + for i, m := range test.metrics { + if test.expectedPanics[i] { + assert.Panics(t, + func() { MustRegister(m) }, + "Did not panic even though we expected it.") + } else { + MustRegister(m) + } + } + }) + } +} + +func TestDeferredRegister(t *testing.T) { + // reset the global registry for this test. + globalRegistryFactory = metricsRegistryFactory{ + registerQueue: make([]metrics.KubeCollector, 0), + mustRegisterQueue: make([]metrics.KubeCollector, 0), + } + var err error + err = Register(alphaDeprecatedCounter) + if err != nil { + t.Errorf("Got err == %v, expected no error", err) + } + err = Register(alphaDeprecatedCounter) + if err != nil { + t.Errorf("Got err == %v, expected no error", err) + } + // set the global registry version + errs := SetRegistryFactoryVersion(&apimachineryversion.Info{ + Major: "1", + Minor: "15", + GitVersion: "v1.15.0-alpha-1.12345", + }) + if len(errs) != 1 { + t.Errorf("Got %d errs, expected 1", len(errs)) + for _, err := range errs { + t.Logf("\t Got %v", err) + } + } +} + +func TestDeferredMustRegister(t *testing.T) { + // reset the global registry for this test. + globalRegistryFactory = metricsRegistryFactory{ + registerQueue: make([]metrics.KubeCollector, 0), + mustRegisterQueue: make([]metrics.KubeCollector, 0), + } + MustRegister(alphaDeprecatedCounter) + + MustRegister(alphaDeprecatedCounter) + assert.Panics(t, + func() { + SetRegistryFactoryVersion(&apimachineryversion.Info{ + Major: "1", + Minor: "15", + GitVersion: "v1.15.0-alpha-1.12345", + }) + }, + "Did not panic even though we expected it.") +} diff --git a/staging/src/k8s.io/component-base/metrics/metric.go b/staging/src/k8s.io/component-base/metrics/metric.go index 4d5bbf85289..81f2456e4bc 100644 --- a/staging/src/k8s.io/component-base/metrics/metric.go +++ b/staging/src/k8s.io/component-base/metrics/metric.go @@ -32,7 +32,7 @@ method call depending on whether the metric is deprecated or not. */ type KubeCollector interface { Collector - LazyMetric + lazyKubeMetric DeprecatedVersion() *semver.Version // Each collector metric should provide an initialization function // for both deprecated and non-deprecated variants of a metric. This @@ -43,11 +43,11 @@ type KubeCollector interface { } /* -LazyMetric defines our registration functionality. LazyMetric objects are expected +lazyKubeMetric defines our metric registration interface. lazyKubeMetric objects are expected to lazily instantiate metrics (i.e defer metric instantiation until when the Create() function is explicitly called). */ -type LazyMetric interface { +type lazyKubeMetric interface { Create(*semver.Version) bool IsCreated() bool IsHidden() bool @@ -55,7 +55,7 @@ type LazyMetric interface { } /* -lazyMetric implements LazyMetric. A lazy metric is lazy because it waits until metric +lazyMetric implements lazyKubeMetric. A lazy metric is lazy because it waits until metric registration time before instantiation. Add it as an anonymous field to a struct that implements KubeCollector to get deferred registration behavior. You must call lazyInit with the KubeCollector itself as an argument. diff --git a/staging/src/k8s.io/component-base/metrics/registry.go b/staging/src/k8s.io/component-base/metrics/registry.go index 0b67308845d..a91dd25a982 100644 --- a/staging/src/k8s.io/component-base/metrics/registry.go +++ b/staging/src/k8s.io/component-base/metrics/registry.go @@ -21,28 +21,8 @@ import ( "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" apimachineryversion "k8s.io/apimachinery/pkg/version" - "sync" ) -var globalRegistryFactory = metricsRegistryFactory{ - globalRegistry: &noopKubeRegistry{}, -} - -// NewKubeRegistry creates a new kubernetes metric registry, loading in the kubernetes -// version information available to the binary. -func (r metricsRegistryFactory) newKubeRegistry() KubeRegistry { - if r.kubeVersion == nil { - return noopKubeRegistry{} - } - return newKubeRegistry(r.kubeVersion) -} - -type metricsRegistryFactory struct { - globalRegistry KubeRegistry - kubeVersion *apimachineryversion.Info - setVersionOnce sync.Once -} - // KubeRegistry is an interface which implements a subset of prometheus.Registerer and // prometheus.Gatherer interfaces type KubeRegistry interface { @@ -60,28 +40,6 @@ type kubeRegistry struct { version semver.Version } -// SetRegistryFactoryVersion sets the kubernetes version information for all -// subsequent metrics registry initializations. Only the first call has an effect. -// If a version is not set, then metrics registry creation will no-opt -func SetRegistryFactoryVersion(ver *apimachineryversion.Info) { - globalRegistryFactory.setVersionOnce.Do(func() { - globalRegistryFactory.globalRegistry = newKubeRegistry(ver) - globalRegistryFactory.kubeVersion = ver - }) -} - -// Register registers a collectable metric, but it uses a global registry. -func Register(c KubeCollector) error { - return globalRegistryFactory.globalRegistry.Register(c) -} - -// MustRegister works like Register but registers any number of -// Collectors and panics upon the first registration that causes an -// error. -func MustRegister(cs ...KubeCollector) { - globalRegistryFactory.globalRegistry.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 @@ -128,27 +86,11 @@ func (kr *kubeRegistry) Gather() ([]*dto.MetricFamily, error) { return kr.PromRegistry.Gather() } -// NewKubeRegistry creates a new kubernetes metric registry, loading in the kubernetes -// version information available to the binary. -func NewKubeRegistry() KubeRegistry { - return globalRegistryFactory.newKubeRegistry() -} - -// newKubeRegistry creates a new vanilla Registry without any Collectors +// NewKubeRegistry creates a new vanilla Registry without any Collectors // pre-registered. -func newKubeRegistry(v *apimachineryversion.Info) KubeRegistry { +func NewKubeRegistry(v *apimachineryversion.Info) KubeRegistry { return &kubeRegistry{ PromRegistry: prometheus.NewRegistry(), version: parseVersion(*v), } } - -// noop registry -var noopRegistry = &noopKubeRegistry{} - -type noopKubeRegistry struct{} - -func (noopKubeRegistry) Register(KubeCollector) error { return nil } -func (noopKubeRegistry) MustRegister(...KubeCollector) {} -func (noopKubeRegistry) Unregister(KubeCollector) bool { return false } -func (noopKubeRegistry) Gather() ([]*dto.MetricFamily, error) { return nil, nil } diff --git a/staging/src/k8s.io/component-base/metrics/registry_test.go b/staging/src/k8s.io/component-base/metrics/registry_test.go index 020cca747f2..4c8e071e25d 100644 --- a/staging/src/k8s.io/component-base/metrics/registry_test.go +++ b/staging/src/k8s.io/component-base/metrics/registry_test.go @@ -56,15 +56,6 @@ var ( DeprecatedVersion: &v114, }, ) - stableCounter = NewCounter( - &CounterOpts{ - Namespace: "some_namespace", - Name: "test_some_other_counter", - Subsystem: "subsystem", - StabilityLevel: STABLE, - Help: "counter help", - }, - ) ) func TestRegister(t *testing.T) { @@ -99,7 +90,7 @@ func TestRegister(t *testing.T) { desc: "test alpha deprecated metric", metrics: []*Counter{alphaDeprecatedCounter}, registryVersion: &v115, - expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, + expectedErrors: []error{nil}, expectedIsCreatedValues: []bool{true}, expectedIsDeprecated: []bool{true}, expectedIsHidden: []bool{false}, @@ -108,7 +99,7 @@ func TestRegister(t *testing.T) { desc: "test alpha hidden metric", metrics: []*Counter{alphaHiddenCounter}, registryVersion: &v115, - expectedErrors: []error{nil, prometheus.AlreadyRegisteredError{}}, + expectedErrors: []error{nil}, expectedIsCreatedValues: []bool{false}, expectedIsDeprecated: []bool{true}, expectedIsHidden: []bool{true}, @@ -117,7 +108,7 @@ func TestRegister(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := newKubeRegistry(&apimachineryversion.Info{ + registry := NewKubeRegistry(&apimachineryversion.Info{ Major: "1", Minor: "15", GitVersion: "v1.15.0-alpha-1.12345", @@ -188,7 +179,7 @@ func TestMustRegister(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := newKubeRegistry(&apimachineryversion.Info{ + registry := NewKubeRegistry(&apimachineryversion.Info{ Major: "1", Minor: "15", GitVersion: "v1.15.0-alpha-1.12345",