Skip to content

Commit 6ceb6c6

Browse files
authored
Merge pull request kubernetes#93134 from logicalhan/metric-handler
Add reset handler to the instrumentation metric library and expose Reset on the metric registries
2 parents 363c3b8 + 45af3ae commit 6ceb6c6

File tree

7 files changed

+189
-37
lines changed

7 files changed

+189
-37
lines changed

staging/src/k8s.io/apiserver/pkg/server/routes/metrics.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ limitations under the License.
1717
package routes
1818

1919
import (
20-
"io"
21-
"net/http"
22-
2320
apimetrics "k8s.io/apiserver/pkg/endpoints/metrics"
2421
"k8s.io/apiserver/pkg/server/mux"
2522
etcd3metrics "k8s.io/apiserver/pkg/storage/etcd3/metrics"
@@ -43,18 +40,7 @@ type MetricsWithReset struct{}
4340
// Install adds the MetricsWithReset handler
4441
func (m MetricsWithReset) Install(c *mux.PathRecorderMux) {
4542
register()
46-
defaultMetricsHandler := legacyregistry.Handler().ServeHTTP
47-
c.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
48-
if req.Method == "DELETE" {
49-
apimetrics.Reset()
50-
etcd3metrics.Reset()
51-
flowcontrolmetrics.Reset()
52-
53-
io.WriteString(w, "metrics reset\n")
54-
return
55-
}
56-
defaultMetricsHandler(w, req)
57-
})
43+
c.Handle("/metrics", legacyregistry.HandlerWithReset())
5844
}
5945

