k8s.io/apiserver@v0.31.1/pkg/endpoints/filterlatency/filterlatency.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package filterlatency
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"time"
    24  
    25  	"go.opentelemetry.io/otel/trace"
    26  
    27  	"k8s.io/apiserver/pkg/endpoints/metrics"
    28  	apirequest "k8s.io/apiserver/pkg/endpoints/request"
    29  	"k8s.io/apiserver/pkg/server/httplog"
    30  	"k8s.io/klog/v2"
    31  	"k8s.io/utils/clock"
    32  )
    33  
    34  type requestFilterRecordKeyType int
    35  
    36  // requestFilterRecordKey is the context key for a request filter record struct.
    37  const requestFilterRecordKey requestFilterRecordKeyType = iota
    38  
    39  const minFilterLatencyToLog = 100 * time.Millisecond
    40  
    41  type requestFilterRecord struct {
    42  	name             string
    43  	startedTimestamp time.Time
    44  }
    45  
    46  // withRequestFilterRecord attaches the given request filter record to the parent context.
    47  func withRequestFilterRecord(parent context.Context, fr *requestFilterRecord) context.Context {
    48  	return apirequest.WithValue(parent, requestFilterRecordKey, fr)
    49  }
    50  
    51  // requestFilterRecordFrom returns the request filter record from the given context.
    52  func requestFilterRecordFrom(ctx context.Context) *requestFilterRecord {
    53  	fr, _ := ctx.Value(requestFilterRecordKey).(*requestFilterRecord)
    54  	return fr
    55  }
    56  
    57  // TrackStarted measures the timestamp the given handler has started execution
    58  // by attaching a handler to the chain.
    59  func TrackStarted(handler http.Handler, tp trace.TracerProvider, name string) http.Handler {
    60  	return trackStarted(handler, tp, name, clock.RealClock{})
    61  }
    62  
    63  // TrackCompleted measures the timestamp the given handler has completed execution and then
    64  // it updates the corresponding metric with the filter latency duration.
    65  func TrackCompleted(handler http.Handler) http.Handler {
    66  	return trackCompleted(handler, clock.RealClock{}, func(ctx context.Context, fr *requestFilterRecord, completedAt time.Time) {
    67  		latency := completedAt.Sub(fr.startedTimestamp)
    68  		metrics.RecordFilterLatency(ctx, fr.name, latency)
    69  		if klog.V(3).Enabled() && latency > minFilterLatencyToLog {
    70  			httplog.AddKeyValue(ctx, fmt.Sprintf("fl_%s", fr.name), latency.String())
    71  		}
    72  	})
    73  }
    74  
    75  func trackStarted(handler http.Handler, tp trace.TracerProvider, name string, clock clock.PassiveClock) http.Handler {
    76  	// This is a noop if the tracing is disabled, since tp will be a NoopTracerProvider
    77  	tracer := tp.Tracer("k8s.op/apiserver/pkg/endpoints/filterlatency")
    78  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    79  		ctx := r.Context()
    80  		if fr := requestFilterRecordFrom(ctx); fr != nil {
    81  			fr.name = name
    82  			fr.startedTimestamp = clock.Now()
    83  
    84  			handler.ServeHTTP(w, r)
    85  			return
    86  		}
    87  
    88  		fr := &requestFilterRecord{
    89  			name:             name,
    90  			startedTimestamp: clock.Now(),
    91  		}
    92  		ctx, _ = tracer.Start(ctx, name)
    93  		r = r.WithContext(withRequestFilterRecord(ctx, fr))
    94  		handler.ServeHTTP(w, r)
    95  	})
    96  }
    97  
    98  func trackCompleted(handler http.Handler, clock clock.PassiveClock, action func(context.Context, *requestFilterRecord, time.Time)) http.Handler {
    99  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   100  		// The previous filter has just completed.
   101  		completedAt := clock.Now()
   102  
   103  		defer handler.ServeHTTP(w, r)
   104  
   105  		ctx := r.Context()
   106  		if fr := requestFilterRecordFrom(ctx); fr != nil {
   107  			action(ctx, fr, completedAt)
   108  		}
   109  		trace.SpanFromContext(ctx).End()
   110  	})
   111  }