diff --git a/pkg/agent/protocol/http/proxy.go b/pkg/agent/protocol/http/proxy.go index 541cc11d..4fa2a326 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. @@ -145,7 +147,10 @@ func (h *httpHandler) fault(rw http.ResponseWriter, wait <-chan time.Time) { } 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, time.After(0)) return @@ -160,6 +165,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { responseTimer := time.After(delay) if h.disruption.ErrorRate > 0 && rand.Float32() <= h.disruption.ErrorRate { + h.metrics.Inc(protocol.MetricRequestsFaulted) h.fault(rw, responseTimer) return } @@ -179,6 +185,7 @@ func (p *proxy) Start() error { upstreamURL: *upstreamURL, disruption: p.disruption, client: http.DefaultClient, + metrics: &p.metrics, } p.srv = &http.Server{ @@ -203,7 +210,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..3014a1fa 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 @@ -415,3 +416,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) + } + }) + } +}