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.
This commit is contained in:
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

@ -189,6 +189,7 @@ type SaturationTime struct {
type APICall struct { type APICall struct {
Resource string `json:"resource"` Resource string `json:"resource"`
Subresource string `json:"subresource"`
Verb string `json:"verb"` Verb string `json:"verb"`
Latency LatencyMetric `json:"latency"` Latency LatencyMetric `json:"latency"`
Count int `json:"count"` Count int `json:"count"`
@ -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))
} }
} }