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

     1  /*
     2  Copyright 2021 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 request
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"time"
    23  
    24  	"k8s.io/utils/clock"
    25  )
    26  
    27  func sumDuration(d1 time.Duration, d2 time.Duration) time.Duration {
    28  	return d1 + d2
    29  }
    30  
    31  func maxDuration(d1 time.Duration, d2 time.Duration) time.Duration {
    32  	if d1 > d2 {
    33  		return d1
    34  	}
    35  	return d2
    36  }
    37  
    38  // DurationTracker is a simple interface for tracking functions duration,
    39  // it is safe for concurrent use by multiple goroutines.
    40  type DurationTracker interface {
    41  	// Track measures time spent in the given function f and
    42  	// aggregates measured duration using aggregateFunction.
    43  	// if Track is invoked with f from multiple goroutines concurrently,
    44  	// then f must be safe to be invoked concurrently by multiple goroutines.
    45  	Track(f func())
    46  
    47  	// TrackDuration tracks latency from the specified duration
    48  	// and aggregate it using aggregateFunction
    49  	TrackDuration(time.Duration)
    50  
    51  	// GetLatency returns the total latency incurred so far
    52  	GetLatency() time.Duration
    53  }
    54  
    55  // durationTracker implements DurationTracker by measuring function time
    56  // using given clock and aggregates the duration using given aggregate function
    57  type durationTracker struct {
    58  	clock             clock.Clock
    59  	latency           time.Duration
    60  	mu                sync.Mutex
    61  	aggregateFunction func(time.Duration, time.Duration) time.Duration
    62  }
    63  
    64  // Track measures time spent in given function and aggregates measured
    65  // duration using aggregateFunction
    66  func (t *durationTracker) Track(f func()) {
    67  	startedAt := t.clock.Now()
    68  	defer func() {
    69  		duration := t.clock.Since(startedAt)
    70  		t.mu.Lock()
    71  		defer t.mu.Unlock()
    72  		t.latency = t.aggregateFunction(t.latency, duration)
    73  	}()
    74  
    75  	f()
    76  }
    77  
    78  // TrackDuration tracks latency from the given duration
    79  // using aggregateFunction
    80  func (t *durationTracker) TrackDuration(d time.Duration) {
    81  	t.mu.Lock()
    82  	defer t.mu.Unlock()
    83  	t.latency = t.aggregateFunction(t.latency, d)
    84  }
    85  
    86  // GetLatency returns aggregated latency tracked by a tracker
    87  func (t *durationTracker) GetLatency() time.Duration {
    88  	t.mu.Lock()
    89  	defer t.mu.Unlock()
    90  	return t.latency
    91  }
    92  
    93  func newSumLatencyTracker(c clock.Clock) DurationTracker {
    94  	return &durationTracker{
    95  		clock:             c,
    96  		aggregateFunction: sumDuration,
    97  	}
    98  }
    99  
   100  func newMaxLatencyTracker(c clock.Clock) DurationTracker {
   101  	return &durationTracker{
   102  		clock:             c,
   103  		aggregateFunction: maxDuration,
   104  	}
   105  }
   106  
   107  // LatencyTrackers stores trackers used to measure latecny incurred in
   108  // components within the apiserver.
   109  type LatencyTrackers struct {
   110  	// MutatingWebhookTracker tracks the latency incurred in mutating webhook(s).
   111  	// Since mutating webhooks are done sequentially, latency
   112  	// is aggregated using sum function.
   113  	MutatingWebhookTracker DurationTracker
   114  
   115  	// ValidatingWebhookTracker tracks the latency incurred in validating webhook(s).
   116  	// Validate webhooks are done in parallel, so max function is used.
   117  	ValidatingWebhookTracker DurationTracker
   118  
   119  	// APFQueueWaitTracker tracks the latency incurred by queue wait times
   120  	// from priority & fairness.
   121  	APFQueueWaitTracker DurationTracker
   122  
   123  	// StorageTracker tracks the latency incurred inside the storage layer,
   124  	// it accounts for the time it takes to send data to the underlying
   125  	// storage layer (etcd) and get the complete response back.
   126  	// If a request involves N (N>=1) round trips to the underlying
   127  	// stogare layer, the latency will account for the total duration
   128  	// from these N round trips.
   129  	// It does not include the time incurred in admission, or validation.
   130  	StorageTracker DurationTracker
   131  
   132  	// TransformTracker tracks the latency incurred in transforming the
   133  	// response object(s) returned from the underlying storage layer.
   134  	// This includes transforming the object to user's desired form
   135  	// (ie. as Table), and also setting appropriate API level fields.
   136  	// This does not include the latency incurred in serialization
   137  	// (json or protobuf) of the response object or writing
   138  	// of it to the http ResponseWriter object.
   139  	TransformTracker DurationTracker
   140  
   141  	// SerializationTracker tracks the latency incurred in serialization
   142  	// (json or protobuf) of the response object.
   143  	// NOTE: serialization and writing of the serialized raw bytes to the
   144  	// associated http ResponseWriter object are interleaved, and hence
   145  	// the latency measured here will include the time spent writing the
   146  	// serialized raw bytes to the http ResponseWriter object.
   147  	SerializationTracker DurationTracker
   148  
   149  	// ResponseWriteTracker tracks the latency incurred in writing the
   150  	// serialized raw bytes to the http ResponseWriter object (via the
   151  	// Write method) associated with the request.
   152  	// The Write method can be invoked multiple times, so we use a
   153  	// latency tracker that sums up the duration from each call.
   154  	ResponseWriteTracker DurationTracker
   155  
   156  	// DecodeTracker is used to track latency incurred inside the function
   157  	// that takes an object returned from the underlying storage layer
   158  	// (etcd) and performs decoding of the response object.
   159  	// When called multiple times, the latency incurred inside to
   160  	// decode func each time will be summed up.
   161  	DecodeTracker DurationTracker
   162  }
   163  
   164  type latencyTrackersKeyType int
   165  
   166  // latencyTrackersKey is the key that associates a LatencyTrackers
   167  // instance with the request context.
   168  const latencyTrackersKey latencyTrackersKeyType = iota
   169  
   170  // WithLatencyTrackers returns a copy of parent context to which an
   171  // instance of LatencyTrackers is added.
   172  func WithLatencyTrackers(parent context.Context) context.Context {
   173  	return WithLatencyTrackersAndCustomClock(parent, clock.RealClock{})
   174  }
   175  
   176  // WithLatencyTrackersAndCustomClock returns a copy of parent context to which
   177  // an instance of LatencyTrackers is added. Tracers use given clock.
   178  func WithLatencyTrackersAndCustomClock(parent context.Context, c clock.Clock) context.Context {
   179  	return WithValue(parent, latencyTrackersKey, &LatencyTrackers{
   180  		MutatingWebhookTracker:   newSumLatencyTracker(c),
   181  		ValidatingWebhookTracker: newMaxLatencyTracker(c),
   182  		APFQueueWaitTracker:      newMaxLatencyTracker(c),
   183  		StorageTracker:           newSumLatencyTracker(c),
   184  		TransformTracker:         newSumLatencyTracker(c),
   185  		SerializationTracker:     newSumLatencyTracker(c),
   186  		ResponseWriteTracker:     newSumLatencyTracker(c),
   187  		DecodeTracker:            newSumLatencyTracker(c),
   188  	})
   189  }
   190  
   191  // LatencyTrackersFrom returns the associated LatencyTrackers instance
   192  // from the specified context.
   193  func LatencyTrackersFrom(ctx context.Context) (*LatencyTrackers, bool) {
   194  	wd, ok := ctx.Value(latencyTrackersKey).(*LatencyTrackers)
   195  	return wd, ok && wd != nil
   196  }
   197  
   198  // TrackTransformResponseObjectLatency is used to track latency incurred
   199  // inside the function that takes an object returned from the underlying
   200  // storage layer (etcd) and performs any necessary transformations
   201  // of the response object. This does not include the latency incurred in
   202  // serialization (json or protobuf) of the response object or writing of
   203  // it to the http ResponseWriter object.
   204  // When called multiple times, the latency incurred inside the
   205  // transform func each time will be summed up.
   206  func TrackTransformResponseObjectLatency(ctx context.Context, transform func()) {
   207  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   208  		tracker.TransformTracker.Track(transform)
   209  		return
   210  	}
   211  
   212  	transform()
   213  }
   214  
   215  // TrackStorageLatency is used to track latency incurred
   216  // inside the underlying storage layer.
   217  // When called multiple times, the latency provided will be summed up.
   218  func TrackStorageLatency(ctx context.Context, d time.Duration) {
   219  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   220  		tracker.StorageTracker.TrackDuration(d)
   221  	}
   222  }
   223  
   224  // TrackSerializeResponseObjectLatency is used to track latency incurred in
   225  // serialization (json or protobuf) of the response object.
   226  // When called multiple times, the latency provided will be summed up.
   227  func TrackSerializeResponseObjectLatency(ctx context.Context, f func()) {
   228  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   229  		tracker.SerializationTracker.Track(f)
   230  		return
   231  	}
   232  
   233  	f()
   234  }
   235  
   236  // TrackResponseWriteLatency is used to track latency incurred in writing
   237  // the serialized raw bytes to the http ResponseWriter object (via the
   238  // Write method) associated with the request.
   239  // When called multiple times, the latency provided will be summed up.
   240  func TrackResponseWriteLatency(ctx context.Context, d time.Duration) {
   241  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   242  		tracker.ResponseWriteTracker.TrackDuration(d)
   243  	}
   244  }
   245  
   246  // TrackAPFQueueWaitLatency is used to track latency incurred
   247  // by priority and fairness queues.
   248  func TrackAPFQueueWaitLatency(ctx context.Context, d time.Duration) {
   249  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   250  		tracker.APFQueueWaitTracker.TrackDuration(d)
   251  	}
   252  }
   253  
   254  // TrackDecodeLatency is used to track latency incurred inside the function
   255  // that takes an object returned from the underlying storage layer
   256  // (etcd) and performs decoding of the response object.
   257  // When called multiple times, the latency incurred inside to
   258  // decode func each time will be summed up.
   259  func TrackDecodeLatency(ctx context.Context, d time.Duration) {
   260  	if tracker, ok := LatencyTrackersFrom(ctx); ok {
   261  		tracker.DecodeTracker.TrackDuration(d)
   262  	}
   263  }
   264  
   265  // AuditAnnotationsFromLatencyTrackers will inspect each latency tracker
   266  // associated with the request context and return a set of audit
   267  // annotations that can be added to the API audit entry.
   268  func AuditAnnotationsFromLatencyTrackers(ctx context.Context) map[string]string {
   269  	const (
   270  		transformLatencyKey         = "apiserver.latency.k8s.io/transform-response-object"
   271  		storageLatencyKey           = "apiserver.latency.k8s.io/etcd"
   272  		serializationLatencyKey     = "apiserver.latency.k8s.io/serialize-response-object"
   273  		responseWriteLatencyKey     = "apiserver.latency.k8s.io/response-write"
   274  		mutatingWebhookLatencyKey   = "apiserver.latency.k8s.io/mutating-webhook"
   275  		validatingWebhookLatencyKey = "apiserver.latency.k8s.io/validating-webhook"
   276  		decodeLatencyKey            = "apiserver.latency.k8s.io/decode-response-object"
   277  		apfQueueWaitLatencyKey      = "apiserver.latency.k8s.io/apf-queue-wait"
   278  	)
   279  
   280  	tracker, ok := LatencyTrackersFrom(ctx)
   281  	if !ok {
   282  		return nil
   283  	}
   284  
   285  	annotations := map[string]string{}
   286  	if latency := tracker.TransformTracker.GetLatency(); latency != 0 {
   287  		annotations[transformLatencyKey] = latency.String()
   288  	}
   289  	if latency := tracker.StorageTracker.GetLatency(); latency != 0 {
   290  		annotations[storageLatencyKey] = latency.String()
   291  	}
   292  	if latency := tracker.SerializationTracker.GetLatency(); latency != 0 {
   293  		annotations[serializationLatencyKey] = latency.String()
   294  	}
   295  	if latency := tracker.ResponseWriteTracker.GetLatency(); latency != 0 {
   296  		annotations[responseWriteLatencyKey] = latency.String()
   297  	}
   298  	if latency := tracker.MutatingWebhookTracker.GetLatency(); latency != 0 {
   299  		annotations[mutatingWebhookLatencyKey] = latency.String()
   300  	}
   301  	if latency := tracker.ValidatingWebhookTracker.GetLatency(); latency != 0 {
   302  		annotations[validatingWebhookLatencyKey] = latency.String()
   303  	}
   304  	if latency := tracker.DecodeTracker.GetLatency(); latency != 0 {
   305  		annotations[decodeLatencyKey] = latency.String()
   306  	}
   307  	if latency := tracker.APFQueueWaitTracker.GetLatency(); latency != 0 {
   308  		annotations[apfQueueWaitLatencyKey] = latency.String()
   309  	}
   310  	return annotations
   311  }