6046
// register apiserver and etcd metrics

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ go_test(
4545
"desc_test.go",
4646
"gauge_test.go",
4747
"histogram_test.go",
48+
"http_test.go",
4849
"opts_test.go",
4950
"registry_test.go",
5051
"summary_test.go",

staging/src/k8s.io/component-base/metrics/http.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package metrics
1818

1919
import (
20+
"io"
2021
"net/http"
2122

2223
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -61,3 +62,16 @@ func (ho *HandlerOpts) toPromhttpHandlerOpts() promhttp.HandlerOpts {
6162
func HandlerFor(reg Gatherer, opts HandlerOpts) http.Handler {
6263
return promhttp.HandlerFor(reg, opts.toPromhttpHandlerOpts())
6364
}
65+
66+
// HandlerWithReset return an http.Handler with Reset
67+
func HandlerWithReset(reg KubeRegistry, opts HandlerOpts) http.Handler {
68+
defaultHandler := promhttp.HandlerFor(reg, opts.toPromhttpHandlerOpts())
69+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
70+
if r.Method == http.MethodDelete {
71+
reg.Reset()
72+
io.WriteString(w, "metrics reset\n")
73+
return
74+
}
75+
defaultHandler.ServeHTTP(w, r)
76+
})
77+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2020 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+
"io/ioutil"
21+
"net/http"
22+
"net/http/httptest"
23+
"testing"
24+
25+
apimachineryversion "k8s.io/apimachinery/pkg/version"
26+
)
27+
28+
func TestResetHandler(t *testing.T) {
29+
currentVersion := apimachineryversion.Info{
30+
Major: "1",
31+
Minor: "17",
32+
GitVersion: "v1.17.1-alpha-1.12345",
33+
}
34+
registry := newKubeRegistry(currentVersion)
35+
resetHandler := HandlerWithReset(registry, HandlerOpts{})
36+
testCases := []struct {
37+
desc string
38+
method string
39+
expectedBody string
40+
}{
41+
{
42+
desc: "Should return empty body on a get",
43+
method: http.MethodGet,
44+
expectedBody: "",
45+
},
46+
{
47+
desc: "Should return 'metrics reset' in the body on a delete",
48+
method: http.MethodDelete,
49+
expectedBody: "metrics reset\n",
50+
},
51+
}
52+
for _, tc := range testCases {
53+
t.Run(tc.desc, func(t *testing.T) {
54+
req, err := http.NewRequest(tc.method, "http://sample.com/metrics", nil)
55+
if err != nil {
56+
t.Fatalf("Error creating http request")
57+
}
58+
rec := httptest.NewRecorder()
59+
resetHandler.ServeHTTP(rec, req)
60+
body, err := ioutil.ReadAll(rec.Result().Body)
61+
if err != nil {
62+
t.Fatalf("Error reading response body")
63+
}
64+
if string(body) != tc.expectedBody {
65+
t.Errorf("Got '%s' as the response body, but want '%v'", body, tc.expectedBody)
66+
}
67+
})
68+
}
69+
}

staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ var (
2929
defaultRegistry = metrics.NewKubeRegistry()
3030
// DefaultGatherer exposes the global registry gatherer
3131
DefaultGatherer metrics.Gatherer = defaultRegistry
32+
// Reset calls reset on the global registry
33+
Reset = defaultRegistry.Reset
34+
// MustRegister registers registerable metrics but uses the global registry.
35+
MustRegister = defaultRegistry.MustRegister
36+
// RawMustRegister registers prometheus collectors but uses the global registry, this
37+
// bypasses the metric stability framework
38+
//
39+
// Deprecated
40+
RawMustRegister = defaultRegistry.RawMustRegister
41+
42+
// Register registers a collectable metric but uses the global registry
43+
Register = defaultRegistry.Register
3244
)
3345

3446
func init() {
@@ -46,23 +58,12 @@ func Handler() http.Handler {
4658
return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(defaultRegistry, promhttp.HandlerOpts{}))
4759
}
4860

49-
// Register registers a collectable metric but uses the global registry
50-
func Register(c metrics.Registerable) error {
51-
err := defaultRegistry.Register(c)
52-
return err
53-
}
54-
55-
// MustRegister registers registerable metrics but uses the global registry.
56-
func MustRegister(cs ...metrics.Registerable) {
57-
defaultRegistry.MustRegister(cs...)
58-
}
59-
60-
// RawMustRegister registers prometheus collectors but uses the global registry, this
61-
// bypasses the metric stability framework
62-
//
63-
// Deprecated
64-
func RawMustRegister(cs ...prometheus.Collector) {
65-
defaultRegistry.RawMustRegister(cs...)
61+
// HandlerWithReset returns an HTTP handler for the DefaultGatherer but invokes
62+
// registry reset if the http method is DELETE.
63+
func HandlerWithReset() http.Handler {
64+
return promhttp.InstrumentMetricHandler(
65+
prometheus.DefaultRegisterer,
66+
metrics.HandlerWithReset(defaultRegistry, metrics.HandlerOpts{}))
6667
}
6768

6869
// CustomRegister registers a custom collector but uses the global registry.

staging/src/k8s.io/component-base/metrics/registry.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,30 @@ type Registerable interface {
9797
FQName() string
9898
}
9999

100+
type resettable interface {
101+
Reset()
102+
}
103+
100104
// KubeRegistry is an interface which implements a subset of prometheus.Registerer and
101105
// prometheus.Gatherer interfaces
102106
type KubeRegistry interface {
103107
// Deprecated
104108
RawMustRegister(...prometheus.Collector)
109+
// CustomRegister is our internal variant of Prometheus registry.Register
105110
CustomRegister(c StableCollector) error
111+
// CustomMustRegister is our internal variant of Prometheus registry.MustRegister
106112
CustomMustRegister(cs ...StableCollector)
113+
// Register conforms to Prometheus registry.Register
107114
Register(Registerable) error
115+
// MustRegister conforms to Prometheus registry.MustRegister
108116
MustRegister(...Registerable)
117+
// Unregister conforms to Prometheus registry.Unregister
109118
Unregister(collector Collector) bool
119+
// Gather conforms to Prometheus gatherer.Gather
110120
Gather() ([]*dto.MetricFamily, error)
121+
// Reset invokes the Reset() function on all items in the registry
122+
// which are added as resettables.
123+
Reset()
111124
}
112125

113126
// kubeRegistry is a wrapper around a prometheus registry-type object. Upon initialization
@@ -120,6 +133,8 @@ type kubeRegistry struct {
120133
stableCollectors []StableCollector // stores all stable collector
121134
hiddenCollectorsLock sync.RWMutex
122135
stableCollectorsLock sync.RWMutex
136+
resetLock sync.RWMutex
137+
resettables []resettable
123138
}
124139

125140
// Register registers a new Collector to be included in metrics
@@ -129,11 +144,11 @@ type kubeRegistry struct {
129144
// uniqueness criteria described in the documentation of metric.Desc.
130145
func (kr *kubeRegistry) Register(c Registerable) error {
131146
if c.Create(&kr.version) {
147+
defer kr.addResettable(c)
132148
return kr.PromRegistry.Register(c)
133149
}
134150

135151
kr.trackHiddenCollector(c)
136-
137152
return nil
138153
}
139154

@@ -145,6 +160,7 @@ func (kr *kubeRegistry) MustRegister(cs ...Registerable) {
145160
for _, c := range cs {
146161
if c.Create(&kr.version) {
147162
metrics = append(metrics, c)
163+
kr.addResettable(c)
148164
} else {
149165
kr.trackHiddenCollector(c)
150166
}
@@ -155,7 +171,7 @@ func (kr *kubeRegistry) MustRegister(cs ...Registerable) {
155171
// CustomRegister registers a new custom collector.
156172
func (kr *kubeRegistry) CustomRegister(c StableCollector) error {
157173
kr.trackStableCollectors(c)
158-
174+
defer kr.addResettable(c)
159175
if c.Create(&kr.version, c) {
160176
return kr.PromRegistry.Register(c)
161177
}
@@ -167,14 +183,13 @@ func (kr *kubeRegistry) CustomRegister(c StableCollector) error {
167183
// error.
168184
func (kr *kubeRegistry) CustomMustRegister(cs ...StableCollector) {
169185
kr.trackStableCollectors(cs...)
170-
171186
collectors := make([]prometheus.Collector, 0, len(cs))
172187
for _, c := range cs {
173188
if c.Create(&kr.version, c) {
189+
kr.addResettable(c)
174190
collectors = append(collectors, c)
175191
}
176192
}
177-
178193
kr.PromRegistry.MustRegister(collectors...)
179194
}
180195

@@ -185,6 +200,19 @@ func (kr *kubeRegistry) CustomMustRegister(cs ...StableCollector) {
185200
// Deprecated
186201
func (kr *kubeRegistry) RawMustRegister(cs ...prometheus.Collector) {
187202
kr.PromRegistry.MustRegister(cs...)
203+
for _, c := range cs {
204+
kr.addResettable(c)
205+
}
206+
}
207+
208+
// addResettable will automatically add our metric to our reset
209+
// list if it satisfies the interface
210+
func (kr *kubeRegistry) addResettable(i interface{}) {
211+
kr.resetLock.Lock()
212+
defer kr.resetLock.Unlock()
213+
if resettable, ok := i.(resettable); ok {
214+
kr.resettables = append(kr.resettables, resettable)
215+
}
188216
}
189217

190218
// Unregister unregisters the Collector that equals the Collector passed
@@ -266,6 +294,15 @@ func (kr *kubeRegistry) enableHiddenStableCollectors() {
266294
kr.CustomMustRegister(cs...)
267295
}
268296

297+
// Reset invokes Reset on all metrics that are resettable.
298+
func (kr *kubeRegistry) Reset() {
299+
kr.resetLock.RLock()
300+
defer kr.resetLock.RUnlock()
301+
for _, r := range kr.resettables {
302+
r.Reset()
303+
}
304+
}
305+
269306
// BuildVersion is a helper function that can be easily mocked.
270307
var BuildVersion = version.Get
271308

@@ -274,6 +311,7 @@ func newKubeRegistry(v apimachineryversion.Info) *kubeRegistry {
274311
PromRegistry: prometheus.NewRegistry(),
275312
version: parseVersion(v),
276313
hiddenCollectors: make(map[string]Registerable),
314+
resettables: make([]resettable, 0),
277315
}
278316

279317
registriesLock.Lock()
@@ -287,6 +325,5 @@ func newKubeRegistry(v apimachineryversion.Info) *kubeRegistry {
287325
// pre-registered.
288326
func NewKubeRegistry() KubeRegistry {
289327
r := newKubeRegistry(BuildVersion())
290-
291328
return r
292329
}

staging/src/k8s.io/component-base/metrics/registry_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,3 +471,47 @@ func TestEnableHiddenStableCollector(t *testing.T) {
471471
})
472472
}
473473
}
474+
475+
func TestRegistryReset(t *testing.T) {
476+
currentVersion := apimachineryversion.Info{
477+
Major: "1",
478+
Minor: "17",
479+
GitVersion: "v1.17.1-alpha-1.12345",
480+
}
481+
registry := newKubeRegistry(currentVersion)
482+
resettableMetric := NewCounterVec(&CounterOpts{
483+
Name: "reset_metric",
484+
Help: "this metric can be reset",
485+
}, []string{"label"})
486+
// gauges cannot be reset
487+
nonResettableMetric := NewGauge(&GaugeOpts{
488+
Name: "not_reset_metric",
489+
Help: "this metric cannot be reset",
490+
})
491+
492+
registry.MustRegister(resettableMetric)
493+
registry.MustRegister(nonResettableMetric)
494+
resettableMetric.WithLabelValues("one").Inc()
495+
resettableMetric.WithLabelValues("two").Inc()
496+
resettableMetric.WithLabelValues("two").Inc()
497+
nonResettableMetric.Inc()
498+
499+
nonResettableOutput := `
500+
# HELP not_reset_metric [ALPHA] this metric cannot be reset
501+
# TYPE not_reset_metric gauge
502+
not_reset_metric 1
503+
`
504+
resettableOutput := `
505+
# HELP reset_metric [ALPHA] this metric can be reset
506+
# TYPE reset_metric counter
507+
reset_metric{label="one"} 1
508+
reset_metric{label="two"} 2
509+
`
510+
if err := testutil.GatherAndCompare(registry, strings.NewReader(nonResettableOutput+resettableOutput), "reset_metric", "not_reset_metric"); err != nil {
511+
t.Fatal(err)
512+
}
513+
registry.Reset()
514+
if err := testutil.GatherAndCompare(registry, strings.NewReader(nonResettableOutput), "reset_metric", "not_reset_metric"); err != nil {
515+
t.Fatal(err)
516+
}
517+
}

0 commit comments

Comments
 (0)