From abe64acc8d1f88988a370468f1276a22905ac48b Mon Sep 17 00:00:00 2001 From: Han Kang Date: Fri, 26 Apr 2019 16:43:15 -0700 Subject: [PATCH] add version parsing to metrics framework, use build version information for registry version --- pkg/util/metrics/BUILD | 5 +++ pkg/util/metrics/counter.go | 8 ++-- pkg/util/metrics/counter_test.go | 20 ++++----- pkg/util/metrics/metric.go | 8 ++-- pkg/util/metrics/opts.go | 2 +- pkg/util/metrics/registry.go | 25 ++++++++--- pkg/util/metrics/registry_test.go | 12 +++--- pkg/util/metrics/version_parser.go | 40 ++++++++++++++++++ pkg/util/metrics/version_parser_test.go | 56 +++++++++++++++++++++++++ 9 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 pkg/util/metrics/version_parser.go create mode 100644 pkg/util/metrics/version_parser_test.go diff --git a/pkg/util/metrics/BUILD b/pkg/util/metrics/BUILD index 2a93ba857d5..9545c23ea10 100644 --- a/pkg/util/metrics/BUILD +++ b/pkg/util/metrics/BUILD @@ -14,10 +14,13 @@ go_library( "opts.go", "registry.go", "util.go", + "version_parser.go", "wrappers.go", ], importpath = "k8s.io/kubernetes/pkg/util/metrics", deps = [ + "//pkg/version:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", "//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", @@ -32,9 +35,11 @@ go_test( "counter_test.go", "registry_test.go", "util_test.go", + "version_parser_test.go", ], embed = [":go_default_library"], deps = [ + "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", "//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", diff --git a/pkg/util/metrics/counter.go b/pkg/util/metrics/counter.go index 29d87742a3f..69367ac5822 100644 --- a/pkg/util/metrics/counter.go +++ b/pkg/util/metrics/counter.go @@ -33,13 +33,13 @@ type kubeCounter struct { // NewCounter returns an object which satisfies the KubeCollector and KubeCounter interfaces. // However, the object returned will not measure anything unless the collector is first // registered, since the metric is lazily instantiated. -func NewCounter(opts CounterOpts) *kubeCounter { +func NewCounter(opts *CounterOpts) *kubeCounter { // todo: handle defaulting better if opts.StabilityLevel == "" { opts.StabilityLevel = ALPHA } kc := &kubeCounter{ - CounterOpts: &opts, + CounterOpts: opts, lazyMetric: lazyMetric{}, } kc.setPrometheusCounter(noop) @@ -85,10 +85,10 @@ type kubeCounterVec struct { // NewCounterVec returns an object which satisfies the KubeCollector and KubeCounterVec interfaces. // However, the object returned will not measure anything unless the collector is first // registered, since the metric is lazily instantiated. -func NewCounterVec(opts CounterOpts, labels []string) *kubeCounterVec { +func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec { cv := &kubeCounterVec{ CounterVec: noopCounterVec, - CounterOpts: &opts, + CounterOpts: opts, originalLabels: labels, lazyMetric: lazyMetric{}, } diff --git a/pkg/util/metrics/counter_test.go b/pkg/util/metrics/counter_test.go index 62b4944c27b..742230b51e4 100644 --- a/pkg/util/metrics/counter_test.go +++ b/pkg/util/metrics/counter_test.go @@ -28,14 +28,14 @@ func TestCounter(t *testing.T) { v114 := semver.MustParse("1.14.0") var tests = []struct { desc string - CounterOpts + *CounterOpts registryVersion *semver.Version expectedMetricCount int expectedHelp string }{ { desc: "Test non deprecated", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -48,7 +48,7 @@ func TestCounter(t *testing.T) { }, { desc: "Test deprecated", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -62,7 +62,7 @@ func TestCounter(t *testing.T) { }, { desc: "Test hidden", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -77,7 +77,7 @@ func TestCounter(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := NewKubeRegistry(*test.registryVersion) + registry := newKubeRegistry(*test.registryVersion) c := NewCounter(test.CounterOpts) registry.MustRegister(c) @@ -126,7 +126,7 @@ func TestCounterVec(t *testing.T) { v114 := semver.MustParse("1.14.0") var tests = []struct { desc string - CounterOpts + *CounterOpts labels []string registryVersion *semver.Version expectedMetricFamilyCount int @@ -134,7 +134,7 @@ func TestCounterVec(t *testing.T) { }{ { desc: "Test non deprecated", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -147,7 +147,7 @@ func TestCounterVec(t *testing.T) { }, { desc: "Test deprecated", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -161,7 +161,7 @@ func TestCounterVec(t *testing.T) { }, { desc: "Test hidden", - CounterOpts: CounterOpts{ + CounterOpts: &CounterOpts{ Namespace: "namespace", Name: "metric_test_name", Subsystem: "subsystem", @@ -177,7 +177,7 @@ func TestCounterVec(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := NewKubeRegistry(*test.registryVersion) + registry := newKubeRegistry(*test.registryVersion) c := NewCounterVec(test.CounterOpts, test.labels) registry.MustRegister(c) c.WithLabelValues("1", "2").Inc() diff --git a/pkg/util/metrics/metric.go b/pkg/util/metrics/metric.go index d63bf9e408a..7231c2af2ce 100644 --- a/pkg/util/metrics/metric.go +++ b/pkg/util/metrics/metric.go @@ -29,7 +29,7 @@ This extends the prometheus.Collector interface to allow customization of the me registration process. Defer metric initialization until Create() is called, which then delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric method call depending on whether the metric is deprecated or not. - */ +*/ type KubeCollector interface { Collector LazyMetric @@ -46,7 +46,7 @@ type KubeCollector interface { LazyMetric defines our registration functionality. LazyMetric objects are expected to lazily instantiate metrics (i.e defer metric instantiation until when the Create() function is explicitly called). - */ +*/ type LazyMetric interface { Create(*semver.Version) bool IsCreated() bool @@ -59,7 +59,7 @@ lazyMetric implements LazyMetric. A lazy metric is lazy because it waits until m 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. - */ +*/ type lazyMetric struct { isDeprecated bool isHidden bool @@ -136,7 +136,7 @@ This code is directly lifted from the prometheus codebase. It's a convenience st allows you satisfy the Collector interface automatically if you already satisfy the Metric interface. For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/collector.go#L98-L120 - */ +*/ type selfCollector struct { metric prometheus.Metric } diff --git a/pkg/util/metrics/opts.go b/pkg/util/metrics/opts.go index ad1d9980634..f13e7edffd1 100644 --- a/pkg/util/metrics/opts.go +++ b/pkg/util/metrics/opts.go @@ -67,7 +67,7 @@ func (o *CounterOpts) annotateStabilityLevel() { // convenience function to allow easy transformation to the prometheus // counterpart. This will do more once we have a proper label abstraction -func (o CounterOpts) toPromCounterOpts() prometheus.CounterOpts { +func (o *CounterOpts) toPromCounterOpts() prometheus.CounterOpts { return prometheus.CounterOpts{ Namespace: o.Namespace, Subsystem: o.Subsystem, diff --git a/pkg/util/metrics/registry.go b/pkg/util/metrics/registry.go index 57afeb149c7..c26b137d885 100644 --- a/pkg/util/metrics/registry.go +++ b/pkg/util/metrics/registry.go @@ -20,12 +20,11 @@ import ( "github.com/blang/semver" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/version" ) -var ( - // todo: load the version dynamically at application boot. - DefaultGlobalRegistry = NewKubeRegistry(semver.MustParse("1.15.0")) -) +var DefaultGlobalRegistry = NewKubeRegistry() type KubeRegistry struct { PromRegistry @@ -69,9 +68,23 @@ func (kr *KubeRegistry) Gather() ([]*dto.MetricFamily, error) { return kr.PromRegistry.Gather() } -// NewRegistry creates a new vanilla Registry without any Collectors +func NewKubeRegistry() *KubeRegistry { + v, err := parseVersion(version.Get()) + if err != nil { + klog.Fatalf("Can't initialize a registry without a valid version %v", err) + } + if v == nil { + klog.Fatalf("No valid version %v", *v) + } + return &KubeRegistry{ + PromRegistry: prometheus.NewRegistry(), + version: semver.MustParse(*v), + } +} + +// newKubeRegistry creates a new vanilla Registry without any Collectors // pre-registered. -func NewKubeRegistry(version semver.Version) *KubeRegistry { +func newKubeRegistry(version semver.Version) *KubeRegistry { return &KubeRegistry{ PromRegistry: prometheus.NewRegistry(), version: version, diff --git a/pkg/util/metrics/registry_test.go b/pkg/util/metrics/registry_test.go index f9cabe87cd8..aeea754bb88 100644 --- a/pkg/util/metrics/registry_test.go +++ b/pkg/util/metrics/registry_test.go @@ -27,7 +27,7 @@ var ( v115 = semver.MustParse("1.15.0") v114 = semver.MustParse("1.14.0") alphaCounter = NewCounter( - CounterOpts{ + &CounterOpts{ Namespace: "some_namespace", Name: "test_counter_name", Subsystem: "subsystem", @@ -36,7 +36,7 @@ var ( }, ) alphaDeprecatedCounter = NewCounter( - CounterOpts{ + &CounterOpts{ Namespace: "some_namespace", Name: "test_alpha_dep_counter", Subsystem: "subsystem", @@ -46,7 +46,7 @@ var ( }, ) alphaHiddenCounter = NewCounter( - CounterOpts{ + &CounterOpts{ Namespace: "some_namespace", Name: "test_alpha_hidden_counter", Subsystem: "subsystem", @@ -56,7 +56,7 @@ var ( }, ) stableCounter = NewCounter( - CounterOpts{ + &CounterOpts{ Namespace: "some_namespace", Name: "test_some_other_counter", Subsystem: "subsystem", @@ -116,7 +116,7 @@ func TestRegister(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := NewKubeRegistry(*test.registryVersion) + registry := newKubeRegistry(*test.registryVersion) for i, m := range test.metrics { err := registry.Register(m) if err != test.expectedErrors[i] && err.Error() != test.expectedErrors[i].Error() { @@ -183,7 +183,7 @@ func TestMustRegister(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - registry := NewKubeRegistry(*test.registryVersion) + registry := newKubeRegistry(*test.registryVersion) for i, m := range test.metrics { if test.expectedPanics[i] { assert.Panics(t, diff --git a/pkg/util/metrics/version_parser.go b/pkg/util/metrics/version_parser.go new file mode 100644 index 00000000000..df79582d372 --- /dev/null +++ b/pkg/util/metrics/version_parser.go @@ -0,0 +1,40 @@ +/* +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 ( + "fmt" + apimachineryversion "k8s.io/apimachinery/pkg/version" + "regexp" +) + +const ( + versionRegexpString = `^v(\d+\.\d+\.\d+)` +) + +var ( + versionRe = regexp.MustCompile(versionRegexpString) +) + +func parseVersion(ver apimachineryversion.Info) (*string, error) { + matches := versionRe.FindAllStringSubmatch(ver.String(), -1) + + if len(matches) != 1 { + return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", ver.String(), versionRe.String()) + } + return &matches[0][1], nil +} diff --git a/pkg/util/metrics/version_parser_test.go b/pkg/util/metrics/version_parser_test.go new file mode 100644 index 00000000000..648f78948ac --- /dev/null +++ b/pkg/util/metrics/version_parser_test.go @@ -0,0 +1,56 @@ +/* +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 ( + apimachineryversion "k8s.io/apimachinery/pkg/version" + "testing" +) + +func TestVersionParsing(t *testing.T) { + var tests = []struct { + desc string + versionString string + expectedVersion string + }{ + { + "v1.15.0-alpha-1.12345", + "v1.15.0-alpha-1.12345", + "1.15.0", + }, + { + "Parse out defaulted string", + "v0.0.0-master", + "0.0.0", + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + version := apimachineryversion.Info{ + GitVersion: test.versionString, + } + parsedV, err := parseVersion(version) + if err != nil { + t.Fatalf("Should be able to parse %v", version) + } + if test.expectedVersion != *parsedV { + t.Errorf("Got %v, wanted %v", *parsedV, test.expectedVersion) + } + }) + } +}