1
16
17 package conversion
18
19 import (
20 "context"
21 "strconv"
22 "sync"
23 "time"
24
25 "k8s.io/apimachinery/pkg/runtime"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "k8s.io/component-base/metrics"
28 "k8s.io/component-base/metrics/legacyregistry"
29 )
30
31 var (
32 latencyBuckets = metrics.ExponentialBuckets(0.001, 2, 15)
33 )
34
35
36 type converterMetricFactory struct {
37
38
39 durations map[string]*metrics.HistogramVec
40 factoryLock sync.Mutex
41 }
42
43 func newConverterMetricFactory() *converterMetricFactory {
44 return &converterMetricFactory{durations: map[string]*metrics.HistogramVec{}, factoryLock: sync.Mutex{}}
45 }
46
47 var _ crConverterInterface = &converterMetric{}
48
49 type converterMetric struct {
50 delegate crConverterInterface
51 latencies *metrics.HistogramVec
52 crdName string
53 }
54
55 func (c *converterMetricFactory) addMetrics(crdName string, converter crConverterInterface) (crConverterInterface, error) {
56 c.factoryLock.Lock()
57 defer c.factoryLock.Unlock()
58 metric, exists := c.durations["webhook"]
59 if !exists {
60 metric = metrics.NewHistogramVec(
61 &metrics.HistogramOpts{
62 Name: "apiserver_crd_conversion_webhook_duration_seconds",
63 Help: "CRD webhook conversion duration in seconds",
64 Buckets: latencyBuckets,
65 StabilityLevel: metrics.ALPHA,
66 },
67 []string{"crd_name", "from_version", "to_version", "succeeded"})
68 err := legacyregistry.Register(metric)
69 if err != nil {
70 return nil, err
71 }
72 c.durations["webhook"] = metric
73 }
74 return &converterMetric{latencies: metric, delegate: converter, crdName: crdName}, nil
75 }
76
77 func (m *converterMetric) Convert(in runtime.Object, targetGV schema.GroupVersion) (runtime.Object, error) {
78 start := time.Now()
79 obj, err := m.delegate.Convert(in, targetGV)
80 fromVersion := in.GetObjectKind().GroupVersionKind().Version
81 toVersion := targetGV.Version
82
83
84 if fromVersion != toVersion {
85 m.latencies.WithLabelValues(
86 m.crdName, fromVersion, toVersion, strconv.FormatBool(err == nil)).Observe(time.Since(start).Seconds())
87 }
88 return obj, err
89 }
90
91 type ConversionWebhookErrorType string
92
93 const (
94 ConversionWebhookCallFailure ConversionWebhookErrorType = "conversion_webhook_call_failure"
95 ConversionWebhookMalformedResponseFailure ConversionWebhookErrorType = "conversion_webhook_malformed_response_failure"
96 ConversionWebhookPartialResponseFailure ConversionWebhookErrorType = "conversion_webhook_partial_response_failure"
97 ConversionWebhookInvalidConvertedObjectFailure ConversionWebhookErrorType = "conversion_webhook_invalid_converted_object_failure"
98 ConversionWebhookNoObjectsReturnedFailure ConversionWebhookErrorType = "conversion_webhook_no_objects_returned_failure"
99 )
100
101 var (
102 Metrics = newConversionWebhookMetrics()
103 namespace = "apiserver"
104 )
105
106
107 type ConversionWebhookMetrics struct {
108 conversionWebhookRequest *metrics.CounterVec
109 conversionWebhookLatency *metrics.HistogramVec
110 }
111
112 func newConversionWebhookMetrics() *ConversionWebhookMetrics {
113 conversionWebhookRequest := metrics.NewCounterVec(
114 &metrics.CounterOpts{
115 Name: "conversion_webhook_request_total",
116 Namespace: namespace,
117 Help: "Counter for conversion webhook requests with success/failure and failure error type",
118 StabilityLevel: metrics.ALPHA,
119 },
120 []string{"result", "failure_type"})
121
122 conversionWebhookLatency := metrics.NewHistogramVec(
123 &metrics.HistogramOpts{
124 Name: "conversion_webhook_duration_seconds",
125 Namespace: namespace,
126 Help: "Conversion webhook request latency",
127
128 Buckets: []float64{0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 30, 45, 60},
129 StabilityLevel: metrics.ALPHA,
130 },
131 []string{"result", "failure_type"},
132 )
133
134 legacyregistry.MustRegister(conversionWebhookRequest)
135 legacyregistry.MustRegister(conversionWebhookLatency)
136
137 return &ConversionWebhookMetrics{conversionWebhookRequest: conversionWebhookRequest, conversionWebhookLatency: conversionWebhookLatency}
138 }
139
140
141 func (m *ConversionWebhookMetrics) ObserveConversionWebhookSuccess(ctx context.Context, elapsed time.Duration) {
142 result := "success"
143 m.conversionWebhookRequest.WithContext(ctx).WithLabelValues(result, "").Inc()
144 m.observe(ctx, elapsed, result, "")
145 }
146
147
148 func (m *ConversionWebhookMetrics) ObserveConversionWebhookFailure(ctx context.Context, elapsed time.Duration, errorType ConversionWebhookErrorType) {
149 result := "failure"
150 m.conversionWebhookRequest.WithContext(ctx).WithLabelValues(result, string(errorType)).Inc()
151 m.observe(ctx, elapsed, result, errorType)
152 }
153
154
155 func (m *ConversionWebhookMetrics) observe(ctx context.Context, elapsed time.Duration, result string, errorType ConversionWebhookErrorType) {
156 elapsedSeconds := elapsed.Seconds()
157 m.conversionWebhookLatency.WithContext(ctx).WithLabelValues(result, string(errorType)).Observe(elapsedSeconds)
158 }
159
View as plain text