github.com/letsencrypt/boulder@v0.20251208.0/metrics/measured_http/http_test.go (about) 1 package measured_http 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "net/url" 7 "testing" 8 "time" 9 10 "github.com/jmhodges/clock" 11 "github.com/prometheus/client_golang/prometheus" 12 io_prometheus_client "github.com/prometheus/client_model/go" 13 ) 14 15 type sleepyHandler struct { 16 clk clock.FakeClock 17 } 18 19 func (h sleepyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 20 h.clk.Sleep(999 * time.Second) 21 w.WriteHeader(302) 22 } 23 24 func collect(m prometheus.Collector) *io_prometheus_client.Metric { 25 ch := make(chan prometheus.Metric, 10) 26 m.Collect(ch) 27 result := <-ch 28 var iom = new(io_prometheus_client.Metric) 29 _ = result.Write(iom) 30 return iom 31 } 32 33 func TestMeasuring(t *testing.T) { 34 clk := clock.NewFake() 35 36 // Create a local histogram stat with the same labels as the real one, but 37 // don't register it; we will collect its data here in the test to verify it. 38 stat := prometheus.NewHistogramVec( 39 prometheus.HistogramOpts{ 40 Name: "fake", 41 Help: "fake", 42 }, 43 []string{"endpoint", "method", "code"}) 44 45 inFlightRequestsGauge := prometheus.NewGaugeVec( 46 prometheus.GaugeOpts{ 47 Name: "in_flight_requests", 48 Help: "Tracks the number of WFE requests currently in flight, labeled by endpoint.", 49 }, 50 []string{"endpoint"}, 51 ) 52 53 mux := http.NewServeMux() 54 mux.Handle("/foo", sleepyHandler{clk}) 55 mh := MeasuredHandler{ 56 serveMux: mux, 57 clk: clk, 58 stat: stat, 59 inFlightRequestsGauge: inFlightRequestsGauge, 60 } 61 mh.ServeHTTP(httptest.NewRecorder(), &http.Request{ 62 URL: &url.URL{Path: "/foo"}, 63 Method: "GET", 64 }) 65 iom := collect(stat) 66 67 hist := iom.Histogram 68 if *hist.SampleCount != 1 { 69 t.Errorf("SampleCount = %d (expected 1)", *hist.SampleCount) 70 } 71 if *hist.SampleSum != 999 { 72 t.Errorf("SampleSum = %g (expected 999)", *hist.SampleSum) 73 } 74 75 expectedLabels := map[string]string{ 76 "endpoint": "/foo", 77 "method": "GET", 78 "code": "302", 79 } 80 for _, labelPair := range iom.Label { 81 if expectedLabels[*labelPair.Name] == "" { 82 t.Errorf("Unexpected label %s", *labelPair.Name) 83 } else if expectedLabels[*labelPair.Name] != *labelPair.Value { 84 t.Errorf("labels[%q] = %q (expected %q)", *labelPair.Name, *labelPair.Value, 85 expectedLabels[*labelPair.Name]) 86 } 87 delete(expectedLabels, *labelPair.Name) 88 } 89 if len(expectedLabels) != 0 { 90 t.Errorf("Some labels were expected, but not observed: %v", expectedLabels) 91 } 92 } 93 94 // Make an HTTP request with an unknown method and ensure we use the appropriate 95 // label value. 96 func TestUnknownMethod(t *testing.T) { 97 clk := clock.NewFake() 98 99 // Create a local histogram stat with the same labels as the real one, but 100 // don't register it; we will collect its data here in the test to verify it. 101 stat := prometheus.NewHistogramVec( 102 prometheus.HistogramOpts{ 103 Name: "fake", 104 Help: "fake", 105 }, 106 []string{"endpoint", "method", "code"}) 107 inFlightRequestsGauge := prometheus.NewGaugeVec( 108 prometheus.GaugeOpts{ 109 Name: "in_flight_requests", 110 Help: "Tracks the number of WFE requests currently in flight, labeled by endpoint.", 111 }, 112 []string{"endpoint"}, 113 ) 114 115 mux := http.NewServeMux() 116 mux.Handle("/foo", sleepyHandler{clk}) 117 mh := MeasuredHandler{ 118 serveMux: mux, 119 clk: clk, 120 stat: stat, 121 inFlightRequestsGauge: inFlightRequestsGauge, 122 } 123 mh.ServeHTTP(httptest.NewRecorder(), &http.Request{ 124 URL: &url.URL{Path: "/foo"}, 125 Method: "POKE", 126 }) 127 iom := collect(stat) 128 129 expectedLabels := map[string]string{ 130 "endpoint": "/foo", 131 "method": "unknown", 132 "code": "302", 133 } 134 for _, labelPair := range iom.Label { 135 if expectedLabels[*labelPair.Name] == "" { 136 t.Errorf("Unexpected label %s", *labelPair.Name) 137 } else if expectedLabels[*labelPair.Name] != *labelPair.Value { 138 t.Errorf("labels[%q] = %q (expected %q)", *labelPair.Name, *labelPair.Value, 139 expectedLabels[*labelPair.Name]) 140 } 141 delete(expectedLabels, *labelPair.Name) 142 } 143 if len(expectedLabels) != 0 { 144 t.Errorf("Some labels were expected, but not observed: %v", expectedLabels) 145 } 146 } 147 148 func TestWrite(t *testing.T) { 149 clk := clock.NewFake() 150 151 // Create a local histogram stat with the same labels as the real one, but 152 // don't register it; we will collect its data here in the test to verify it. 153 stat := prometheus.NewHistogramVec( 154 prometheus.HistogramOpts{ 155 Name: "fake", 156 Help: "fake", 157 }, 158 []string{"endpoint", "method", "code"}) 159 160 inFlightRequestsGauge := prometheus.NewGaugeVec( 161 prometheus.GaugeOpts{ 162 Name: "in_flight_requests", 163 Help: "Tracks the number of WFE requests currently in flight, labeled by endpoint.", 164 }, 165 []string{"endpoint"}) 166 167 mux := http.NewServeMux() 168 mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { 169 w.Write([]byte{}) 170 }) 171 mh := MeasuredHandler{ 172 serveMux: mux, 173 clk: clk, 174 stat: stat, 175 inFlightRequestsGauge: inFlightRequestsGauge, 176 } 177 mh.ServeHTTP(httptest.NewRecorder(), &http.Request{ 178 URL: &url.URL{Path: "/foo"}, 179 Method: "GET", 180 }) 181 iom := collect(stat) 182 183 stat = prometheus.NewHistogramVec( 184 prometheus.HistogramOpts{ 185 Name: "fake", 186 Help: "fake", 187 }, 188 []string{"endpoint", "method", "code"}) 189 mh.stat = stat 190 mh.inFlightRequestsGauge = inFlightRequestsGauge 191 expectedLabels := map[string]string{ 192 "endpoint": "/foo", 193 "method": "GET", 194 "code": "200", 195 } 196 for _, labelPair := range iom.Label { 197 if expectedLabels[*labelPair.Name] == "" { 198 t.Errorf("Unexpected label %s", *labelPair.Name) 199 } else if expectedLabels[*labelPair.Name] != *labelPair.Value { 200 t.Errorf("labels[%q] = %q (expected %q)", *labelPair.Name, *labelPair.Value, 201 expectedLabels[*labelPair.Name]) 202 } 203 delete(expectedLabels, *labelPair.Name) 204 } 205 if len(expectedLabels) != 0 { 206 t.Errorf("Some labels were expected, but not observed: %v", expectedLabels) 207 } 208 209 mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { 210 w.WriteHeader(202) 211 w.Write([]byte{}) 212 }) 213 mh.ServeHTTP(httptest.NewRecorder(), &http.Request{ 214 URL: &url.URL{Path: "/bar"}, 215 Method: "GET", 216 }) 217 iom = collect(stat) 218 219 expectedLabels = map[string]string{ 220 "endpoint": "/bar", 221 "method": "GET", 222 "code": "202", 223 } 224 for _, labelPair := range iom.Label { 225 if expectedLabels[*labelPair.Name] == "" { 226 t.Errorf("Unexpected label %s", *labelPair.Name) 227 } else if expectedLabels[*labelPair.Name] != *labelPair.Value { 228 t.Errorf("labels[%q] = %q (expected %q)", *labelPair.Name, *labelPair.Value, 229 expectedLabels[*labelPair.Name]) 230 } 231 delete(expectedLabels, *labelPair.Name) 232 } 233 if len(expectedLabels) != 0 { 234 t.Errorf("Some labels were expected, but not observed: %v", expectedLabels) 235 } 236 }