diff --git a/instrumentation/net/http/otelhttp/test/transport_test.go b/instrumentation/net/http/otelhttp/test/transport_test.go index f0d07b68926..641ebce808e 100644 --- a/instrumentation/net/http/otelhttp/test/transport_test.go +++ b/instrumentation/net/http/otelhttp/test/transport_test.go @@ -17,6 +17,8 @@ package test import ( "bytes" "context" + "go.opentelemetry.io/otel/sdk/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "io" "net" "net/http" @@ -27,15 +29,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" - "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/codes" @@ -250,64 +249,139 @@ func TestWithHTTPTrace(t *testing.T) { } func TestTransportMetrics(t *testing.T) { - reader := metric.NewManualReader() - meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + requestBody := []byte("john") responseBody := []byte("Hello, world!") - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - if _, err := w.Write(responseBody); err != nil { + t.Run("make http request and read entire response at once", func(t *testing.T) { + reader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write(responseBody); err != nil { + t.Fatal(err) + } + })) + defer ts.Close() + + r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) + if err != nil { t.Fatal(err) } - })) - defer ts.Close() - requestBody := []byte("john") - r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) - if err != nil { - t.Fatal(err) - } + tr := otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithMeterProvider(meterProvider), + ) - tr := otelhttp.NewTransport( - http.DefaultTransport, - otelhttp.WithMeterProvider(meterProvider), - ) + c := http.Client{Transport: tr} + res, err := c.Do(r) + if err != nil { + t.Fatal(err) + } - c := http.Client{Transport: tr} - res, err := c.Do(r) - if err != nil { - t.Fatal(err) - } + // Must read the body or else we won't get response metrics + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + require.Len(t, bodyBytes, 13) + require.NoError(t, res.Body.Close()) - // Must read the body or else we won't get response metrics - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - require.Len(t, bodyBytes, 13) - require.NoError(t, res.Body.Close()) + host, portStr, _ := net.SplitHostPort(r.Host) + if host == "" { + host = "127.0.0.1" + } + port, err := strconv.Atoi(portStr) + if err != nil { + port = 0 + } - host, portStr, _ := net.SplitHostPort(r.Host) - if host == "" { - host = "127.0.0.1" - } - port, err := strconv.Atoi(portStr) - if err != nil { - port = 0 - } + rm := metricdata.ResourceMetrics{} + err = reader.Collect(context.Background(), &rm) + require.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + attrs := attribute.NewSet( + semconv.NetPeerName(host), + semconv.NetPeerPort(port), + semconv.HTTPMethod("GET"), + semconv.HTTPStatusCode(200), + ) + assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) + }) + + t.Run("make http request and buffer response", func(t *testing.T) { + reader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(reader)) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write(responseBody); err != nil { + t.Fatal(err) + } + })) + defer ts.Close() + + r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) + if err != nil { + t.Fatal(err) + } + + tr := otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithMeterProvider(meterProvider), + ) + + c := http.Client{Transport: tr} + res, err := c.Do(r) + if err != nil { + t.Fatal(err) + } + + // Must read the body or else we won't get response metrics + var buf []byte + smallBuf := make([]byte, 10) + + // Read first 10 bytes + bc, err := res.Body.Read(smallBuf) + if err != nil { + t.Fatal(err) + } + require.Equal(t, 10, bc) + buf = append(buf, smallBuf...) + + // reset byte array + // Read last 3 bytes + bc, err = res.Body.Read(smallBuf) + require.Equal(t, io.EOF, err) + require.Equal(t, 3, bc) + buf = append(buf, smallBuf[:bc]...) + + require.NoError(t, res.Body.Close()) + + host, portStr, _ := net.SplitHostPort(r.Host) + if host == "" { + host = "127.0.0.1" + } + port, err := strconv.Atoi(portStr) + if err != nil { + port = 0 + } + + rm := metricdata.ResourceMetrics{} + err = reader.Collect(context.Background(), &rm) + require.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + attrs := attribute.NewSet( + semconv.NetPeerName(host), + semconv.NetPeerPort(port), + semconv.HTTPMethod("GET"), + semconv.HTTPStatusCode(200), + ) + assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) + }) - rm := metricdata.ResourceMetrics{} - err = reader.Collect(context.Background(), &rm) - require.NoError(t, err) - require.Len(t, rm.ScopeMetrics, 1) - attrs := attribute.NewSet( - semconv.NetPeerName(host), - semconv.NetPeerPort(port), - semconv.HTTPMethod("GET"), - semconv.HTTPStatusCode(200), - ) - assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) } func assertClientScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set) {