From 59a2c1df41505736af0954e45f1b09a9b9cfad5a Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Thu, 30 May 2024 22:14:13 +0200 Subject: [PATCH] Add otelhttp Handler.ServeHTTP and Transport.RoundTrip benchmarks (#5681) This adds high-level benchmarks for `otelhttp.Handler.ServeHTTP` and `otelhttp.Transport.RoundTrip`, as well as a benchmark comparison without those wrappers. Related: #5664. Current results on my machine: ``` goos: darwin goarch: arm64 pkg: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test BenchmarkHandlerServeHTTP/without_the_otelhttp_handler-10 37971928 31.35 ns/op 28 B/op 0 allocs/op BenchmarkHandlerServeHTTP/with_the_otelhttp_handler-10 435142 2723 ns/op 5585 B/op 35 allocs/op BenchmarkTransportRoundTrip/without_the_otelhttp_transport-10 11430 401735 ns/op 24436 B/op 117 allocs/op BenchmarkTransportRoundTrip/with_the_otelhttp_transport-10 278 4367998 ns/op 5416 B/op 85 allocs/op PASS ok go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test 10.056s ``` --------- Co-authored-by: Tyler Yahn --- .../net/http/otelhttp/test/handler_test.go | 51 +++++++++++++-- .../net/http/otelhttp/test/transport_test.go | 64 +++++++++++++++---- 2 files changed, 99 insertions(+), 16 deletions(-) diff --git a/instrumentation/net/http/otelhttp/test/handler_test.go b/instrumentation/net/http/otelhttp/test/handler_test.go index 2fe39555243..e500b493d42 100644 --- a/instrumentation/net/http/otelhttp/test/handler_test.go +++ b/instrumentation/net/http/otelhttp/test/handler_test.go @@ -21,7 +21,7 @@ import ( "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" - "go.opentelemetry.io/otel/sdk/metric" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -80,8 +80,8 @@ func TestHandlerBasics(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) - reader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + reader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -469,8 +469,8 @@ func TestWithRouteTag(t *testing.T) { tracerProvider := sdktrace.NewTracerProvider() tracerProvider.RegisterSpanProcessor(spanRecorder) - metricReader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader)) + metricReader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(metricReader)) h := otelhttp.NewHandler( otelhttp.WithRouteTag( @@ -528,3 +528,44 @@ func TestWithRouteTag(t *testing.T) { } } } + +func BenchmarkHandlerServeHTTP(b *testing.B) { + tp := sdktrace.NewTracerProvider() + mp := sdkmetric.NewMeterProvider() + + r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) + require.NoError(b, err) + + for _, bb := range []struct { + name string + handler http.Handler + }{ + { + name: "without the otelhttp handler", + handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello World") + }), + }, + { + name: "with the otelhttp handler", + handler: otelhttp.NewHandler( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello World") + }), + "test_handler", + otelhttp.WithTracerProvider(tp), + otelhttp.WithMeterProvider(mp), + ), + }, + } { + b.Run(bb.name, func(b *testing.B) { + rr := httptest.NewRecorder() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bb.handler.ServeHTTP(rr, r) + } + }) + } +} diff --git a/instrumentation/net/http/otelhttp/test/transport_test.go b/instrumentation/net/http/otelhttp/test/transport_test.go index d96aedb29ba..cfbc27cc2b2 100644 --- a/instrumentation/net/http/otelhttp/test/transport_test.go +++ b/instrumentation/net/http/otelhttp/test/transport_test.go @@ -6,6 +6,7 @@ package test import ( "bytes" "context" + "fmt" "io" "net" "net/http" @@ -16,7 +17,6 @@ import ( "strings" "testing" - "go.opentelemetry.io/otel/sdk/metric" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "github.com/stretchr/testify/assert" @@ -24,14 +24,15 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) @@ -244,8 +245,8 @@ func TestTransportMetrics(t *testing.T) { responseBody := []byte("Hello, world!") t.Run("make http request and read entire response at once", func(t *testing.T) { - reader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + reader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -302,8 +303,8 @@ func TestTransportMetrics(t *testing.T) { }) t.Run("make http request and buffer response", func(t *testing.T) { - reader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + reader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -370,8 +371,8 @@ func TestTransportMetrics(t *testing.T) { }) t.Run("make http request and close body before reading completely", func(t *testing.T) { - reader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + reader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -484,8 +485,8 @@ func TestCustomAttributesHandling(t *testing.T) { clientDuration = "http.client.duration" ) ctx := context.TODO() - reader := metric.NewManualReader() - provider := metric.NewMeterProvider(metric.WithReader(reader)) + reader := sdkmetric.NewManualReader() + provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) if err != nil { @@ -550,3 +551,44 @@ func TestCustomAttributesHandling(t *testing.T) { } } } + +func BenchmarkTransportRoundTrip(b *testing.B) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello World") + })) + defer ts.Close() + + tp := sdktrace.NewTracerProvider() + mp := sdkmetric.NewMeterProvider() + + r, err := http.NewRequest(http.MethodGet, ts.URL, nil) + require.NoError(b, err) + + for _, bb := range []struct { + name string + transport http.RoundTripper + }{ + { + name: "without the otelhttp transport", + transport: http.DefaultTransport, + }, + { + name: "with the otelhttp transport", + transport: otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithTracerProvider(tp), + otelhttp.WithMeterProvider(mp), + ), + }, + } { + b.Run(bb.name, func(b *testing.B) { + c := http.Client{Transport: bb.transport} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = c.Do(r) + } + }) + } +}