@@ -18,9 +18,16 @@ import (
18
18
"bytes"
19
19
"context"
20
20
"errors"
21
+ "fmt"
22
+ "go.opentelemetry.io/otel/attribute"
23
+ "go.opentelemetry.io/otel/sdk/instrumentation"
24
+ "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
25
+ semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
21
26
"io"
27
+ "net"
22
28
"net/http"
23
29
"net/http/httptest"
30
+ "strconv"
24
31
"strings"
25
32
"testing"
26
33
@@ -30,6 +37,9 @@ import (
30
37
"go.opentelemetry.io/otel/codes"
31
38
"go.opentelemetry.io/otel/propagation"
32
39
"go.opentelemetry.io/otel/trace"
40
+
41
+ "go.opentelemetry.io/otel/sdk/metric"
42
+ "go.opentelemetry.io/otel/sdk/metric/metricdata"
33
43
)
34
44
35
45
func TestTransportFormatter (t * testing.T ) {
@@ -432,3 +442,121 @@ func TestTransportOriginRequestNotModify(t *testing.T) {
432
442
433
443
assert .Equal (t , expectedRequest , r )
434
444
}
445
+
446
+ func TestTransportMetrics (t * testing.T ) {
447
+ reader := metric .NewManualReader ()
448
+ meterProvider := metric .NewMeterProvider (metric .WithReader (reader ))
449
+
450
+ responseBody := []byte ("Hello, world!" )
451
+
452
+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
453
+ w .WriteHeader (http .StatusOK )
454
+ if _ , err := w .Write (responseBody ); err != nil {
455
+ t .Fatal (err )
456
+ }
457
+ }))
458
+ defer ts .Close ()
459
+
460
+ requestBody := []byte ("john" )
461
+ r , err := http .NewRequest (http .MethodGet , ts .URL , bytes .NewReader (requestBody ))
462
+ if err != nil {
463
+ t .Fatal (err )
464
+ }
465
+
466
+ tr := NewTransport (
467
+ http .DefaultTransport ,
468
+ WithMeterProvider (meterProvider ),
469
+ )
470
+
471
+ c := http.Client {Transport : tr }
472
+ res , err := c .Do (r )
473
+ if err != nil {
474
+ t .Fatal (err )
475
+ }
476
+
477
+ // Must read the body or else we won't get response metrics
478
+ bodyBytes , err := io .ReadAll (res .Body )
479
+ if err != nil {
480
+ t .Fatal (err )
481
+ }
482
+ require .Len (t , bodyBytes , 13 )
483
+ require .NoError (t , res .Body .Close ())
484
+
485
+ host , portStr , _ := net .SplitHostPort (r .Host )
486
+ if host == "" {
487
+ host = "127.0.0.1"
488
+ }
489
+ port , err := strconv .Atoi (portStr )
490
+ if err != nil {
491
+ port = 0
492
+ }
493
+
494
+ rm := metricdata.ResourceMetrics {}
495
+ err = reader .Collect (context .Background (), & rm )
496
+ require .NoError (t , err )
497
+ require .Len (t , rm .ScopeMetrics , 1 )
498
+ attrs := attribute .NewSet (
499
+ semconv .NetPeerName (host ),
500
+ semconv .NetPeerPort (port ),
501
+ semconv .HTTPMethod ("GET" ),
502
+ semconv .HTTPStatusCode (200 ),
503
+ )
504
+ assertClientScopeMetrics (t , rm .ScopeMetrics [0 ], attrs )
505
+ }
506
+
507
+ func assertClientScopeMetrics (t * testing.T , sm metricdata.ScopeMetrics , attrs attribute.Set ) {
508
+
509
+ assert .Equal (t , instrumentation.Scope {
510
+ Name : "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ,
511
+ Version : Version (),
512
+ }, sm .Scope )
513
+
514
+ require .Len (t , sm .Metrics , 3 )
515
+
516
+ want := metricdata.Metrics {
517
+ Name : "http.client.request_content_length" ,
518
+ Data : metricdata.Sum [int64 ]{
519
+ DataPoints : []metricdata.DataPoint [int64 ]{{Attributes : attrs , Value : 4 }},
520
+ Temporality : metricdata .CumulativeTemporality ,
521
+ IsMonotonic : true ,
522
+ },
523
+ Description : "Measures the size of HTTP request content length (uncompressed)" ,
524
+ Unit : "By" ,
525
+ }
526
+ metricdatatest .AssertEqual (t , want , sm .Metrics [0 ], metricdatatest .IgnoreTimestamp ())
527
+
528
+ want = metricdata.Metrics {
529
+ Name : "http.client.response_content_length" ,
530
+ Data : metricdata.Sum [int64 ]{
531
+ DataPoints : []metricdata.DataPoint [int64 ]{{Attributes : attrs , Value : 13 }},
532
+ Temporality : metricdata .CumulativeTemporality ,
533
+ IsMonotonic : true ,
534
+ },
535
+ Description : "Measures the size of HTTP response content length (uncompressed)" ,
536
+ Unit : "By" ,
537
+ }
538
+ metricdatatest .AssertEqual (t , want , sm .Metrics [1 ], metricdatatest .IgnoreTimestamp ())
539
+
540
+ dur := sm .Metrics [2 ]
541
+ assert .Equal (t , "http.client.duration" , dur .Name )
542
+ require .IsType (t , dur .Data , metricdata.Histogram [float64 ]{})
543
+ hist := dur .Data .(metricdata.Histogram [float64 ])
544
+ assert .Equal (t , metricdata .CumulativeTemporality , hist .Temporality )
545
+ require .Len (t , hist .DataPoints , 1 )
546
+ dPt := hist .DataPoints [0 ]
547
+ assert .Equal (t , attrs , dPt .Attributes , "attributes" )
548
+ assert .Equal (t , uint64 (1 ), dPt .Count , "count" )
549
+ assert .Equal (t , []float64 {0 , 5 , 10 , 25 , 50 , 75 , 100 , 250 , 500 , 750 , 1000 , 2500 , 5000 , 7500 , 10000 }, dPt .Bounds , "bounds" )
550
+
551
+ t .Log (fmt .Sprintf ("%+v" , dPt .BucketCounts ))
552
+
553
+ // Duration value is not deterministic because the code does not support a pluggable clock.
554
+ // So just value that one of the buckets has been incremented.
555
+ bucketSum := uint64 (0 )
556
+ for _ , bucketCount := range dPt .BucketCounts {
557
+ bucketSum += bucketCount
558
+ }
559
+
560
+ require .Equal (t , uint64 (1 ), bucketSum )
561
+
562
+ }
0 commit comments