Skip to content

Commit 76d04cf

Browse files
authored
Merge pull request kubernetes#83062 from RainbowMango/pr_provide_custom_collector_support
Provide a mechanism for custom collectors to use the metrics stability framework
2 parents f23dd40 + d20e223 commit 76d04cf

File tree

7 files changed

+554
-0
lines changed

7 files changed

+554
-0
lines changed

staging/src/k8s.io/component-base/metrics/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
33
go_library(
44
name = "go_default_library",
55
srcs = [
6+
"collector.go",
67
"counter.go",
8+
"desc.go",
79
"gauge.go",
810
"histogram.go",
911
"http.go",
@@ -13,6 +15,7 @@ go_library(
1315
"processstarttime.go",
1416
"registry.go",
1517
"summary.go",
18+
"value.go",
1619
"version.go",
1720
"version_parser.go",
1821
"wrappers.go",
@@ -35,6 +38,7 @@ go_library(
3538
go_test(
3639
name = "go_default_test",
3740
srcs = [
41+
"collector_test.go",
3842
"counter_test.go",
3943
"gauge_test.go",
4044
"histogram_test.go",
@@ -49,6 +53,7 @@ go_test(
4953
"//vendor/github.com/blang/semver:go_default_library",
5054
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
5155
"//vendor/github.com/prometheus/client_golang/prometheus/testutil:go_default_library",
56+
"//vendor/github.com/prometheus/client_model/go:go_default_library",
5257
"//vendor/github.com/prometheus/common/expfmt:go_default_library",
5358
"//vendor/github.com/stretchr/testify/assert:go_default_library",
5459
],
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/blang/semver"
23+
"github.com/prometheus/client_golang/prometheus"
24+
)
25+
26+
// StableCollector extends the prometheus.Collector interface to allow customization of the
27+
// metric registration process, it's especially intend to be used in scenario of custom collector.
28+
type StableCollector interface {
29+
prometheus.Collector
30+
31+
// DescribeWithStability sends the super-set of all possible metrics.Desc collected
32+
// by this StableCollector to the provided channel.
33+
DescribeWithStability(chan<- *Desc)
34+
35+
// CollectWithStability sends each collected metrics.Metric via the provide channel.
36+
CollectWithStability(chan<- Metric)
37+
38+
// Create will initialize all Desc and it intends to be called by registry.
39+
Create(version *semver.Version, self StableCollector) bool
40+
}
41+
42+
// BaseStableCollector which implements almost all of the methods defined by StableCollector
43+
// is a convenient assistant for custom collectors.
44+
// It is recommend that inherit BaseStableCollector when implementing custom collectors.
45+
type BaseStableCollector struct {
46+
descriptors []*Desc // stores all Desc collected from DescribeWithStability().
47+
registrable []*Desc // stores registrable Desc(not be hidden), is a subset of descriptors.
48+
self StableCollector
49+
}
50+
51+
// DescribeWithStability sends all descriptors to the provided channel.
52+
// Every custom collector should over-write this method.
53+
func (bsc *BaseStableCollector) DescribeWithStability(ch chan<- *Desc) {
54+
panic(fmt.Errorf("custom collector should over-write DescribeWithStability method"))
55+
}
56+
57+
// Describe sends all descriptors to the provided channel.
58+
// It intend to be called by prometheus registry.
59+
func (bsc *BaseStableCollector) Describe(ch chan<- *prometheus.Desc) {
60+
for _, d := range bsc.registrable {
61+
ch <- d.toPrometheusDesc()
62+
}
63+
}
64+
65+
// CollectWithStability sends all metrics to the provided channel.
66+
// Every custom collector should over-write this method.
67+
func (bsc *BaseStableCollector) CollectWithStability(ch chan<- Metric) {
68+
panic(fmt.Errorf("custom collector should over-write CollectWithStability method"))
69+
}
70+
71+
// Collect is called by the Prometheus registry when collecting metrics.
72+
func (bsc *BaseStableCollector) Collect(ch chan<- prometheus.Metric) {
73+
mch := make(chan Metric)
74+
75+
go func() {
76+
bsc.self.CollectWithStability(mch)
77+
close(mch)
78+
}()
79+
80+
for m := range mch {
81+
// nil Metric usually means hidden metrics
82+
if m == nil {
83+
continue
84+
}
85+
86+
ch <- prometheus.Metric(m)
87+
}
88+
}
89+
90+
func (bsc *BaseStableCollector) add(d *Desc) {
91+
bsc.descriptors = append(bsc.descriptors, d)
92+
}
93+
94+
// Init intends to be called by registry.
95+
func (bsc *BaseStableCollector) init(self StableCollector) {
96+
bsc.self = self
97+
98+
dch := make(chan *Desc)
99+
100+
// collect all possible descriptions from custom side
101+
go func() {
102+
bsc.self.DescribeWithStability(dch)
103+
close(dch)
104+
}()
105+
106+
for d := range dch {
107+
bsc.add(d)
108+
}
109+
}
110+
111+
// Create intends to be called by registry.
112+
// Create will return true as long as there is one or more metrics not be hidden.
113+
// Otherwise return false, that means the whole collector will be ignored by registry.
114+
func (bsc *BaseStableCollector) Create(version *semver.Version, self StableCollector) bool {
115+
bsc.init(self)
116+
117+
for _, d := range bsc.descriptors {
118+
if version != nil {
119+
d.determineDeprecationStatus(*version)
120+
}
121+
122+
d.createOnce.Do(func() {
123+
d.createLock.Lock()
124+
defer d.createLock.Unlock()
125+
126+
if d.IsHidden() {
127+
// do nothing for hidden metrics
128+
} else if d.IsDeprecated() {
129+
d.initializeDeprecatedDesc()
130+
bsc.registrable = append(bsc.registrable, d)
131+
d.isCreated = true
132+
} else {
133+
d.initialize()
134+
bsc.registrable = append(bsc.registrable, d)
135+
d.isCreated = true
136+
}
137+
})
138+
}
139+
140+
if len(bsc.registrable) > 0 {
141+
return true
142+
}
143+
144+
return false
145+
}
146+
147+
// Check if our BaseStableCollector implements necessary interface
148+
var _ StableCollector = &BaseStableCollector{}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"testing"
21+
22+
dto "github.com/prometheus/client_model/go"
23+
24+
apimachineryversion "k8s.io/apimachinery/pkg/version"
25+
)
26+
27+
type testCustomCollector struct {
28+
BaseStableCollector
29+
}
30+
31+
var (
32+
currentVersion = apimachineryversion.Info{
33+
Major: "1",
34+
Minor: "17",
35+
GitVersion: "v1.17.0-alpha-1.12345",
36+
}
37+
alphaDesc = NewDesc("metric_alpha", "alpha metric", []string{"name"}, nil,
38+
ALPHA, "")
39+
stableDesc = NewDesc("metric_stable", "stable metrics", []string{"name"}, nil,
40+
STABLE, "")
41+
deprecatedDesc = NewDesc("metric_deprecated", "stable deprecated metrics", []string{"name"}, nil,
42+
STABLE, "1.17.0")
43+
hiddenDesc = NewDesc("metric_hidden", "stable hidden metrics", []string{"name"}, nil,
44+
STABLE, "1.16.0")
45+
)
46+
47+
func (tc *testCustomCollector) DescribeWithStability(ch chan<- *Desc) {
48+
ch <- alphaDesc
49+
ch <- stableDesc
50+
ch <- deprecatedDesc
51+
ch <- hiddenDesc
52+
}
53+
54+
func (tc *testCustomCollector) CollectWithStability(ch chan<- Metric) {
55+
ch <- NewLazyConstMetric(
56+
alphaDesc,
57+
GaugeValue,
58+
1,
59+
"value",
60+
)
61+
ch <- NewLazyConstMetric(
62+
stableDesc,
63+
GaugeValue,
64+
1,
65+
"value",
66+
)
67+
ch <- NewLazyConstMetric(
68+
deprecatedDesc,
69+
GaugeValue,
70+
1,
71+
"value",
72+
)
73+
ch <- NewLazyConstMetric(
74+
hiddenDesc,
75+
GaugeValue,
76+
1,
77+
"value",
78+
)
79+
80+
}
81+
82+
func getMetric(metrics []*dto.MetricFamily, fqName string) *dto.MetricFamily {
83+
for _, m := range metrics {
84+
if *m.Name == fqName {
85+
return m
86+
}
87+
}
88+
89+
return nil
90+
}
91+
92+
func TestBaseCustomCollector(t *testing.T) {
93+
var tests = []struct {
94+
name string
95+
d *Desc
96+
shouldHidden bool
97+
expectedHelp string
98+
}{
99+
{
100+
name: "alpha metric should contains stability metadata",
101+
d: alphaDesc,
102+
shouldHidden: false,
103+
expectedHelp: "[ALPHA] alpha metric",
104+
},
105+
{
106+
name: "stable metric should contains stability metadata",
107+
d: stableDesc,
108+
shouldHidden: false,
109+
expectedHelp: "[STABLE] stable metrics",
110+
},
111+
{
112+
name: "deprecated metric should contains stability metadata",
113+
d: deprecatedDesc,
114+
shouldHidden: false,
115+
expectedHelp: "[STABLE] (Deprecated since 1.17.0) stable deprecated metrics",
116+
},
117+
{
118+
name: "hidden metric should be ignored",
119+
d: hiddenDesc,
120+
shouldHidden: true,
121+
expectedHelp: "[STABLE] stable hidden metrics",
122+
},
123+
}
124+
125+
registry := newKubeRegistry(currentVersion)
126+
customCollector := &testCustomCollector{}
127+
128+
if err := registry.CustomRegister(customCollector); err != nil {
129+
t.Fatalf("register collector failed with err: %v", err)
130+
}
131+
132+
metrics, err := registry.Gather()
133+
if err != nil {
134+
t.Fatalf("failed to get metrics from collector, %v", err)
135+
}
136+
137+
for _, test := range tests {
138+
tc := test
139+
t.Run(tc.name, func(t *testing.T) {
140+
m := getMetric(metrics, tc.d.fqName)
141+
if m == nil {
142+
if !tc.shouldHidden {
143+
t.Fatalf("Want metric: %s", tc.d.fqName)
144+
}
145+
} else {
146+
if m.GetHelp() != tc.expectedHelp {
147+
t.Fatalf("Metric(%s) HELP(%s) not contains: %s", tc.d.fqName, *m.Help, tc.expectedHelp)
148+
}
149+
}
150+
151+
})
152+
}
153+
}

0 commit comments

Comments
 (0)