sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/metrics/http_test.go (about) 1 /* 2 Copyright 2019 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 metrics 18 19 import ( 20 "net/http" 21 "net/http/httptest" 22 "reflect" 23 "strings" 24 "testing" 25 "time" 26 27 "github.com/prometheus/client_golang/prometheus/testutil" 28 "k8s.io/utils/diff" 29 "sigs.k8s.io/prow/pkg/simplifypath" 30 ) 31 32 func TestPowersOfTwoBetween(t *testing.T) { 33 var testCases = []struct { 34 name string 35 min, max float64 36 powers []float64 37 }{ 38 { 39 name: "bounds are powers", 40 min: 2, 41 max: 32, 42 powers: []float64{2, 4, 8, 16, 32}, 43 }, 44 { 45 name: "bounds are integers", 46 min: 1, 47 max: 33, 48 powers: []float64{1, 2, 4, 8, 16, 32, 33}, 49 }, 50 { 51 name: "bounds are <1", 52 min: 0.05, 53 max: 0.5, 54 powers: []float64{0.05, 0.0625, 0.125, 0.25, 0.5}, 55 }, 56 } 57 for _, testCase := range testCases { 58 t.Run(testCase.name, func(t *testing.T) { 59 if actual, expected := powersOfTwoBetween(testCase.min, testCase.max), testCase.powers; !reflect.DeepEqual(actual, expected) { 60 t.Errorf("%s: got incorrect powers between (%v,%v): %s", testCase.name, testCase.min, testCase.max, diff.ObjectReflectDiff(actual, expected)) 61 } 62 }) 63 } 64 } 65 66 func TestWriteHeader(t *testing.T) { 67 testcases := []struct { 68 name string 69 statusCode int 70 }{ 71 { 72 "StatusOK", 73 http.StatusOK, 74 }, 75 { 76 "StatusNotFound", 77 http.StatusNotFound, 78 }, 79 } 80 for _, tc := range testcases { 81 t.Run(tc.name, func(t *testing.T) { 82 rr := httptest.NewRecorder() 83 trw := &traceResponseWriter{ResponseWriter: rr, statusCode: http.StatusOK} 84 trw.WriteHeader(tc.statusCode) 85 if rr.Code != tc.statusCode { 86 t.Errorf("mismatch in response headers: expected %s, got %s", http.StatusText(tc.statusCode), http.StatusText(rr.Code)) 87 } 88 if trw.statusCode != tc.statusCode { 89 t.Errorf("mismatch in TraceResponseWriter headers: expected %s, got %s", http.StatusText(tc.statusCode), http.StatusText(trw.statusCode)) 90 } 91 }) 92 93 } 94 95 } 96 97 func TestWrite(t *testing.T) { 98 testcases := []struct { 99 name string 100 responseBody string 101 }{ 102 { 103 "SimpleText for trace", 104 "Simple text to traceResponseWriter.size", 105 }, 106 } 107 for _, tc := range testcases { 108 t.Run(tc.name, func(t *testing.T) { 109 rr := httptest.NewRecorder() 110 trw := &traceResponseWriter{ResponseWriter: rr, statusCode: http.StatusOK} 111 resp := []byte(tc.responseBody) 112 _, err := trw.Write(resp) 113 if err != nil { 114 t.Fatalf("failed to write to TraceResponseWriter") 115 } 116 if rr.Body.String() != tc.responseBody { 117 t.Errorf("mismatch in response body: expected %s, got %s", tc.responseBody, rr.Body.String()) 118 } 119 if trw.size != len(resp) { 120 t.Errorf("mismatch in TraceResponseWriter size: expected %d, got %d", len(resp), trw.size) 121 } 122 }) 123 } 124 } 125 126 func TestRecordError(t *testing.T) { 127 testcases := []struct { 128 name string 129 namespace string 130 expectedError string 131 expectedCount int 132 expectedOut string 133 }{ 134 { 135 name: "Simple Error String", 136 namespace: "testnamespace", 137 expectedError: "sample error message to ensure proper working", 138 expectedOut: `# HELP testnamespace_error_rate number of errors, sorted by label/type. 139 # TYPE testnamespace_error_rate counter 140 testnamespace_error_rate{error="sample error message to ensure proper working"} 1 141 `, 142 }, 143 } 144 for _, tc := range testcases { 145 t.Run(tc.name, func(t *testing.T) { 146 errorRate := ErrorRate(tc.namespace) 147 RecordError(tc.expectedError, errorRate) 148 if err := testutil.CollectAndCompare(errorRate, strings.NewReader(tc.expectedOut)); err != nil { 149 t.Errorf("unexpected metrics for ErrorRate:\n%s", err) 150 } 151 152 }) 153 } 154 } 155 156 func oneByteWriter(t *testing.T) http.HandlerFunc { 157 return func(w http.ResponseWriter, r *http.Request) { 158 oneByteLength := []byte{'1'} 159 _, err := w.Write(oneByteLength) 160 if err != nil { 161 t.Fatalf("failed to write to TraceResponseWriter: %v", err) 162 } 163 } 164 } 165 166 func halfSecLatency(_ time.Time) time.Duration { 167 return time.Millisecond * 500 168 } 169 func TestHandleWithMetricsCustomTimer(t *testing.T) { 170 testcases := []struct { 171 name string 172 namespace string 173 customTimer func(time.Time) time.Duration 174 dummyWriter http.HandlerFunc 175 expectedResponseTimeOut string 176 expectedResponseSizeOut string 177 }{ 178 { 179 name: "Simple call to dummy handler with 0.5 sec latency and 1 byte response", 180 namespace: "testnamespace", 181 customTimer: halfSecLatency, 182 dummyWriter: oneByteWriter(t), 183 expectedResponseTimeOut: ` 184 # HELP testnamespace_http_request_duration_seconds http request duration in seconds 185 # TYPE testnamespace_http_request_duration_seconds histogram 186 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.0001"} 0 187 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.0001220703125"} 0 188 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.000244140625"} 0 189 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.00048828125"} 0 190 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.0009765625"} 0 191 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.001953125"} 0 192 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.00390625"} 0 193 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.0078125"} 0 194 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.015625"} 0 195 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.03125"} 0 196 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.0625"} 0 197 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.125"} 0 198 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.25"} 0 199 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="0.5"} 1 200 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="1"} 1 201 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="2"} 1 202 testnamespace_http_request_duration_seconds_bucket{method="GET",path="",status="200",user_agent="",le="+Inf"} 1 203 testnamespace_http_request_duration_seconds_sum{method="GET",path="",status="200",user_agent=""} 0.5 204 testnamespace_http_request_duration_seconds_count{method="GET",path="",status="200",user_agent=""} 1 205 `, 206 expectedResponseSizeOut: ` 207 # HELP testnamespace_http_response_size_bytes http response size in bytes 208 # TYPE testnamespace_http_response_size_bytes histogram 209 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="256"} 1 210 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="512"} 1 211 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="1024"} 1 212 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="2048"} 1 213 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="4096"} 1 214 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="8192"} 1 215 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="16384"} 1 216 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="32768"} 1 217 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="65536"} 1 218 testnamespace_http_response_size_bytes_bucket{method="GET",path="",status="200",user_agent="",le="+Inf"} 1 219 testnamespace_http_response_size_bytes_sum{method="GET",path="",status="200",user_agent=""} 1 220 testnamespace_http_response_size_bytes_count{method="GET",path="",status="200",user_agent=""} 1 221 `, 222 }, 223 } 224 for _, tc := range testcases { 225 t.Run(tc.name, func(t *testing.T) { 226 httpResponseSize := HttpResponseSize(tc.namespace, 256, 65536) 227 httpRequestDuration := HttpRequestDuration(tc.namespace, 0.0001, 2) 228 229 simplifier := simplifypath.NewSimplifier(simplifypath.L("")) 230 handler := traceHandlerWithCustomTimer(simplifier, httpRequestDuration, httpResponseSize, tc.customTimer)(tc.dummyWriter) 231 rr := httptest.NewRecorder() 232 req, err := http.NewRequest("GET", "http://example.com", nil) 233 if err != nil { 234 t.Errorf("error while creating dummy request: %v", err) 235 } 236 handler.ServeHTTP(rr, req) 237 if err := testutil.CollectAndCompare(httpResponseSize, strings.NewReader(tc.expectedResponseSizeOut)); err != nil { 238 t.Errorf("unexpected metrics for HTTPResponseSize:\n%s", err) 239 } 240 if err := testutil.CollectAndCompare(httpRequestDuration, strings.NewReader(tc.expectedResponseTimeOut)); err != nil { 241 t.Errorf("unexpected metrics for HTTPRequestDuration:\n%s", err) 242 } 243 }) 244 } 245 }