From 8612538f332b77a92746c935c1f29d41d65b9799 Mon Sep 17 00:00:00 2001 From: AdityaS8804 Date: Thu, 27 Feb 2025 19:17:23 +0530 Subject: [PATCH] Changed thrift_processor_test.go --- receiver/jaegerreceiver/go.mod | 3 +- receiver/jaegerreceiver/go.sum | 89 ++-- .../buffered_read_transport.go | 68 +++ .../buffered_read_transport_test.go | 67 +++ .../internal/cmd/processors/package_test.go | 15 + .../internal/cmd/processors/processor.go | 12 + .../cmd/processors/thrift_processor.go | 121 ++++++ .../cmd/processors/thrift_processor_test.go | 213 ++++++++++ .../internal/cmd/servers/server.go | 42 ++ .../internal/cmd/servers/server_test.go | 36 ++ .../internal/cmd/servers/tbuffered_server.go | 144 +++++++ .../cmd/servers/tbuffered_server_test.go | 166 ++++++++ .../cmd/servers/thriftudp/socket_buffer.go | 33 ++ .../thriftudp/socket_buffer_windows.go | 14 + .../cmd/servers/thriftudp/transport.go | 155 +++++++ .../cmd/servers/thriftudp/transport_test.go | 237 +++++++++++ .../internal/cmd/testdata/README.md | 15 + .../internal/cmd/testdata/bad-CA-cert.txt | 3 + .../internal/cmd/testdata/example-CA-cert.pem | 20 + .../cmd/testdata/example-client-cert.pem | 20 + .../cmd/testdata/example-client-key.pem | 27 ++ .../cmd/testdata/example-server-cert.pem | 20 + .../cmd/testdata/example-server-key.pem | 27 ++ .../internal/cmd/testdata/gen-certs.sh | 122 ++++++ .../internal/cmd/testdata/wrong-CA-cert.pem | 20 + .../cmd/testutils/in_memory_reporter.go | 59 +++ .../cmd/testutils/in_memory_reporter_test.go | 33 ++ .../internal/cmd/testutils/leakcheck.go | 85 ++++ .../internal/cmd/testutils/leakcheck_test.go | 17 + .../internal/cmd/testutils/logger.go | 113 +++++ .../internal/cmd/testutils/logger_test.go | 89 ++++ .../internal/cmd/testutils/mock_collector.go | 58 +++ .../internal/cmd/testutils/mock_reporter.go | 80 ++++ .../cmd/testutils/thriftudp_client.go | 38 ++ .../cmd/testutils/thriftudp_client_test.go | 33 ++ .../internal/internal/metricstest/keys.go | 26 ++ .../internal/internal/metricstest/local.go | 387 ++++++++++++++++++ .../internal/metricstest/local_test.go | 164 ++++++++ .../internal/metricstest/metricstest.go | 44 ++ .../internal/metricstest/metricstest_test.go | 19 + .../internal/metricstest/package_test.go | 15 + .../internal/metrics/metrics_test.go | 140 +++++++ .../internal/pkg/metrics/counter.go | 19 + .../internal/pkg/metrics/factory.go | 72 ++++ .../internal/pkg/metrics/gauge.go | 19 + .../internal/pkg/metrics/histogram.go | 18 + .../internal/pkg/metrics/metrics.go | 130 ++++++ .../internal/pkg/metrics/metrics_test.go | 140 +++++++ .../internal/pkg/metrics/package.go | 8 + .../internal/pkg/metrics/stopwatch.go | 34 ++ .../internal/pkg/metrics/timer.go | 24 ++ receiver/jaegerreceiver/jaeger_agent_test.go | 2 +- receiver/jaegerreceiver/trace_receiver.go | 8 +- 53 files changed, 3507 insertions(+), 56 deletions(-) create mode 100755 receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport.go create mode 100755 receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/processors/package_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/processors/processor.go create mode 100755 receiver/jaegerreceiver/internal/cmd/processors/thrift_processor.go create mode 100755 receiver/jaegerreceiver/internal/cmd/processors/thrift_processor_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/server.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/server_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer_windows.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport.go create mode 100755 receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/README.md create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/bad-CA-cert.txt create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/example-CA-cert.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/example-client-cert.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/example-client-key.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/example-server-cert.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/example-server-key.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/gen-certs.sh create mode 100755 receiver/jaegerreceiver/internal/cmd/testdata/wrong-CA-cert.pem create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/leakcheck.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/leakcheck_test.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/logger.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/logger_test.go create mode 100644 receiver/jaegerreceiver/internal/cmd/testutils/mock_collector.go create mode 100644 receiver/jaegerreceiver/internal/cmd/testutils/mock_reporter.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client.go create mode 100755 receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client_test.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/keys.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/local.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/local_test.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/metricstest.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/metricstest_test.go create mode 100755 receiver/jaegerreceiver/internal/internal/metricstest/package_test.go create mode 100644 receiver/jaegerreceiver/internal/metrics/metrics_test.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/counter.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/factory.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/gauge.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/histogram.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/metrics.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/metrics_test.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/package.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/stopwatch.go create mode 100755 receiver/jaegerreceiver/internal/pkg/metrics/timer.go diff --git a/receiver/jaegerreceiver/go.mod b/receiver/jaegerreceiver/go.mod index 5a8695fc09be..9e88cf37b1bf 100644 --- a/receiver/jaegerreceiver/go.mod +++ b/receiver/jaegerreceiver/go.mod @@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaeger go 1.23.0 require ( + github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/apache/thrift v0.21.0 github.com/gorilla/mux v1.8.1 github.com/jaegertracing/jaeger v1.66.0 @@ -11,7 +12,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.120.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/collector/component v0.120.1-0.20250226024140-8099e51f9a77 - go.opentelemetry.io/collector/component/componentstatus v0.120.1-0.20250226024140-8099e51f9a77 + go.opentelemetry.io/collector/component/componentstatus v0.119.0 go.opentelemetry.io/collector/component/componenttest v0.120.1-0.20250226024140-8099e51f9a77 go.opentelemetry.io/collector/config/configgrpc v0.120.1-0.20250226024140-8099e51f9a77 go.opentelemetry.io/collector/config/confighttp v0.120.1-0.20250226024140-8099e51f9a77 diff --git a/receiver/jaegerreceiver/go.sum b/receiver/jaegerreceiver/go.sum index 30e1246e2e9d..be1898343e4b 100644 --- a/receiver/jaegerreceiver/go.sum +++ b/receiver/jaegerreceiver/go.sum @@ -1,19 +1,21 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -25,33 +27,28 @@ github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0 github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= -github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jaegertracing/jaeger v1.66.0 h1:tmgkukU+YMdrhXyKC7O96GshvuSl9+6fB8ZzucLKKdM= github.com/jaegertracing/jaeger v1.66.0/go.mod h1:BVwtpsjm+8rky99h+dJ0fAb5OSl4vbCgAKgTV2WGlmU= github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -64,14 +61,12 @@ github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= -github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -81,49 +76,21 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I= github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -133,8 +100,8 @@ go.opentelemetry.io/collector/client v1.26.1-0.20250226024140-8099e51f9a77 h1:ky go.opentelemetry.io/collector/client v1.26.1-0.20250226024140-8099e51f9a77/go.mod h1:H7dkvh+4BbglV1QiyI+AD/aWuqJ3iE5oiYr5oDKtBLw= go.opentelemetry.io/collector/component v0.120.1-0.20250226024140-8099e51f9a77 h1:yz63enLYYcZkHQ+5GZKL2YUf1fqrwb0OKBQMdIRMF48= go.opentelemetry.io/collector/component v0.120.1-0.20250226024140-8099e51f9a77/go.mod h1:Ya5O+5NWG9XdhJPnOVhKtBrNXHN3hweQbB98HH4KPNU= -go.opentelemetry.io/collector/component/componentstatus v0.120.1-0.20250226024140-8099e51f9a77 h1:VqZscK/gQc2thbK/FIoLX5ZPvxq/Tufo3FDWFKFf0l8= -go.opentelemetry.io/collector/component/componentstatus v0.120.1-0.20250226024140-8099e51f9a77/go.mod h1:kbuAEddxvcyjGLXGmys3nckAj4jTGC0IqDIEXAOr3Ag= +go.opentelemetry.io/collector/component/componentstatus v0.119.0 h1:H8isEInGaWhnDfuG1Ax663dlsPgF4aM20sgraM6HmSI= +go.opentelemetry.io/collector/component/componentstatus v0.119.0/go.mod h1:Hr7scHUFPhyT32IkzKq06cdhRH9jMKvnKbDVYRUEnqE= go.opentelemetry.io/collector/component/componenttest v0.120.1-0.20250226024140-8099e51f9a77 h1:acRutss2nHDMMJBG1rgNq/Gc0QvntS4ERonMxqsAyN8= go.opentelemetry.io/collector/component/componenttest v0.120.1-0.20250226024140-8099e51f9a77/go.mod h1:STkHntimFEYz+yFBxNXmZUIoUog7gW0PQpyzXMccTl8= go.opentelemetry.io/collector/config/configauth v0.120.1-0.20250226024140-8099e51f9a77 h1:bN2RbdDNIRjk8ksh0v+++t3/ONylOaHnNsME+nQy/SM= @@ -208,10 +175,21 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -224,6 +202,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= @@ -232,7 +211,10 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -240,6 +222,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= @@ -247,9 +235,10 @@ google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40Rmc google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport.go b/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport.go new file mode 100755 index 000000000000..ea8335d5516c --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport.go @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package customtransport + +import ( + "bytes" + "context" + + "github.com/apache/thrift/lib/go/thrift" +) + +// TBufferedReadTransport is a thrift.TTransport that reads from a buffer +type TBufferedReadTransport struct { + readBuf *bytes.Buffer +} + +var _ thrift.TTransport = (*TBufferedReadTransport)(nil) + +// NewTBufferedReadTransport creates a buffer backed TTransport +func NewTBufferedReadTransport(readBuf *bytes.Buffer) (*TBufferedReadTransport, error) { + return &TBufferedReadTransport{readBuf: readBuf}, nil +} + +// IsOpen does nothing as transport is not maintaining the connection +// Required to maintain thrift.TTransport interface +func (*TBufferedReadTransport) IsOpen() bool { + return true +} + +// Open does nothing as transport is not maintaining the connection +// Required to maintain thrift.TTransport interface +func (*TBufferedReadTransport) Open() error { + return nil +} + +// Close does nothing as transport is not maintaining the connection +// Required to maintain thrift.TTransport interface +func (*TBufferedReadTransport) Close() error { + return nil +} + +// Read reads bytes from the local buffer and puts them in the specified buf +func (p *TBufferedReadTransport) Read(buf []byte) (int, error) { + in, err := p.readBuf.Read(buf) + return in, thrift.NewTTransportExceptionFromError(err) +} + +// RemainingBytes returns the number of bytes left to be read from the readBuf +func (p *TBufferedReadTransport) RemainingBytes() uint64 { + //nolint: gosec // G115 + return uint64(p.readBuf.Len()) +} + +// Write writes bytes into the read buffer +// Required to maintain thrift.TTransport interface +func (p *TBufferedReadTransport) Write(buf []byte) (int, error) { + p.readBuf = bytes.NewBuffer(buf) + return len(buf), nil +} + +// Flush does nothing as udp server does not write responses back +// Required to maintain thrift.TTransport interface +func (*TBufferedReadTransport) Flush(_ context.Context) error { + return nil +} diff --git a/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport_test.go b/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport_test.go new file mode 100755 index 000000000000..d9e49e01e679 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/customtransport/buffered_read_transport_test.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package customtransport + +import ( + "bytes" + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" +) + +// TestTBufferedReadTransport tests the TBufferedReadTransport +func TestTBufferedReadTransport(t *testing.T) { + buffer := bytes.NewBuffer([]byte("testString")) + trans, err := NewTBufferedReadTransport(buffer) + require.NotNil(t, trans) + require.NoError(t, err) + require.Equal(t, uint64(10), trans.RemainingBytes()) + + firstRead := make([]byte, 4) + n, err := trans.Read(firstRead) + require.NoError(t, err) + require.Equal(t, 4, n) + require.Equal(t, []byte("test"), firstRead) + require.Equal(t, uint64(6), trans.RemainingBytes()) + + secondRead := make([]byte, 7) + n, err = trans.Read(secondRead) + require.NoError(t, err) + require.Equal(t, 6, n) + require.Equal(t, []byte("String"), secondRead[0:6]) + require.Equal(t, uint64(0), trans.RemainingBytes()) +} + +// TestTBufferedReadTransportEmptyFunctions tests the empty functions in TBufferedReadTransport +func TestTBufferedReadTransportEmptyFunctions(t *testing.T) { + byteArr := make([]byte, 1) + trans, err := NewTBufferedReadTransport(bytes.NewBuffer(byteArr)) + require.NotNil(t, trans) + require.NoError(t, err) + + err = trans.Open() + require.NoError(t, err) + + err = trans.Close() + require.NoError(t, err) + + err = trans.Flush(context.Background()) + require.NoError(t, err) + + n, err := trans.Write(byteArr) + require.Equal(t, 1, n) + require.NoError(t, err) + + isOpen := trans.IsOpen() + require.True(t, isOpen) +} + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/cmd/processors/package_test.go b/receiver/jaegerreceiver/internal/cmd/processors/package_test.go new file mode 100755 index 000000000000..469a1c30d5e1 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/processors/package_test.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2024 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package processors + +import ( + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" +) + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/cmd/processors/processor.go b/receiver/jaegerreceiver/internal/cmd/processors/processor.go new file mode 100755 index 000000000000..7bd02b6dc355 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/processors/processor.go @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package processors + +// Processor processes metrics in multiple formats +type Processor interface { + Serve() + Stop() +} diff --git a/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor.go b/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor.go new file mode 100755 index 000000000000..89d89f9bb301 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package processors + +import ( + "context" + "fmt" + "sync" + + "github.com/apache/thrift/lib/go/thrift" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/customtransport" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +// ThriftProcessor is a server that processes spans using a TBuffered Server +type ThriftProcessor struct { + server servers.Server + handler AgentProcessor + protocolPool *sync.Pool + numProcessors int + processing sync.WaitGroup + logger *zap.Logger + metrics struct { + // Amount of time taken for processor to close + ProcessorCloseTimer metrics.Timer `metric:"thrift.udp.t-processor.close-time"` + + // Number of failed buffer process operations + HandlerProcessError metrics.Counter `metric:"thrift.udp.t-processor.handler-errors"` + } +} + +// AgentProcessor handler used by the processor to process thrift and call the reporter +// with the deserialized struct. This interface is implemented directly by Thrift generated +// code, e.g. jaegerThrift.NewAgentProcessor(handler), where handler implements the Agent +// Thrift service interface, which is invoked with the deserialized struct. +type AgentProcessor interface { + Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) +} + +// NewThriftProcessor creates a TBufferedServer backed ThriftProcessor +func NewThriftProcessor( + server servers.Server, + numProcessors int, + mFactory metrics.Factory, + factory thrift.TProtocolFactory, + handler AgentProcessor, + logger *zap.Logger, +) (*ThriftProcessor, error) { + if numProcessors <= 0 { + return nil, fmt.Errorf( + "number of processors must be greater than 0, called with %d", numProcessors) + } + protocolPool := &sync.Pool{ + New: func() any { + trans := &customtransport.TBufferedReadTransport{} + return factory.GetProtocol(trans) + }, + } + + res := &ThriftProcessor{ + server: server, + handler: handler, + protocolPool: protocolPool, + logger: logger, + numProcessors: numProcessors, + } + metrics.Init(&res.metrics, mFactory, nil) + res.processing.Add(res.numProcessors) + for i := 0; i < res.numProcessors; i++ { + go func() { + res.processBuffer() + res.processing.Done() + }() + } + return res, nil +} + +// Serve starts serving traffic +func (s *ThriftProcessor) Serve() { + s.server.Serve() +} + +// IsServing indicates whether the server is currently serving traffic +func (s *ThriftProcessor) IsServing() bool { + return s.server.IsServing() +} + +// Stop stops the serving of traffic and waits until the queue is +// emptied by the readers +func (s *ThriftProcessor) Stop() { + stopwatch := metrics.StartStopwatch(s.metrics.ProcessorCloseTimer) + s.server.Stop() + s.processing.Wait() + stopwatch.Stop() +} + +// processBuffer reads data off the channel and puts it into a custom transport for +// the processor to process +func (s *ThriftProcessor) processBuffer() { + for readBuf := range s.server.DataChan() { + protocol := s.protocolPool.Get().(thrift.TProtocol) + payload := readBuf.GetBytes() + protocol.Transport().Write(payload) + s.logger.Debug("Span(s) received by the agent", zap.Int("bytes-received", len(payload))) + + // NB: oddly, thrift-gen/agent/agent.go:L156 does this: `return true, thrift.WrapTException(err2)` + // So we check for both OK and error. + if ok, err := s.handler.Process(context.Background(), protocol, protocol); !ok || err != nil { + s.logger.Error("Processor failed", zap.Error(err)) + s.metrics.HandlerProcessError.Inc(1) + } + s.protocolPool.Put(protocol) + s.server.DataRecd(readBuf) // acknowledge receipt and release the buffer + } +} diff --git a/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor_test.go b/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor_test.go new file mode 100755 index 000000000000..1c609a508e23 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/processors/thrift_processor_test.go @@ -0,0 +1,213 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package processors + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + + "github.com/jaegertracing/jaeger-idl/thrift-gen/agent" + "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" + "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers/thriftudp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/internal/metricstest" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +// TODO make these tests faster, they take almost 4 seconds + +var ( + compactFactory = thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{}) + binaryFactory = thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}) + + testSpanName = "span1" + + batch = &jaeger.Batch{ + Process: jaeger.NewProcess(), + Spans: []*jaeger.Span{{OperationName: testSpanName}}, + } +) + +func createProcessor(t *testing.T, mFactory metrics.Factory, tFactory thrift.TProtocolFactory, handler AgentProcessor) (string, Processor) { + transport, err := thriftudp.NewTUDPServerTransport("127.0.0.1:0") + require.NoError(t, err) + + queueSize := 10 + maxPacketSize := 65000 + server, err := servers.NewTBufferedServer(transport, queueSize, maxPacketSize, mFactory) + require.NoError(t, err) + + numProcessors := 1 + processor, err := NewThriftProcessor(server, numProcessors, mFactory, tFactory, handler, zaptest.NewLogger(t)) + require.NoError(t, err) + + go processor.Serve() + for i := 0; i < 1000; i++ { + if processor.IsServing() { + break + } + time.Sleep(10 * time.Microsecond) + } + require.True(t, processor.IsServing(), "processor must be serving") + + return transport.Addr().String(), processor +} + +// revive:disable-next-line function-result-limit +func initCollectorAndReporter(t *testing.T) (*metricstest.Factory, *testutils.MockCollector, *agent.AgentProcessor) { + mockCollector := testutils.StartMockCollector(t) + logger := zaptest.NewLogger(t) + + reporter := testutils.NewMockReporter(mockCollector, logger) + metricsFactory := metricstest.NewFactory(0) + + agentProcessor := agent.NewAgentProcessor(reporter) + + return metricsFactory, mockCollector, agentProcessor +} +func TestNewThriftProcessor_ZeroCount(t *testing.T) { + _, err := NewThriftProcessor(nil, 0, nil, nil, nil, zaptest.NewLogger(t)) + require.EqualError(t, err, "number of processors must be greater than 0, called with 0") +} + +func TestProcessorWithCompactZipkin(t *testing.T) { + metricsFactory, collector, agentProcessor := initCollectorAndReporter(t) + defer collector.Close() + + hostPort, processor := createProcessor(t, metricsFactory, compactFactory, agentProcessor) + defer processor.Stop() + + client, clientCloser, err := testutils.NewZipkinThriftUDPClient(hostPort) + require.NoError(t, err) + defer clientCloser.Close() + + span := zipkincore.NewSpan() + span.Name = testSpanName + span.Annotations = []*zipkincore.Annotation{{Value: zipkincore.CLIENT_SEND, Host: &zipkincore.Endpoint{ServiceName: "foo"}}} + + err = client.EmitZipkinBatch(context.Background(), []*zipkincore.Span{span}) + require.NoError(t, err) + + assertZipkinProcessorCorrectness(t, collector, metricsFactory) +} + + +type failingHandler struct { + err error +} + +func (h failingHandler) Process(context.Context, thrift.TProtocol /* iprot */, thrift.TProtocol /* oprot */) (success bool, err thrift.TException) { + return false, thrift.NewTApplicationException(0, h.err.Error()) +} + +func TestProcessor_HandlerError(t *testing.T) { + metricsFactory := metricstest.NewFactory(0) + + handler := failingHandler{err: errors.New("doh")} + + hostPort, processor := createProcessor(t, metricsFactory, compactFactory, handler) + defer processor.Stop() + + client, clientCloser, err := testutils.NewZipkinThriftUDPClient(hostPort) + require.NoError(t, err) + defer clientCloser.Close() + + err = client.EmitZipkinBatch(context.Background(), []*zipkincore.Span{{Name: testSpanName}}) + require.NoError(t, err) + + for i := 0; i < 10; i++ { + c, _ := metricsFactory.Snapshot() + if _, ok := c["thrift.udp.t-processor.handler-errors"]; ok { + break + } + time.Sleep(time.Millisecond) + } + + metricsFactory.AssertCounterMetrics(t, + metricstest.ExpectedMetric{Name: "thrift.udp.t-processor.handler-errors", Value: 1}, + metricstest.ExpectedMetric{Name: "thrift.udp.server.packets.processed", Value: 1}, + ) +} + +// TestJaegerProcessor instantiates a real UDP receiver and a mock collector +// and executes end-to-end batch submission. +func TestJaegerProcessor(t *testing.T) { + tests := []struct { + factory thrift.TProtocolFactory + name string + }{ + {compactFactory, "compact"}, + {binaryFactory, "binary"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + metricsFactory, collector, agentProcessor := initCollectorAndReporter(t) + defer collector.Close() + + hostPort, processor := createProcessor(t, metricsFactory, test.factory, agentProcessor) + defer processor.Stop() + + client, clientCloser, err := testutils.NewJaegerThriftUDPClient(hostPort, test.factory) + require.NoError(t, err) + defer clientCloser.Close() + + err = client.EmitBatch(context.Background(), batch) + require.NoError(t, err) + + assertJaegerProcessorCorrectness(t, collector, metricsFactory) + }) + } +} +func assertJaegerProcessorCorrectness(t *testing.T, collector *testutils.MockCollector, metricsFactory *metricstest.Factory) { + sizeF := func() int { + return len(collector.GetJaegerBatches()) + } + nameF := func() string { + return collector.GetJaegerBatches()[0].Spans[0].OperationName + } + assertCollectorReceivedData(t, metricsFactory, sizeF, nameF, "jaeger") +} + +func assertZipkinProcessorCorrectness(t *testing.T, collector *testutils.MockCollector, metricsFactory *metricstest.Factory) { + sizeF := func() int { + return len(collector.GetJaegerBatches()) + } + nameF := func() string { + return collector.GetJaegerBatches()[0].Spans[0].OperationName + } + assertCollectorReceivedData(t, metricsFactory, sizeF, nameF, "zipkin") +} + +// Simplify the metrics assertions as needed +func assertCollectorReceivedData( + t *testing.T, + metricsFactory *metricstest.Factory, + sizeF func() int, + nameF func() string, + format string, +) { + require.Eventually(t, + func() bool { return sizeF() == 1 }, + 5*time.Second, + time.Millisecond, + "server should have received spans") + assert.Equal(t, testSpanName, nameF()) + + // Only check server packet metrics since we removed the reporter metrics + metricsFactory.AssertCounterMetrics(t, []metricstest.ExpectedMetric{ + {Name: "thrift.udp.server.packets.processed", Value: 1}, + }...) +} \ No newline at end of file diff --git a/receiver/jaegerreceiver/internal/cmd/servers/server.go b/receiver/jaegerreceiver/internal/cmd/servers/server.go new file mode 100755 index 000000000000..5e939a6c34bf --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/server.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package servers + +import ( + "io" +) + +// Server is the interface for servers that receive inbound span submissions from client. +type Server interface { + Serve() + IsServing() bool + Stop() + DataChan() chan *ReadBuf + DataRecd(*ReadBuf) // must be called by consumer after reading data from the ReadBuf +} + +// ReadBuf is a structure that holds the bytes to read into as well as the number of bytes +// that was read. The slice is typically pre-allocated to the max packet size and the buffers +// themselves are polled to avoid memory allocations for every new inbound message. +type ReadBuf struct { + bytes []byte + n int +} + +// GetBytes returns the contents of the ReadBuf as bytes +func (r *ReadBuf) GetBytes() []byte { + return r.bytes[:r.n] +} + +func (r *ReadBuf) Read(p []byte) (int, error) { + if r.n == 0 { + return 0, io.EOF + } + n := r.n + copied := copy(p, r.bytes[:n]) + r.n -= copied + return n, nil +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/server_test.go b/receiver/jaegerreceiver/internal/cmd/servers/server_test.go new file mode 100755 index 000000000000..d83c97b7067c --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/server_test.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package servers + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" +) + +func TestReadBuf_EOF(t *testing.T) { + b := ReadBuf{} + n, err := b.Read(nil) + assert.Equal(t, 0, n) + assert.Equal(t, io.EOF, err) +} + +func TestReadBuf_Read(t *testing.T) { + b := &ReadBuf{bytes: []byte("hello"), n: 5} + r := make([]byte, 5) + n, err := b.Read(r) + require.NoError(t, err) + assert.Equal(t, 5, n) + assert.Equal(t, "hello", string(r)) +} + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server.go b/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server.go new file mode 100755 index 000000000000..9d1237a9fb6b --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server.go @@ -0,0 +1,144 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package servers + +import ( + "io" + "sync" + "sync/atomic" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +// ThriftTransport is a subset of thrift.TTransport methods, for easier mocking. +type ThriftTransport interface { + io.Reader + io.Closer +} + +// TBufferedServer is a custom thrift server that reads traffic using the transport provided +// and places messages into a buffered channel to be processed by the processor provided +type TBufferedServer struct { + // NB. queueLength HAS to be at the top of the struct or it will SIGSEV for certain architectures. + // See https://github.com/golang/go/issues/13868 + queueSize int64 + dataChan chan *ReadBuf + maxPacketSize int + maxQueueSize int + serving uint32 + transport ThriftTransport + readBufPool *sync.Pool + metrics struct { + // Size of the current server queue + QueueSize metrics.Gauge `metric:"thrift.udp.server.queue_size"` + + // Size (in bytes) of packets received by server + PacketSize metrics.Gauge `metric:"thrift.udp.server.packet_size"` + + // Number of packets dropped by server + PacketsDropped metrics.Counter `metric:"thrift.udp.server.packets.dropped"` + + // Number of packets processed by server + PacketsProcessed metrics.Counter `metric:"thrift.udp.server.packets.processed"` + + // Number of malformed packets the server received + ReadError metrics.Counter `metric:"thrift.udp.server.read.errors"` + } +} + +// state values for TBufferedServer.serving +// +// init -> serving -> stopped +// init -> stopped (might happen in unit tests) +const ( + stateStopped = iota + stateServing + stateInit +) + +// NewTBufferedServer creates a TBufferedServer +func NewTBufferedServer( + transport ThriftTransport, + maxQueueSize int, + maxPacketSize int, + mFactory metrics.Factory, +) (*TBufferedServer, error) { + dataChan := make(chan *ReadBuf, maxQueueSize) + + readBufPool := &sync.Pool{ + New: func() any { + return &ReadBuf{bytes: make([]byte, maxPacketSize)} + }, + } + + res := &TBufferedServer{ + dataChan: dataChan, + transport: transport, + maxQueueSize: maxQueueSize, + maxPacketSize: maxPacketSize, + readBufPool: readBufPool, + serving: stateInit, + } + + metrics.MustInit(&res.metrics, mFactory, nil) + return res, nil +} + +// Serve initiates the readers and starts serving traffic +func (s *TBufferedServer) Serve() { + defer close(s.dataChan) + if !atomic.CompareAndSwapUint32(&s.serving, stateInit, stateServing) { + return // Stop already called + } + + for s.IsServing() { + readBuf := s.readBufPool.Get().(*ReadBuf) + n, err := s.transport.Read(readBuf.bytes) + if err == nil { + readBuf.n = n + s.metrics.PacketSize.Update(int64(n)) + select { + case s.dataChan <- readBuf: + s.metrics.PacketsProcessed.Inc(1) + s.updateQueueSize(1) + default: + s.readBufPool.Put(readBuf) + s.metrics.PacketsDropped.Inc(1) + } + } else { + s.readBufPool.Put(readBuf) + s.metrics.ReadError.Inc(1) + } + } +} + +func (s *TBufferedServer) updateQueueSize(delta int64) { + atomic.AddInt64(&s.queueSize, delta) + s.metrics.QueueSize.Update(atomic.LoadInt64(&s.queueSize)) +} + +// IsServing indicates whether the server is currently serving traffic +func (s *TBufferedServer) IsServing() bool { + return atomic.LoadUint32(&s.serving) == stateServing +} + +// Stop stops the serving of traffic and waits until the queue is +// emptied by the readers +func (s *TBufferedServer) Stop() { + atomic.StoreUint32(&s.serving, stateStopped) + _ = s.transport.Close() +} + +// DataChan returns the data chan of the buffered server +func (s *TBufferedServer) DataChan() chan *ReadBuf { + return s.dataChan +} + +// DataRecd is called by the consumers every time they read a data item from DataChan +func (s *TBufferedServer) DataRecd(buf *ReadBuf) { + s.updateQueueSize(-1) + s.readBufPool.Put(buf) +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server_test.go b/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server_test.go new file mode 100755 index 000000000000..2d660ad68bc5 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/tbuffered_server_test.go @@ -0,0 +1,166 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package servers + +import ( + "context" + "io" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/jaegertracing/jaeger-idl/thrift-gen/agent" + "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" + "github.com/jaegertracing/jaeger/cmd/agent/app/customtransport" + "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" + "github.com/jaegertracing/jaeger/cmd/agent/app/testutils" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/internal/metricstest" +) + +func TestTBufferedServerSendReceive(t *testing.T) { + metricsFactory := metricstest.NewFactory(0) + + transport, err := thriftudp.NewTUDPServerTransport("127.0.0.1:0") + require.NoError(t, err) + + maxPacketSize := 65000 + server, err := NewTBufferedServer(transport, 100, maxPacketSize, metricsFactory) + require.NoError(t, err) + go server.Serve() + defer server.Stop() + + hostPort := transport.Addr().String() + client, clientCloser, err := testutils.NewZipkinThriftUDPClient(hostPort) + require.NoError(t, err) + defer clientCloser.Close() + + span := zipkincore.NewSpan() + span.Name = "span1" + + for i := 0; i < 1000; i++ { + err := client.EmitZipkinBatch(context.Background(), []*zipkincore.Span{span}) + require.NoError(t, err) + + select { + case readBuf := <-server.DataChan(): + assert.NotEmpty(t, readBuf.GetBytes()) + + inMemReporter := testutils.NewInMemoryReporter() + protoFact := thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{}) + trans := &customtransport.TBufferedReadTransport{} + protocol := protoFact.GetProtocol(trans) + + _, err = protocol.Transport().Write(readBuf.GetBytes()) + require.NoError(t, err) + + server.DataRecd(readBuf) // return to pool + + handler := agent.NewAgentProcessor(inMemReporter) + _, err = handler.Process(context.Background(), protocol, protocol) + require.NoError(t, err) + + require.Len(t, inMemReporter.ZipkinSpans(), 1) + assert.Equal(t, "span1", inMemReporter.ZipkinSpans()[0].Name) + + return // exit test on successful receipt + default: + time.Sleep(10 * time.Millisecond) + } + } + t.Fatal("server did not receive packets") +} + +// The fakeTransport allows the server to read two packets, one filled with 1's, another with 2's, +// then returns an error, and then blocks on the semaphore. The semaphore is only released when +// the test is exiting. +type fakeTransport struct { + packet atomic.Int64 + wg sync.WaitGroup +} + +// Read simulates three packets received, then blocks until semaphore is released at the end of the test. +// First packet is returned as normal. +// Second packet is simulated as error. +// Third packet is returned as normal, but will be dropped as overflow by the server whose queue size = 1. +func (t *fakeTransport) Read(p []byte) (n int, err error) { + packet := t.packet.Add(1) + if packet == 2 { + // return some error packet, followed by valid one + return 0, io.ErrNoProgress + } + if packet > 3 { + // block after 3 packets until the server is shutdown and semaphore released + t.wg.Wait() + return 0, io.EOF + } + for i := range p { + p[i] = byte(packet) + } + return len(p), nil +} + +func (*fakeTransport) Close() error { + return nil +} + +func TestTBufferedServerMetrics(t *testing.T) { + metricsFactory := metricstest.NewFactory(0) + + transport := new(fakeTransport) + transport.wg.Add(1) + defer transport.wg.Done() + + maxPacketSize := 65000 + server, err := NewTBufferedServer(transport, 1, maxPacketSize, metricsFactory) + require.NoError(t, err) + go server.Serve() + defer server.Stop() + + // The fakeTransport will allow the server to read exactly two packets and one error in between. + // Since we use the server with queue size == 1, the first packet will be + // sent to channel, the error will increment the metric, and the second valid packet dropped. + + packetDropped := false + for i := 0; i < 5000; i++ { + c, _ := metricsFactory.Snapshot() + if c["thrift.udp.server.packets.dropped"] == 1 { + packetDropped = true + break + } + time.Sleep(time.Millisecond) + } + require.True(t, packetDropped, "packetDropped") + + var readBuf *ReadBuf + select { + case readBuf = <-server.DataChan(): + b := readBuf.GetBytes() + assert.Len(t, b, 65000) + assert.EqualValues(t, 1, b[0], "first packet must be all 0x01's") + default: + t.Fatal("expecting a packet in the channel") + } + + metricsFactory.AssertCounterMetrics(t, + metricstest.ExpectedMetric{Name: "thrift.udp.server.packets.processed", Value: 1}, + metricstest.ExpectedMetric{Name: "thrift.udp.server.packets.dropped", Value: 1}, + metricstest.ExpectedMetric{Name: "thrift.udp.server.read.errors", Value: 1}, + ) + metricsFactory.AssertGaugeMetrics(t, + metricstest.ExpectedMetric{Name: "thrift.udp.server.packet_size", Value: 65000}, + metricstest.ExpectedMetric{Name: "thrift.udp.server.queue_size", Value: 1}, + ) + + server.DataRecd(readBuf) + metricsFactory.AssertGaugeMetrics(t, + metricstest.ExpectedMetric{Name: "thrift.udp.server.queue_size", Value: 0}, + ) +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer.go b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer.go new file mode 100755 index 000000000000..51ff5b3416e8 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2020 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:build !windows + +package thriftudp + +import ( + "fmt" + "net" + "syscall" +) + +func setSocketBuffer(conn *net.UDPConn, bufferSize int) error { + rawConn, err := conn.SyscallConn() + if err != nil { + return fmt.Errorf("failed to get raw connection: %w", err) + } + + var syscallErr error + controlErr := rawConn.Control(func(fd uintptr) { + syscallErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF, bufferSize) + }) + if controlErr != nil { + return fmt.Errorf("rawconn control failed: %w", controlErr) + } + if syscallErr != nil { + return fmt.Errorf("syscall failed: %w", syscallErr) + } + + return nil +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer_windows.go b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer_windows.go new file mode 100755 index 000000000000..efb1e4b894be --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/socket_buffer_windows.go @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2020 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package thriftudp + +import ( + "net" +) + +// Not supported on windows, so windows version just returns nil +func setSocketBuffer(_ *net.UDPConn, _ int) error { + return nil +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport.go b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport.go new file mode 100755 index 000000000000..9aa78be7cd63 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport.go @@ -0,0 +1,155 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package thriftudp + +import ( + "bytes" + "context" + "errors" + "net" + "sync/atomic" + + "github.com/apache/thrift/lib/go/thrift" +) + +// MaxLength of UDP packet +const ( + MaxLength = 65000 +) + +var errConnAlreadyClosed = errors.New("connection already closed") + +// TUDPTransport does UDP as a thrift.TTransport +type TUDPTransport struct { + conn *net.UDPConn + addr net.Addr + writeBuf bytes.Buffer + closed uint32 // atomic flag +} + +var _ thrift.TTransport = (*TUDPTransport)(nil) + +// NewTUDPClientTransport creates a net.UDPConn-backed TTransport for Thrift clients +// All writes are buffered and flushed in one UDP packet. If locHostPort is not "", it +// will be used as the local address for the connection +// Example: +// +// trans, err := thriftudp.NewTUDPClientTransport("192.168.1.1:9090", "") +func NewTUDPClientTransport(destHostPort string, locHostPort string) (*TUDPTransport, error) { + destAddr, err := net.ResolveUDPAddr("udp", destHostPort) + if err != nil { + return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error()) + } + + var locAddr *net.UDPAddr + if locHostPort != "" { + locAddr, err = net.ResolveUDPAddr("udp", locHostPort) + if err != nil { + return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error()) + } + } + + return createClient(destAddr, locAddr) +} + +func createClient(destAddr, locAddr *net.UDPAddr) (*TUDPTransport, error) { + conn, err := net.DialUDP(destAddr.Network(), locAddr, destAddr) + if err != nil { + return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error()) + } + return &TUDPTransport{addr: destAddr, conn: conn}, nil +} + +// NewTUDPServerTransport creates a net.UDPConn-backed TTransport for Thrift servers +// It will listen for incoming udp packets on the specified host/port +// Example: +// +// trans, err := thriftudp.NewTUDPClientTransport("localhost:9001") +func NewTUDPServerTransport(hostPort string) (*TUDPTransport, error) { + addr, err := net.ResolveUDPAddr("udp", hostPort) + if err != nil { + return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error()) + } + conn, err := net.ListenUDP(addr.Network(), addr) + if err != nil { + return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error()) + } + + return &TUDPTransport{addr: conn.LocalAddr(), conn: conn}, nil +} + +// Open does nothing as connection is opened on creation +// Required to maintain thrift.TTransport interface +func (*TUDPTransport) Open() error { + return nil +} + +// Conn retrieves the underlying net.UDPConn +func (p *TUDPTransport) Conn() *net.UDPConn { + return p.conn +} + +// IsOpen returns true if the connection is open +func (p *TUDPTransport) IsOpen() bool { + return atomic.LoadUint32(&p.closed) == 0 +} + +// Close closes the connection +func (p *TUDPTransport) Close() error { + if atomic.CompareAndSwapUint32(&p.closed, 0, 1) { + return p.conn.Close() + } + return errConnAlreadyClosed +} + +// Addr returns the address that the transport is listening on or writing to +func (p *TUDPTransport) Addr() net.Addr { + return p.addr +} + +// Read reads one UDP packet and puts it in the specified buf +func (p *TUDPTransport) Read(buf []byte) (int, error) { + if !p.IsOpen() { + return 0, thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open") + } + n, err := p.conn.Read(buf) + return n, thrift.NewTTransportExceptionFromError(err) +} + +// RemainingBytes returns the max number of bytes (same as Thrift's StreamTransport) as we +// do not know how many bytes we have left. +func (*TUDPTransport) RemainingBytes() uint64 { + const maxSize = ^uint64(0) + return maxSize +} + +// Write writes specified buf to the write buffer +func (p *TUDPTransport) Write(buf []byte) (int, error) { + if !p.IsOpen() { + return 0, thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open") + } + if len(p.writeBuf.Bytes())+len(buf) > MaxLength { + return 0, thrift.NewTTransportException(thrift.INVALID_DATA, "Data does not fit within one UDP packet") + } + n, err := p.writeBuf.Write(buf) + return n, thrift.NewTTransportExceptionFromError(err) +} + +// Flush flushes the write buffer as one udp packet +func (p *TUDPTransport) Flush(_ context.Context) error { + if !p.IsOpen() { + return thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open") + } + + _, err := p.conn.Write(p.writeBuf.Bytes()) + p.writeBuf.Reset() // always reset the buffer, even in case of an error + return err +} + +// SetSocketBufferSize sets udp buffer size +func (p *TUDPTransport) SetSocketBufferSize(bufferSize int) error { + return setSocketBuffer(p.Conn(), bufferSize) +} diff --git a/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport_test.go b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport_test.go new file mode 100755 index 000000000000..4525f1cb1baf --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/servers/thriftudp/transport_test.go @@ -0,0 +1,237 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package thriftudp + +import ( + "context" + "net" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" +) + +var localListenAddr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)} + +func TestNewTUDPClientTransport(t *testing.T) { + _, err := NewTUDPClientTransport("fakeAddressAndPort", "") + require.Error(t, err) + + _, err = NewTUDPClientTransport("localhost:9090", "fakeaddressandport") + require.Error(t, err) + + withLocalServer(t, func(addr string) { + trans, err := NewTUDPClientTransport(addr, "") + require.NoError(t, err) + require.True(t, trans.IsOpen()) + require.NotNil(t, trans.Addr()) + + // Check address + assert.True(t, strings.HasPrefix(trans.Addr().String(), "127.0.0.1:"), "address check") + require.Equal(t, "udp", trans.Addr().Network()) + + err = trans.Open() + require.NoError(t, err) + + err = trans.Close() + require.NoError(t, err) + require.False(t, trans.IsOpen()) + }) +} + +func TestNewTUDPServerTransport(t *testing.T) { + _, err := NewTUDPServerTransport("fakeAddressAndPort") + require.Error(t, err) + + trans, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + require.True(t, trans.IsOpen()) + require.Equal(t, ^uint64(0), trans.RemainingBytes()) + + // Ensure a second server can't be created on the same address + trans2, err := NewTUDPServerTransport(trans.Addr().String()) + if trans2 != nil { + // close the second server if one got created + trans2.Close() + } + require.Error(t, err) + + err = trans.Close() + require.NoError(t, err) + require.False(t, trans.IsOpen()) +} + +func TestSetSocketBufferSize(t *testing.T) { + trans, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + require.True(t, trans.IsOpen()) + require.Equal(t, ^uint64(0), trans.RemainingBytes()) + + err = trans.SetSocketBufferSize(1024) + require.NoError(t, err) + + err = trans.Close() + require.NoError(t, err) + require.False(t, trans.IsOpen()) +} + +func TestTUDPServerTransportIsOpen(t *testing.T) { + _, err := NewTUDPServerTransport("fakeAddressAndPort") + require.Error(t, err) + + trans, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + require.True(t, trans.IsOpen()) + require.Equal(t, ^uint64(0), trans.RemainingBytes()) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + time.Sleep(2 * time.Millisecond) + err = trans.Close() + assert.NoError(t, err) + wg.Done() + }() + + go func() { + for i := 0; i < 4; i++ { + time.Sleep(1 * time.Millisecond) + trans.IsOpen() + } + wg.Done() + }() + + wg.Wait() + require.False(t, trans.IsOpen()) +} + +func TestWriteRead(t *testing.T) { + server, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + defer server.Close() + + client, err := NewTUDPClientTransport(server.Addr().String(), "") + require.NoError(t, err) + defer client.Close() + + n, err := client.Write([]byte("test")) + require.NoError(t, err) + require.Equal(t, 4, n) + n, err = client.Write([]byte("string")) + require.NoError(t, err) + require.Equal(t, 6, n) + err = client.Flush(context.Background()) + require.NoError(t, err) + + expected := []byte("teststring") + readBuf := make([]byte, 20) + n, err = server.Read(readBuf) + require.NoError(t, err) + require.Len(t, expected, n) + require.Equal(t, expected, readBuf[0:n]) +} + +func TestDoubleCloseError(t *testing.T) { + trans, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + require.True(t, trans.IsOpen()) + + // Close connection object directly + conn := trans.Conn() + require.NotNil(t, conn) + conn.Close() + + err = trans.Close() + require.Error(t, err, "must return error when underlying connection is closed") + + assert.Equal(t, errConnAlreadyClosed, trans.Close(), "second Close() returns an error") +} + +func TestConnClosedReadWrite(t *testing.T) { + trans, err := NewTUDPServerTransport(localListenAddr.String()) + require.NoError(t, err) + require.True(t, trans.IsOpen()) + require.NoError(t, trans.Close()) + require.False(t, trans.IsOpen()) + + _, err = trans.Read(make([]byte, 1)) + require.Error(t, err) + _, err = trans.Write([]byte("test")) + require.Error(t, err) +} + +func TestHugeWrite(t *testing.T) { + withLocalServer(t, func(addr string) { + trans, err := NewTUDPClientTransport(addr, "") + require.NoError(t, err) + + hugeMessage := make([]byte, 40000) + _, err = trans.Write(hugeMessage) + require.NoError(t, err) + + // expect buffer to exceed max + _, err = trans.Write(hugeMessage) + require.Error(t, err) + }) +} + +func TestFlushErrors(t *testing.T) { + withLocalServer(t, func(addr string) { + trans, err := NewTUDPClientTransport(addr, "") + require.NoError(t, err) + + // flushing closed transport + trans.Close() + err = trans.Flush(context.Background()) + require.Error(t, err) + + // error when trying to write in flush + trans, err = NewTUDPClientTransport(addr, "") + require.NoError(t, err) + trans.conn.Close() + + trans.Write([]byte{1, 2, 3, 4}) + err = trans.Flush(context.Background()) + require.Error(t, err, "Flush with data should fail") + }) +} + +func TestResetInFlush(t *testing.T) { + conn, err := net.ListenUDP(localListenAddr.Network(), localListenAddr) + require.NoError(t, err, "ListenUDP failed") + + trans, err := NewTUDPClientTransport(conn.LocalAddr().String(), "") + require.NoError(t, err) + + trans.Write([]byte("some nonsense")) + trans.conn.Close() // close the transport's connection via back door + + err = trans.Flush(context.Background()) + require.Error(t, err, "should fail to write to closed connection") + assert.Equal(t, 0, trans.writeBuf.Len(), "should reset the buffer") +} + +func withLocalServer(t *testing.T, f func(addr string)) { + conn, err := net.ListenUDP(localListenAddr.Network(), localListenAddr) + require.NoError(t, err, "ListenUDP failed") + + f(conn.LocalAddr().String()) + require.NoError(t, conn.Close(), "Close failed") +} + +func TestCreateClient(t *testing.T) { + _, err := createClient(nil, nil) + require.EqualError(t, err, "dial udp: missing address") +} + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/README.md b/receiver/jaegerreceiver/internal/cmd/testdata/README.md new file mode 100755 index 000000000000..6b3d1bbca55b --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/README.md @@ -0,0 +1,15 @@ +# Example Certificate Authority and Certificate creation for testing + +The PEM files located in this directory are used by unit tests in this package. + +To generate and update the PEM files in this directory, run the following from the project root: + + make certs + +To only generate the PEM files without copying them to this directory: + + make certs-dryrun + +The location of the generated PEM files will be printed to STDOUT like so: + + # Dry-run complete. Generated files can be found in /var/folders/3p/yms48z2s6v7c8fy2m_1481g00000gn/T/certificates.p7pFHXpy diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/bad-CA-cert.txt b/receiver/jaegerreceiver/internal/cmd/testdata/bad-CA-cert.txt new file mode 100755 index 000000000000..2f4aad65c7f9 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/bad-CA-cert.txt @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +bad certificate +-----END CERTIFICATE----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/example-CA-cert.pem b/receiver/jaegerreceiver/internal/cmd/testdata/example-CA-cert.pem new file mode 100755 index 000000000000..ca50b7c7e389 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/example-CA-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMTCCAhkCFCi65dSe1JONpNGghyam61+4gTL7MA0GCSqGSIb3DQEBCwUAMFUx +CzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5l +eTEQMA4GA1UECgwHTG9nei5pbzEPMA0GA1UEAwwGSmFlZ2VyMB4XDTIyMDkxMDAw +MjE0NFoXDTMyMDkwNzAwMjE0NFowVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1 +c3RyYWxpYTEPMA0GA1UEBwwGU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYD +VQQDDAZKYWVnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLlEq/ +DF2pkhfSedvAd5h6BXCjpC/mUA6BN3RyMHUjTWr9hhBtaIYv68O12GMVf//ST/Fs +CjRrjOcqrz2QQn3P8UelGRd2vJfcMhJElQ/lnKmZZlAHEOMF8TC7nQfsReLCwcpj +T6bXqvDcfHjDye+45F2rPDpRGLzyysg7pgdINp0Duph0Z16ggrBgz7RVNBmWsYVe +sGD3VOR3hLd8GTDzJ5amRpkq8nfliJ+U3JLGcDG/7Wkuvl/YZZxf21v9f4yYVEZZ +aLAcKsHIUoFRDJtdrBeaPZRJjL/I9B1M6En+Styxb5wJw42h9BXtJd2IeQPp15pP +KfPbkmOj+X+2s9n1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJbm7WXgQirWQbaa +E304K8tvdpC2E1ewxTTrUEN8jUONER4KC+epRnsTgkEpVlj7sehiAgSMnbT4E3ve +GjmsUrZiJcKPaf+ogn49Cj0weD99wbJtUNgbH4HiqR1ePOHIRDQ7GD5G0zdFq7oO +Il09eHAbbWM61x04I3XDQ0OwXyeVXIEWJcR1R6wnuNMJm54czbXvn6SrIuoMCvs6 +oSkVm43Q+plk0hlDZnA/KiOxqFRLVHBuX/SgRf5NBg8m7id3fNzIJnWWK+zqoDoZ +ryja7dFIJnLqEXJxJkc5ubT1/j9PDE51WbM5MyPB6lnuQKdZTbDziyKiVXg0au3E +QK5K/Ow= +-----END CERTIFICATE----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/example-client-cert.pem b/receiver/jaegerreceiver/internal/cmd/testdata/example-client-cert.pem new file mode 100755 index 000000000000..569e23ac033d --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/example-client-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjqgAwIBAgIUE56RLVss9rH/ojHQlVqysg6vJQYwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3RyYWxpYTEPMA0GA1UEBwwG +U3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYDVQQDDAZKYWVnZXIwHhcNMjIw +OTEwMDAyMTQ0WhcNMzIwOTA3MDAyMTQ0WjBVMQswCQYDVQQGEwJBVTESMBAGA1UE +CAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEDAOBgNVBAoMB0xvZ3ouaW8x +DzANBgNVBAMMBkphZWdlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALwEHBIe2nffXpJtYRUQ2GzuIwzPhI6fwT/KYXc/ao6mhHQ/oGyMZJFBxxpB0Inz +GuurMtJBIgAlrhKmX5Dg5tB05iMpqA1Hbxa4fQS34iw9bBEvH/7SkuQ7gox6ht/n +ZX9UuyAw751B/KlGQlVInGzySAgR9T7RdT7YOAGoaQtXNsE6b3/Jm6z/uRW3Buqp +1jzqL9VHzUdC7k8nRRdTTivcDUiZ+ocp4j2lRVP4hOylU0DSAG7mfwR8YQ/Xt1cU +kn+2pe+D4tcx23lQcQFFWeJ2CKVx+Gx2BwJNoqPJ0LJLLSAQyY+S2wGSjoc8nqvM +8mhgykFU9dW+GEwJzhLqRRMCAwEAAaMaMBgwFgYDVR0RBA8wDYILZXhhbXBsZS5j +b20wDQYJKoZIhvcNAQELBQADggEBABqjQPg5voqMNnBBtnAKDnuTF4hOBNAo0Wq/ +KzD5QqvaWPPspx+oIahSIwq+8aL+NzptfYwbke10Q5qmOzq/ZgVTela+k/hgbjn6 +I/nOTCg5/v7m0AN3HgIGdgh5TOBiZMEsNpS+Lr2DangjaBKwpe4sucsgevJpggg1 +m/FT8rL7X5AjNx+mgsjdzQaboe6SkaGSSzByN8jEO03ceYpLvfqMGdAJpF4MEGiZ +BlAAMHn3m5NLuBsHM/SiewTEmLBa6AEo33/XI0rOjDlYOj7A0xj2NLz0EwfRf3AG +UpDuAB9O5n3iVXrHtaHDMRihGjBbeDEVaf68uodz7nH/UIWc2Rs= +-----END CERTIFICATE----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/example-client-key.pem b/receiver/jaegerreceiver/internal/cmd/testdata/example-client-key.pem new file mode 100755 index 000000000000..25148bb9455b --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/example-client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvAQcEh7ad99ekm1hFRDYbO4jDM+Ejp/BP8phdz9qjqaEdD+g +bIxkkUHHGkHQifMa66sy0kEiACWuEqZfkODm0HTmIymoDUdvFrh9BLfiLD1sES8f +/tKS5DuCjHqG3+dlf1S7IDDvnUH8qUZCVUicbPJICBH1PtF1Ptg4AahpC1c2wTpv +f8mbrP+5FbcG6qnWPOov1UfNR0LuTydFF1NOK9wNSJn6hyniPaVFU/iE7KVTQNIA +buZ/BHxhD9e3VxSSf7al74Pi1zHbeVBxAUVZ4nYIpXH4bHYHAk2io8nQskstIBDJ +j5LbAZKOhzyeq8zyaGDKQVT11b4YTAnOEupFEwIDAQABAoIBAEWCa3JTj8dDgG44 +G+0y1iCnhbPFwKcN7t8Lji8M9fMZItzrbP7UhJWjMN3HOTbW9rvsBhTvWYeeZpWk +hq5ER3EH1tFnJCcMoshOmoG1Ddv3NU3BE14dMYtJaQFQhy6eGMsTYz8KeHu2Gpfm +Tr3C43nvtKuvH/ECdQsv2rzaK0OybxwN0GQGPPeKFhGJ8/v+s2NGQvfuaDFqPZ3u +9e/gZ8DHAPrj6kf87j1SzDAytuaDItPwQJv01Kc9gVPajuoWMTkz1IcGB5KhRohO +CO8h33PSA1gq7d7yn4fCqtwWORJTqFc7Bq1512bM4dh+lqhm+LAYPI7s899x2KI3 +A837xLkCgYEA6O/6nN2npwhPEROBL7T5KGKOwWrgKLO1XL/zwtrXAXFO4pN0Pvcf +K5Aqrm1TjhDqwvMUmtjFVrB6FkejGw/22NlKNrsBQ5HQvHv8RjLu7kC5VxysUei1 +S7IlS8HjA6APap9cgELWocivrFIOijXXvRmO0EgIqWYl2TF60Cyx6sUCgYEAzqGO +TR1IE8s2bJGxwF3toSR69Q3i5GgUnELATNEqIg0i/j9kYDNl8oJmyJ0DEsKlIe6X +72JSDMLX0mwzBHit7LXvBUYAXCu/i91Rnkn7ME7KTPIJaKnZHNT1KZQi0vClv9St +gQeAl1YGHSlEh98lEhHwykchmqaVFiOo0zlNzfcCgYEAwDHRvEB/JiiK5HINc4mE +0zeOxjQixDKS//Y5cJsUL9KH3hcAITvRciY/sS/vcxauPTBH3gPhv0dZVKzC/X9M +k1umCkZ+InxbmElMu7cmwVqSEjhMTkEN5WkVsM5HOySD09utfP6pDVAC8tG5wXvv +h81gsqXcz7jCndRfmwhlvGkCgYEAhVOLDUj6jAMQX+d2aShyPwrZ56sJHtXljpon +mKlR5VzSmnju3H/tpRftGD7vj7hWctmP4Z9wT9mdBqJYHOd9WgJecumjK9Xyp12r +31XfJWGBeTqnRYhqlgb3FdgGzFMIsAmb1miv2XZhRYmuNXmPYuR+mRZioXYhNoLV +2UzdXisCgYAofFoRrtFAUZn1AY7no1MSXrOZ0fAwnRm6va73aSOFHHJG9swI2GOi +hGUuABh6TbpU6G7FIDD/E9zjXoz43j6muN9RUqynVK4x+fEUQDGUtHXavtL97vWg +eHbzNbxx46DlaGj2VQkQZ8iFsIMXbeAp5vPGbdau5dCFEgL+DfC87w== +-----END RSA PRIVATE KEY----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/example-server-cert.pem b/receiver/jaegerreceiver/internal/cmd/testdata/example-server-cert.pem new file mode 100755 index 000000000000..dbaad9f6f33f --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/example-server-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjqgAwIBAgIUE56RLVss9rH/ojHQlVqysg6vJQUwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3RyYWxpYTEPMA0GA1UEBwwG +U3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYDVQQDDAZKYWVnZXIwHhcNMjIw +OTEwMDAyMTQ0WhcNMzIwOTA3MDAyMTQ0WjBVMQswCQYDVQQGEwJBVTESMBAGA1UE +CAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEDAOBgNVBAoMB0xvZ3ouaW8x +DzANBgNVBAMMBkphZWdlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN17nlVlHzFoEDnAA7kvrjzuKiZQZ70znDW5TrqtwXqHr5XG0m7rdQlt9xyr3HFg +DbXbkg7wBidqUySWZ7N/cxiqB/oMnfbntapwmBP77Ss8KLLQx17Geb8pryIHrhcE +a/E556epv3WRkoz3j8ph3DY7g+ghQWNtWI3UvBdaIkmPaS+wVfH6hwzpT4rbdVSF +1n7SnMcJccKPEPgqASiEsYZeQgnZUedayKzHRnJeQD3lOPXLHAOIGHajGvyQFMqE +fG9dJfWNVxH/+GxMNul9jsUfJMc99mG/vy3B1WROOl2EiTi8FzfM64lo8SvEs3Db +jcAFItI7BcyM/MJxqYtYFQ0CAwEAAaMaMBgwFgYDVR0RBA8wDYILZXhhbXBsZS5j +b20wDQYJKoZIhvcNAQELBQADggEBAFjZrgLJiezjX2enrh1pJDRrj9NClTKM8Vck +dnpI4OFmViqSyUkyY28PO9omoXUPAbcVuXcGQ/f4PR7tlKmv1lGH/4vGGgmvLjus +Mm0vYZoBos/KPN92RIUkpO1Lvt3es96CFI0k6G0JmstXn4EShQibm1424jTWU3tF +praOAsaTVWO/ukVPbULJ8dWzKoQVTyb/cNQiPiL0IXx7XYc/cqCB2yqzELtMOmIe +kQuyCmUNzK1qQaezxwkMl2P+121QdOvKkxcu7XlAEo0SRNNNkpOkyRqLvC2iou39 +SHxqc/Vbf+Pj9N6oC0twI7KAJELHMi9qhlQsNssxUMjYe7BRYmQ= +-----END CERTIFICATE----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/example-server-key.pem b/receiver/jaegerreceiver/internal/cmd/testdata/example-server-key.pem new file mode 100755 index 000000000000..0e9cb5c9663a --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/example-server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA3XueVWUfMWgQOcADuS+uPO4qJlBnvTOcNblOuq3BeoevlcbS +but1CW33HKvccWANtduSDvAGJ2pTJJZns39zGKoH+gyd9ue1qnCYE/vtKzwostDH +XsZ5vymvIgeuFwRr8Tnnp6m/dZGSjPePymHcNjuD6CFBY21YjdS8F1oiSY9pL7BV +8fqHDOlPitt1VIXWftKcxwlxwo8Q+CoBKISxhl5CCdlR51rIrMdGcl5APeU49csc +A4gYdqMa/JAUyoR8b10l9Y1XEf/4bEw26X2OxR8kxz32Yb+/LcHVZE46XYSJOLwX +N8zriWjxK8SzcNuNwAUi0jsFzIz8wnGpi1gVDQIDAQABAoIBAQCAE55J74IMRgsr ++hetHR966JbDNTfoN1Ib1x7p4NTDkHc++4xwzAQQAeEmWVPO1CbZhTF/JdnJLTkL +LVamfAsItjqKpIUsZG2vNBEdbU+G8vDuBsFj0w5QN0CpQxuu/8WT51JIqGapDBdd +IUOrWs/HJL9wmtp/LppI2j4ymtK9Cffce8AVTazfHspVF2e05b8GEeBjoMmvpPgw +bvHPLdCVoWPGsYOFUWG9V1eCo2CFtvspsa8CYghpaXg7EOElF73W1gEoEd5SdMx9 +svHeH4bJAzrWoqDrC5kOJUZRip9YjF8WXRudVmSaRPHptwN6qRvF8HWiGrYrdTtJ +j1seb87BAoGBAPUrxCI64EN/6YYNziM49RpORLVrZGLaZQCf0IJkoH3DcsBcrtF8 +hqJC73z75kj1Y+oOzulYPBlhQr+4hvbMSzHwsffi5nepPXSSGK2+D1O5rASou7b3 +Re/OiJNex7IrDAy354PV4B/7iFmgGOVUn+sXIKoprqnor7f3mALAa/yZAoGBAOdE +AMKktCQYIHweKPF0mYDsOnoJ8TEAydxShOan5r5gkVTnZhDHa3fD9eGh19Mfi9qC +cDro5Sq1+8OLoX6Ta/Ju3PNfI2Qn4KLF9CZrEQhrV90HmXluCflZXyL71SB8pGVo +5ybr8UtalUXVPXKi+inK7CXaJBZaboJWnqmaqJCVAoGAdIX0lgA9jldA+gGds4fi +ljoU1dTQxVrfHkjWpOKGlL9Lzrk+LTpuEriVcmWWsZ5PenLHTIgvKDDdtJlTLAE0 +y+uF6jbhKoY5OyokqI7oYfahFyXK8c7cYnla2A/4AWoMNA9D7Zi9CPZXe6Fns7dg +ui8nyzg8V2zL9zep+8TQjiECgYEAm2zTif0BaGSiqGfoomX3qHKa1lwKMiHSiHUZ +Bp9+7yGdas9dhBdSPZqAjJSlpSlFZ6RUYvMU2UCXJJOaBKR1XuhtLE8bTPuT+DFL +5en894iU82JhHf/7Sg5rZuqTERNTtSfsefcGItuNCPLIKlwn/qB3VvUlXbSHIqeu +WFQtx4UCgYEA1FIVEc4BjRE6jH80X7RSSOLJ6PwPglZzM8JEVyiYHAHE65zdORF1 +iCiuI+pRQc3yHkm2gbB+hY5HSrCmyJrJc0tcUd4QoMqOHV8UEGLVwxtr/4DPMsl4 +JIEmzmgvs56TJeKX0YlXnD612zjDWCPV6q+LWlUUzd8qLwk6L1+EFhE= +-----END RSA PRIVATE KEY----- diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/gen-certs.sh b/receiver/jaegerreceiver/internal/cmd/testdata/gen-certs.sh new file mode 100755 index 000000000000..9d5d8e3135f4 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/gen-certs.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash + +# Copyright (c) 2024 The Jaeger Authors. +# SPDX-License-Identifier: Apache-2.0 + +# The following commands were used to create the CA, server and client's certificates and keys in this directory used by unit tests. +# These certificates use the Subject Alternative Name extension rather than the Common Name, which will be unsupported in Go 1.15. + +usage() { + echo "Usage: $0 [-d]" + echo + echo "-d Dry-run mode. PEM files will not be modified." + exit 1 +} + +dry_run=false + +while getopts "d" o; do + case "${o}" in + d) + dry_run=true + ;; + *) + usage + ;; + esac +done +shift $((OPTIND-1)) + +set -ex + +# Create temp dir for generated files. +tmp_dir=$(mktemp -d -t certificates) +clean_up() { + ARG=$? + if [ $dry_run = true ]; then + echo "Dry-run complete. Generated files can be found in $tmp_dir" + else + rm -rf "$tmp_dir" + fi + exit $ARG +} +trap clean_up EXIT + +gen_ssl_conf() { + domain_name=$1 + output_file=$2 + + cat << EOF > "$output_file" +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_distinguished_name +req_extensions = req_ext + +[ req_distinguished_name ] +countryName = AU +stateOrProvinceName = Australia +localityName = Sydney +organizationName = Logz.io +commonName = Jaeger + +[ req_ext ] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = $domain_name +EOF +} + +# Generate config files. +# The server name (under alt_names in the ssl.conf) is `example.com`. (in accordance to [RFC 2006](https://tools.ietf.org/html/rfc2606)) +gen_ssl_conf example.com "$tmp_dir/ssl.conf" +gen_ssl_conf wrong.com "$tmp_dir/wrong-ssl.conf" + +# Create CA (accept defaults from prompts). +openssl genrsa -out "$tmp_dir/example-CA-key.pem" 2048 +openssl req -new -key "$tmp_dir/example-CA-key.pem" -x509 -days 3650 -out "$tmp_dir/example-CA-cert.pem" -config "$tmp_dir/ssl.conf" + +# Create Wrong CA (a dummy CA which doesn't provide any certificate; accept defaults from prompts). +openssl genrsa -out "$tmp_dir/wrong-CA-key.pem" 2048 +openssl req -new -key "$tmp_dir/wrong-CA-key.pem" -x509 -days 3650 -out "$tmp_dir/wrong-CA-cert.pem" -config "$tmp_dir/wrong-ssl.conf" + +# Create client and server keys. +openssl genrsa -out "$tmp_dir/example-server-key.pem" 2048 +openssl genrsa -out "$tmp_dir/example-client-key.pem" 2048 + +# Create certificate sign request using the above created keys and configuration given and commandline arguments. +openssl req -new -nodes -key "$tmp_dir/example-server-key.pem" -out "$tmp_dir/example-server.csr" -config "$tmp_dir/ssl.conf" +openssl req -new -nodes -key "$tmp_dir/example-client-key.pem" -out "$tmp_dir/example-client.csr" -config "$tmp_dir/ssl.conf" + +# Creating the client and server certificate. +openssl x509 -req \ + -sha256 \ + -days 3650 \ + -in "$tmp_dir/example-server.csr" \ + -out "$tmp_dir/example-server-cert.pem" \ + -extensions req_ext \ + -CA "$tmp_dir/example-CA-cert.pem" \ + -CAkey "$tmp_dir/example-CA-key.pem" \ + -CAcreateserial \ + -extfile "$tmp_dir/ssl.conf" +openssl x509 -req \ + -sha256 \ + -days 3650 \ + -in "$tmp_dir/example-client.csr" \ + -out "$tmp_dir/example-client-cert.pem" \ + -extensions req_ext \ + -CA "$tmp_dir/example-CA-cert.pem" \ + -CAkey "$tmp_dir/example-CA-key.pem" \ + -CAcreateserial \ + -extfile "$tmp_dir/ssl.conf" + +# Copy PEM files. +if [ $dry_run = false ]; then + cp "$tmp_dir/example-CA-cert.pem" \ + "$tmp_dir/example-client-cert.pem" \ + "$tmp_dir/example-client-key.pem" \ + "$tmp_dir/example-server-cert.pem" \ + "$tmp_dir/example-server-key.pem" \ + "$tmp_dir/wrong-CA-cert.pem" . +fi diff --git a/receiver/jaegerreceiver/internal/cmd/testdata/wrong-CA-cert.pem b/receiver/jaegerreceiver/internal/cmd/testdata/wrong-CA-cert.pem new file mode 100755 index 000000000000..7de314c45e1e --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testdata/wrong-CA-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMTCCAhkCFHHHf3IkwJYPKija3g7MR53ttN+QMA0GCSqGSIb3DQEBCwUAMFUx +CzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5l +eTEQMA4GA1UECgwHTG9nei5pbzEPMA0GA1UEAwwGSmFlZ2VyMB4XDTIyMDkxMDAw +MjE0NFoXDTMyMDkwNzAwMjE0NFowVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1 +c3RyYWxpYTEPMA0GA1UEBwwGU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYD +VQQDDAZKYWVnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDWRKQ +VcJGGiQVrAreflpWMaurA+dPBhKwrfiHZ1glFEevEyOcgXVP6pSvRsUYJ1x36yGq +8EV2bEAmpkBx0QEnPOiQIRfA8nuFmy9bE6RWl2amXoEg81E7LhW0uC9qGkF9eLG6 +o9F/knd0WGNCcEpfk9NdXH1HtNJJvjNNi6no/9KHHfT2pg/3OSzt3dCPOHwXZEdL +72rLs+NoV0sM1oP1MFZmYHzSNhquOKGWDEPTk58YvA5uRe06nacLt30ZCZh3oBso +vJxS8Cs7mAGZMnZrMbTcNd7iYN850lTlcYFmv/3zlzF7fO84mPBzWYSY90bJ2AwQ +AdJFrZl5sol/j9STAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALYxUQPwRg+i3yVe +6Q4LuaJdTTJMF/ABHHffAZqAEjkfnzODimRyroGN6l5ixCRokbJ9q2Az7JLSj/Tv +y+5O4Tu8P3Z5nFkTPE70JDEw+sFKxHCGdQ8eNf+DlL7Ado+75a8Yug2RM4wHODVh +TKfHwj7c7vHnPkv8o8a4DiCQBNZmH6qXwCqLAYdeScrrhUzX04+3ZEq0boIYUTFn +FYqsdpKYFwwrQ1njlUu4VDGl02QXD3HYnkbzBzEI7HW7lxfcb2py8xrdIw9bh1fl +r+ou4bKctTJ4NOf3TezWT01MEGCgLgI8vdsyVGRUbPrfuHHVyuYARPXmEe89Q34+ +fHP/TLM= +-----END CERTIFICATE----- diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter.go b/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter.go new file mode 100755 index 000000000000..0580f91c4b40 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "context" + "sync" + + "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" + "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" +) + +// InMemoryReporter collects spans in memory +type InMemoryReporter struct { + zSpans []*zipkincore.Span + jSpans []*jaeger.Span + mutex sync.Mutex +} + +// NewInMemoryReporter creates new InMemoryReporter +func NewInMemoryReporter() *InMemoryReporter { + return &InMemoryReporter{ + zSpans: make([]*zipkincore.Span, 0, 10), + jSpans: make([]*jaeger.Span, 0, 10), + } +} + +// EmitZipkinBatch implements the corresponding method of the Reporter interface +func (i *InMemoryReporter) EmitZipkinBatch(_ context.Context, spans []*zipkincore.Span) error { + i.mutex.Lock() + defer i.mutex.Unlock() + i.zSpans = append(i.zSpans, spans...) + return nil +} + +// EmitBatch implements the corresponding method of the Reporter interface +func (i *InMemoryReporter) EmitBatch(_ context.Context, batch *jaeger.Batch) (err error) { + i.mutex.Lock() + defer i.mutex.Unlock() + i.jSpans = append(i.jSpans, batch.Spans...) + return nil +} + +// ZipkinSpans returns accumulated Zipkin spans as a copied slice +func (i *InMemoryReporter) ZipkinSpans() []*zipkincore.Span { + i.mutex.Lock() + defer i.mutex.Unlock() + return i.zSpans +} + +// Spans returns accumulated spans as a copied slice +func (i *InMemoryReporter) Spans() []*jaeger.Span { + i.mutex.Lock() + defer i.mutex.Unlock() + return i.jSpans +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter_test.go b/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter_test.go new file mode 100755 index 000000000000..ade655ff12f4 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/in_memory_reporter_test.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" + "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" +) + +func TestInMemoryReporter(t *testing.T) { + r := NewInMemoryReporter() + e1 := r.EmitZipkinBatch(context.Background(), []*zipkincore.Span{ + {}, + }) + e2 := r.EmitBatch(context.Background(), &jaeger.Batch{ + Spans: []*jaeger.Span{ + {}, + }, + }) + require.NoError(t, e1) + require.NoError(t, e2) + assert.Len(t, r.ZipkinSpans(), 1) + assert.Len(t, r.Spans(), 1) +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck.go b/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck.go new file mode 100755 index 000000000000..e89a1677eddc --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2023 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "testing" + + "go.uber.org/goleak" +) + +// IgnoreGlogFlushDaemonLeak returns a goleak.Option that ignores the flushDaemon function +// from the glog package that can cause false positives in leak detection. +// This is necessary because glog starts a goroutine in the background that may not +// be stopped when the test finishes, leading to a detected but expected leak. +func IgnoreGlogFlushDaemonLeak() goleak.Option { + return goleak.IgnoreTopFunction("github.com/golang/glog.(*fileSink).flushDaemon") +} + +// IgnoreOpenCensusWorkerLeak This prevent catching the leak generated by opencensus defaultWorker.Start which at the time is part of the package's init call. +// See https://github.com/jaegertracing/jaeger/pull/5055#discussion_r1438702168 for more context. +func IgnoreOpenCensusWorkerLeak() goleak.Option { + return goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start") +} + +// IgnoreGoMetricsMeterLeak prevents the leak created by go-metrics which is +// used by Sarama (Kafka Client) in Jaeger v1. This reason of this leak is +// not Jaeger but the go-metrics used by Samara. +// See these issues for the context +// - https://github.com/IBM/sarama/issues/1321 +// - https://github.com/IBM/sarama/issues/1340 +// - https://github.com/IBM/sarama/issues/2832 +func IgnoreGoMetricsMeterLeak() goleak.Option { + return goleak.IgnoreTopFunction("github.com/rcrowley/go-metrics.(*meterArbiter).tick") +} + +// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch +// These leaks are from olivere client not from the jaeger +// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339 +func ignoreHttpTransportWriteLoopLeak() goleak.Option { + return goleak.IgnoreTopFunction("net/http.(*persistConn).writeLoop") +} + +// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch +// These leaks are from olivere client not from the jaeger +// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339 +func ignoreHttpTransportPollRuntimeLeak() goleak.Option { + return goleak.IgnoreTopFunction("internal/poll.runtime_pollWait") +} + +// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch +// These leaks are from olivere client not from the jaeger +// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339 +func ignoreHttpTransportReadLoopLeak() goleak.Option { + return goleak.IgnoreTopFunction("net/http.(*persistConn).readLoop") +} + +// VerifyGoLeaks verifies that unit tests do not leak any goroutines. +// It should be called in TestMain. +func VerifyGoLeaks(m *testing.M) { + goleak.VerifyTestMain(m, IgnoreGlogFlushDaemonLeak(), IgnoreOpenCensusWorkerLeak(), IgnoreGoMetricsMeterLeak()) +} + +// VerifyGoLeaksOnce verifies that a given unit test does not leak any goroutines. +// Occasionally useful to troubleshoot specific tests that are flaky due to leaks, +// since VerifyGoLeaks cannot distiguish which specific test caused the leak. +// It should be called via defer or from Cleanup: +// +// defer testutils.VerifyGoLeaksOnce(t) +func VerifyGoLeaksOnce(t *testing.T) { + goleak.VerifyNone(t, IgnoreGlogFlushDaemonLeak(), IgnoreOpenCensusWorkerLeak(), IgnoreGoMetricsMeterLeak()) +} + +// VerifyGoLeaksOnceForES is go leak check for ElasticSearch integration tests (v1) +// This must not be used anywhere else other than integration package for v1 +func VerifyGoLeaksOnceForES(t *testing.T) { + goleak.VerifyNone(t, ignoreHttpTransportWriteLoopLeak(), ignoreHttpTransportPollRuntimeLeak(), ignoreHttpTransportReadLoopLeak()) +} + +// VerifyGoLeaksForES is go leak check for integration package in ElasticSearch Environment +// This must not be used anywhere else other than integration package in ES environment for v1 +func VerifyGoLeaksForES(m *testing.M) { + goleak.VerifyTestMain(m, ignoreHttpTransportWriteLoopLeak(), ignoreHttpTransportPollRuntimeLeak(), ignoreHttpTransportReadLoopLeak()) +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck_test.go b/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck_test.go new file mode 100755 index 000000000000..3558518ef286 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/leakcheck_test.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2023 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "testing" +) + +func TestVerifyGoLeaksOnce(t *testing.T) { + defer VerifyGoLeaksOnce(t) +} + +func TestMain(m *testing.M) { + VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/logger.go b/receiver/jaegerreceiver/internal/cmd/testutils/logger.go new file mode 100755 index 000000000000..d9219a0b5ccf --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/logger.go @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" +) + +// NewLogger creates a new zap.Logger backed by a zaptest.Buffer, which is also returned. +func NewLogger() (*zap.Logger, *Buffer) { + core, buf := newRecordingCore() + logger := zap.New(core, zap.WithFatalHook(zapcore.WriteThenPanic)) + return logger, buf +} + +func newRecordingCore() (zapcore.Core, *Buffer) { + encoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + }) + buf := &Buffer{} + return zapcore.NewCore(encoder, buf, zapcore.DebugLevel), buf +} + +// NewEchoLogger is similar to NewLogger, but the logs are also echoed to t.Log. +func NewEchoLogger(t *testing.T) (*zap.Logger, *Buffer) { + core, buf := newRecordingCore() + echo := zaptest.NewLogger(t).Core() + logger := zap.New(zapcore.NewTee(core, echo)) + return logger, buf +} + +// Buffer wraps zaptest.Buffer and provides convenience method JSONLine(n) +type Buffer struct { + sync.RWMutex + zaptest.Buffer +} + +// JSONLine reads n-th line from the buffer and converts it to JSON. +func (b *Buffer) JSONLine(n int) map[string]string { + data := make(map[string]string) + line := b.Lines()[n] + if err := json.Unmarshal([]byte(line), &data); err != nil { + return map[string]string{ + "error": err.Error(), + } + } + return data +} + +// NB. the below functions overwrite the existing functions so that logger is threadsafe. +// This is not that fragile given how if the API were to change underneath in zap, the overwritten +// function will fail to compile. + +// Lines overwrites zaptest.Buffer.Lines() to make it thread safe +func (b *Buffer) Lines() []string { + b.RLock() + defer b.RUnlock() + return b.Buffer.Lines() +} + +// Stripped overwrites zaptest.Buffer.Stripped() to make it thread safe +func (b *Buffer) Stripped() string { + b.RLock() + defer b.RUnlock() + return b.Buffer.Stripped() +} + +// String overwrites zaptest.Buffer.String() to make it thread safe +func (b *Buffer) String() string { + b.RLock() + defer b.RUnlock() + return b.Buffer.String() +} + +// Write overwrites zaptest.Buffer.bytes.Buffer.Write() to make it thread safe +func (b *Buffer) Write(p []byte) (int, error) { + b.Lock() + defer b.Unlock() + return b.Buffer.Write(p) +} + +// LogMatcher is a helper func that returns true if the subStr appears more than 'occurrences' times in the logs. +var LogMatcher = func(occurrences int, subStr string, logs []string) (bool, string) { + errMsg := fmt.Sprintf("subStr '%s' does not occur %d time(s) in %v", subStr, occurrences, logs) + if len(logs) < occurrences { + return false, errMsg + } + var count int + for _, log := range logs { + if strings.Contains(log, subStr) { + count++ + } + } + if count >= occurrences { + return true, "" + } + return false, errMsg +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/logger_test.go b/receiver/jaegerreceiver/internal/cmd/testutils/logger_test.go new file mode 100755 index 000000000000..a100692594b2 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/logger_test.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "strconv" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestNewLogger(t *testing.T) { + logger, log := NewLogger() + logger.Warn("hello", zap.String("x", "y")) + + assert.JSONEq(t, `{"level":"warn","msg":"hello","x":"y"}`, log.Lines()[0]) + assert.Equal(t, map[string]string{ + "level": "warn", + "msg": "hello", + "x": "y", + }, log.JSONLine(0)) +} + +func TestNewEchoLogger(t *testing.T) { + logger, _ := NewEchoLogger(t) + logger.Warn("hello", zap.String("x", "y")) +} + +func TestJSONLineError(t *testing.T) { + log := &Buffer{} + log.WriteString("bad-json\n") + _, ok := log.JSONLine(0)["error"] + assert.True(t, ok, "must have 'error' key") +} + +// NB. Run with -race to ensure no race condition +func TestRaceCondition(*testing.T) { + logger, buffer := NewLogger() + + start := make(chan struct{}) + finish := sync.WaitGroup{} + finish.Add(2) + + go func() { + <-start + logger.Info("test") + finish.Done() + }() + + go func() { + <-start + buffer.Lines() + buffer.Stripped() + _ = buffer.String() + finish.Done() + }() + + close(start) + finish.Wait() +} + +func TestLogMatcher(t *testing.T) { + tests := []struct { + occurrences int + subStr string + logs []string + expected bool + errMsg string + }{ + {occurrences: 1, expected: false, errMsg: "subStr '' does not occur 1 time(s) in []"}, + {occurrences: 1, subStr: "hi", logs: []string{"hi"}, expected: true}, + {occurrences: 3, subStr: "hi", logs: []string{"hi", "hi"}, expected: false, errMsg: "subStr 'hi' does not occur 3 time(s) in [hi hi]"}, + {occurrences: 3, subStr: "hi", logs: []string{"hi", "hi", "hi"}, expected: true}, + {occurrences: 1, subStr: "hi", logs: []string{"bye", "bye"}, expected: false, errMsg: "subStr 'hi' does not occur 1 time(s) in [bye bye]"}, + } + for i, tt := range tests { + test := tt + t.Run(strconv.Itoa(i), func(t *testing.T) { + match, errMsg := LogMatcher(test.occurrences, test.subStr, test.logs) + assert.Equal(t, test.expected, match) + assert.Equal(t, test.errMsg, errMsg) + }) + } +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/mock_collector.go b/receiver/jaegerreceiver/internal/cmd/testutils/mock_collector.go new file mode 100644 index 000000000000..8d82486e3970 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/mock_collector.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "net" + "sync" + "testing" + + "github.com/jaegertracing/jaeger-idl/model/v1" + "github.com/stretchr/testify/require" +) + +// MockCollector is a mock collector for tests that stores batches +type MockCollector struct { + listener net.Listener + batches []*model.Batch + mu sync.Mutex +} + +// StartMockCollector starts a mock collector on a random port +func StartMockCollector(t *testing.T) *MockCollector { + lis, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + + collector := &MockCollector{ + listener: lis, + batches: make([]*model.Batch, 0), + } + + return collector +} + +// Listener returns server's listener +func (c *MockCollector) Listener() net.Listener { + return c.listener +} + +// Close closes the collector +func (c *MockCollector) Close() error { + return c.listener.Close() +} + +// StoreBatch allows direct storing of batches for testing +func (c *MockCollector) StoreBatch(batch *model.Batch) { + c.mu.Lock() + defer c.mu.Unlock() + c.batches = append(c.batches, batch) +} + +// GetJaegerBatches returns accumulated Jaeger batches +func (c *MockCollector) GetJaegerBatches() []*model.Batch { + c.mu.Lock() + defer c.mu.Unlock() + return c.batches +} \ No newline at end of file diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/mock_reporter.go b/receiver/jaegerreceiver/internal/cmd/testutils/mock_reporter.go new file mode 100644 index 000000000000..9ca2850f1d88 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/mock_reporter.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "context" + + "github.com/jaegertracing/jaeger-idl/model/v1" + "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" + "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" + "go.uber.org/zap" +) + +// MockReporter is a test reporter that forwards to a mock collector +type MockReporter struct { + Collector *MockCollector + Logger *zap.Logger +} + +// NewMockReporter creates a new MockReporter +func NewMockReporter(collector *MockCollector, logger *zap.Logger) *MockReporter { + return &MockReporter{ + Collector: collector, + Logger: logger, + } +} + +// EmitBatch implements the agent.Agent processing functionality +func (r *MockReporter) EmitBatch(ctx context.Context, batch *jaeger.Batch) error { + r.Logger.Debug("MockReporter received jaeger batch", zap.Int("spans", len(batch.Spans))) + + // Convert to model.Batch +modelBatch := &model.Batch{ + Process: &model.Process{ + ServiceName: batch.Process.ServiceName, + }, + Spans: make([]*model.Span, len(batch.Spans)), +} + + + for i, span := range batch.Spans { + modelBatch.Spans[i] = &model.Span{ + OperationName: span.OperationName, + } + } + + r.Collector.StoreBatch(modelBatch) + return nil +} + +// EmitZipkinBatch implements the agent.Agent processing functionality for Zipkin +func (r *MockReporter) EmitZipkinBatch(ctx context.Context, spans []*zipkincore.Span) error { + r.Logger.Debug("MockReporter received zipkin batch", zap.Int("spans", len(spans))) + + // Convert to model.Batch using a pointer for Process. + modelBatch := &model.Batch{ + Process: &model.Process{ + ServiceName: "zipkin", // Default for test + }, + Spans: make([]*model.Span, len(spans)), + } + + for i, span := range spans { + serviceName := "unknown" + if len(span.Annotations) > 0 && span.Annotations[0].Host != nil { + serviceName = span.Annotations[0].Host.ServiceName + } + + // Use modelBatch, not batch. + modelBatch.Process.ServiceName = serviceName + modelBatch.Spans[i] = &model.Span{ + OperationName: span.Name, + } + } + + r.Collector.StoreBatch(modelBatch) + return nil +} \ No newline at end of file diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client.go b/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client.go new file mode 100755 index 000000000000..7da773cb3482 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client.go @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "io" + + "github.com/apache/thrift/lib/go/thrift" + + "github.com/jaegertracing/jaeger-idl/thrift-gen/agent" + "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" +) + +// NewZipkinThriftUDPClient creates a new zipking agent client that works like Jaeger client +func NewZipkinThriftUDPClient(hostPort string) (*agent.AgentClient, io.Closer, error) { + clientTransport, err := thriftudp.NewTUDPClientTransport(hostPort, "") + if err != nil { + return nil, nil, err + } + + protocolFactory := thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{}) + client := agent.NewAgentClientFactory(clientTransport, protocolFactory) + return client, clientTransport, nil +} + +// NewJaegerThriftUDPClient creates a new jaeger agent client that works like Jaeger client +func NewJaegerThriftUDPClient(hostPort string, protocolFactory thrift.TProtocolFactory) (*agent.AgentClient, io.Closer, error) { + clientTransport, err := thriftudp.NewTUDPClientTransport(hostPort, "") + if err != nil { + return nil, nil, err + } + + client := agent.NewAgentClientFactory(clientTransport, protocolFactory) + return client, clientTransport, nil +} diff --git a/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client_test.go b/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client_test.go new file mode 100755 index 000000000000..fa1edf6c9e42 --- /dev/null +++ b/receiver/jaegerreceiver/internal/cmd/testutils/thriftudp_client_test.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "testing" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/stretchr/testify/require" +) + +func TestNewZipkinThriftUDPClient(t *testing.T) { + _, _, err := NewZipkinThriftUDPClient("256.2.3:0") + require.Error(t, err) + + _, cl, err := NewZipkinThriftUDPClient("127.0.0.1:12345") + require.NoError(t, err) + cl.Close() +} + +func TestNewJaegerThriftUDPClient(t *testing.T) { + compactFactory := thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{}) + + _, _, err := NewJaegerThriftUDPClient("256.2.3:0", compactFactory) + require.Error(t, err) + + _, cl, err := NewJaegerThriftUDPClient("127.0.0.1:12345", compactFactory) + require.NoError(t, err) + cl.Close() +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/keys.go b/receiver/jaegerreceiver/internal/internal/metricstest/keys.go new file mode 100755 index 000000000000..d7752fbc2b3c --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/keys.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "sort" +) + +// GetKey converts name+tags into a single string of the form +// "name|tag1=value1|...|tagN=valueN", where tag names are +// sorted alphabetically. +func GetKey(name string, tags map[string]string, tagsSep string, tagKVSep string) string { + keys := make([]string, 0, len(tags)) + for k := range tags { + keys = append(keys, k) + } + sort.Strings(keys) + key := name + for _, k := range keys { + key = key + tagsSep + k + tagKVSep + tags[k] + } + return key +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/local.go b/receiver/jaegerreceiver/internal/internal/metricstest/local.go new file mode 100755 index 000000000000..59eed1eca4b4 --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/local.go @@ -0,0 +1,387 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/HdrHistogram/hdrhistogram-go" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +// This is intentionally very similar to github.com/codahale/metrics, the +// main difference being that counters/gauges are scoped to the provider +// rather than being global (to facilitate testing). + +// A Backend is a metrics provider which aggregates data in-vm, and +// allows exporting snapshots to shove the data into a remote collector +type Backend struct { + cm sync.Mutex + gm sync.Mutex + tm sync.Mutex + hm sync.Mutex + counters map[string]*int64 + gauges map[string]*int64 + timers map[string]*localBackendTimer + histograms map[string]*localBackendHistogram + stop chan struct{} + wg sync.WaitGroup + TagsSep string + TagKVSep string +} + +// NewBackend returns a new Backend. The collectionInterval is the histogram +// time window for each timer. +func NewBackend(collectionInterval time.Duration) *Backend { + b := &Backend{ + counters: make(map[string]*int64), + gauges: make(map[string]*int64), + timers: make(map[string]*localBackendTimer), + histograms: make(map[string]*localBackendHistogram), + stop: make(chan struct{}), + TagsSep: "|", + TagKVSep: "=", + } + if collectionInterval == 0 { + // Use one histogram time window for all timers + return b + } + b.wg.Add(1) + go b.runLoop(collectionInterval) + return b +} + +// Clear discards accumulated stats +func (b *Backend) Clear() { + b.cm.Lock() + defer b.cm.Unlock() + b.gm.Lock() + defer b.gm.Unlock() + b.tm.Lock() + defer b.tm.Unlock() + b.hm.Lock() + defer b.hm.Unlock() + b.counters = make(map[string]*int64) + b.gauges = make(map[string]*int64) + b.timers = make(map[string]*localBackendTimer) + b.histograms = make(map[string]*localBackendHistogram) +} + +func (b *Backend) runLoop(collectionInterval time.Duration) { + defer b.wg.Done() + ticker := time.NewTicker(collectionInterval) + for { + select { + case <-ticker.C: + b.tm.Lock() + timers := make(map[string]*localBackendTimer, len(b.timers)) + for timerName, timer := range b.timers { + timers[timerName] = timer + } + b.tm.Unlock() + + for _, t := range timers { + t.Lock() + t.hist.Rotate() + t.Unlock() + } + case <-b.stop: + ticker.Stop() + return + } + } +} + +// IncCounter increments a counter value +func (b *Backend) IncCounter(name string, tags map[string]string, delta int64) { + name = GetKey(name, tags, b.TagsSep, b.TagKVSep) + b.cm.Lock() + defer b.cm.Unlock() + counter := b.counters[name] + if counter == nil { + b.counters[name] = new(int64) + *b.counters[name] = delta + return + } + atomic.AddInt64(counter, delta) +} + +// UpdateGauge updates the value of a gauge +func (b *Backend) UpdateGauge(name string, tags map[string]string, value int64) { + name = GetKey(name, tags, b.TagsSep, b.TagKVSep) + b.gm.Lock() + defer b.gm.Unlock() + gauge := b.gauges[name] + if gauge == nil { + b.gauges[name] = new(int64) + *b.gauges[name] = value + return + } + atomic.StoreInt64(gauge, value) +} + +// RecordHistogram records a timing duration +func (b *Backend) RecordHistogram(name string, tags map[string]string, v float64) { + name = GetKey(name, tags, b.TagsSep, b.TagKVSep) + histogram := b.findOrCreateHistogram(name) + histogram.Lock() + histogram.hist.Current.RecordValue(int64(v)) + histogram.Unlock() +} + +func (b *Backend) findOrCreateHistogram(name string) *localBackendHistogram { + b.hm.Lock() + defer b.hm.Unlock() + if t, ok := b.histograms[name]; ok { + return t + } + + t := &localBackendHistogram{ + hist: hdrhistogram.NewWindowed(5, 0, int64((5*time.Minute)/time.Millisecond), 1), + } + b.histograms[name] = t + return t +} + +type localBackendHistogram struct { + sync.Mutex + hist *hdrhistogram.WindowedHistogram +} + +// RecordTimer records a timing duration +func (b *Backend) RecordTimer(name string, tags map[string]string, d time.Duration) { + name = GetKey(name, tags, b.TagsSep, b.TagKVSep) + timer := b.findOrCreateTimer(name) + timer.Lock() + timer.hist.Current.RecordValue(int64(d / time.Millisecond)) + timer.Unlock() +} + +func (b *Backend) findOrCreateTimer(name string) *localBackendTimer { + b.tm.Lock() + defer b.tm.Unlock() + if t, ok := b.timers[name]; ok { + return t + } + + t := &localBackendTimer{ + hist: hdrhistogram.NewWindowed(5, 0, int64((5*time.Minute)/time.Millisecond), 1), + } + b.timers[name] = t + return t +} + +type localBackendTimer struct { + sync.Mutex + hist *hdrhistogram.WindowedHistogram +} + +var percentiles = map[string]float64{ + "P50": 50, + "P75": 75, + "P90": 90, + "P95": 95, + "P99": 99, + "P999": 99.9, +} + +// Snapshot captures a snapshot of the current counter and gauge values +func (b *Backend) Snapshot() (counters, gauges map[string]int64) { + b.cm.Lock() + defer b.cm.Unlock() + + counters = make(map[string]int64, len(b.counters)) + for name, value := range b.counters { + counters[name] = atomic.LoadInt64(value) + } + + b.gm.Lock() + defer b.gm.Unlock() + + gauges = make(map[string]int64, len(b.gauges)) + for name, value := range b.gauges { + gauges[name] = atomic.LoadInt64(value) + } + + b.tm.Lock() + timers := make(map[string]*localBackendTimer) + for timerName, timer := range b.timers { + timers[timerName] = timer + } + b.tm.Unlock() + + for timerName, timer := range timers { + timer.Lock() + hist := timer.hist.Merge() + timer.Unlock() + for name, q := range percentiles { + gauges[timerName+"."+name] = hist.ValueAtQuantile(q) + } + } + + b.hm.Lock() + histograms := make(map[string]*localBackendHistogram) + for histogramName, histogram := range b.histograms { + histograms[histogramName] = histogram + } + b.hm.Unlock() + + for histogramName, histogram := range histograms { + histogram.Lock() + hist := histogram.hist.Merge() + histogram.Unlock() + for name, q := range percentiles { + gauges[histogramName+"."+name] = hist.ValueAtQuantile(q) + } + } + + return counters, gauges +} + +// Stop cleanly closes the background goroutine spawned by NewBackend. +func (b *Backend) Stop() { + close(b.stop) + b.wg.Wait() +} + +type stats struct { + name string + tags map[string]string + buckets []float64 + durationBuckets []time.Duration + localBackend *Backend +} + +type localTimer struct { + stats +} + +func (l *localTimer) Record(d time.Duration) { + l.localBackend.RecordTimer(l.name, l.tags, d) +} + +type localHistogram struct { + stats +} + +func (l *localHistogram) Record(v float64) { + l.localBackend.RecordHistogram(l.name, l.tags, v) +} + +type localCounter struct { + stats +} + +func (l *localCounter) Inc(delta int64) { + l.localBackend.IncCounter(l.name, l.tags, delta) +} + +type localGauge struct { + stats +} + +func (l *localGauge) Update(value int64) { + l.localBackend.UpdateGauge(l.name, l.tags, value) +} + +// Factory stats factory that creates metrics that are stored locally +type Factory struct { + *Backend + namespace string + tags map[string]string +} + +// NewFactory returns a new LocalMetricsFactory +func NewFactory(collectionInterval time.Duration) *Factory { + return &Factory{ + Backend: NewBackend(collectionInterval), + } +} + +// appendTags adds the tags to the namespace tags and returns a combined map. +func (l *Factory) appendTags(tags map[string]string) map[string]string { + newTags := make(map[string]string) + for k, v := range l.tags { + newTags[k] = v + } + for k, v := range tags { + newTags[k] = v + } + return newTags +} + +func (l *Factory) newNamespace(name string) string { + if l.namespace == "" { + return name + } + + if name == "" { + return l.namespace + } + + return l.namespace + "." + name +} + +// Counter returns a local stats counter +func (l *Factory) Counter(options metrics.Options) metrics.Counter { + return &localCounter{ + stats{ + name: l.newNamespace(options.Name), + tags: l.appendTags(options.Tags), + localBackend: l.Backend, + }, + } +} + +// Timer returns a local stats timer. +func (l *Factory) Timer(options metrics.TimerOptions) metrics.Timer { + return &localTimer{ + stats{ + name: l.newNamespace(options.Name), + tags: l.appendTags(options.Tags), + durationBuckets: options.Buckets, + localBackend: l.Backend, + }, + } +} + +// Gauge returns a local stats gauge. +func (l *Factory) Gauge(options metrics.Options) metrics.Gauge { + return &localGauge{ + stats{ + name: l.newNamespace(options.Name), + tags: l.appendTags(options.Tags), + localBackend: l.Backend, + }, + } +} + +// Histogram returns a local stats histogram. +func (l *Factory) Histogram(options metrics.HistogramOptions) metrics.Histogram { + return &localHistogram{ + stats{ + name: l.newNamespace(options.Name), + tags: l.appendTags(options.Tags), + buckets: options.Buckets, + localBackend: l.Backend, + }, + } +} + +// Namespace returns a new namespace. +func (l *Factory) Namespace(scope metrics.NSOptions) metrics.Factory { + return &Factory{ + namespace: l.newNamespace(scope.Name), + tags: l.appendTags(scope.Tags), + Backend: l.Backend, + } +} + +func (l *Factory) Stop() { + l.Backend.Stop() +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/local_test.go b/receiver/jaegerreceiver/internal/internal/metricstest/local_test.go new file mode 100755 index 000000000000..e37b1bfb94ad --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/local_test.go @@ -0,0 +1,164 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "testing" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLocalMetrics(t *testing.T) { + tags := map[string]string{ + "x": "y", + } + + f := NewFactory(0) + defer f.Stop() + f.Counter(metrics.Options{ + Name: "my-counter", + Tags: tags, + }).Inc(4) + f.Counter(metrics.Options{ + Name: "my-counter", + Tags: tags, + }).Inc(6) + f.Counter(metrics.Options{ + Name: "my-counter", + }).Inc(6) + f.Counter(metrics.Options{ + Name: "other-counter", + }).Inc(8) + f.Gauge(metrics.Options{ + Name: "my-gauge", + }).Update(25) + f.Gauge(metrics.Options{ + Name: "my-gauge", + }).Update(43) + f.Gauge(metrics.Options{ + Name: "other-gauge", + }).Update(74) + f.Namespace(metrics.NSOptions{ + Name: "namespace", + Tags: tags, + }).Counter(metrics.Options{ + Name: "my-counter", + }).Inc(7) + f.Namespace(metrics.NSOptions{ + Name: "ns.subns", + }).Counter(metrics.Options{ + Tags: map[string]string{"service": "a-service"}, + }).Inc(9) + + timings := map[string][]time.Duration{ + "foo-latency": { + time.Second * 35, + time.Second * 6, + time.Millisecond * 576, + time.Second * 12, + }, + "bar-latency": { + time.Minute*4 + time.Second*34, + time.Minute*7 + time.Second*12, + time.Second * 625, + time.Second * 12, + }, + } + + for metric, timing := range timings { + for _, d := range timing { + f.Timer(metrics.TimerOptions{ + Name: metric, + }).Record(d) + } + } + + histogram := f.Histogram(metrics.HistogramOptions{ + Name: "my-histo", + }) + histogram.Record(321) + histogram.Record(42) + + c, g := f.Snapshot() + require.NotNil(t, c) + require.NotNil(t, g) + + assert.Equal(t, map[string]int64{ + "my-counter|x=y": 10, + "my-counter": 6, + "other-counter": 8, + "namespace.my-counter|x=y": 7, + "ns.subns|service=a-service": 9, + }, c) + + assert.Equal(t, map[string]int64{ + "bar-latency.P50": 278527, + "bar-latency.P75": 278527, + "bar-latency.P90": 442367, + "bar-latency.P95": 442367, + "bar-latency.P99": 442367, + "bar-latency.P999": 442367, + "foo-latency.P50": 6143, + "foo-latency.P75": 12287, + "foo-latency.P90": 36863, + "foo-latency.P95": 36863, + "foo-latency.P99": 36863, + "foo-latency.P999": 36863, + "my-gauge": 43, + "my-histo.P50": 43, + "my-histo.P75": 335, + "my-histo.P90": 335, + "my-histo.P95": 335, + "my-histo.P99": 335, + "my-histo.P999": 335, + "other-gauge": 74, + }, g) + + f.Clear() + c, g = f.Snapshot() + require.Empty(t, c) + require.Empty(t, g) +} + +func TestLocalMetricsInterval(t *testing.T) { + refreshInterval := time.Millisecond + const relativeCheckFrequency = 5 // check 5 times per refreshInterval + const maxChecks = 2 * relativeCheckFrequency + checkInterval := (refreshInterval * relativeCheckFrequency) / maxChecks + + f := NewFactory(refreshInterval) + defer f.Stop() + + f.Timer(metrics.TimerOptions{ + Name: "timer", + }).Record(1) + + f.tm.Lock() + timer := f.timers["timer"] + f.tm.Unlock() + assert.NotNil(t, timer) + + // timer.hist.Current is modified on every Rotate(), which is called by Backend after every refreshInterval + getCurr := func() any { + timer.Lock() + defer timer.Unlock() + return timer.hist.Current + } + + curr := getCurr() + + // wait for twice as long as the refresh interval + for i := 0; i < maxChecks; i++ { + time.Sleep(checkInterval) + + if getCurr() != curr { + return + } + } + t.Fail() +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/metricstest.go b/receiver/jaegerreceiver/internal/internal/metricstest/metricstest.go new file mode 100755 index 000000000000..09f9282885fa --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/metricstest.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// ExpectedMetric contains metrics under test. +type ExpectedMetric struct { + Name string + Tags map[string]string + Value int +} + +// TODO do something similar for Timers + +// AssertCounterMetrics checks if counter metrics exist. +func (f *Factory) AssertCounterMetrics(t *testing.T, expectedMetrics ...ExpectedMetric) { + counters, _ := f.Snapshot() + assertMetrics(t, counters, expectedMetrics...) +} + +// AssertGaugeMetrics checks if gauge metrics exist. +func (f *Factory) AssertGaugeMetrics(t *testing.T, expectedMetrics ...ExpectedMetric) { + _, gauges := f.Snapshot() + assertMetrics(t, gauges, expectedMetrics...) +} + +func assertMetrics(t *testing.T, actualMetrics map[string]int64, expectedMetrics ...ExpectedMetric) { + for _, expected := range expectedMetrics { + key := GetKey(expected.Name, expected.Tags, "|", "=") + assert.EqualValues(t, + expected.Value, + actualMetrics[key], + "expected metric name=%s tags: %+v; got: %+v", expected.Name, expected.Tags, actualMetrics, + ) + } +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/metricstest_test.go b/receiver/jaegerreceiver/internal/internal/metricstest/metricstest_test.go new file mode 100755 index 000000000000..b33a4254ac08 --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/metricstest_test.go @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "testing" +) + +func TestAssertMetrics(t *testing.T) { + f := NewFactory(0) + tags := map[string]string{"key": "value"} + f.IncCounter("counter", tags, 1) + f.UpdateGauge("gauge", tags, 11) + + f.AssertCounterMetrics(t, ExpectedMetric{Name: "counter", Tags: tags, Value: 1}) + f.AssertGaugeMetrics(t, ExpectedMetric{Name: "gauge", Tags: tags, Value: 11}) +} diff --git a/receiver/jaegerreceiver/internal/internal/metricstest/package_test.go b/receiver/jaegerreceiver/internal/internal/metricstest/package_test.go new file mode 100755 index 000000000000..634e62a3dfe8 --- /dev/null +++ b/receiver/jaegerreceiver/internal/internal/metricstest/package_test.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2023 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +package metricstest + +import ( + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" +) + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/metrics/metrics_test.go b/receiver/jaegerreceiver/internal/metrics/metrics_test.go new file mode 100644 index 000000000000..36651ae8aca3 --- /dev/null +++ b/receiver/jaegerreceiver/internal/metrics/metrics_test.go @@ -0,0 +1,140 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Must use separate test package to break import cycle. +package metrics_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/internal/metricstest" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +func TestInitMetrics(t *testing.T) { + testMetrics := struct { + Gauge metrics.Gauge `metric:"gauge" tags:"1=one,2=two"` + Counter metrics.Counter `metric:"counter"` + Timer metrics.Timer `metric:"timer"` + Histogram metrics.Histogram `metric:"histogram" buckets:"20,40,60,80"` + }{} + + f := metricstest.NewFactory(0) + defer f.Stop() + + globalTags := map[string]string{"key": "value"} + + err := metrics.Init(&testMetrics, f, globalTags) + require.NoError(t, err) + + testMetrics.Gauge.Update(10) + testMetrics.Counter.Inc(5) + testMetrics.Timer.Record(time.Duration(time.Second * 35)) + testMetrics.Histogram.Record(42) + + // wait for metrics + for i := 0; i < 1000; i++ { + c, _ := f.Snapshot() + if _, ok := c["counter"]; ok { + break + } + time.Sleep(1 * time.Millisecond) + } + + c, g := f.Snapshot() + + assert.EqualValues(t, 5, c["counter|key=value"]) + assert.EqualValues(t, 10, g["gauge|1=one|2=two|key=value"]) + assert.EqualValues(t, 36863, g["timer|key=value.P50"]) + assert.EqualValues(t, 43, g["histogram|key=value.P50"]) + + stopwatch := metrics.StartStopwatch(testMetrics.Timer) + stopwatch.Stop() + assert.Positive(t, stopwatch.ElapsedTime()) +} + +var ( + noMetricTag = struct { + NoMetricTag metrics.Counter + }{} + + badTags = struct { + BadTags metrics.Counter `metric:"counter" tags:"1=one,noValue"` + }{} + + invalidMetricType = struct { + InvalidMetricType int64 `metric:"counter"` + }{} + + badHistogramBucket = struct { + BadHistogramBucket metrics.Histogram `metric:"histogram" buckets:"1,2,a,4"` + }{} + + badTimerBucket = struct { + BadTimerBucket metrics.Timer `metric:"timer" buckets:"1"` + }{} + + invalidBuckets = struct { + InvalidBuckets metrics.Counter `metric:"counter" buckets:"1"` + }{} +) + +func TestInitMetricsFailures(t *testing.T) { + require.EqualError(t, metrics.Init(&noMetricTag, nil, nil), "Field NoMetricTag is missing a tag 'metric'") + + require.EqualError(t, metrics.Init(&badTags, nil, nil), + "Field [BadTags]: Tag [noValue] is not of the form key=value in 'tags' string [1=one,noValue]") + + require.EqualError(t, metrics.Init(&invalidMetricType, nil, nil), + "Field InvalidMetricType is not a pointer to timer, gauge, or counter") + + require.EqualError(t, metrics.Init(&badHistogramBucket, nil, nil), + "Field [BadHistogramBucket]: Bucket [a] could not be converted to float64 in 'buckets' string [1,2,a,4]") + + require.EqualError(t, metrics.Init(&badTimerBucket, nil, nil), + "Field [BadTimerBucket]: Buckets are not currently initialized for timer metrics") + + require.EqualError(t, metrics.Init(&invalidBuckets, nil, nil), + "Field [InvalidBuckets]: Buckets should only be defined for Timer and Histogram metric types") +} + +func TestInitPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("The code did not panic") + } + }() + + metrics.MustInit(&noMetricTag, metrics.NullFactory, nil) +} + +func TestNullMetrics(*testing.T) { + // This test is just for cover + metrics.NullFactory.Timer(metrics.TimerOptions{ + Name: "name", + }).Record(0) + metrics.NullFactory.Counter(metrics.Options{ + Name: "name", + }).Inc(0) + metrics.NullFactory.Gauge(metrics.Options{ + Name: "name", + }).Update(0) + metrics.NullFactory.Histogram(metrics.HistogramOptions{ + Name: "name", + }).Record(0) + metrics.NullFactory.Namespace(metrics.NSOptions{ + Name: "name", + }).Gauge(metrics.Options{ + Name: "name2", + }).Update(0) +} + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/counter.go b/receiver/jaegerreceiver/internal/pkg/metrics/counter.go new file mode 100755 index 000000000000..2a70300997bb --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/counter.go @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +// Counter tracks the number of times an event has occurred +type Counter interface { + // Inc adds the given value to the counter. + Inc(int64) +} + +// NullCounter counter that does nothing +var NullCounter Counter = nullCounter{} + +type nullCounter struct{} + +func (nullCounter) Inc(int64) {} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/factory.go b/receiver/jaegerreceiver/internal/pkg/metrics/factory.go new file mode 100755 index 000000000000..1917aed196fd --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/factory.go @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "time" +) + +// NSOptions defines the name and tags map associated with a factory namespace +type NSOptions struct { + Name string + Tags map[string]string +} + +// Options defines the information associated with a metric +type Options struct { + Name string + Tags map[string]string + Help string +} + +// TimerOptions defines the information associated with a metric +type TimerOptions struct { + Name string + Tags map[string]string + Help string + Buckets []time.Duration +} + +// HistogramOptions defines the information associated with a metric +type HistogramOptions struct { + Name string + Tags map[string]string + Help string + Buckets []float64 +} + +// Factory creates new metrics +type Factory interface { + Counter(metric Options) Counter + Timer(metric TimerOptions) Timer + Gauge(metric Options) Gauge + Histogram(metric HistogramOptions) Histogram + + // Namespace returns a nested metrics factory. + Namespace(scope NSOptions) Factory +} + +// NullFactory is a metrics factory that returns NullCounter, NullTimer, and NullGauge. +var NullFactory Factory = nullFactory{} + +type nullFactory struct{} + +func (nullFactory) Counter(Options) Counter { + return NullCounter +} + +func (nullFactory) Timer(TimerOptions) Timer { + return NullTimer +} + +func (nullFactory) Gauge(Options) Gauge { + return NullGauge +} + +func (nullFactory) Histogram(HistogramOptions) Histogram { + return NullHistogram +} +func (nullFactory) Namespace(NSOptions /* scope */) Factory { return NullFactory } diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/gauge.go b/receiver/jaegerreceiver/internal/pkg/metrics/gauge.go new file mode 100755 index 000000000000..b16603868a6c --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/gauge.go @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +// Gauge returns instantaneous measurements of something as an int64 value +type Gauge interface { + // Update the gauge to the value passed in. + Update(int64) +} + +// NullGauge gauge that does nothing +var NullGauge Gauge = nullGauge{} + +type nullGauge struct{} + +func (nullGauge) Update(int64) {} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/histogram.go b/receiver/jaegerreceiver/internal/pkg/metrics/histogram.go new file mode 100755 index 000000000000..1bbe6b708a1f --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/histogram.go @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2018 The Jaeger Authors +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +// Histogram that keeps track of a distribution of values. +type Histogram interface { + // Records the value passed in. + Record(float64) +} + +// NullHistogram that does nothing +var NullHistogram Histogram = nullHistogram{} + +type nullHistogram struct{} + +func (nullHistogram) Record(float64) {} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/metrics.go b/receiver/jaegerreceiver/internal/pkg/metrics/metrics.go new file mode 100755 index 000000000000..1fbd1c6276b9 --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/metrics.go @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// MustInit initializes the passed in metrics and initializes its fields using the passed in factory. +// +// It uses reflection to initialize a struct containing metrics fields +// by assigning new Counter/Gauge/Timer values with the metric name retrieved +// from the `metric` tag and stats tags retrieved from the `tags` tag. +// +// Note: all fields of the struct must be exported, have a `metric` tag, and be +// of type Counter or Gauge or Timer. +// +// Errors during Init lead to a panic. +func MustInit(metrics any, factory Factory, globalTags map[string]string) { + if err := Init(metrics, factory, globalTags); err != nil { + panic(err.Error()) + } +} + +// Init does the same as MustInit, but returns an error instead of +// panicking. +func Init(m any, factory Factory, globalTags map[string]string) error { + // Allow user to opt out of reporting metrics by passing in nil. + if factory == nil { + factory = NullFactory + } + + counterPtrType := reflect.TypeOf((*Counter)(nil)).Elem() + gaugePtrType := reflect.TypeOf((*Gauge)(nil)).Elem() + timerPtrType := reflect.TypeOf((*Timer)(nil)).Elem() + histogramPtrType := reflect.TypeOf((*Histogram)(nil)).Elem() + + v := reflect.ValueOf(m).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + tags := make(map[string]string) + for k, v := range globalTags { + tags[k] = v + } + var buckets []float64 + field := t.Field(i) + metric := field.Tag.Get("metric") + if metric == "" { + return fmt.Errorf("Field %s is missing a tag 'metric'", field.Name) + } + if tagString := field.Tag.Get("tags"); tagString != "" { + tagPairs := strings.Split(tagString, ",") + for _, tagPair := range tagPairs { + tag := strings.Split(tagPair, "=") + if len(tag) != 2 { + return fmt.Errorf( + "Field [%s]: Tag [%s] is not of the form key=value in 'tags' string [%s]", + field.Name, tagPair, tagString) + } + tags[tag[0]] = tag[1] + } + } + if bucketString := field.Tag.Get("buckets"); bucketString != "" { + switch { + case field.Type.AssignableTo(timerPtrType): + // TODO: Parse timer duration buckets + return fmt.Errorf( + "Field [%s]: Buckets are not currently initialized for timer metrics", + field.Name) + case field.Type.AssignableTo(histogramPtrType): + bucketValues := strings.Split(bucketString, ",") + for _, bucket := range bucketValues { + b, err := strconv.ParseFloat(bucket, 64) + if err != nil { + return fmt.Errorf( + "Field [%s]: Bucket [%s] could not be converted to float64 in 'buckets' string [%s]", + field.Name, bucket, bucketString) + } + buckets = append(buckets, b) + } + default: + return fmt.Errorf( + "Field [%s]: Buckets should only be defined for Timer and Histogram metric types", + field.Name) + } + } + help := field.Tag.Get("help") + var obj any + switch { + case field.Type.AssignableTo(counterPtrType): + obj = factory.Counter(Options{ + Name: metric, + Tags: tags, + Help: help, + }) + case field.Type.AssignableTo(gaugePtrType): + obj = factory.Gauge(Options{ + Name: metric, + Tags: tags, + Help: help, + }) + case field.Type.AssignableTo(timerPtrType): + // TODO: Add buckets once parsed (see TODO above) + obj = factory.Timer(TimerOptions{ + Name: metric, + Tags: tags, + Help: help, + }) + case field.Type.AssignableTo(histogramPtrType): + obj = factory.Histogram(HistogramOptions{ + Name: metric, + Tags: tags, + Help: help, + Buckets: buckets, + }) + default: + return fmt.Errorf( + "Field %s is not a pointer to timer, gauge, or counter", + field.Name) + } + v.Field(i).Set(reflect.ValueOf(obj)) + } + return nil +} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/metrics_test.go b/receiver/jaegerreceiver/internal/pkg/metrics/metrics_test.go new file mode 100755 index 000000000000..36651ae8aca3 --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/metrics_test.go @@ -0,0 +1,140 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Must use separate test package to break import cycle. +package metrics_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/testutils" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/internal/metricstest" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" +) + +func TestInitMetrics(t *testing.T) { + testMetrics := struct { + Gauge metrics.Gauge `metric:"gauge" tags:"1=one,2=two"` + Counter metrics.Counter `metric:"counter"` + Timer metrics.Timer `metric:"timer"` + Histogram metrics.Histogram `metric:"histogram" buckets:"20,40,60,80"` + }{} + + f := metricstest.NewFactory(0) + defer f.Stop() + + globalTags := map[string]string{"key": "value"} + + err := metrics.Init(&testMetrics, f, globalTags) + require.NoError(t, err) + + testMetrics.Gauge.Update(10) + testMetrics.Counter.Inc(5) + testMetrics.Timer.Record(time.Duration(time.Second * 35)) + testMetrics.Histogram.Record(42) + + // wait for metrics + for i := 0; i < 1000; i++ { + c, _ := f.Snapshot() + if _, ok := c["counter"]; ok { + break + } + time.Sleep(1 * time.Millisecond) + } + + c, g := f.Snapshot() + + assert.EqualValues(t, 5, c["counter|key=value"]) + assert.EqualValues(t, 10, g["gauge|1=one|2=two|key=value"]) + assert.EqualValues(t, 36863, g["timer|key=value.P50"]) + assert.EqualValues(t, 43, g["histogram|key=value.P50"]) + + stopwatch := metrics.StartStopwatch(testMetrics.Timer) + stopwatch.Stop() + assert.Positive(t, stopwatch.ElapsedTime()) +} + +var ( + noMetricTag = struct { + NoMetricTag metrics.Counter + }{} + + badTags = struct { + BadTags metrics.Counter `metric:"counter" tags:"1=one,noValue"` + }{} + + invalidMetricType = struct { + InvalidMetricType int64 `metric:"counter"` + }{} + + badHistogramBucket = struct { + BadHistogramBucket metrics.Histogram `metric:"histogram" buckets:"1,2,a,4"` + }{} + + badTimerBucket = struct { + BadTimerBucket metrics.Timer `metric:"timer" buckets:"1"` + }{} + + invalidBuckets = struct { + InvalidBuckets metrics.Counter `metric:"counter" buckets:"1"` + }{} +) + +func TestInitMetricsFailures(t *testing.T) { + require.EqualError(t, metrics.Init(&noMetricTag, nil, nil), "Field NoMetricTag is missing a tag 'metric'") + + require.EqualError(t, metrics.Init(&badTags, nil, nil), + "Field [BadTags]: Tag [noValue] is not of the form key=value in 'tags' string [1=one,noValue]") + + require.EqualError(t, metrics.Init(&invalidMetricType, nil, nil), + "Field InvalidMetricType is not a pointer to timer, gauge, or counter") + + require.EqualError(t, metrics.Init(&badHistogramBucket, nil, nil), + "Field [BadHistogramBucket]: Bucket [a] could not be converted to float64 in 'buckets' string [1,2,a,4]") + + require.EqualError(t, metrics.Init(&badTimerBucket, nil, nil), + "Field [BadTimerBucket]: Buckets are not currently initialized for timer metrics") + + require.EqualError(t, metrics.Init(&invalidBuckets, nil, nil), + "Field [InvalidBuckets]: Buckets should only be defined for Timer and Histogram metric types") +} + +func TestInitPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("The code did not panic") + } + }() + + metrics.MustInit(&noMetricTag, metrics.NullFactory, nil) +} + +func TestNullMetrics(*testing.T) { + // This test is just for cover + metrics.NullFactory.Timer(metrics.TimerOptions{ + Name: "name", + }).Record(0) + metrics.NullFactory.Counter(metrics.Options{ + Name: "name", + }).Inc(0) + metrics.NullFactory.Gauge(metrics.Options{ + Name: "name", + }).Update(0) + metrics.NullFactory.Histogram(metrics.HistogramOptions{ + Name: "name", + }).Record(0) + metrics.NullFactory.Namespace(metrics.NSOptions{ + Name: "name", + }).Gauge(metrics.Options{ + Name: "name2", + }).Update(0) +} + +func TestMain(m *testing.M) { + testutils.VerifyGoLeaks(m) +} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/package.go b/receiver/jaegerreceiver/internal/pkg/metrics/package.go new file mode 100755 index 000000000000..b0da2b7c1a62 --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/package.go @@ -0,0 +1,8 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Package metrics provides an internal abstraction for metrics API, +// and command line flags for configuring the metrics backend. +package metrics diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/stopwatch.go b/receiver/jaegerreceiver/internal/pkg/metrics/stopwatch.go new file mode 100755 index 000000000000..7aa98f500eae --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/stopwatch.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "time" +) + +// StartStopwatch begins recording the executing time of an event, returning +// a Stopwatch that should be used to stop the recording the time for +// that event. Multiple events can be occurring simultaneously each +// represented by different active Stopwatches +func StartStopwatch(timer Timer) Stopwatch { + return Stopwatch{t: timer, start: time.Now()} +} + +// A Stopwatch tracks the execution time of a specific event +type Stopwatch struct { + t Timer + start time.Time +} + +// Stop stops executing of the stopwatch and records the amount of elapsed time +func (s Stopwatch) Stop() { + s.t.Record(s.ElapsedTime()) +} + +// ElapsedTime returns the amount of elapsed time (in time.Duration) +func (s Stopwatch) ElapsedTime() time.Duration { + return time.Since(s.start) +} diff --git a/receiver/jaegerreceiver/internal/pkg/metrics/timer.go b/receiver/jaegerreceiver/internal/pkg/metrics/timer.go new file mode 100755 index 000000000000..75b346ce5446 --- /dev/null +++ b/receiver/jaegerreceiver/internal/pkg/metrics/timer.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// Copyright (c) 2022 The Jaeger Authors. +// Copyright (c) 2017 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "time" +) + +// Timer accumulates observations about how long some operation took, +// and also maintains a historgam of percentiles. +type Timer interface { + // Records the time passed in. + Record(time.Duration) +} + +// NullTimer timer that does nothing +var NullTimer Timer = nullTimer{} + +type nullTimer struct{} + +func (nullTimer) Record(time.Duration) {} diff --git a/receiver/jaegerreceiver/jaeger_agent_test.go b/receiver/jaegerreceiver/jaeger_agent_test.go index 48acf02852d7..0dd871bcee5f 100644 --- a/receiver/jaegerreceiver/jaeger_agent_test.go +++ b/receiver/jaegerreceiver/jaeger_agent_test.go @@ -13,7 +13,7 @@ import ( "github.com/jaegertracing/jaeger-idl/model/v1" "github.com/jaegertracing/jaeger-idl/thrift-gen/agent" jaegerthrift "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" - "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers/thriftudp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" diff --git a/receiver/jaegerreceiver/trace_receiver.go b/receiver/jaegerreceiver/trace_receiver.go index 554456e5cbe8..876cf82b0d21 100644 --- a/receiver/jaegerreceiver/trace_receiver.go +++ b/receiver/jaegerreceiver/trace_receiver.go @@ -20,10 +20,10 @@ import ( "github.com/jaegertracing/jaeger-idl/thrift-gen/agent" "github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger" "github.com/jaegertracing/jaeger-idl/thrift-gen/zipkincore" - "github.com/jaegertracing/jaeger/cmd/agent/app/processors" - "github.com/jaegertracing/jaeger/cmd/agent/app/servers" - "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" - "github.com/jaegertracing/jaeger/pkg/metrics" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/processors" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/cmd/servers/thriftudp" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver/internal/pkg/metrics" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componentstatus" "go.opentelemetry.io/collector/consumer"