add version parsing to metrics framework, use build version information for registry version

This commit is contained in:
Han Kang 2019-04-26 16:43:15 -07:00
parent cebad0da66
commit abe64acc8d
9 changed files with 145 additions and 31 deletions

View File

@ -14,10 +14,13 @@ go_library(
"opts.go", "opts.go",
"registry.go", "registry.go",
"util.go", "util.go",
"version_parser.go",
"wrappers.go", "wrappers.go",
], ],
importpath = "k8s.io/kubernetes/pkg/util/metrics", importpath = "k8s.io/kubernetes/pkg/util/metrics",
deps = [ 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", "//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//vendor/github.com/blang/semver: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:go_default_library",
@ -32,9 +35,11 @@ go_test(
"counter_test.go", "counter_test.go",
"registry_test.go", "registry_test.go",
"util_test.go", "util_test.go",
"version_parser_test.go",
], ],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/version:go_default_library",
"//staging/src/k8s.io/client-go/util/flowcontrol: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/blang/semver:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",

View File

@ -33,13 +33,13 @@ type kubeCounter struct {
// NewCounter returns an object which satisfies the KubeCollector and KubeCounter interfaces. // NewCounter returns an object which satisfies the KubeCollector and KubeCounter interfaces.
// However, the object returned will not measure anything unless the collector is first // 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.
func NewCounter(opts CounterOpts) *kubeCounter { func NewCounter(opts *CounterOpts) *kubeCounter {
// todo: handle defaulting better // todo: handle defaulting better
if opts.StabilityLevel == "" { if opts.StabilityLevel == "" {
opts.StabilityLevel = ALPHA opts.StabilityLevel = ALPHA
} }
kc := &kubeCounter{ kc := &kubeCounter{
CounterOpts: &opts, CounterOpts: opts,
lazyMetric: lazyMetric{}, lazyMetric: lazyMetric{},
} }
kc.setPrometheusCounter(noop) kc.setPrometheusCounter(noop)
@ -85,10 +85,10 @@ type kubeCounterVec struct {
// NewCounterVec returns an object which satisfies the KubeCollector and KubeCounterVec interfaces. // NewCounterVec returns an object which satisfies the KubeCollector and KubeCounterVec interfaces.
// However, the object returned will not measure anything unless the collector is first // 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.
func NewCounterVec(opts CounterOpts, labels []string) *kubeCounterVec { func NewCounterVec(opts *CounterOpts, labels []string) *kubeCounterVec {
cv := &kubeCounterVec{ cv := &kubeCounterVec{
CounterVec: noopCounterVec, CounterVec: noopCounterVec,
CounterOpts: &opts, CounterOpts: opts,
originalLabels: labels, originalLabels: labels,
lazyMetric: lazyMetric{}, lazyMetric: lazyMetric{},
} }

View File

@ -28,14 +28,14 @@ func TestCounter(t *testing.T) {
v114 := semver.MustParse("1.14.0") v114 := semver.MustParse("1.14.0")
var tests = []struct { var tests = []struct {
desc string desc string
CounterOpts *CounterOpts
registryVersion *semver.Version registryVersion *semver.Version
expectedMetricCount int expectedMetricCount int
expectedHelp string expectedHelp string
}{ }{
{ {
desc: "Test non deprecated", desc: "Test non deprecated",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -48,7 +48,7 @@ func TestCounter(t *testing.T) {
}, },
{ {
desc: "Test deprecated", desc: "Test deprecated",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -62,7 +62,7 @@ func TestCounter(t *testing.T) {
}, },
{ {
desc: "Test hidden", desc: "Test hidden",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -77,7 +77,7 @@ func TestCounter(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
registry := NewKubeRegistry(*test.registryVersion) registry := newKubeRegistry(*test.registryVersion)
c := NewCounter(test.CounterOpts) c := NewCounter(test.CounterOpts)
registry.MustRegister(c) registry.MustRegister(c)
@ -126,7 +126,7 @@ func TestCounterVec(t *testing.T) {
v114 := semver.MustParse("1.14.0") v114 := semver.MustParse("1.14.0")
var tests = []struct { var tests = []struct {
desc string desc string
CounterOpts *CounterOpts
labels []string labels []string
registryVersion *semver.Version registryVersion *semver.Version
expectedMetricFamilyCount int expectedMetricFamilyCount int
@ -134,7 +134,7 @@ func TestCounterVec(t *testing.T) {
}{ }{
{ {
desc: "Test non deprecated", desc: "Test non deprecated",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -147,7 +147,7 @@ func TestCounterVec(t *testing.T) {
}, },
{ {
desc: "Test deprecated", desc: "Test deprecated",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -161,7 +161,7 @@ func TestCounterVec(t *testing.T) {
}, },
{ {
desc: "Test hidden", desc: "Test hidden",
CounterOpts: CounterOpts{ CounterOpts: &CounterOpts{
Namespace: "namespace", Namespace: "namespace",
Name: "metric_test_name", Name: "metric_test_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -177,7 +177,7 @@ func TestCounterVec(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
registry := NewKubeRegistry(*test.registryVersion) registry := newKubeRegistry(*test.registryVersion)
c := NewCounterVec(test.CounterOpts, test.labels) c := NewCounterVec(test.CounterOpts, test.labels)
registry.MustRegister(c) registry.MustRegister(c)
c.WithLabelValues("1", "2").Inc() c.WithLabelValues("1", "2").Inc()

View File

@ -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 registration process. Defer metric initialization until Create() is called, which then
delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric delegates to the underlying metric's initializeMetric or initializeDeprecatedMetric
method call depending on whether the metric is deprecated or not. method call depending on whether the metric is deprecated or not.
*/ */
type KubeCollector interface { type KubeCollector interface {
Collector Collector
LazyMetric LazyMetric
@ -46,7 +46,7 @@ type KubeCollector interface {
LazyMetric defines our registration functionality. LazyMetric objects are expected LazyMetric defines our registration functionality. LazyMetric objects are expected
to lazily instantiate metrics (i.e defer metric instantiation until when to lazily instantiate metrics (i.e defer metric instantiation until when
the Create() function is explicitly called). the Create() function is explicitly called).
*/ */
type LazyMetric interface { type LazyMetric interface {
Create(*semver.Version) bool Create(*semver.Version) bool
IsCreated() 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 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 implements KubeCollector to get deferred registration behavior. You must call lazyInit
with the KubeCollector itself as an argument. with the KubeCollector itself as an argument.
*/ */
type lazyMetric struct { type lazyMetric struct {
isDeprecated bool isDeprecated bool
isHidden 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. 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 For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/collector.go#L98-L120
*/ */
type selfCollector struct { type selfCollector struct {
metric prometheus.Metric metric prometheus.Metric
} }

View File

@ -67,7 +67,7 @@ func (o *CounterOpts) annotateStabilityLevel() {
// convenience function to allow easy transformation to the prometheus // convenience function to allow easy transformation to the prometheus
// counterpart. This will do more once we have a proper label abstraction // 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{ return prometheus.CounterOpts{
Namespace: o.Namespace, Namespace: o.Namespace,
Subsystem: o.Subsystem, Subsystem: o.Subsystem,

View File

@ -20,12 +20,11 @@ import (
"github.com/blang/semver" "github.com/blang/semver"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/version"
) )
var ( var DefaultGlobalRegistry = NewKubeRegistry()
// todo: load the version dynamically at application boot.
DefaultGlobalRegistry = NewKubeRegistry(semver.MustParse("1.15.0"))
)
type KubeRegistry struct { type KubeRegistry struct {
PromRegistry PromRegistry
@ -69,9 +68,23 @@ func (kr *KubeRegistry) Gather() ([]*dto.MetricFamily, error) {
return kr.PromRegistry.Gather() 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. // pre-registered.
func NewKubeRegistry(version semver.Version) *KubeRegistry { func newKubeRegistry(version semver.Version) *KubeRegistry {
return &KubeRegistry{ return &KubeRegistry{
PromRegistry: prometheus.NewRegistry(), PromRegistry: prometheus.NewRegistry(),
version: version, version: version,

View File

@ -27,7 +27,7 @@ var (
v115 = semver.MustParse("1.15.0") v115 = semver.MustParse("1.15.0")
v114 = semver.MustParse("1.14.0") v114 = semver.MustParse("1.14.0")
alphaCounter = NewCounter( alphaCounter = NewCounter(
CounterOpts{ &CounterOpts{
Namespace: "some_namespace", Namespace: "some_namespace",
Name: "test_counter_name", Name: "test_counter_name",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -36,7 +36,7 @@ var (
}, },
) )
alphaDeprecatedCounter = NewCounter( alphaDeprecatedCounter = NewCounter(
CounterOpts{ &CounterOpts{
Namespace: "some_namespace", Namespace: "some_namespace",
Name: "test_alpha_dep_counter", Name: "test_alpha_dep_counter",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -46,7 +46,7 @@ var (
}, },
) )
alphaHiddenCounter = NewCounter( alphaHiddenCounter = NewCounter(
CounterOpts{ &CounterOpts{
Namespace: "some_namespace", Namespace: "some_namespace",
Name: "test_alpha_hidden_counter", Name: "test_alpha_hidden_counter",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -56,7 +56,7 @@ var (
}, },
) )
stableCounter = NewCounter( stableCounter = NewCounter(
CounterOpts{ &CounterOpts{
Namespace: "some_namespace", Namespace: "some_namespace",
Name: "test_some_other_counter", Name: "test_some_other_counter",
Subsystem: "subsystem", Subsystem: "subsystem",
@ -116,7 +116,7 @@ func TestRegister(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
registry := NewKubeRegistry(*test.registryVersion) registry := newKubeRegistry(*test.registryVersion)
for i, m := range test.metrics { for i, m := range test.metrics {
err := registry.Register(m) err := registry.Register(m)
if err != test.expectedErrors[i] && err.Error() != test.expectedErrors[i].Error() { if err != test.expectedErrors[i] && err.Error() != test.expectedErrors[i].Error() {
@ -183,7 +183,7 @@ func TestMustRegister(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
registry := NewKubeRegistry(*test.registryVersion) registry := newKubeRegistry(*test.registryVersion)
for i, m := range test.metrics { for i, m := range test.metrics {
if test.expectedPanics[i] { if test.expectedPanics[i] {
assert.Panics(t, assert.Panics(t,

View File

@ -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
}

View File

@ -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)
}
})
}
}