bf51655a7b
Signed-off-by: Jacek J. Łakis <jacek.lakis@intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
245 lines
5.5 KiB
Go
245 lines
5.5 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package metrics
|
|
|
|
import (
|
|
"bufio"
|
|
"net"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
|
|
|
"github.com/emicklei/go-restful"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
var (
|
|
// TODO(a-robinson): Add unit tests for the handling of these metrics once
|
|
// the upstream library supports it.
|
|
requestCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "apiserver_request_count",
|
|
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"},
|
|
)
|
|
requestLatencies = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: "apiserver_request_latencies",
|
|
Help: "Response latency distribution in microseconds for each verb, resource and client.",
|
|
// Use buckets ranging from 125 ms to 8 seconds.
|
|
Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
|
|
},
|
|
[]string{"verb", "resource"},
|
|
)
|
|
requestLatenciesSummary = prometheus.NewSummaryVec(
|
|
prometheus.SummaryOpts{
|
|
Name: "apiserver_request_latencies_summary",
|
|
Help: "Response latency summary in microseconds for each verb and resource.",
|
|
// Make the sliding window of 1h.
|
|
MaxAge: time.Hour,
|
|
},
|
|
[]string{"verb", "resource"},
|
|
)
|
|
)
|
|
|
|
// Register all metrics.
|
|
func Register() {
|
|
prometheus.MustRegister(requestCounter)
|
|
prometheus.MustRegister(requestLatencies)
|
|
prometheus.MustRegister(requestLatenciesSummary)
|
|
}
|
|
|
|
func Monitor(verb, resource *string, client, contentType string, httpCode int, reqStart time.Time) {
|
|
elapsed := float64((time.Since(reqStart)) / time.Microsecond)
|
|
requestCounter.WithLabelValues(*verb, *resource, client, contentType, codeToString(httpCode)).Inc()
|
|
requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed)
|
|
requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed)
|
|
}
|
|
|
|
func Reset() {
|
|
requestCounter.Reset()
|
|
requestLatencies.Reset()
|
|
requestLatenciesSummary.Reset()
|
|
}
|
|
|
|
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
|
|
// the go-restful RouteFunction instead of a HandlerFunc
|
|
func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
|
return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
|
|
now := time.Now()
|
|
|
|
delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter}
|
|
|
|
_, cn := response.ResponseWriter.(http.CloseNotifier)
|
|
_, fl := response.ResponseWriter.(http.Flusher)
|
|
_, hj := response.ResponseWriter.(http.Hijacker)
|
|
var rw http.ResponseWriter
|
|
if cn && fl && hj {
|
|
rw = &fancyResponseWriterDelegator{delegate}
|
|
} else {
|
|
rw = delegate
|
|
}
|
|
response.ResponseWriter = rw
|
|
|
|
routeFunc(request, response)
|
|
Monitor(&verb, &resource, utilnet.GetHTTPClient(request.Request), rw.Header().Get("Content-Type"), delegate.status, now)
|
|
})
|
|
}
|
|
|
|
type responseWriterDelegator struct {
|
|
http.ResponseWriter
|
|
|
|
status int
|
|
written int64
|
|
wroteHeader bool
|
|
}
|
|
|
|
func (r *responseWriterDelegator) WriteHeader(code int) {
|
|
r.status = code
|
|
r.wroteHeader = true
|
|
r.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
|
if !r.wroteHeader {
|
|
r.WriteHeader(http.StatusOK)
|
|
}
|
|
n, err := r.ResponseWriter.Write(b)
|
|
r.written += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
type fancyResponseWriterDelegator struct {
|
|
*responseWriterDelegator
|
|
}
|
|
|
|
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
|
|
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
}
|
|
|
|
func (f *fancyResponseWriterDelegator) Flush() {
|
|
f.ResponseWriter.(http.Flusher).Flush()
|
|
}
|
|
|
|
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
return f.ResponseWriter.(http.Hijacker).Hijack()
|
|
}
|
|
|
|
// Small optimization over Itoa
|
|
func codeToString(s int) string {
|
|
switch s {
|
|
case 100:
|
|
return "100"
|
|
case 101:
|
|
return "101"
|
|
|
|
case 200:
|
|
return "200"
|
|
case 201:
|
|
return "201"
|
|
case 202:
|
|
return "202"
|
|
case 203:
|
|
return "203"
|
|
case 204:
|
|
return "204"
|
|
case 205:
|
|
return "205"
|
|
case 206:
|
|
return "206"
|
|
|
|
case 300:
|
|
return "300"
|
|
case 301:
|
|
return "301"
|
|
case 302:
|
|
return "302"
|
|
case 304:
|
|
return "304"
|
|
case 305:
|
|
return "305"
|
|
case 307:
|
|
return "307"
|
|
|
|
case 400:
|
|
return "400"
|
|
case 401:
|
|
return "401"
|
|
case 402:
|
|
return "402"
|
|
case 403:
|
|
return "403"
|
|
case 404:
|
|
return "404"
|
|
case 405:
|
|
return "405"
|
|
case 406:
|
|
return "406"
|
|
case 407:
|
|
return "407"
|
|
case 408:
|
|
return "408"
|
|
case 409:
|
|
return "409"
|
|
case 410:
|
|
return "410"
|
|
case 411:
|
|
return "411"
|
|
case 412:
|
|
return "412"
|
|
case 413:
|
|
return "413"
|
|
case 414:
|
|
return "414"
|
|
case 415:
|
|
return "415"
|
|
case 416:
|
|
return "416"
|
|
case 417:
|
|
return "417"
|
|
case 418:
|
|
return "418"
|
|
|
|
case 500:
|
|
return "500"
|
|
case 501:
|
|
return "501"
|
|
case 502:
|
|
return "502"
|
|
case 503:
|
|
return "503"
|
|
case 504:
|
|
return "504"
|
|
case 505:
|
|
return "505"
|
|
|
|
case 428:
|
|
return "428"
|
|
case 429:
|
|
return "429"
|
|
case 431:
|
|
return "431"
|
|
case 511:
|
|
return "511"
|
|
|
|
default:
|
|
return strconv.Itoa(s)
|
|
}
|
|
}
|