Subresources are not included in apiserver prometheus metrics

Subresources are very often completely different code paths and errors
generated on those code paths are important to distinguish.
pull/6/head
Clayton Coleman 2017-05-24 11:34:24 -04:00
parent 1153ef19ce
commit ad431c454c
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
4 changed files with 35 additions and 33 deletions

View File

@ -56,11 +56,11 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
proxyHandlerTraceID := rand.Int63() proxyHandlerTraceID := rand.Int63()
var verb string var verb string
var apiResource string var apiResource, subresource string
var httpCode int var httpCode int
reqStart := time.Now() reqStart := time.Now()
defer func() { defer func() {
metrics.Monitor(&verb, &apiResource, metrics.Monitor(&verb, &apiResource, &subresource,
net.GetHTTPClient(req), net.GetHTTPClient(req),
w.Header().Get("Content-Type"), w.Header().Get("Content-Type"),
httpCode, reqStart) httpCode, reqStart)
@ -85,7 +85,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
verb = requestInfo.Verb verb = requestInfo.Verb
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts namespace, resource, subresource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Subresource, requestInfo.Parts
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
if len(parts) < 2 { if len(parts) < 2 {

View File

@ -572,7 +572,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
} else { } else {
handler = restfulGetResource(getter, exporter, reqScope) handler = restfulGetResource(getter, exporter, reqScope)
} }
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, handler)
doc := "read the specified " + kind doc := "read the specified " + kind
if hasSubresource { if hasSubresource {
doc = "read " + subresource + " of the specified " + kind doc = "read " + subresource + " of the specified " + kind
@ -601,7 +601,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "list " + subresource + " of objects of kind " + kind doc = "list " + subresource + " of objects of kind " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
route := ws.GET(action.Path).To(handler). route := ws.GET(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -633,7 +633,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "replace " + subresource + " of the specified " + kind doc = "replace " + subresource + " of the specified " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulUpdateResource(updater, reqScope, a.group.Typer, admit)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulUpdateResource(updater, reqScope, a.group.Typer, admit))
route := ws.PUT(action.Path).To(handler). route := ws.PUT(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -649,7 +649,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "partially update " + subresource + " of the specified " + kind doc = "partially update " + subresource + " of the specified " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulPatchResource(patcher, reqScope, admit, mapping.ObjectConvertor)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulPatchResource(patcher, reqScope, admit, mapping.ObjectConvertor))
route := ws.PATCH(action.Path).To(handler). route := ws.PATCH(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -668,7 +668,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
} else { } else {
handler = restfulCreateResource(creater, reqScope, a.group.Typer, admit) handler = restfulCreateResource(creater, reqScope, a.group.Typer, admit)
} }
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, handler)
article := getArticleForNoun(kind, " ") article := getArticleForNoun(kind, " ")
doc := "create" + article + kind doc := "create" + article + kind
if hasSubresource { if hasSubresource {
@ -690,7 +690,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "delete " + subresource + " of" + article + kind doc = "delete " + subresource + " of" + article + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
route := ws.DELETE(action.Path).To(handler). route := ws.DELETE(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -711,7 +711,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "delete collection of " + subresource + " of a " + kind doc = "delete collection of " + subresource + " of a " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
route := ws.DELETE(action.Path).To(handler). route := ws.DELETE(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -730,7 +730,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "watch changes to " + subresource + " of an object of kind " + kind doc = "watch changes to " + subresource + " of an object of kind " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
route := ws.GET(action.Path).To(handler). route := ws.GET(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -749,7 +749,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "watch individual changes to a list of " + subresource + " of " + kind doc = "watch individual changes to a list of " + subresource + " of " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
route := ws.GET(action.Path).To(handler). route := ws.GET(action.Path).To(handler).
Doc(doc). Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
@ -780,7 +780,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if hasSubresource { if hasSubresource {
doc = "connect " + method + " requests to " + subresource + " of " + kind doc = "connect " + method + " requests to " + subresource + " of " + kind
} }
handler := metrics.InstrumentRouteFunc(action.Verb, resource, restfulConnectResource(connecter, reqScope, admit, path, hasSubresource)) handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulConnectResource(connecter, reqScope, admit, path, hasSubresource))
route := ws.Method(method).Path(action.Path). route := ws.Method(method).Path(action.Path).
To(handler). To(handler).
Doc(doc). Doc(doc).
@ -841,7 +841,7 @@ func buildProxyRoute(ws *restful.WebService, method string, prefix string, path
if hasSubresource { if hasSubresource {
doc = "proxy " + method + " requests to " + subresource + " of " + kind doc = "proxy " + method + " requests to " + subresource + " of " + kind
} }
handler := metrics.InstrumentRouteFunc("PROXY", resource, routeFunction(proxyHandler)) handler := metrics.InstrumentRouteFunc("PROXY", resource, subresource, routeFunction(proxyHandler))
proxyRoute := ws.Method(method).Path(path).To(handler). proxyRoute := ws.Method(method).Path(path).To(handler).
Doc(doc). Doc(doc).
Operation("proxy" + strings.Title(method) + namespaced + kind + strings.Title(subresource) + operationSuffix). Operation("proxy" + strings.Title(method) + namespaced + kind + strings.Title(subresource) + operationSuffix).

View File

@ -39,7 +39,7 @@ var (
Name: "apiserver_request_count", Name: "apiserver_request_count",
Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.", Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
}, },
[]string{"verb", "resource", "client", "contentType", "code"}, []string{"verb", "resource", "subresource", "client", "contentType", "code"},
) )
requestLatencies = prometheus.NewHistogramVec( requestLatencies = prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
@ -48,7 +48,7 @@ var (
// 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),
}, },
[]string{"verb", "resource"}, []string{"verb", "resource", "subresource"},
) )
requestLatenciesSummary = prometheus.NewSummaryVec( requestLatenciesSummary = prometheus.NewSummaryVec(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
@ -57,7 +57,7 @@ var (
// Make the sliding window of 1h. // Make the sliding window of 1h.
MaxAge: time.Hour, MaxAge: time.Hour,
}, },
[]string{"verb", "resource"}, []string{"verb", "resource", "subresource"},
) )
kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`) kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
) )
@ -69,11 +69,11 @@ func Register() {
prometheus.MustRegister(requestLatenciesSummary) prometheus.MustRegister(requestLatenciesSummary)
} }
func Monitor(verb, resource *string, client, contentType string, httpCode int, reqStart time.Time) { func Monitor(verb, resource, subresource *string, client, contentType string, httpCode int, reqStart time.Time) {
elapsed := float64((time.Since(reqStart)) / time.Microsecond) elapsed := float64((time.Since(reqStart)) / time.Microsecond)
requestCounter.WithLabelValues(*verb, *resource, client, contentType, codeToString(httpCode)).Inc() requestCounter.WithLabelValues(*verb, *resource, *subresource, client, contentType, codeToString(httpCode)).Inc()
requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed) requestLatencies.WithLabelValues(*verb, *resource, *subresource).Observe(elapsed)
requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed) requestLatenciesSummary.WithLabelValues(*verb, *resource, *subresource).Observe(elapsed)
} }
func Reset() { func Reset() {
@ -84,7 +84,7 @@ func 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 string, routeFunc restful.RouteFunction) restful.RouteFunction { func InstrumentRouteFunc(verb, resource, subresource 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()
@ -107,7 +107,7 @@ func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction)
if verb == "LIST" && strings.ToLower(request.QueryParameter("watch")) == "true" { if verb == "LIST" && strings.ToLower(request.QueryParameter("watch")) == "true" {
reportedVerb = "WATCH" reportedVerb = "WATCH"
} }
Monitor(&reportedVerb, &resource, cleanUserAgent(utilnet.GetHTTPClient(request.Request)), rw.Header().Get("Content-Type"), delegate.status, now) Monitor(&reportedVerb, &resource, &subresource, cleanUserAgent(utilnet.GetHTTPClient(request.Request)), rw.Header().Get("Content-Type"), delegate.status, now)
}) })
} }

View File

@ -188,10 +188,11 @@ type SaturationTime struct {
} }
type APICall struct { type APICall struct {
Resource string `json:"resource"` Resource string `json:"resource"`
Verb string `json:"verb"` Subresource string `json:"subresource"`
Latency LatencyMetric `json:"latency"` Verb string `json:"verb"`
Count int `json:"count"` Latency LatencyMetric `json:"latency"`
Count int `json:"count"`
} }
type APIResponsiveness struct { type APIResponsiveness struct {
@ -221,14 +222,14 @@ func (a *APIResponsiveness) Less(i, j int) bool {
// Set request latency for a particular quantile in the APICall metric entry (creating one if necessary). // Set request latency for a particular quantile in the APICall metric entry (creating one if necessary).
// 0 <= quantile <=1 (e.g. 0.95 is 95%tile, 0.5 is median) // 0 <= quantile <=1 (e.g. 0.95 is 95%tile, 0.5 is median)
// Only 0.5, 0.9 and 0.99 quantiles are supported. // Only 0.5, 0.9 and 0.99 quantiles are supported.
func (a *APIResponsiveness) addMetricRequestLatency(resource, verb string, quantile float64, latency time.Duration) { func (a *APIResponsiveness) addMetricRequestLatency(resource, subresource, verb string, quantile float64, latency time.Duration) {
for i, apicall := range a.APICalls { for i, apicall := range a.APICalls {
if apicall.Resource == resource && apicall.Verb == verb { if apicall.Resource == resource && apicall.Verb == verb {
a.APICalls[i] = setQuantileAPICall(apicall, quantile, latency) a.APICalls[i] = setQuantileAPICall(apicall, quantile, latency)
return return
} }
} }
apicall := setQuantileAPICall(APICall{Resource: resource, Verb: verb}, quantile, latency) apicall := setQuantileAPICall(APICall{Resource: resource, Subresource: subresource, Verb: verb}, quantile, latency)
a.APICalls = append(a.APICalls, apicall) a.APICalls = append(a.APICalls, apicall)
} }
@ -252,14 +253,14 @@ func setQuantile(metric *LatencyMetric, quantile float64, latency time.Duration)
} }
// Add request count to the APICall metric entry (creating one if necessary). // Add request count to the APICall metric entry (creating one if necessary).
func (a *APIResponsiveness) addMetricRequestCount(resource, verb string, count int) { func (a *APIResponsiveness) addMetricRequestCount(resource, subresource, verb string, count int) {
for i, apicall := range a.APICalls { for i, apicall := range a.APICalls {
if apicall.Resource == resource && apicall.Verb == verb { if apicall.Resource == resource && apicall.Verb == verb {
a.APICalls[i].Count += count a.APICalls[i].Count += count
return return
} }
} }
apicall := APICall{Resource: resource, Verb: verb, Count: count} apicall := APICall{Resource: resource, Subresource: subresource, Verb: verb, Count: count}
a.APICalls = append(a.APICalls, apicall) a.APICalls = append(a.APICalls, apicall)
} }
@ -290,6 +291,7 @@ func readLatencyMetrics(c clientset.Interface) (*APIResponsiveness, error) {
} }
resource := string(sample.Metric["resource"]) resource := string(sample.Metric["resource"])
subresource := string(sample.Metric["subresource"])
verb := string(sample.Metric["verb"]) verb := string(sample.Metric["verb"])
if ignoredResources.Has(resource) || ignoredVerbs.Has(verb) { if ignoredResources.Has(resource) || ignoredVerbs.Has(verb) {
continue continue
@ -302,10 +304,10 @@ func readLatencyMetrics(c clientset.Interface) (*APIResponsiveness, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
a.addMetricRequestLatency(resource, verb, quantile, time.Duration(int64(latency))*time.Microsecond) a.addMetricRequestLatency(resource, subresource, verb, quantile, time.Duration(int64(latency))*time.Microsecond)
case "apiserver_request_count": case "apiserver_request_count":
count := sample.Value count := sample.Value
a.addMetricRequestCount(resource, verb, int(count)) a.addMetricRequestCount(resource, subresource, verb, int(count))
} }
} }