Commit 3cd5bece authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #49117 from shyamjvs/add-size-metrics-to-apiserver

Automatic merge from submit-queue Add apiserver metric for response sizes Fixes https://github.com/kubernetes/kubernetes/issues/47728 This should help us understand GET/LIST call latencies better. It'll also help catch differences in object sizes across kubemark and real cluster. I'm labelling the metrics by namespace (hoping that there won't be toooo many of them). /cc @smarterclayton @gmarek cc @kubernetes/sig-scalability-misc @kubernetes/sig-api-machinery-misc @lavalamp @wojtek-t
parents 4a73f19a 5facb628
...@@ -53,18 +53,22 @@ type ProxyHandler struct { ...@@ -53,18 +53,22 @@ type ProxyHandler struct {
} }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
reqStart := time.Now()
proxyHandlerTraceID := rand.Int63() proxyHandlerTraceID := rand.Int63()
var verb string var verb, apiResource, subresource, scope string
var apiResource, subresource string
var httpCode int var httpCode int
reqStart := time.Now()
defer func() { defer func() {
responseLength := 0
if rw, ok := w.(*metrics.ResponseWriterDelegator); ok {
responseLength = rw.ContentLength()
}
metrics.Monitor( metrics.Monitor(
verb, apiResource, subresource, verb, apiResource, subresource, scope,
net.GetHTTPClient(req), net.GetHTTPClient(req),
w.Header().Get("Content-Type"), w.Header().Get("Content-Type"),
httpCode, reqStart, httpCode, responseLength, reqStart,
) )
}() }()
...@@ -88,6 +92,10 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -88,6 +92,10 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
verb = requestInfo.Verb verb = requestInfo.Verb
namespace, resource, subresource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Subresource, requestInfo.Parts namespace, resource, subresource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Subresource, requestInfo.Parts
scope = "cluster"
if namespace != "" {
scope = "namespace"
}
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
if len(parts) < 2 { if len(parts) < 2 {
......
...@@ -18,6 +18,7 @@ package metrics ...@@ -18,6 +18,7 @@ package metrics
import ( import (
"bufio" "bufio"
//"fmt"
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
...@@ -26,6 +27,7 @@ import ( ...@@ -26,6 +27,7 @@ import (
"time" "time"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
//utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
...@@ -44,7 +46,7 @@ var ( ...@@ -44,7 +46,7 @@ var (
requestLatencies = prometheus.NewHistogramVec( requestLatencies = prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "apiserver_request_latencies", Name: "apiserver_request_latencies",
Help: "Response latency distribution in microseconds for each verb, resource and client.", Help: "Response latency distribution in microseconds for each verb, resource and subresource.",
// Use buckets ranging from 125 ms to 8 seconds. // Use buckets ranging from 125 ms to 8 seconds.
Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7), Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
}, },
...@@ -53,12 +55,21 @@ var ( ...@@ -53,12 +55,21 @@ var (
requestLatenciesSummary = prometheus.NewSummaryVec( requestLatenciesSummary = prometheus.NewSummaryVec(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
Name: "apiserver_request_latencies_summary", Name: "apiserver_request_latencies_summary",
Help: "Response latency summary in microseconds for each verb and resource.", Help: "Response latency summary in microseconds for each verb, resource and subresource.",
// Make the sliding window of 1h. // Make the sliding window of 1h.
MaxAge: time.Hour, MaxAge: time.Hour,
}, },
[]string{"verb", "resource", "subresource"}, []string{"verb", "resource", "subresource"},
) )
responseSizes = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "apiserver_response_sizes",
Help: "Response size distribution in bytes for each verb, resource, subresource and scope (namespace/cluster).",
// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
Buckets: prometheus.ExponentialBuckets(1000, 10.0, 7),
},
[]string{"verb", "resource", "subresource", "scope"},
)
kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`) kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
) )
...@@ -67,20 +78,25 @@ func Register() { ...@@ -67,20 +78,25 @@ func Register() {
prometheus.MustRegister(requestCounter) prometheus.MustRegister(requestCounter)
prometheus.MustRegister(requestLatencies) prometheus.MustRegister(requestLatencies)
prometheus.MustRegister(requestLatenciesSummary) prometheus.MustRegister(requestLatenciesSummary)
prometheus.MustRegister(responseSizes)
} }
// Monitor records a request to the apiserver endpoints that follow the Kubernetes API conventions. verb must be // Monitor records a request to the apiserver endpoints that follow the Kubernetes API conventions. verb must be
// uppercase to be backwards compatible with existing monitoring tooling. // uppercase to be backwards compatible with existing monitoring tooling.
func Monitor(verb, resource, subresource, client, contentType string, httpCode int, reqStart time.Time) { func Monitor(verb, resource, subresource, scope, client, contentType string, httpCode, respSize int, reqStart time.Time) {
elapsed := float64((time.Since(reqStart)) / time.Microsecond) elapsed := float64((time.Since(reqStart)) / time.Microsecond)
requestCounter.WithLabelValues(verb, resource, subresource, client, contentType, codeToString(httpCode)).Inc() requestCounter.WithLabelValues(verb, resource, subresource, client, contentType, codeToString(httpCode)).Inc()
requestLatencies.WithLabelValues(verb, resource, subresource).Observe(elapsed) requestLatencies.WithLabelValues(verb, resource, subresource).Observe(elapsed)
requestLatenciesSummary.WithLabelValues(verb, resource, subresource).Observe(elapsed) requestLatenciesSummary.WithLabelValues(verb, resource, subresource).Observe(elapsed)
// We are only interested in response sizes of read requests.
if verb == "GET" || verb == "LIST" {
responseSizes.WithLabelValues(verb, resource, subresource, scope).Observe(float64(respSize))
}
} }
// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record // MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling. // a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
func MonitorRequest(request *http.Request, verb, resource, subresource, contentType string, httpCode int, reqStart time.Time) { func MonitorRequest(request *http.Request, verb, resource, subresource, scope, contentType string, httpCode, respSize int, reqStart time.Time) {
reportedVerb := verb reportedVerb := verb
if verb == "LIST" { if verb == "LIST" {
// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool // see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
...@@ -90,23 +106,25 @@ func MonitorRequest(request *http.Request, verb, resource, subresource, contentT ...@@ -90,23 +106,25 @@ func MonitorRequest(request *http.Request, verb, resource, subresource, contentT
} }
} }
} }
client := cleanUserAgent(utilnet.GetHTTPClient(request)) client := cleanUserAgent(utilnet.GetHTTPClient(request))
Monitor(reportedVerb, resource, subresource, client, contentType, httpCode, reqStart) Monitor(reportedVerb, resource, subresource, scope, client, contentType, httpCode, respSize, reqStart)
} }
func Reset() { func Reset() {
requestCounter.Reset() requestCounter.Reset()
requestLatencies.Reset() requestLatencies.Reset()
requestLatenciesSummary.Reset() requestLatenciesSummary.Reset()
responseSizes.Reset()
} }
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps // InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc // the go-restful RouteFunction instead of a HandlerFunc
func InstrumentRouteFunc(verb, resource, subresource string, routeFunc restful.RouteFunction) restful.RouteFunction { func InstrumentRouteFunc(verb, resource, subresource, scope string, routeFunc restful.RouteFunction) restful.RouteFunction {
return restful.RouteFunction(func(request *restful.Request, response *restful.Response) { return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
now := time.Now() now := time.Now()
delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter} delegate := &ResponseWriterDelegator{ResponseWriter: response.ResponseWriter}
_, cn := response.ResponseWriter.(http.CloseNotifier) _, cn := response.ResponseWriter.(http.CloseNotifier)
_, fl := response.ResponseWriter.(http.Flusher) _, fl := response.ResponseWriter.(http.Flusher)
...@@ -121,7 +139,7 @@ func InstrumentRouteFunc(verb, resource, subresource string, routeFunc restful.R ...@@ -121,7 +139,7 @@ func InstrumentRouteFunc(verb, resource, subresource string, routeFunc restful.R
routeFunc(request, response) routeFunc(request, response)
MonitorRequest(request.Request, verb, resource, subresource, rw.Header().Get("Content-Type"), delegate.status, now) MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), now)
}) })
} }
...@@ -135,7 +153,8 @@ func cleanUserAgent(ua string) string { ...@@ -135,7 +153,8 @@ func cleanUserAgent(ua string) string {
return ua return ua
} }
type responseWriterDelegator struct { // ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
type ResponseWriterDelegator struct {
http.ResponseWriter http.ResponseWriter
status int status int
...@@ -143,13 +162,13 @@ type responseWriterDelegator struct { ...@@ -143,13 +162,13 @@ type responseWriterDelegator struct {
wroteHeader bool wroteHeader bool
} }
func (r *responseWriterDelegator) WriteHeader(code int) { func (r *ResponseWriterDelegator) WriteHeader(code int) {
r.status = code r.status = code
r.wroteHeader = true r.wroteHeader = true
r.ResponseWriter.WriteHeader(code) r.ResponseWriter.WriteHeader(code)
} }
func (r *responseWriterDelegator) Write(b []byte) (int, error) { func (r *ResponseWriterDelegator) Write(b []byte) (int, error) {
if !r.wroteHeader { if !r.wroteHeader {
r.WriteHeader(http.StatusOK) r.WriteHeader(http.StatusOK)
} }
...@@ -158,8 +177,16 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) { ...@@ -158,8 +177,16 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
return n, err return n, err
} }
func (r *ResponseWriterDelegator) Status() int {
return r.status
}
func (r *ResponseWriterDelegator) ContentLength() int {
return int(r.written)
}
type fancyResponseWriterDelegator struct { type fancyResponseWriterDelegator struct {
*responseWriterDelegator *ResponseWriterDelegator
} }
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
......
...@@ -109,7 +109,11 @@ func WithMaxInFlightLimit( ...@@ -109,7 +109,11 @@ func WithMaxInFlightLimit(
} }
} }
} }
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", errors.StatusTooManyRequests, time.Now()) scope := "cluster"
if requestInfo.Namespace != "" {
scope = "namespace"
}
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, errors.StatusTooManyRequests, 0, time.Now())
tooManyRequests(r, w) tooManyRequests(r, w)
} }
} }
......
...@@ -60,7 +60,11 @@ func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMa ...@@ -60,7 +60,11 @@ func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMa
} }
now := time.Now() now := time.Now()
metricFn := func() { metricFn := func() {
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", http.StatusInternalServerError, now) scope := "cluster"
if requestInfo.Namespace != "" {
scope = "namespace"
}
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, http.StatusInternalServerError, 0, now)
} }
return time.After(globalTimeout), metricFn, apierrors.NewServerTimeout(schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource}, requestInfo.Verb, 0) return time.After(globalTimeout), metricFn, apierrors.NewServerTimeout(schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource}, requestInfo.Verb, 0)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment