components-contrib/middleware/http/wasm/benchmark_test.go

141 lines
3.6 KiB
Go

package wasm
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"testing"
"github.com/dapr/components-contrib/internal/httputils"
"github.com/dapr/components-contrib/metadata"
dapr "github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)
const parallel = 10
func BenchmarkNative(b *testing.B) {
benchmarkAll(b, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if httputils.RequestURI(r) == "/v1.0/hi?name=panda" {
httputils.SetRequestURI(r, "/v1.0/hello?name=teddy")
}
next.ServeHTTP(w, r)
})
})
}
func BenchmarkTinygo(b *testing.B) {
path := "file://internal/e2e-guests/rewrite/main.wasm"
benchmarkMiddleware(b, path)
}
// BenchmarkWat gives baseline performance for the same handler by
// writing it directly in WebAssembly Text Format.
func BenchmarkWat(b *testing.B) {
url := "file://internal/testdata/rewrite.wasm"
benchmarkMiddleware(b, url)
}
func benchmarkMiddleware(b *testing.B, path string) {
md := metadata.Base{Properties: map[string]string{"url": path}}
l := logger.NewLogger(b.Name())
l.SetOutput(io.Discard)
handlerFn, err := NewMiddleware(l).GetHandler(context.Background(), dapr.Metadata{Base: md})
if err != nil {
b.Fatal(err)
}
benchmarkAll(b, handlerFn)
}
var benches = map[string]struct {
newRequest func() *http.Request
test http.Handler
}{
"rewrite": {
newRequest: func() *http.Request {
u, err := url.Parse("https://test.io/v1.0/hi?name=panda")
if err != nil {
panic(err)
}
return &http.Request{
Method: http.MethodGet,
URL: u,
Host: "test.io",
Header: map[string][]string{
"User-Agent": {"curl/7.79.1"},
"Accept": {"*/*"},
},
}
},
test: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if method := r.Method; method != http.MethodGet {
body := fmt.Sprintf("Unexpected request method: %q", method)
httputils.RespondWithErrorAndMessage(w, http.StatusInternalServerError, body)
}
if path := r.URL.Path; path != "/v1.0/hello" {
body := fmt.Sprintf("Expected wasm to rewrite path: %s", path)
httputils.RespondWithErrorAndMessage(w, http.StatusInternalServerError, body)
}
if query := r.URL.RawQuery; query != "name=teddy" {
body := fmt.Sprintf("Expected wasm to retain query: %s", query)
httputils.RespondWithErrorAndMessage(w, http.StatusInternalServerError, body)
}
w.Header().Set("Content-Type", "text/plain")
if _, err := w.Write([]byte("Hello, world!")); err != nil {
panic(err)
}
}),
},
}
func benchmarkAll(b *testing.B, mw func(http.Handler) http.Handler) {
for n, s := range benches {
b.Run(n, func(b *testing.B) {
b.SetParallelism(parallel)
benchmark(b, mw, n, s.newRequest, s.test)
})
}
}
func benchmark(
b *testing.B,
mw func(http.Handler) http.Handler,
name string,
newRequest func() *http.Request,
test http.Handler,
) {
h := mw(test)
b.Run(name, func(b *testing.B) {
// We don't report allocations because memory allocations for TinyGo are
// in wasm which isn't visible to the Go benchmark.
for i := 0; i < b.N; i++ {
h.ServeHTTP(fakeResponseWriter{}, newRequest())
}
})
}
var _ http.ResponseWriter = fakeResponseWriter{}
type fakeResponseWriter struct{}
func (rw fakeResponseWriter) Header() http.Header {
return http.Header{}
}
func (rw fakeResponseWriter) Write(b []byte) (int, error) {
return len(b), nil
}
func (rw fakeResponseWriter) WriteHeader(statusCode int) {
// None of our benchmark tests should send failure status. If there's a
// failure, it is likely there's a problem in the test data.
if statusCode != 200 {
panic(statusCode)
}
}