Commit 5facb628 authored by Shyam Jeedigunta's avatar Shyam Jeedigunta

Add apiserver metric for response sizes split by namespace scope

parent d368afd8
...@@ -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