knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/observability/metrics/k8s/tools.go (about) 1 /* 2 Copyright 2025 The Knative 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 k8s 18 19 import ( 20 "context" 21 "net/url" 22 "strconv" 23 "strings" 24 "time" 25 26 "knative.dev/pkg/observability/semconv" 27 "knative.dev/pkg/observability/semconv/httpconv" 28 29 "go.opentelemetry.io/otel" 30 "go.opentelemetry.io/otel/attribute" 31 "go.opentelemetry.io/otel/metric" 32 "k8s.io/client-go/tools/metrics" 33 ) 34 35 const ( 36 scopeName = "knative.dev/pkg/observability/metrics/k8s" 37 38 resultMetricName = "kn.k8s.client.http.response.status_code" 39 resultMetricDescription = "Count of response codes partitioned by method and host" 40 ) 41 42 var latencyBounds = []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10} 43 44 type ClientProvider struct { 45 latencyMetric httpconv.ClientRequestDuration 46 resultMetric metric.Int64Counter 47 } 48 49 func NewClientMetricProvider(opts ...Option) (*ClientProvider, error) { 50 options := options{ 51 meterProvider: otel.GetMeterProvider(), 52 } 53 54 cp := &ClientProvider{} 55 56 for _, opt := range opts { 57 opt(&options) 58 } 59 60 meter := options.meterProvider.Meter(scopeName) 61 62 var err error 63 cp.latencyMetric, err = httpconv.NewClientRequestDuration( 64 meter, 65 metric.WithExplicitBucketBoundaries(latencyBounds...), 66 ) 67 if err != nil { 68 return nil, err 69 } 70 71 cp.resultMetric, err = meter.Int64Counter( 72 resultMetricName, 73 metric.WithDescription(resultMetricDescription), 74 metric.WithUnit("{item}"), 75 ) 76 if err != nil { 77 return nil, err 78 } 79 80 return cp, nil 81 } 82 83 func (cp *ClientProvider) RequestLatencyMetric() metrics.LatencyMetric { 84 return &latency{cp} 85 } 86 87 func (cp *ClientProvider) RequestResultMetric() metrics.ResultMetric { 88 return &result{cp} 89 } 90 91 type latency struct { 92 cp *ClientProvider 93 } 94 95 func (l *latency) Observe(ctx context.Context, verb string, u url.URL, latency time.Duration) { 96 serverAddress := u.Hostname() 97 serverPort := 80 98 99 if u.Scheme == "https" { 100 serverPort = 443 101 } 102 103 if portStr := u.Port(); portStr != "" { 104 if port, err := strconv.Atoi(portStr); err != nil { 105 serverPort = port 106 } 107 } 108 109 elapsedTime := float64(latency) / float64(time.Second) 110 111 l.cp.latencyMetric.Record(ctx, elapsedTime, 112 httpconv.RequestMethodAttr(strings.ToUpper(verb)), 113 serverAddress, 114 serverPort, 115 l.cp.latencyMetric.AttrURLTemplate(u.Path), 116 l.cp.latencyMetric.AttrURLScheme(u.Scheme), 117 ) 118 } 119 120 type result struct { 121 cp *ClientProvider 122 } 123 124 func (r *result) Increment(ctx context.Context, code string, method string, host string) { 125 var attrs attribute.Set 126 127 // assume we hit the happy path most frequently then in 128 // that case we want to avoid the strconv.Atoi parsing 129 if code == "200" { 130 attrs = attribute.NewSet( 131 semconv.ServerAddressKey.String(host), 132 semconv.HTTPRequestMethodKey.String(strings.ToUpper(method)), 133 semconv.HTTPResponseStatusCodeKey.Int(200), 134 ) 135 } else if c, err := strconv.Atoi(code); err != nil { 136 attrs = attribute.NewSet( 137 semconv.ServerAddressKey.String(host), 138 semconv.HTTPRequestMethodKey.String(strings.ToUpper(method)), 139 semconv.HTTPResponseStatusCodeKey.Int(c), 140 ) 141 } else { 142 attrs = attribute.NewSet( 143 semconv.ServerAddressKey.String(host), 144 semconv.HTTPRequestMethodKey.String(strings.ToUpper(method)), 145 ) 146 } 147 148 r.cp.resultMetric.Add(ctx, 1, metric.WithAttributeSet(attrs)) 149 } 150 151 var ( 152 _ metrics.LatencyMetric = (*latency)(nil) 153 _ metrics.ResultMetric = (*result)(nil) 154 )