2019-01-12 04:58:27 +00:00
/ *
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"
2021-03-18 22:40:29 +00:00
"context"
2019-01-12 04:58:27 +00:00
"net"
"net/http"
2019-08-30 18:33:25 +00:00
"net/url"
2019-01-12 04:58:27 +00:00
"strconv"
"strings"
"sync"
"time"
2020-03-26 21:07:15 +00:00
restful "github.com/emicklei/go-restful"
2019-04-07 17:07:55 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
2019-08-30 18:33:25 +00:00
"k8s.io/apimachinery/pkg/types"
2019-04-07 17:07:55 +00:00
utilsets "k8s.io/apimachinery/pkg/util/sets"
2020-08-10 17:43:49 +00:00
"k8s.io/apiserver/pkg/audit"
2020-12-01 01:06:26 +00:00
"k8s.io/apiserver/pkg/authentication/user"
2019-04-07 17:07:55 +00:00
"k8s.io/apiserver/pkg/endpoints/request"
2019-08-30 18:33:25 +00:00
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-09-27 21:51:53 +00:00
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
2019-01-12 04:58:27 +00:00
)
// resettableCollector is the interface implemented by prometheus.MetricVec
// that can be used by Prometheus to collect metrics and reset their values.
type resettableCollector interface {
2019-09-27 21:51:53 +00:00
compbasemetrics . Registerable
2019-01-12 04:58:27 +00:00
Reset ( )
}
2019-04-07 17:07:55 +00:00
const (
APIServerComponent string = "apiserver"
2020-06-23 22:12:14 +00:00
OtherRequestMethod string = "other"
2019-04-07 17:07:55 +00:00
)
2019-09-27 21:51:53 +00:00
/ *
* By default , all the following metrics are defined as falling under
2021-03-18 22:40:29 +00:00
* ALPHA stability level https : //github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
2019-09-27 21:51:53 +00:00
*
* Promoting the stability level of the metric is a responsibility of the component owner , since it
* involves explicitly acknowledging support for the metric across multiple releases , in accordance with
* the metric stability policy .
* /
2019-01-12 04:58:27 +00:00
var (
2020-08-10 17:43:49 +00:00
deprecatedRequestGauge = compbasemetrics . NewGaugeVec (
& compbasemetrics . GaugeOpts {
Name : "apiserver_requested_deprecated_apis" ,
Help : "Gauge of deprecated APIs that have been requested, broken out by API group, version, resource, subresource, and removed_release." ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "group" , "version" , "resource" , "subresource" , "removed_release" } ,
)
2019-01-12 04:58:27 +00:00
// TODO(a-robinson): Add unit tests for the handling of these metrics once
// the upstream library supports it.
2019-09-27 21:51:53 +00:00
requestCounter = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_request_total" ,
2021-03-18 22:40:29 +00:00
Help : "Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code." ,
StabilityLevel : compbasemetrics . STABLE ,
2019-04-07 17:07:55 +00:00
} ,
2021-03-18 22:40:29 +00:00
[ ] string { "verb" , "dry_run" , "group" , "version" , "resource" , "subresource" , "scope" , "component" , "code" } ,
2019-01-12 04:58:27 +00:00
)
2019-09-27 21:51:53 +00:00
longRunningRequestGauge = compbasemetrics . NewGaugeVec (
& compbasemetrics . GaugeOpts {
Name : "apiserver_longrunning_gauge" ,
Help : "Gauge of all active long-running apiserver requests broken out by verb, group, version, resource, scope and component. Not all requests are tracked this way." ,
StabilityLevel : compbasemetrics . ALPHA ,
2019-01-12 04:58:27 +00:00
} ,
2019-04-07 17:07:55 +00:00
[ ] string { "verb" , "group" , "version" , "resource" , "subresource" , "scope" , "component" } ,
2019-01-12 04:58:27 +00:00
)
2019-09-27 21:51:53 +00:00
requestLatencies = compbasemetrics . NewHistogramVec (
& compbasemetrics . HistogramOpts {
2019-04-07 17:07:55 +00:00
Name : "apiserver_request_duration_seconds" ,
Help : "Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope and component." ,
// This metric is used for verifying api call latencies SLO,
// as well as tracking regressions in this aspects.
// Thus we customize buckets significantly, to empower both usecases.
Buckets : [ ] float64 { 0.05 , 0.1 , 0.15 , 0.2 , 0.25 , 0.3 , 0.35 , 0.4 , 0.45 , 0.5 , 0.6 , 0.7 , 0.8 , 0.9 , 1.0 ,
1.25 , 1.5 , 1.75 , 2.0 , 2.5 , 3.0 , 3.5 , 4.0 , 4.5 , 5 , 6 , 7 , 8 , 9 , 10 , 15 , 20 , 25 , 30 , 40 , 50 , 60 } ,
2021-03-18 22:40:29 +00:00
StabilityLevel : compbasemetrics . STABLE ,
2019-04-07 17:07:55 +00:00
} ,
[ ] string { "verb" , "dry_run" , "group" , "version" , "resource" , "subresource" , "scope" , "component" } ,
)
2019-09-27 21:51:53 +00:00
responseSizes = compbasemetrics . NewHistogramVec (
& compbasemetrics . HistogramOpts {
2019-01-12 04:58:27 +00:00
Name : "apiserver_response_sizes" ,
2019-04-07 17:07:55 +00:00
Help : "Response size distribution in bytes for each group, version, verb, resource, subresource, scope and component." ,
2019-01-12 04:58:27 +00:00
// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
2019-12-12 01:27:03 +00:00
Buckets : compbasemetrics . ExponentialBuckets ( 1000 , 10.0 , 7 ) ,
2019-09-27 21:51:53 +00:00
StabilityLevel : compbasemetrics . ALPHA ,
2019-01-12 04:58:27 +00:00
} ,
2019-04-07 17:07:55 +00:00
[ ] string { "verb" , "group" , "version" , "resource" , "subresource" , "scope" , "component" } ,
2019-01-12 04:58:27 +00:00
)
// DroppedRequests is a number of requests dropped with 'Try again later' response"
2019-09-27 21:51:53 +00:00
DroppedRequests = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_dropped_requests_total" ,
Help : "Number of requests dropped with 'Try again later' response" ,
StabilityLevel : compbasemetrics . ALPHA ,
2019-01-12 04:58:27 +00:00
} ,
2020-08-10 17:43:49 +00:00
[ ] string { "request_kind" } ,
)
// TLSHandshakeErrors is a number of requests dropped with 'TLS handshake error from' error
TLSHandshakeErrors = compbasemetrics . NewCounter (
& compbasemetrics . CounterOpts {
Name : "apiserver_tls_handshake_errors_total" ,
Help : "Number of requests dropped with 'TLS handshake error from' error" ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
2019-01-12 04:58:27 +00:00
)
// RegisteredWatchers is a number of currently registered watchers splitted by resource.
2019-09-27 21:51:53 +00:00
RegisteredWatchers = compbasemetrics . NewGaugeVec (
& compbasemetrics . GaugeOpts {
Name : "apiserver_registered_watchers" ,
Help : "Number of currently registered watchers for a given resources" ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "group" , "version" , "kind" } ,
)
WatchEvents = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_watch_events_total" ,
Help : "Number of events sent in watch clients" ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "group" , "version" , "kind" } ,
)
WatchEventsSizes = compbasemetrics . NewHistogramVec (
& compbasemetrics . HistogramOpts {
Name : "apiserver_watch_events_sizes" ,
Help : "Watch event size distribution in bytes" ,
2019-12-12 01:27:03 +00:00
Buckets : compbasemetrics . ExponentialBuckets ( 1024 , 2.0 , 8 ) , // 1K, 2K, 4K, 8K, ..., 128K.
2019-09-27 21:51:53 +00:00
StabilityLevel : compbasemetrics . ALPHA ,
2019-01-12 04:58:27 +00:00
} ,
[ ] string { "group" , "version" , "kind" } ,
)
2019-11-14 18:56:24 +00:00
// Because of volatility of the base metric this is pre-aggregated one. Instead of reporting current usage all the time
2019-01-12 04:58:27 +00:00
// it reports maximal usage during the last second.
2019-09-27 21:51:53 +00:00
currentInflightRequests = compbasemetrics . NewGaugeVec (
& compbasemetrics . GaugeOpts {
Name : "apiserver_current_inflight_requests" ,
Help : "Maximal number of currently used inflight request limit of this apiserver per request kind in last second." ,
StabilityLevel : compbasemetrics . ALPHA ,
2019-01-12 04:58:27 +00:00
} ,
2020-08-10 17:43:49 +00:00
[ ] string { "request_kind" } ,
)
currentInqueueRequests = compbasemetrics . NewGaugeVec (
& compbasemetrics . GaugeOpts {
Name : "apiserver_current_inqueue_requests" ,
Help : "Maximal number of queued requests in this apiserver per request kind in last second." ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "request_kind" } ,
2019-01-12 04:58:27 +00:00
)
2019-11-14 18:56:24 +00:00
requestTerminationsTotal = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_request_terminations_total" ,
Help : "Number of requests which apiserver terminated in self-defense." ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "verb" , "group" , "version" , "resource" , "subresource" , "scope" , "component" , "code" } ,
)
2020-12-01 01:06:26 +00:00
apiSelfRequestCounter = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_selfrequest_total" ,
Help : "Counter of apiserver self-requests broken out for each verb, API resource and subresource." ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "verb" , "resource" , "subresource" } ,
)
requestFilterDuration = compbasemetrics . NewHistogramVec (
& compbasemetrics . HistogramOpts {
Name : "apiserver_request_filter_duration_seconds" ,
Help : "Request filter latency distribution in seconds, for each filter type" ,
Buckets : [ ] float64 { 0.0001 , 0.0003 , 0.001 , 0.003 , 0.01 , 0.03 , 0.1 , 0.3 , 1.0 , 5.0 } ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "filter" } ,
)
// requestAbortsTotal is a number of aborted requests with http.ErrAbortHandler
requestAbortsTotal = compbasemetrics . NewCounterVec (
& compbasemetrics . CounterOpts {
Name : "apiserver_request_aborts_total" ,
Help : "Number of requests which apiserver aborted possibly due to a timeout, for each group, version, verb, resource, subresource and scope" ,
StabilityLevel : compbasemetrics . ALPHA ,
} ,
[ ] string { "verb" , "group" , "version" , "resource" , "subresource" , "scope" } ,
)
2019-01-12 04:58:27 +00:00
metrics = [ ] resettableCollector {
2020-08-10 17:43:49 +00:00
deprecatedRequestGauge ,
2019-01-12 04:58:27 +00:00
requestCounter ,
longRunningRequestGauge ,
requestLatencies ,
responseSizes ,
DroppedRequests ,
2020-08-10 17:43:49 +00:00
TLSHandshakeErrors ,
2019-01-12 04:58:27 +00:00
RegisteredWatchers ,
2019-09-27 21:51:53 +00:00
WatchEvents ,
WatchEventsSizes ,
2019-01-12 04:58:27 +00:00
currentInflightRequests ,
2020-08-10 17:43:49 +00:00
currentInqueueRequests ,
2019-11-14 18:56:24 +00:00
requestTerminationsTotal ,
2020-12-01 01:06:26 +00:00
apiSelfRequestCounter ,
requestFilterDuration ,
requestAbortsTotal ,
2019-01-12 04:58:27 +00:00
}
2020-06-23 22:12:14 +00:00
// these are the valid request methods which we report in our metrics. Any other request methods
// will be aggregated under 'unknown'
validRequestMethods = utilsets . NewString (
"APPLY" ,
"CONNECT" ,
"CREATE" ,
"DELETE" ,
"DELETECOLLECTION" ,
"GET" ,
"LIST" ,
"PATCH" ,
"POST" ,
"PROXY" ,
"PUT" ,
"UPDATE" ,
"WATCH" ,
"WATCHLIST" )
2019-01-12 04:58:27 +00:00
)
const (
// ReadOnlyKind is a string identifying read only request kind
ReadOnlyKind = "readOnly"
// MutatingKind is a string identifying mutating request kind
MutatingKind = "mutating"
2020-08-10 17:43:49 +00:00
// WaitingPhase is the phase value for a request waiting in a queue
WaitingPhase = "waiting"
// ExecutingPhase is the phase value for an executing request
ExecutingPhase = "executing"
)
const (
// deprecatedAnnotationKey is a key for an audit annotation set to
// "true" on requests made to deprecated API versions
deprecatedAnnotationKey = "k8s.io/deprecated"
// removedReleaseAnnotationKey is a key for an audit annotation set to
// the target removal release, in "<major>.<minor>" format,
// on requests made to deprecated API versions with a target removal release
removedReleaseAnnotationKey = "k8s.io/removed-release"
2019-01-12 04:58:27 +00:00
)
var registerMetrics sync . Once
// Register all metrics.
func Register ( ) {
registerMetrics . Do ( func ( ) {
for _ , metric := range metrics {
2019-09-27 21:51:53 +00:00
legacyregistry . MustRegister ( metric )
2019-01-12 04:58:27 +00:00
}
} )
}
// Reset all metrics.
func Reset ( ) {
for _ , metric := range metrics {
metric . Reset ( )
}
}
2020-08-10 17:43:49 +00:00
// UpdateInflightRequestMetrics reports concurrency metrics classified by
// mutating vs Readonly.
func UpdateInflightRequestMetrics ( phase string , nonmutating , mutating int ) {
for _ , kc := range [ ] struct {
kind string
count int
} { { ReadOnlyKind , nonmutating } , { MutatingKind , mutating } } {
if phase == ExecutingPhase {
currentInflightRequests . WithLabelValues ( kc . kind ) . Set ( float64 ( kc . count ) )
} else {
currentInqueueRequests . WithLabelValues ( kc . kind ) . Set ( float64 ( kc . count ) )
}
}
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
func RecordFilterLatency ( ctx context . Context , name string , elapsed time . Duration ) {
requestFilterDuration . WithContext ( ctx ) . WithLabelValues ( name ) . Observe ( elapsed . Seconds ( ) )
2020-12-01 01:06:26 +00:00
}
// RecordRequestAbort records that the request was aborted possibly due to a timeout.
func RecordRequestAbort ( req * http . Request , requestInfo * request . RequestInfo ) {
if requestInfo == nil {
requestInfo = & request . RequestInfo { Verb : req . Method , Path : req . URL . Path }
}
scope := CleanScope ( requestInfo )
2021-08-12 22:41:05 +00:00
reportedVerb := cleanVerb ( canonicalVerb ( strings . ToUpper ( req . Method ) , scope ) , "" , req )
2020-12-01 01:06:26 +00:00
resource := requestInfo . Resource
subresource := requestInfo . Subresource
group := requestInfo . APIGroup
version := requestInfo . APIVersion
2021-03-18 22:40:29 +00:00
requestAbortsTotal . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , group , version , resource , subresource , scope ) . Inc ( )
2020-12-01 01:06:26 +00:00
}
2019-11-14 18:56:24 +00:00
// RecordRequestTermination records that the request was terminated early as part of a resource
// preservation or apiserver self-defense mechanism (e.g. timeouts, maxinflight throttling,
// proxyHandler errors). RecordRequestTermination should only be called zero or one times
// per request.
func RecordRequestTermination ( req * http . Request , requestInfo * request . RequestInfo , component string , code int ) {
2019-01-12 04:58:27 +00:00
if requestInfo == nil {
requestInfo = & request . RequestInfo { Verb : req . Method , Path : req . URL . Path }
}
scope := CleanScope ( requestInfo )
2020-12-01 01:06:26 +00:00
// We don't use verb from <requestInfo>, as this may be propagated from
// InstrumentRouteFunc which is registered in installer.go with predefined
// list of verbs (different than those translated to RequestInfo).
2019-09-27 21:51:53 +00:00
// However, we need to tweak it e.g. to differentiate GET from LIST.
2021-08-12 22:41:05 +00:00
reportedVerb := cleanVerb ( canonicalVerb ( strings . ToUpper ( req . Method ) , scope ) , "" , req )
2020-12-01 01:06:26 +00:00
2019-01-12 04:58:27 +00:00
if requestInfo . IsResourceRequest {
2021-03-18 22:40:29 +00:00
requestTerminationsTotal . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , requestInfo . APIGroup , requestInfo . APIVersion , requestInfo . Resource , requestInfo . Subresource , scope , component , codeToString ( code ) ) . Inc ( )
2019-01-12 04:58:27 +00:00
} else {
2021-03-18 22:40:29 +00:00
requestTerminationsTotal . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , "" , "" , "" , requestInfo . Path , scope , component , codeToString ( code ) ) . Inc ( )
2019-01-12 04:58:27 +00:00
}
}
// 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.
2019-04-07 17:07:55 +00:00
func RecordLongRunning ( req * http . Request , requestInfo * request . RequestInfo , component string , fn func ( ) ) {
2019-01-12 04:58:27 +00:00
if requestInfo == nil {
requestInfo = & request . RequestInfo { Verb : req . Method , Path : req . URL . Path }
}
2019-09-27 21:51:53 +00:00
var g compbasemetrics . GaugeMetric
2019-01-12 04:58:27 +00:00
scope := CleanScope ( requestInfo )
2020-12-01 01:06:26 +00:00
// We don't use verb from <requestInfo>, as this may be propagated from
// InstrumentRouteFunc which is registered in installer.go with predefined
// list of verbs (different than those translated to RequestInfo).
2019-09-27 21:51:53 +00:00
// However, we need to tweak it e.g. to differentiate GET from LIST.
2021-08-12 22:41:05 +00:00
reportedVerb := cleanVerb ( canonicalVerb ( strings . ToUpper ( req . Method ) , scope ) , "" , req )
2020-12-01 01:06:26 +00:00
2019-01-12 04:58:27 +00:00
if requestInfo . IsResourceRequest {
2021-03-18 22:40:29 +00:00
g = longRunningRequestGauge . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , requestInfo . APIGroup , requestInfo . APIVersion , requestInfo . Resource , requestInfo . Subresource , scope , component )
2019-01-12 04:58:27 +00:00
} else {
2021-03-18 22:40:29 +00:00
g = longRunningRequestGauge . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , "" , "" , "" , requestInfo . Path , scope , component )
2019-01-12 04:58:27 +00:00
}
g . Inc ( )
defer g . Dec ( )
fn ( )
}
// 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.
2021-03-18 22:40:29 +00:00
func MonitorRequest ( req * http . Request , verb , group , version , resource , subresource , scope , component string , deprecated bool , removedRelease string , httpCode , respSize int , elapsed time . Duration ) {
2020-12-01 01:06:26 +00:00
// We don't use verb from <requestInfo>, as this may be propagated from
// InstrumentRouteFunc which is registered in installer.go with predefined
// list of verbs (different than those translated to RequestInfo).
// However, we need to tweak it e.g. to differentiate GET from LIST.
2021-08-12 22:41:05 +00:00
reportedVerb := cleanVerb ( canonicalVerb ( strings . ToUpper ( req . Method ) , scope ) , verb , req )
2020-12-01 01:06:26 +00:00
2019-08-30 18:33:25 +00:00
dryRun := cleanDryRun ( req . URL )
2019-04-07 17:07:55 +00:00
elapsedSeconds := elapsed . Seconds ( )
2021-03-18 22:40:29 +00:00
requestCounter . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , dryRun , group , version , resource , subresource , scope , component , codeToString ( httpCode ) ) . Inc ( )
2020-12-01 01:06:26 +00:00
// MonitorRequest happens after authentication, so we can trust the username given by the request
info , ok := request . UserFrom ( req . Context ( ) )
if ok && info . GetName ( ) == user . APIServerUser {
2021-03-18 22:40:29 +00:00
apiSelfRequestCounter . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , resource , subresource ) . Inc ( )
2020-12-01 01:06:26 +00:00
}
2020-08-10 17:43:49 +00:00
if deprecated {
2021-03-18 22:40:29 +00:00
deprecatedRequestGauge . WithContext ( req . Context ( ) ) . WithLabelValues ( group , version , resource , subresource , removedRelease ) . Set ( 1 )
2020-08-10 17:43:49 +00:00
audit . AddAuditAnnotation ( req . Context ( ) , deprecatedAnnotationKey , "true" )
if len ( removedRelease ) > 0 {
audit . AddAuditAnnotation ( req . Context ( ) , removedReleaseAnnotationKey , removedRelease )
}
}
2021-03-18 22:40:29 +00:00
requestLatencies . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , dryRun , group , version , resource , subresource , scope , component ) . Observe ( elapsedSeconds )
2019-01-12 04:58:27 +00:00
// We are only interested in response sizes of read requests.
if verb == "GET" || verb == "LIST" {
2021-03-18 22:40:29 +00:00
responseSizes . WithContext ( req . Context ( ) ) . WithLabelValues ( reportedVerb , group , version , resource , subresource , scope , component ) . Observe ( float64 ( respSize ) )
2019-01-12 04:58:27 +00:00
}
}
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc plus some Kubernetes endpoint specific information.
2020-08-10 17:43:49 +00:00
func InstrumentRouteFunc ( verb , group , version , resource , subresource , scope , component string , deprecated bool , removedRelease string , routeFunc restful . RouteFunction ) restful . RouteFunction {
2020-12-01 01:06:26 +00:00
return restful . RouteFunction ( func ( req * restful . Request , response * restful . Response ) {
requestReceivedTimestamp , ok := request . ReceivedTimestampFrom ( req . Request . Context ( ) )
if ! ok {
requestReceivedTimestamp = time . Now ( )
}
2019-01-12 04:58:27 +00:00
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
2020-12-01 01:06:26 +00:00
routeFunc ( req , response )
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
MonitorRequest ( req . Request , verb , group , version , resource , subresource , scope , component , deprecated , removedRelease , delegate . Status ( ) , delegate . ContentLength ( ) , time . Since ( requestReceivedTimestamp ) )
2019-01-12 04:58:27 +00:00
} )
}
// InstrumentHandlerFunc works like Prometheus' InstrumentHandlerFunc but adds some Kubernetes endpoint specific information.
2020-08-10 17:43:49 +00:00
func InstrumentHandlerFunc ( verb , group , version , resource , subresource , scope , component string , deprecated bool , removedRelease string , handler http . HandlerFunc ) http . HandlerFunc {
2019-01-12 04:58:27 +00:00
return func ( w http . ResponseWriter , req * http . Request ) {
2020-12-01 01:06:26 +00:00
requestReceivedTimestamp , ok := request . ReceivedTimestampFrom ( req . Context ( ) )
if ! ok {
requestReceivedTimestamp = time . Now ( )
}
2019-01-12 04:58:27 +00:00
delegate := & ResponseWriterDelegator { ResponseWriter : w }
_ , cn := w . ( http . CloseNotifier )
_ , fl := w . ( http . Flusher )
_ , hj := w . ( http . Hijacker )
if cn && fl && hj {
w = & fancyResponseWriterDelegator { delegate }
} else {
w = delegate
}
handler ( w , req )
2021-03-18 22:40:29 +00:00
MonitorRequest ( req , verb , group , version , resource , subresource , scope , component , deprecated , removedRelease , delegate . Status ( ) , delegate . ContentLength ( ) , time . Since ( requestReceivedTimestamp ) )
2020-06-23 22:12:14 +00:00
}
}
2019-01-12 04:58:27 +00:00
// CleanScope returns the scope of the request.
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 ""
}
2019-09-27 21:51:53 +00:00
func canonicalVerb ( verb string , scope string ) string {
switch verb {
case "GET" , "HEAD" :
2020-12-01 01:06:26 +00:00
if scope != "resource" && scope != "" {
2019-09-27 21:51:53 +00:00
return "LIST"
}
return "GET"
default :
return verb
}
}
2021-08-12 22:41:05 +00:00
func cleanVerb ( verb , suggestedVerb string , request * http . Request ) string {
2019-01-12 04:58:27 +00:00
reportedVerb := verb
2021-08-12 22:41:05 +00:00
// CanonicalVerb (being an input for this function) doesn't handle correctly the
// deprecated path pattern for watch of:
// GET /api/{version}/watch/{resource}
// We correct it manually based on the pass verb from the installer.
if suggestedVerb == "WATCH" || suggestedVerb == "WATCHLIST" {
reportedVerb = "WATCH"
}
2019-01-12 04:58:27 +00:00
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"
}
2019-08-30 18:33:25 +00:00
if verb == "PATCH" && request . Header . Get ( "Content-Type" ) == string ( types . ApplyPatchType ) && utilfeature . DefaultFeatureGate . Enabled ( features . ServerSideApply ) {
reportedVerb = "APPLY"
}
2020-06-23 22:12:14 +00:00
if validRequestMethods . Has ( reportedVerb ) {
return reportedVerb
}
return OtherRequestMethod
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
func cleanDryRun ( u * url . URL ) string {
// avoid allocating when we don't see dryRun in the query
if ! strings . Contains ( u . RawQuery , "dryRun" ) {
return ""
}
dryRun := u . Query ( ) [ "dryRun" ]
2019-04-07 17:07:55 +00:00
if errs := validation . ValidateDryRun ( nil , dryRun ) ; len ( errs ) > 0 {
return "invalid"
}
// Since dryRun could be valid with any arbitrarily long length
// we have to dedup and sort the elements before joining them together
2019-08-30 18:33:25 +00:00
// TODO: this is a fairly large allocation for what it does, consider
// a sort and dedup in a single pass
2019-04-07 17:07:55 +00:00
return strings . Join ( utilsets . NewString ( dryRun ... ) . List ( ) , "," )
}
2019-01-12 04:58:27 +00:00
// ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
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
}
func ( r * ResponseWriterDelegator ) Status ( ) int {
return r . status
}
func ( r * ResponseWriterDelegator ) ContentLength ( ) int {
return int ( r . written )
}
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 )
}
}