diff --git a/pkg/agent/protocol/http/proxy.go b/pkg/agent/protocol/http/proxy.go index 39732458..f69a7ca5 100644 --- a/pkg/agent/protocol/http/proxy.go +++ b/pkg/agent/protocol/http/proxy.go @@ -44,6 +44,7 @@ type proxy struct { config ProxyConfig disruption Disruption srv *http.Server + metrics protocol.MetricMap } // NewProxy return a new Proxy for HTTP requests @@ -85,6 +86,7 @@ type httpHandler struct { upstreamURL url.URL disruption Disruption client httpClient + metrics *protocol.MetricMap } // isExcluded checks whether a request should be proxied through without any kind of modification whatsoever. @@ -146,7 +148,10 @@ func (h *httpHandler) fault(rw http.ResponseWriter, delay time.Duration) { } func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + h.metrics.Inc(protocol.MetricRequests) + if h.isExcluded(req) { + h.metrics.Inc(protocol.MetricRequestsExcluded) //nolint:contextcheck // Unclear which context the linter requires us to propagate here. h.proxy(rw, req, 0) return @@ -159,6 +164,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } if h.disruption.ErrorRate > 0 && rand.Float32() <= h.disruption.ErrorRate { + h.metrics.Inc(protocol.MetricRequestsFaulted) h.fault(rw, delay) return } @@ -178,6 +184,7 @@ func (p *proxy) Start() error { upstreamURL: *upstreamURL, disruption: p.disruption, client: http.DefaultClient, + metrics: &p.metrics, } p.srv = &http.Server{ @@ -202,7 +209,7 @@ func (p *proxy) Stop() error { // Metrics returns runtime metrics for the proxy. func (p *proxy) Metrics() map[string]uint { - return nil + return p.metrics.Map() } // Force stops the proxy without waiting for connections to drain diff --git a/pkg/agent/protocol/http/proxy_test.go b/pkg/agent/protocol/http/proxy_test.go index fa466927..7ccacdd2 100644 --- a/pkg/agent/protocol/http/proxy_test.go +++ b/pkg/agent/protocol/http/proxy_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/grafana/xk6-disruptor/pkg/agent/protocol" ) // fakeHTTPClient mocks the execution of a request returning the predefines @@ -385,6 +386,7 @@ func Test_ProxyHandler(t *testing.T) { upstreamURL: *upstreamURL, client: client, disruption: tc.disruption, + metrics: &protocol.MetricMap{}, } reqURL := fmt.Sprintf("http://%s%s", tc.config.ListenAddress, tc.path) @@ -415,3 +417,71 @@ func Test_ProxyHandler(t *testing.T) { }) } } + +// TODO: This test covers metrics generated by the handler, but not the proxy. The reason for this is that the proxy is +// currently not easily testable, as it coupled with `http.ListenAndServe`. +func Test_Metrics(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + config Disruption + endpoints []string + expectedMetrics map[string]uint + }{ + { + name: "no requests", + expectedMetrics: map[string]uint{}, + }, + { + name: "requests", + config: Disruption{ + Excluded: []string{"/excluded"}, + ErrorRate: 1.0, + ErrorCode: http.StatusTeapot, + }, + endpoints: []string{"/included", "/excluded"}, + expectedMetrics: map[string]uint{ + protocol.MetricRequests: 2, + protocol.MetricRequestsExcluded: 1, + protocol.MetricRequestsFaulted: 1, + }, + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + upstreamServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + upstreamURL, err := url.Parse(upstreamServer.URL) + if err != nil { + t.Fatalf("error parsing httptest url") + } + + metrics := protocol.MetricMap{} + + handler := &httpHandler{ + upstreamURL: *upstreamURL, + disruption: tc.config, + client: http.DefaultClient, + metrics: &metrics, + } + + proxyServer := httptest.NewServer(handler) + + for _, endpoint := range tc.endpoints { + _, err = http.Get(proxyServer.URL + endpoint) + if err != nil { + t.Fatalf("requesting %s: %v", endpoint, err) + } + } + + if diff := cmp.Diff(tc.expectedMetrics, metrics.Map()); diff != "" { + t.Fatalf("expected metrics do not match output:\n%s", diff) + } + }) + } +}