Merge pull request #52302 from smarterclayton/simplify_metrics_registration
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.. Collapse all metrics handlers into common code Remove the MonitorRequest method and replace with a method that takes request.RequestInfo, which is our default way to talk about API objects. Preserves existing semantics for calls. Not for 1.8, but fixes the ugliness and code duplication in #52237
This commit is contained in:
commit
30f015a6fc
@ -54,22 +54,18 @@ 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()
|
reqStart := time.Now()
|
||||||
proxyHandlerTraceID := rand.Int63()
|
|
||||||
|
|
||||||
var verb, apiResource, subresource, scope string
|
|
||||||
var httpCode int
|
var httpCode int
|
||||||
|
var requestInfo *request.RequestInfo
|
||||||
defer func() {
|
defer func() {
|
||||||
responseLength := 0
|
responseLength := 0
|
||||||
if rw, ok := w.(*metrics.ResponseWriterDelegator); ok {
|
if rw, ok := w.(*metrics.ResponseWriterDelegator); ok {
|
||||||
responseLength = rw.ContentLength()
|
responseLength = rw.ContentLength()
|
||||||
|
if httpCode == 0 {
|
||||||
|
httpCode = rw.Status()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
metrics.Monitor(
|
metrics.Record(req, requestInfo, w.Header().Get("Content-Type"), httpCode, responseLength, time.Now().Sub(reqStart))
|
||||||
verb, apiResource, subresource, scope,
|
|
||||||
net.GetHTTPClient(req),
|
|
||||||
w.Header().Get("Content-Type"),
|
|
||||||
httpCode, responseLength, reqStart,
|
|
||||||
)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx, ok := r.Mapper.Get(req)
|
ctx, ok := r.Mapper.Get(req)
|
||||||
@ -79,32 +75,32 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
requestInfo, ok = request.RequestInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
responsewriters.InternalError(w, req, errors.New("Error getting RequestInfo from context"))
|
responsewriters.InternalError(w, req, errors.New("Error getting RequestInfo from context"))
|
||||||
httpCode = http.StatusInternalServerError
|
httpCode = http.StatusInternalServerError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||||
|
httpCode = r.serveHTTP(w, req, ctx, requestInfo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveHTTP performs proxy handling and returns the status code of the operation.
|
||||||
|
func (r *ProxyHandler) serveHTTP(w http.ResponseWriter, req *http.Request, ctx request.Context, requestInfo *request.RequestInfo) int {
|
||||||
|
proxyHandlerTraceID := rand.Int63()
|
||||||
|
|
||||||
if !requestInfo.IsResourceRequest {
|
if !requestInfo.IsResourceRequest {
|
||||||
responsewriters.NotFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
return http.StatusNotFound
|
||||||
return
|
|
||||||
}
|
|
||||||
verb = requestInfo.Verb
|
|
||||||
namespace, resource, subresource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Subresource, requestInfo.Parts
|
|
||||||
scope = "cluster"
|
|
||||||
if namespace != "" {
|
|
||||||
scope = "namespace"
|
|
||||||
}
|
|
||||||
if requestInfo.Name != "" {
|
|
||||||
scope = "resource"
|
|
||||||
}
|
}
|
||||||
|
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||||
|
|
||||||
ctx = request.WithNamespace(ctx, namespace)
|
ctx = request.WithNamespace(ctx, namespace)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
responsewriters.NotFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
return http.StatusNotFound
|
||||||
return
|
|
||||||
}
|
}
|
||||||
id := parts[1]
|
id := parts[1]
|
||||||
remainder := ""
|
remainder := ""
|
||||||
@ -122,31 +118,26 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
||||||
responsewriters.NotFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
return http.StatusNotFound
|
||||||
return
|
|
||||||
}
|
}
|
||||||
apiResource = resource
|
|
||||||
|
|
||||||
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
||||||
|
|
||||||
redirector, ok := storage.(rest.Redirector)
|
redirector, ok := storage.(rest.Redirector)
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||||
httpCode = responsewriters.ErrorNegotiated(ctx, apierrors.NewMethodNotSupported(schema.GroupResource{Resource: resource}, "proxy"), r.Serializer, gv, w, req)
|
return responsewriters.ErrorNegotiated(ctx, apierrors.NewMethodNotSupported(schema.GroupResource{Resource: resource}, "proxy"), r.Serializer, gv, w, req)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
|
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
|
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
|
||||||
httpCode = responsewriters.ErrorNegotiated(ctx, err, r.Serializer, gv, w, req)
|
return responsewriters.ErrorNegotiated(ctx, err, r.Serializer, gv, w, req)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if location == nil {
|
if location == nil {
|
||||||
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
||||||
responsewriters.NotFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
return http.StatusNotFound
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if roundTripper != nil {
|
if roundTripper != nil {
|
||||||
@ -179,7 +170,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
// https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
|
// https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
|
||||||
// That proxy needs to be modified to support multiple backends, not just 1.
|
// That proxy needs to be modified to support multiple backends, not just 1.
|
||||||
if r.tryUpgrade(ctx, w, req, newReq, location, roundTripper, gv) {
|
if r.tryUpgrade(ctx, w, req, newReq, location, roundTripper, gv) {
|
||||||
return
|
return http.StatusSwitchingProtocols
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/"
|
// Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/"
|
||||||
@ -192,7 +183,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
w.Header().Set("Location", req.URL.Path+"/"+queryPart)
|
w.Header().Set("Location", req.URL.Path+"/"+queryPart)
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
return
|
return http.StatusMovedPermanently
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@ -224,6 +215,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
proxy.Transport = roundTripper
|
proxy.Transport = roundTripper
|
||||||
proxy.FlushInterval = 200 * time.Millisecond
|
proxy.FlushInterval = 200 * time.Millisecond
|
||||||
proxy.ServeHTTP(w, newReq)
|
proxy.ServeHTTP(w, newReq)
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryUpgrade returns true if the request was handled.
|
// tryUpgrade returns true if the request was handled.
|
||||||
|
@ -42,6 +42,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/util/flushwriter"
|
"k8s.io/apiserver/pkg/util/flushwriter"
|
||||||
@ -42,7 +43,10 @@ import (
|
|||||||
func WriteObject(ctx request.Context, statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
func WriteObject(ctx request.Context, statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||||
stream, ok := object.(rest.ResourceStreamer)
|
stream, ok := object.(rest.ResourceStreamer)
|
||||||
if ok {
|
if ok {
|
||||||
StreamObject(ctx, statusCode, gv, s, stream, w, req)
|
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||||
|
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||||
|
StreamObject(ctx, statusCode, gv, s, stream, w, req)
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteObjectNegotiated(ctx, s, gv, w, req, statusCode, object)
|
WriteObjectNegotiated(ctx, s, gv, w, req, statusCode, object)
|
||||||
|
@ -46,6 +46,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||||
@ -261,12 +262,15 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w})
|
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||||
if err != nil {
|
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||||
scope.err(err, w, req)
|
handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w})
|
||||||
return
|
if err != nil {
|
||||||
}
|
scope.err(err, w, req)
|
||||||
handler.ServeHTTP(w, req)
|
return
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +370,10 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||||||
scope.err(err, w, req)
|
scope.err(err, w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serveWatch(watcher, scope, req, w, timeout)
|
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||||
|
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||||
|
serveWatch(watcher, scope, req, w, timeout)
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ go_library(
|
|||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
||||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +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"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -43,6 +43,13 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"verb", "resource", "subresource", "scope", "client", "contentType", "code"},
|
[]string{"verb", "resource", "subresource", "scope", "client", "contentType", "code"},
|
||||||
)
|
)
|
||||||
|
longRunningRequestGauge = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "apiserver_longrunning_gauge",
|
||||||
|
Help: "Gauge of all active long-running apiserver requests broken out by verb, API resource, and scope. Not all requests are tracked this way.",
|
||||||
|
},
|
||||||
|
[]string{"verb", "resource", "subresource", "scope"},
|
||||||
|
)
|
||||||
requestLatencies = prometheus.NewHistogramVec(
|
requestLatencies = prometheus.NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "apiserver_request_latencies",
|
Name: "apiserver_request_latencies",
|
||||||
@ -77,43 +84,59 @@ var (
|
|||||||
// Register all metrics.
|
// Register all metrics.
|
||||||
func Register() {
|
func Register() {
|
||||||
prometheus.MustRegister(requestCounter)
|
prometheus.MustRegister(requestCounter)
|
||||||
|
prometheus.MustRegister(longRunningRequestGauge)
|
||||||
prometheus.MustRegister(requestLatencies)
|
prometheus.MustRegister(requestLatencies)
|
||||||
prometheus.MustRegister(requestLatenciesSummary)
|
prometheus.MustRegister(requestLatenciesSummary)
|
||||||
prometheus.MustRegister(responseSizes)
|
prometheus.MustRegister(responseSizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitor records a request to the apiserver endpoints that follow the Kubernetes API conventions. verb must be
|
// Record records a single request to the standard metrics endpoints. For use by handlers that perform their own
|
||||||
// uppercase to be backwards compatible with existing monitoring tooling.
|
// processing. All API paths should use InstrumentRouteFunc implicitly. Use this instead of MonitorRequest if
|
||||||
func Monitor(verb, resource, subresource, scope, client, contentType string, httpCode, respSize int, reqStart time.Time) {
|
// you already have a RequestInfo object.
|
||||||
elapsed := float64((time.Since(reqStart)) / time.Microsecond)
|
func Record(req *http.Request, requestInfo *request.RequestInfo, contentType string, code int, responseSizeInBytes int, elapsed time.Duration) {
|
||||||
requestCounter.WithLabelValues(verb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
|
if requestInfo == nil {
|
||||||
requestLatencies.WithLabelValues(verb, resource, subresource, scope).Observe(elapsed)
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
requestLatenciesSummary.WithLabelValues(verb, resource, subresource, scope).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))
|
|
||||||
}
|
}
|
||||||
|
scope := cleanScope(requestInfo)
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, contentType, scope, code, responseSizeInBytes, elapsed)
|
||||||
|
} else {
|
||||||
|
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, contentType, scope, code, responseSizeInBytes, elapsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordLongRunning tracks the execution of a long running request against the API server. It provides an accurate count
|
||||||
|
// of the total number of open long running requests. requestInfo may be nil if the caller is not in the normal request flow.
|
||||||
|
func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, fn func()) {
|
||||||
|
if requestInfo == nil {
|
||||||
|
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||||
|
}
|
||||||
|
var g prometheus.Gauge
|
||||||
|
scope := cleanScope(requestInfo)
|
||||||
|
reportedVerb := cleanVerb(strings.ToUpper(requestInfo.Verb), req)
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
g = longRunningRequestGauge.WithLabelValues(reportedVerb, requestInfo.Resource, requestInfo.Subresource, scope)
|
||||||
|
} else {
|
||||||
|
g = longRunningRequestGauge.WithLabelValues(reportedVerb, "", requestInfo.Path, scope)
|
||||||
|
}
|
||||||
|
g.Inc()
|
||||||
|
defer g.Dec()
|
||||||
|
fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, scope, contentType string, httpCode, respSize int, reqStart time.Time) {
|
func MonitorRequest(req *http.Request, verb, resource, subresource, scope, contentType string, httpCode, respSize int, elapsed time.Duration) {
|
||||||
reportedVerb := verb
|
reportedVerb := cleanVerb(verb, req)
|
||||||
if verb == "LIST" {
|
client := cleanUserAgent(utilnet.GetHTTPClient(req))
|
||||||
// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
|
elapsedMicroseconds := float64(elapsed / time.Microsecond)
|
||||||
if values := request.URL.Query()["watch"]; len(values) > 0 {
|
requestCounter.WithLabelValues(reportedVerb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
|
||||||
if value := strings.ToLower(values[0]); value != "0" && value != "false" {
|
requestLatencies.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
|
||||||
reportedVerb = "WATCH"
|
requestLatenciesSummary.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
|
||||||
}
|
// We are only interested in response sizes of read requests.
|
||||||
}
|
if verb == "GET" || verb == "LIST" {
|
||||||
|
responseSizes.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(float64(respSize))
|
||||||
}
|
}
|
||||||
// normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
|
|
||||||
if verb == "WATCHLIST" {
|
|
||||||
reportedVerb = "WATCH"
|
|
||||||
}
|
|
||||||
|
|
||||||
client := cleanUserAgent(utilnet.GetHTTPClient(request))
|
|
||||||
Monitor(reportedVerb, resource, subresource, scope, client, contentType, httpCode, respSize, reqStart)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Reset() {
|
func Reset() {
|
||||||
@ -144,10 +167,41 @@ func InstrumentRouteFunc(verb, resource, subresource, scope string, routeFunc re
|
|||||||
|
|
||||||
routeFunc(request, response)
|
routeFunc(request, response)
|
||||||
|
|
||||||
MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), now)
|
MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Now().Sub(now))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanScope(requestInfo *request.RequestInfo) string {
|
||||||
|
if requestInfo.Namespace != "" {
|
||||||
|
return "namespace"
|
||||||
|
}
|
||||||
|
if requestInfo.Name != "" {
|
||||||
|
return "resource"
|
||||||
|
}
|
||||||
|
if requestInfo.IsResourceRequest {
|
||||||
|
return "cluster"
|
||||||
|
}
|
||||||
|
// this is the empty scope
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanVerb(verb string, request *http.Request) string {
|
||||||
|
reportedVerb := verb
|
||||||
|
if verb == "LIST" {
|
||||||
|
// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
|
||||||
|
if values := request.URL.Query()["watch"]; len(values) > 0 {
|
||||||
|
if value := strings.ToLower(values[0]); value != "0" && value != "false" {
|
||||||
|
reportedVerb = "WATCH"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
|
||||||
|
if verb == "WATCHLIST" {
|
||||||
|
reportedVerb = "WATCH"
|
||||||
|
}
|
||||||
|
return reportedVerb
|
||||||
|
}
|
||||||
|
|
||||||
func cleanUserAgent(ua string) string {
|
func cleanUserAgent(ua string) string {
|
||||||
// We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
|
// We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
|
||||||
if strings.HasPrefix(ua, "Mozilla/") {
|
if strings.HasPrefix(ua, "Mozilla/") {
|
||||||
|
@ -19,8 +19,6 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
@ -108,18 +106,7 @@ func WithMaxInFlightLimit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope := "cluster"
|
metrics.Record(r, requestInfo, "", http.StatusTooManyRequests, 0, 0)
|
||||||
if requestInfo.Namespace != "" {
|
|
||||||
scope = "namespace"
|
|
||||||
}
|
|
||||||
if requestInfo.Name != "" {
|
|
||||||
scope = "resource"
|
|
||||||
}
|
|
||||||
if requestInfo.IsResourceRequest {
|
|
||||||
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, http.StatusTooManyRequests, 0, time.Now())
|
|
||||||
} else {
|
|
||||||
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, "", scope, http.StatusTooManyRequests, 0, time.Now())
|
|
||||||
}
|
|
||||||
tooManyRequests(r, w)
|
tooManyRequests(r, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -55,20 +54,8 @@ func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMa
|
|||||||
if longRunning(req, requestInfo) {
|
if longRunning(req, requestInfo) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
now := time.Now()
|
|
||||||
metricFn := func() {
|
metricFn := func() {
|
||||||
scope := "cluster"
|
metrics.Record(req, requestInfo, "", http.StatusGatewayTimeout, 0, 0)
|
||||||
if requestInfo.Namespace != "" {
|
|
||||||
scope = "namespace"
|
|
||||||
}
|
|
||||||
if requestInfo.Name != "" {
|
|
||||||
scope = "resource"
|
|
||||||
}
|
|
||||||
if requestInfo.IsResourceRequest {
|
|
||||||
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, http.StatusGatewayTimeout, 0, now)
|
|
||||||
} else {
|
|
||||||
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, "", scope, http.StatusGatewayTimeout, 0, now)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return time.After(timeout), metricFn, apierrors.NewTimeoutError(fmt.Sprintf("request did not complete within %s", timeout), 0)
|
return time.After(timeout), metricFn, apierrors.NewTimeoutError(fmt.Sprintf("request did not complete within %s", timeout), 0)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user