Skip to content

Commit bdc6a29

Browse files
author
Han Kang
committed
add wrappers around gauge, histogram & summary
1 parent c14106a commit bdc6a29

File tree

9 files changed

+1249
-0
lines changed

9 files changed

+1249
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ go_library(
1010
name = "go_default_library",
1111
srcs = [
1212
"counter.go",
13+
"gauge.go",
14+
"histogram.go",
1315
"metric.go",
1416
"opts.go",
1517
"registry.go",
18+
"summary.go",
1619
"version_parser.go",
1720
"wrappers.go",
1821
],
@@ -31,7 +34,10 @@ go_test(
3134
name = "go_default_test",
3235
srcs = [
3336
"counter_test.go",
37+
"gauge_test.go",
38+
"histogram_test.go",
3439
"registry_test.go",
40+
"summary_test.go",
3541
"version_parser_test.go",
3642
],
3743
embed = [":go_default_library"],
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
"github.com/blang/semver"
21+
"github.com/prometheus/client_golang/prometheus"
22+
)
23+
24+
// Gauge is our internal representation for our wrapping struct around prometheus
25+
// gauges. kubeGauge implements both KubeCollector and KubeGauge.
26+
type Gauge struct {
27+
GaugeMetric
28+
*GaugeOpts
29+
lazyMetric
30+
selfCollector
31+
}
32+
33+
// NewGauge returns an object which satisfies the KubeCollector and KubeGauge interfaces.
34+
// However, the object returned will not measure anything unless the collector is first
35+
// registered, since the metric is lazily instantiated.
36+
func NewGauge(opts *GaugeOpts) *Gauge {
37+
// todo: handle defaulting better
38+
if opts.StabilityLevel == "" {
39+
opts.StabilityLevel = ALPHA
40+
}
41+
kc := &Gauge{
42+
GaugeOpts: opts,
43+
lazyMetric: lazyMetric{},
44+
}
45+
kc.setPrometheusGauge(noop)
46+
kc.lazyInit(kc)
47+
return kc
48+
}
49+
50+
// setPrometheusGauge sets the underlying KubeGauge object, i.e. the thing that does the measurement.
51+
func (g *Gauge) setPrometheusGauge(gauge prometheus.Gauge) {
52+
g.GaugeMetric = gauge
53+
g.initSelfCollection(gauge)
54+
}
55+
56+
// DeprecatedVersion returns a pointer to the Version or nil
57+
func (g *Gauge) DeprecatedVersion() *semver.Version {
58+
return g.GaugeOpts.DeprecatedVersion
59+
}
60+
61+
// initializeMetric invocation creates the actual underlying Gauge. Until this method is called
62+
// the underlying gauge is a no-op.
63+
func (g *Gauge) initializeMetric() {
64+
g.GaugeOpts.annotateStabilityLevel()
65+
// this actually creates the underlying prometheus gauge.
66+
g.setPrometheusGauge(prometheus.NewGauge(g.GaugeOpts.toPromGaugeOpts()))
67+
}
68+
69+
// initializeDeprecatedMetric invocation creates the actual (but deprecated) Gauge. Until this method
70+
// is called the underlying gauge is a no-op.
71+
func (g *Gauge) initializeDeprecatedMetric() {
72+
g.GaugeOpts.markDeprecated()
73+
g.initializeMetric()
74+
}
75+
76+
// GaugeVec is the internal representation of our wrapping struct around prometheus
77+
// gaugeVecs. kubeGaugeVec implements both KubeCollector and KubeGaugeVec.
78+
type GaugeVec struct {
79+
*prometheus.GaugeVec
80+
*GaugeOpts
81+
lazyMetric
82+
originalLabels []string
83+
}
84+
85+
// NewGaugeVec returns an object which satisfies the KubeCollector and KubeGaugeVec interfaces.
86+
// However, the object returned will not measure anything unless the collector is first
87+
// registered, since the metric is lazily instantiated.
88+
func NewGaugeVec(opts *GaugeOpts, labels []string) *GaugeVec {
89+
// todo: handle defaulting better
90+
if opts.StabilityLevel == "" {
91+
opts.StabilityLevel = ALPHA
92+
}
93+
cv := &GaugeVec{
94+
GaugeVec: noopGaugeVec,
95+
GaugeOpts: opts,
96+
originalLabels: labels,
97+
lazyMetric: lazyMetric{},
98+
}
99+
cv.lazyInit(cv)
100+
return cv
101+
}
102+
103+
// DeprecatedVersion returns a pointer to the Version or nil
104+
func (v *GaugeVec) DeprecatedVersion() *semver.Version {
105+
return v.GaugeOpts.DeprecatedVersion
106+
}
107+
108+
// initializeMetric invocation creates the actual underlying GaugeVec. Until this method is called
109+
// the underlying gaugeVec is a no-op.
110+
func (v *GaugeVec) initializeMetric() {
111+
v.GaugeOpts.annotateStabilityLevel()
112+
v.GaugeVec = prometheus.NewGaugeVec(v.GaugeOpts.toPromGaugeOpts(), v.originalLabels)
113+
}
114+
115+
// initializeDeprecatedMetric invocation creates the actual (but deprecated) GaugeVec. Until this method is called
116+
// the underlying gaugeVec is a no-op.
117+
func (v *GaugeVec) initializeDeprecatedMetric() {
118+
v.GaugeOpts.markDeprecated()
119+
v.initializeMetric()
120+
}
121+
122+
// Default Prometheus behavior actually results in the creation of a new metric
123+
// if a metric with the unique label values is not found in the underlying stored metricMap.
124+
// This means that if this function is called but the underlying metric is not registered
125+
// (which means it will never be exposed externally nor consumed), the metric will exist in memory
126+
// for perpetuity (i.e. throughout application lifecycle).
127+
//
128+
// For reference: https://github.com/prometheus/client_golang/blob/v0.9.2/prometheus/gauge.go#L190-L208
129+
130+
// WithLabelValues returns the GaugeMetric for the given slice of label
131+
// values (same order as the VariableLabels in Desc). If that combination of
132+
// label values is accessed for the first time, a new GaugeMetric is created IFF the gaugeVec
133+
// has been registered to a metrics registry.
134+
func (v *GaugeVec) WithLabelValues(lvs ...string) GaugeMetric {
135+
if !v.IsCreated() {
136+
return noop // return no-op gauge
137+
}
138+
return v.GaugeVec.WithLabelValues(lvs...)
139+
}
140+
141+
// With returns the GaugeMetric for the given Labels map (the label names
142+
// must match those of the VariableLabels in Desc). If that label map is
143+
// accessed for the first time, a new GaugeMetric is created IFF the gaugeVec has
144+
// been registered to a metrics registry.
145+
func (v *GaugeVec) With(labels prometheus.Labels) GaugeMetric {
146+
if !v.IsCreated() {
147+
return noop // return no-op gauge
148+
}
149+
return v.GaugeVec.With(labels)
150+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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+
"github.com/blang/semver"
21+
apimachineryversion "k8s.io/apimachinery/pkg/version"
22+
"testing"
23+
)
24+
25+
func TestGauge(t *testing.T) {
26+
v115 := semver.MustParse("1.15.0")
27+
v114 := semver.MustParse("1.14.0")
28+
var tests = []struct {
29+
desc string
30+
GaugeOpts
31+
registryVersion *semver.Version
32+
expectedMetricCount int
33+
expectedHelp string
34+
}{
35+
{
36+
desc: "Test non deprecated",
37+
GaugeOpts: GaugeOpts{
38+
Namespace: "namespace",
39+
Name: "metric_test_name",
40+
Subsystem: "subsystem",
41+
Help: "gauge help",
42+
},
43+
registryVersion: &v115,
44+
expectedMetricCount: 1,
45+
expectedHelp: "[ALPHA] gauge help",
46+
},
47+
{
48+
desc: "Test deprecated",
49+
GaugeOpts: GaugeOpts{
50+
Namespace: "namespace",
51+
Name: "metric_test_name",
52+
Subsystem: "subsystem",
53+
Help: "gauge help",
54+
DeprecatedVersion: &v115,
55+
},
56+
registryVersion: &v115,
57+
expectedMetricCount: 1,
58+
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) gauge help",
59+
},
60+
{
61+
desc: "Test hidden",
62+
GaugeOpts: GaugeOpts{
63+
Namespace: "namespace",
64+
Name: "metric_test_name",
65+
Subsystem: "subsystem",
66+
Help: "gauge help",
67+
DeprecatedVersion: &v114,
68+
},
69+
registryVersion: &v115,
70+
expectedMetricCount: 0,
71+
expectedHelp: "gauge help",
72+
},
73+
}
74+
75+
for _, test := range tests {
76+
t.Run(test.desc, func(t *testing.T) {
77+
registry := NewKubeRegistry(apimachineryversion.Info{
78+
Major: "1",
79+
Minor: "15",
80+
GitVersion: "v1.15.0-alpha-1.12345",
81+
})
82+
c := NewGauge(&test.GaugeOpts)
83+
registry.MustRegister(c)
84+
85+
ms, err := registry.Gather()
86+
if len(ms) != test.expectedMetricCount {
87+
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
88+
}
89+
if err != nil {
90+
t.Fatalf("Gather failed %v", err)
91+
}
92+
for _, metric := range ms {
93+
if metric.GetHelp() != test.expectedHelp {
94+
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
95+
}
96+
}
97+
98+
// let's increment the counter and verify that the metric still works
99+
c.Set(100)
100+
c.Set(101)
101+
expected := 101
102+
ms, err = registry.Gather()
103+
if err != nil {
104+
t.Fatalf("Gather failed %v", err)
105+
}
106+
for _, mf := range ms {
107+
for _, m := range mf.GetMetric() {
108+
if int(m.GetGauge().GetValue()) != expected {
109+
t.Errorf("Got %v, wanted %v as the count", m.GetGauge().GetValue(), expected)
110+
}
111+
t.Logf("%v\n", m.GetGauge().GetValue())
112+
}
113+
}
114+
})
115+
}
116+
}
117+
118+
func TestGaugeVec(t *testing.T) {
119+
v115 := semver.MustParse("1.15.0")
120+
v114 := semver.MustParse("1.14.0")
121+
var tests = []struct {
122+
desc string
123+
GaugeOpts
124+
labels []string
125+
registryVersion *semver.Version
126+
expectedMetricCount int
127+
expectedHelp string
128+
}{
129+
{
130+
desc: "Test non deprecated",
131+
GaugeOpts: GaugeOpts{
132+
Namespace: "namespace",
133+
Name: "metric_test_name",
134+
Subsystem: "subsystem",
135+
Help: "gauge help",
136+
},
137+
labels: []string{"label_a", "label_b"},
138+
registryVersion: &v115,
139+
expectedMetricCount: 1,
140+
expectedHelp: "[ALPHA] gauge help",
141+
},
142+
{
143+
desc: "Test deprecated",
144+
GaugeOpts: GaugeOpts{
145+
Namespace: "namespace",
146+
Name: "metric_test_name",
147+
Subsystem: "subsystem",
148+
Help: "gauge help",
149+
DeprecatedVersion: &v115,
150+
},
151+
labels: []string{"label_a", "label_b"},
152+
registryVersion: &v115,
153+
expectedMetricCount: 1,
154+
expectedHelp: "[ALPHA] (Deprecated since 1.15.0) gauge help",
155+
},
156+
{
157+
desc: "Test hidden",
158+
GaugeOpts: GaugeOpts{
159+
Namespace: "namespace",
160+
Name: "metric_test_name",
161+
Subsystem: "subsystem",
162+
Help: "gauge help",
163+
DeprecatedVersion: &v114,
164+
},
165+
labels: []string{"label_a", "label_b"},
166+
registryVersion: &v115,
167+
expectedMetricCount: 0,
168+
expectedHelp: "gauge help",
169+
},
170+
}
171+
172+
for _, test := range tests {
173+
t.Run(test.desc, func(t *testing.T) {
174+
registry := NewKubeRegistry(apimachineryversion.Info{
175+
Major: "1",
176+
Minor: "15",
177+
GitVersion: "v1.15.0-alpha-1.12345",
178+
})
179+
c := NewGaugeVec(&test.GaugeOpts, test.labels)
180+
registry.MustRegister(c)
181+
c.WithLabelValues("1", "2").Set(1.0)
182+
ms, err := registry.Gather()
183+
184+
if len(ms) != test.expectedMetricCount {
185+
t.Errorf("Got %v metrics, Want: %v metrics", len(ms), test.expectedMetricCount)
186+
}
187+
if err != nil {
188+
t.Fatalf("Gather failed %v", err)
189+
}
190+
for _, metric := range ms {
191+
if metric.GetHelp() != test.expectedHelp {
192+
t.Errorf("Got %s as help message, want %s", metric.GetHelp(), test.expectedHelp)
193+
}
194+
}
195+
196+
// let's increment the counter and verify that the metric still works
197+
c.WithLabelValues("1", "3").Set(1.0)
198+
c.WithLabelValues("2", "3").Set(1.0)
199+
ms, err = registry.Gather()
200+
if err != nil {
201+
t.Fatalf("Gather failed %v", err)
202+
}
203+
for _, mf := range ms {
204+
if len(mf.GetMetric()) != 3 {
205+
t.Errorf("Got %v metrics, wanted 2 as the count", len(mf.GetMetric()))
206+
}
207+
}
208+
})
209+
}
210+
}

0 commit comments

Comments
 (0)