Skip to content

Commit 1b71fce

Browse files
committed
🌱metrics: Expose client-go metrics in metrics server
Problem: Currently the only client-side metric enabled in the controller processes is `rest_client_requests_total`, which is pretty limited. This patch starts exporting more client-side metrics offered by client-go in the controller process by default by copying what `component-base` does for core Kubernetes components.
1 parent 6ad5c1d commit 1b71fce

File tree

3 files changed

+478
-12
lines changed

3 files changed

+478
-12
lines changed

pkg/metrics/client_go_adapter.go

+165-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2018 The Kubernetes Authors.
2+
Copyright 2025 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package metrics
1818

1919
import (
2020
"context"
21+
"net/url"
22+
"time"
2123

2224
"github.com/prometheus/client_golang/prometheus"
2325
clientmetrics "k8s.io/client-go/tools/metrics"
@@ -28,7 +30,59 @@ import (
2830
// from Kubernetes so that we match the core controllers.
2931

3032
var (
31-
// client metrics.
33+
// client metrics from https://github.com/kubernetes/kubernetes/blob/v1.33.0/staging/src/k8s.io/component-base/metrics/prometheus/restclient/metrics.go
34+
// except for rest_client_exec_plugin_* metrics which controllers wouldn't use
35+
36+
// requestLatency is a Prometheus Histogram metric type partitioned by
37+
// "verb", and "host" labels. It is used for the rest client latency metrics.
38+
requestLatency = prometheus.NewHistogramVec(
39+
prometheus.HistogramOpts{
40+
Name: "rest_client_request_duration_seconds",
41+
Help: "Request latency in seconds. Broken down by verb, and host.",
42+
Buckets: []float64{0.005, 0.025, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 15.0, 30.0, 60.0},
43+
},
44+
[]string{"verb", "host"},
45+
)
46+
47+
// resolverLatency is a Prometheus Histogram metric type partitioned by
48+
// "host" labels. It is used for the rest client DNS resolver latency metrics.
49+
resolverLatency = prometheus.NewHistogramVec(
50+
prometheus.HistogramOpts{
51+
Name: "rest_client_dns_resolution_duration_seconds",
52+
Help: "DNS resolver latency in seconds. Broken down by host.",
53+
Buckets: []float64{0.005, 0.025, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 15.0, 30.0},
54+
},
55+
[]string{"host"},
56+
)
57+
58+
requestSize = prometheus.NewHistogramVec(
59+
prometheus.HistogramOpts{
60+
Name: "rest_client_request_size_bytes",
61+
Help: "Request size in bytes. Broken down by verb and host.",
62+
// 64 bytes to 16MB
63+
Buckets: []float64{64, 256, 512, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216},
64+
},
65+
[]string{"verb", "host"},
66+
)
67+
68+
responseSize = prometheus.NewHistogramVec(
69+
prometheus.HistogramOpts{
70+
Name: "rest_client_response_size_bytes",
71+
Help: "Response size in bytes. Broken down by verb and host.",
72+
// 64 bytes to 16MB
73+
Buckets: []float64{64, 256, 512, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216},
74+
},
75+
[]string{"verb", "host"},
76+
)
77+
78+
rateLimiterLatency = prometheus.NewHistogramVec(
79+
prometheus.HistogramOpts{
80+
Name: "rest_client_rate_limiter_duration_seconds",
81+
Help: "Client side rate limiter latency in seconds. Broken down by verb, and host.",
82+
Buckets: []float64{0.005, 0.025, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 15.0, 30.0, 60.0},
83+
},
84+
[]string{"verb", "host"},
85+
)
3286

3387
requestResult = prometheus.NewCounterVec(
3488
prometheus.CounterOpts{
@@ -37,6 +91,30 @@ var (
3791
},
3892
[]string{"code", "method", "host"},
3993
)
94+
95+
requestRetry = prometheus.NewCounterVec(
96+
prometheus.CounterOpts{
97+
Name: "rest_client_request_retries_total",
98+
Help: "Number of request retries, partitioned by status code, verb, and host.",
99+
},
100+
[]string{"code", "verb", "host"},
101+
)
102+
103+
transportCacheEntries = prometheus.NewGauge(
104+
prometheus.GaugeOpts{
105+
Name: "rest_client_transport_cache_entries",
106+
Help: "Number of transport entries in the internal cache.",
107+
},
108+
)
109+
110+
transportCacheCalls = prometheus.NewCounterVec(
111+
prometheus.CounterOpts{
112+
Name: "rest_client_transport_create_calls_total",
113+
Help: "Number of calls to get a new transport, partitioned by the result of the operation " +
114+
"hit: obtained from the cache, miss: created and added to the cache, uncacheable: created and not cached",
115+
},
116+
[]string{"result"},
117+
)
40118
)
41119

42120
func init() {
@@ -46,26 +124,101 @@ func init() {
46124
// registerClientMetrics sets up the client latency metrics from client-go.
47125
func registerClientMetrics() {
48126
// register the metrics with our registry
49-
Registry.MustRegister(requestResult)
127+
Registry.MustRegister(requestResult,
128+
requestLatency,
129+
resolverLatency,
130+
requestSize,
131+
responseSize,
132+
rateLimiterLatency,
133+
requestRetry,
134+
transportCacheEntries,
135+
transportCacheCalls,
136+
)
50137

51138
// register the metrics with client-go
52139
clientmetrics.Register(clientmetrics.RegisterOpts{
53-
RequestResult: &resultAdapter{metric: requestResult},
140+
RequestResult: &requestResultAdapter{metric: requestResult},
141+
RequestLatency: &requestLatencyAdapter{metric: requestLatency},
142+
ResolverLatency: &resolverLatencyAdapter{metric: resolverLatency},
143+
RequestSize: &requestSizeAdapter{metric: requestSize},
144+
ResponseSize: &responseSizeAdapter{metric: responseSize},
145+
RateLimiterLatency: &rateLimiterLatencyAdapter{metric: rateLimiterLatency},
146+
RequestRetry: &requestRetryAdapter{metric: requestRetry},
147+
TransportCacheEntries: &transportCacheEntriesAdapter{metric: transportCacheEntries},
148+
TransportCreateCalls: &transportCreateCallsAdapter{metric: transportCacheCalls},
54149
})
55150
}
56151

57-
// this section contains adapters, implementations, and other sundry organic, artisanally
58-
// hand-crafted syntax trees required to convince client-go that it actually wants to let
59-
// someone use its metrics.
152+
// Prometheus adapters for client-go metrics hooks.
153+
154+
type requestResultAdapter struct {
155+
metric *prometheus.CounterVec
156+
}
157+
158+
func (r *requestResultAdapter) Increment(_ context.Context, code, method, host string) {
159+
r.metric.WithLabelValues(code, method, host).Inc()
160+
}
161+
162+
type requestLatencyAdapter struct {
163+
metric *prometheus.HistogramVec
164+
}
165+
166+
func (l *requestLatencyAdapter) Observe(_ context.Context, verb string, u url.URL, latency time.Duration) {
167+
l.metric.WithLabelValues(verb, u.Host).Observe(latency.Seconds())
168+
}
169+
170+
type resolverLatencyAdapter struct {
171+
metric *prometheus.HistogramVec
172+
}
173+
174+
func (r *resolverLatencyAdapter) Observe(_ context.Context, host string, latency time.Duration) {
175+
r.metric.WithLabelValues(host).Observe(latency.Seconds())
176+
}
177+
178+
type requestSizeAdapter struct {
179+
metric *prometheus.HistogramVec
180+
}
181+
182+
func (s *requestSizeAdapter) Observe(_ context.Context, verb string, host string, size float64) {
183+
s.metric.WithLabelValues(verb, host).Observe(size)
184+
}
185+
186+
type responseSizeAdapter struct {
187+
metric *prometheus.HistogramVec
188+
}
189+
190+
func (s *responseSizeAdapter) Observe(_ context.Context, verb string, host string, size float64) {
191+
s.metric.WithLabelValues(verb, host).Observe(size)
192+
}
193+
194+
type rateLimiterLatencyAdapter struct {
195+
metric *prometheus.HistogramVec
196+
}
60197

61-
// Client metrics adapters (method #1 for client-go metrics),
62-
// copied (more-or-less directly) from k8s.io/kubernetes setup code
63-
// (which isn't anywhere in an easily-importable place).
198+
func (l *rateLimiterLatencyAdapter) Observe(_ context.Context, verb string, u url.URL, latency time.Duration) {
199+
l.metric.WithLabelValues(verb, u.Host).Observe(latency.Seconds())
200+
}
64201

65-
type resultAdapter struct {
202+
type requestRetryAdapter struct {
66203
metric *prometheus.CounterVec
67204
}
68205

69-
func (r *resultAdapter) Increment(_ context.Context, code, method, host string) {
206+
func (r *requestRetryAdapter) IncrementRetry(_ context.Context, code string, method string, host string) {
70207
r.metric.WithLabelValues(code, method, host).Inc()
71208
}
209+
210+
type transportCacheEntriesAdapter struct {
211+
metric prometheus.Gauge
212+
}
213+
214+
func (t *transportCacheEntriesAdapter) Observe(value int) {
215+
t.metric.Set(float64(value))
216+
}
217+
218+
type transportCreateCallsAdapter struct {
219+
metric *prometheus.CounterVec
220+
}
221+
222+
func (t *transportCreateCallsAdapter) Increment(result string) {
223+
t.metric.WithLabelValues(result).Inc()
224+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025 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+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestClientGoMetrics(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "client-go metric adapters Suite")
29+
}

0 commit comments

Comments
 (0)