From b17a46194257a95d5043ba349e4150be21531fef Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Fri, 14 Aug 2026 17:48:42 +0700 Subject: [PATCH 1/8] feat: migrate router tracing to OpenTelemetry, add Pyroscope profiling Migrate engines/router's tracer from OpenTracing/Jaeger-client to OpenTelemetry (OTLP HTTP exporter, W3C + B3 trace-context propagation), and add pyroscope-go continuous profiling to both engines/router and api. api also gains its own OTel HTTP-layer tracing alongside New Relic, and templates Pyroscope config (including generic HTTP auth headers) into deployed router pods via RouterDefaults. - engines/router: new OTel-based tracing.Tracer interface, B3 propagation, pyroscope-go profiling package, dev-compose wiring. - api: OTel tracing + pyroscope-go profiler in server.Run(), Otel/ Pyroscope config sections, PyroscopeEnabled persisted on RouterVersion.LogConfig, env vars injected into router pods. - Config validation: required_if=Enabled checks and empty-address guards for OTel/Pyroscope endpoints. - OpenAPI spec, example.yaml, and generated SDK client updated to match. Co-Authored-By: Claude Sonnet 5 --- api/api/openapi.bundle.yaml | 4 + api/api/specs/routers.yaml | 2 + api/go.mod | 21 +- api/go.sum | 49 ++--- api/turing/api/request/request.go | 1 + api/turing/api/request/request_test.go | 2 + api/turing/cluster/servicebuilder/router.go | 24 ++ .../cluster/servicebuilder/router_test.go | 77 +++++++ api/turing/config/config.go | 40 ++++ api/turing/config/config_test.go | 63 ++++++ api/turing/config/example.yaml | 27 ++- api/turing/config/testdata/config-1.yaml | 4 + api/turing/models/log_config.go | 2 + api/turing/models/log_config_test.go | 3 + api/turing/server/api.go | 5 +- api/turing/server/application.go | 25 +++ api/turing/server/instrumentation.go | 84 +++++++ api/turing/server/instrumentation_test.go | 65 ++++++ engines/router/.env.development | 7 +- engines/router/compose/tracing.yaml | 10 +- engines/router/go.mod | 82 ++++--- engines/router/go.sum | 206 ++++++++++++------ engines/router/missionctl/config/config.go | 21 +- .../router/missionctl/config/config_test.go | 49 ++++- engines/router/missionctl/fiberapi/fan_in.go | 6 +- .../missionctl/fiberapi/interceptors.go | 10 +- .../missionctl/fiberapi/interceptors_test.go | 72 ++---- .../instrumentation/profiling/profiling.go | 41 ++++ .../profiling/profiling_test.go | 53 +++++ .../instrumentation/tracing/jaeger.go | 58 ----- .../instrumentation/tracing/jaeger_test.go | 75 ------- .../missionctl/instrumentation/tracing/nop.go | 21 +- .../instrumentation/tracing/nop_test.go | 27 +-- .../instrumentation/tracing/otel.go | 108 +++++++++ .../instrumentation/tracing/otel_test.go | 124 +++++++++++ .../instrumentation/tracing/tracing.go | 81 +++---- .../instrumentation/tracing/tracing_test.go | 51 +++-- .../router/missionctl/server/application.go | 29 ++- .../http/handlers/batch_http_handler.go | 6 +- .../server/http/handlers/http_handler.go | 10 +- .../router/missionctl/server/upi/server.go | 6 +- .../model/router_version_log_config.py | 3 + 42 files changed, 1200 insertions(+), 454 deletions(-) create mode 100644 api/turing/server/instrumentation.go create mode 100644 api/turing/server/instrumentation_test.go create mode 100644 engines/router/missionctl/instrumentation/profiling/profiling.go create mode 100644 engines/router/missionctl/instrumentation/profiling/profiling_test.go delete mode 100644 engines/router/missionctl/instrumentation/tracing/jaeger.go delete mode 100644 engines/router/missionctl/instrumentation/tracing/jaeger_test.go create mode 100644 engines/router/missionctl/instrumentation/tracing/otel.go create mode 100644 engines/router/missionctl/instrumentation/tracing/otel_test.go diff --git a/api/api/openapi.bundle.yaml b/api/api/openapi.bundle.yaml index d77a8e746..e393c1a00 100644 --- a/api/api/openapi.bundle.yaml +++ b/api/api/openapi.bundle.yaml @@ -2117,6 +2117,7 @@ components: batch_load: true table: table service_account_secret: service_account_secret + pyroscope_enabled: true kafka_config: brokers: brokers topic: topic @@ -3709,6 +3710,7 @@ components: batch_load: true table: table service_account_secret: service_account_secret + pyroscope_enabled: true kafka_config: brokers: brokers topic: topic @@ -3724,6 +3726,8 @@ components: type: boolean jaeger_enabled: type: boolean + pyroscope_enabled: + type: boolean result_logger_type: $ref: '#/components/schemas/ResultLoggerType' bigquery_config: diff --git a/api/api/specs/routers.yaml b/api/api/specs/routers.yaml index e8bce212e..3e94dd922 100644 --- a/api/api/specs/routers.yaml +++ b/api/api/specs/routers.yaml @@ -573,6 +573,8 @@ components: type: "boolean" jaeger_enabled: type: "boolean" + pyroscope_enabled: + type: "boolean" result_logger_type: $ref: "#/components/schemas/ResultLoggerType" bigquery_config: diff --git a/api/go.mod b/api/go.mod index ffa1f93da..4315e86b0 100644 --- a/api/go.mod +++ b/api/go.mod @@ -23,6 +23,7 @@ require ( github.com/google/go-containerregistry v0.19.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.1.0 + github.com/grafana/pyroscope-go v1.2.0 github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 @@ -35,6 +36,10 @@ require ( github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.9.0 github.com/xanzy/go-gitlab v0.32.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 go.uber.org/zap v1.26.0 golang.org/x/oauth2 v0.18.0 google.golang.org/grpc v1.62.0 @@ -61,7 +66,6 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.39.0 // indirect - github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/ajg/form v1.5.1 // indirect @@ -88,6 +92,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blendle/zapdriver v1.3.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -135,7 +140,8 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect @@ -164,7 +170,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/kevinburke/ssh_config v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.8 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.3 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -185,7 +191,6 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -207,8 +212,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect - github.com/uber/jaeger-lib v2.4.0+incompatible // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.34.0 // indirect github.com/vbatts/tar-split v0.11.3 // indirect @@ -223,11 +226,11 @@ require ( github.com/zaffka/zap-to-hclog v0.10.6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/api/go.sum b/api/go.sum index e812d041b..46ed8088c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -43,8 +43,6 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/GoogleCloudPlatform/spark-on-k8s-operator v0.0.0-20221025152940-c261df66a006 h1:4DDmvGcwJCBvTOI9JVR9Kr0LsrEthCTpewXmX21oPJI= github.com/GoogleCloudPlatform/spark-on-k8s-operator v0.0.0-20221025152940-c261df66a006/go.mod h1:LOBOhAPsC+SOpFPlfpVG7W0pLTypq5woAtAzQ/LbwoE= -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/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -60,7 +58,6 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -131,6 +128,8 @@ github.com/caraml-dev/mlp v1.13.2 h1:N3lk+ToQ281duZImQLTQ28uJtmoc9Zkxx1CR94rS15U github.com/caraml-dev/mlp v1.13.2/go.mod h1:9kPooDSYsVu5q/z2K4T9uu08RGyiFNbCAFnQVBMJxOk= github.com/caraml-dev/universal-prediction-interface v0.3.6 h1:G/D4aukfjLECl8armJqFy/R2+0u/f4AiurSFqAo33uQ= github.com/caraml-dev/universal-prediction-interface v0.3.6/go.mod h1:e0qmFOXQxx8HFg5ObYyQO3WVnrqsr5v5JApFmeF7eJo= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -230,7 +229,6 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -314,7 +312,6 @@ github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1: github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang-migrate/migrate/v4 v4.11.0 h1:uqtd0ysK5WyBQ/T1K2uDIooJV0o2Obt6uPwP062DupQ= github.com/golang-migrate/migrate/v4 v4.11.0/go.mod h1:nqbpDbckcYjsCD5I8q5+NI9Tkk7SVcmaF40Ax1eAWhg= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -407,9 +404,13 @@ github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grafana/pyroscope-go v1.2.0 h1:aILLKjTj8CS8f/24OPMGPewQSYlhmdQMBmol1d3KGj8= +github.com/grafana/pyroscope-go v1.2.0/go.mod h1:2GHr28Nr05bg2pElS+dDsc98f3JTUh2f6Fz1hWXrqwk= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -519,7 +520,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= @@ -530,8 +530,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL 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.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -642,8 +642,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -759,10 +757,6 @@ github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= -github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -815,19 +809,27 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -864,16 +866,12 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -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-20190121172915-509febef88a4/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-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200213203834-85f925bdd4d0/go.mod h1:IX6Eufr4L0ErOUlzqX/aFlHqsiKZRbV42Kb69e9VsTE= -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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1046,12 +1044,10 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1097,10 +1093,6 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -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/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/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1320,7 +1312,6 @@ moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A= mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/api/turing/api/request/request.go b/api/turing/api/request/request.go index c43cf3f1b..4f311d374 100644 --- a/api/turing/api/request/request.go +++ b/api/turing/api/request/request.go @@ -159,6 +159,7 @@ func (r RouterConfig) BuildRouterVersion( CustomMetricsEnabled: defaults.CustomMetricsEnabled, FiberDebugLogEnabled: defaults.FiberDebugLogEnabled, JaegerEnabled: defaults.JaegerEnabled, + PyroscopeEnabled: defaults.PyroscopeEnabled, ResultLoggerType: r.LogConfig.ResultLoggerType, }, } diff --git a/api/turing/api/request/request_test.go b/api/turing/api/request/request_test.go index 8c668f764..07495c58a 100644 --- a/api/turing/api/request/request_test.go +++ b/api/turing/api/request/request_test.go @@ -357,6 +357,7 @@ func TestRequestBuildRouterVersionWithDefaultConfig(t *testing.T) { CustomMetricsEnabled: true, JaegerEnabled: true, JaegerCollectorEndpoint: "jaegerendpoint", + PyroscopeEnabled: true, LogLevel: "DEBUG", FluentdConfig: &config.FluentdConfig{ Image: "fluentdimage", @@ -404,6 +405,7 @@ func TestRequestBuildRouterVersionWithDefaultConfig(t *testing.T) { CustomMetricsEnabled: true, FiberDebugLogEnabled: true, JaegerEnabled: true, + PyroscopeEnabled: true, ResultLoggerType: models.BigQueryLogger, BigQueryConfig: &models.BigQueryConfig{ Table: "project.dataset.table", diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index d7d69d118..5132542c1 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/url" + "sort" "strconv" "strings" @@ -37,6 +38,9 @@ const ( envCustomMetrics = "APP_CUSTOM_METRICS" envJaegerEnabled = "APP_JAEGER_ENABLED" envJaegerEndpoint = "APP_JAEGER_COLLECTOR_ENDPOINT" + envPyroscopeEnabled = "APP_PYROSCOPE_ENABLED" + envPyroscopeServerAddress = "APP_PYROSCOPE_SERVER_ADDRESS" + envPyroscopeHTTPHeaders = "APP_PYROSCOPE_HTTP_HEADERS" envSentryEnabled = "APP_SENTRY_ENABLED" envSentryDSN = "APP_SENTRY_DSN" envResultLogger = "APP_RESULT_LOGGER" @@ -202,6 +206,23 @@ func (sb *clusterSvcBuilder) GetRouterServiceName(routerVersion *models.RouterVe return GetComponentName(routerVersion, ComponentTypes.Router) } +// formatHTTPHeaders serializes headers into the "Key1:Val1,Key2:Val2" format +// expected by the router's envconfig-based map decoding, with keys sorted for +// deterministic output. +func formatHTTPHeaders(headers map[string]string) string { + keys := make([]string, 0, len(headers)) + for k := range headers { + keys = append(keys, k) + } + sort.Strings(keys) + + pairs := make([]string, 0, len(keys)) + for _, k := range keys { + pairs = append(pairs, fmt.Sprintf("%s:%s", k, headers[k])) + } + return strings.Join(pairs, ",") +} + func (sb *clusterSvcBuilder) buildRouterEnvs( namespace string, environmentType string, @@ -219,6 +240,8 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envAppEnvironment, Value: environmentType}, {Name: envRouterTimeout, Value: ver.Timeout}, {Name: envJaegerEndpoint, Value: routerDefaults.JaegerCollectorEndpoint}, + {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, + {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, {Name: envRouterConfigFile, Value: routerConfigMapMountPath + routerConfigFileName}, {Name: envRouterProtocol, Value: string(ver.Protocol)}, {Name: envSentryEnabled, Value: strconv.FormatBool(sentryEnabled)}, @@ -260,6 +283,7 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envLogLevel, Value: string(logConfig.LogLevel)}, {Name: envCustomMetrics, Value: strconv.FormatBool(logConfig.CustomMetricsEnabled)}, {Name: envJaegerEnabled, Value: strconv.FormatBool(logConfig.JaegerEnabled)}, + {Name: envPyroscopeEnabled, Value: strconv.FormatBool(logConfig.PyroscopeEnabled)}, {Name: envResultLogger, Value: string(logConfig.ResultLoggerType)}, {Name: envFiberDebugLog, Value: strconv.FormatBool(logConfig.FiberDebugLogEnabled)}, }) diff --git a/api/turing/cluster/servicebuilder/router_test.go b/api/turing/cluster/servicebuilder/router_test.go index 773f5ee97..3b3d66490 100644 --- a/api/turing/cluster/servicebuilder/router_test.go +++ b/api/turing/cluster/servicebuilder/router_test.go @@ -124,6 +124,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -131,6 +133,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -227,6 +230,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -234,6 +239,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -329,6 +335,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -340,6 +348,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -438,6 +447,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -445,6 +456,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -539,6 +551,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -546,6 +560,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -640,6 +655,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -647,6 +664,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -741,6 +759,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -748,6 +768,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_GCP_PROJECT", Value: "gcp-project-id"}, @@ -842,6 +863,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -851,6 +874,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "nop"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, }, @@ -972,6 +996,8 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, @@ -979,6 +1005,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "nop"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, }, @@ -1048,6 +1075,7 @@ func TestNewRouterService(t *testing.T) { data.expRawConfig, &config.RouterDefaults{ JaegerCollectorEndpoint: "jaeger-endpoint", + PyroscopeServerAddress: "pyroscope-address", FluentdConfig: &config.FluentdConfig{Tag: "fluentd-tag"}, }, true, @@ -1138,6 +1166,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { environmentType: "dev", routerDefaults: &config.RouterDefaults{ JaegerCollectorEndpoint: "", + PyroscopeServerAddress: "http://pyroscope.example.com:4040", FluentdConfig: &config.FluentdConfig{Tag: ""}, KafkaConfig: &config.KafkaConfig{ MaxMessageBytes: 123, @@ -1171,6 +1200,8 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "dev"}, {Name: "ROUTER_TIMEOUT", Value: "10s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "http://pyroscope.example.com:4040"}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "false"}, @@ -1178,6 +1209,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "DEBUG"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "kafka"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_KAFKA_BROKERS", Value: "1.1.1.1:1111"}, @@ -1216,6 +1248,8 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: ""}, {Name: "ROUTER_TIMEOUT", Value: ""}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, + {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: ""}, + {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, {Name: "APP_SENTRY_ENABLED", Value: "false"}, @@ -1223,6 +1257,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_LOGLEVEL", Value: ""}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "upi"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, {Name: "APP_KAFKA_BROKERS", Value: "broker"}, @@ -1250,3 +1285,45 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { }) } } + +func TestFormatHTTPHeaders(t *testing.T) { + tests := []struct { + name string + headers map[string]string + want string + }{ + { + name: "nil", + headers: nil, + want: "", + }, + { + name: "empty", + headers: map[string]string{}, + want: "", + }, + { + name: "single header", + headers: map[string]string{"Authorization": "Bearer token"}, + want: "Authorization:Bearer token", + }, + { + name: "multiple headers sorted by key regardless of map order", + headers: map[string]string{ + "X-Scope-OrgID": "tenant1", + "Authorization": "Bearer token", + "X-Custom": "value", + }, + want: "Authorization:Bearer token,X-Custom:value,X-Scope-OrgID:tenant1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Run repeatedly since Go map iteration order is randomized per run, + // to catch any accidental reliance on iteration order. + for i := 0; i < 5; i++ { + assert.Equal(t, tt.want, formatHTTPHeaders(tt.headers)) + } + }) + } +} diff --git a/api/turing/config/config.go b/api/turing/config/config.go index d7a84a1f3..0db5aa9a4 100644 --- a/api/turing/config/config.go +++ b/api/turing/config/config.go @@ -83,6 +83,8 @@ type Config struct { KnativeServiceDefaults *KnativeServiceDefaults NewRelicConfig newrelic.Config Sentry sentry.Config + Otel OtelConfig + Pyroscope PyroscopeConfig ClusterConfig ClusterConfig `validate:"required"` TuringEncryptionKey string `validate:"required"` AlertConfig *AlertConfig @@ -328,6 +330,14 @@ type RouterDefaults struct { // Jaeger collector endpoint. If JaegerEnabled is true, this value // must be set. JaegerCollectorEndpoint string + // Enable Pyroscope profiling for routers deployed by this instance of the Turing API + PyroscopeEnabled bool + // Pyroscope server address routers should report profiles to. If PyroscopeEnabled is + // true, this value must be set. + PyroscopeServerAddress string `validate:"required_if=PyroscopeEnabled True"` + // HTTP headers routers should attach to every profile push request they make to + // PyroscopeServerAddress, e.g. for auth (Authorization, X-Scope-OrgID, ...). Optional. + PyroscopeHTTPHeaders map[string]string // Router log level LogLevel string `validate:"required"` // Fluentd config for the router @@ -349,6 +359,27 @@ type RouterDefaults struct { UPIConfig *UPIConfig } +// OtelConfig captures the settings for HTTP-layer request tracing using OpenTelemetry +type OtelConfig struct { + Enabled bool + // OtlpEndpoint is the OTLP HTTP endpoint spans are exported to, e.g. http://otel-collector:4318. + // If Enabled is true, this value must be set. + OtlpEndpoint string `validate:"required_if=Enabled True"` + // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 1 (sample all). + SamplingRatio float64 +} + +// PyroscopeConfig captures the settings for continuous profiling of the Turing API using Pyroscope +type PyroscopeConfig struct { + Enabled bool + // ServerAddress is the Pyroscope server address to report profiles to. If Enabled is + // true, this value must be set. + ServerAddress string `validate:"required_if=Enabled True"` + // HTTPHeaders are attached to every profile push request, e.g. for auth + // (Authorization, X-Scope-OrgID, ...). Optional. + HTTPHeaders map[string]string +} + // FluentdConfig captures the defaults used by the Turing Router when Fluentd is enabled type FluentdConfig struct { // Image to use for fluentd deployments, in the format registry/repository:version. @@ -610,6 +641,8 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("RouterDefaults::CustomMetricsEnabled", "false") v.SetDefault("RouterDefaults::JaegerEnabled", "false") v.SetDefault("RouterDefaults::JaegerCollectorEndpoint", "") + v.SetDefault("RouterDefaults::PyroscopeEnabled", "false") + v.SetDefault("RouterDefaults::PyroscopeServerAddress", "") v.SetDefault("RouterDefaults::LogLevel", "INFO") v.SetDefault("RouterDefaults::FluentdConfig::Image", "") v.SetDefault("RouterDefaults::FluentdConfig::Tag", "turing-result.log") @@ -622,6 +655,13 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("Sentry::Enabled", "false") v.SetDefault("Sentry::DSN", "") + v.SetDefault("Otel::Enabled", "false") + v.SetDefault("Otel::OtlpEndpoint", "") + v.SetDefault("Otel::SamplingRatio", "1") + + v.SetDefault("Pyroscope::Enabled", "false") + v.SetDefault("Pyroscope::ServerAddress", "") + v.SetDefault("TuringEncryptionKey", "") v.SetDefault("AlertConfig::Enabled", "false") diff --git a/api/turing/config/config_test.go b/api/turing/config/config_test.go index 114ced468..518ef9339 100644 --- a/api/turing/config/config_test.go +++ b/api/turing/config/config_test.go @@ -186,6 +186,7 @@ func TestLoad(t *testing.T) { CompressionType: "none", }, }, + Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{}, ClusterConfig: config.ClusterConfig{ InClusterConfig: false, @@ -301,7 +302,14 @@ func TestLoad(t *testing.T) { MaxMessageBytes: 1048588, CompressionType: "none", }, + PyroscopeEnabled: true, + PyroscopeServerAddress: "http://pyroscope.example.com:4040", + // viper lowercases YAML map keys, so header names configured this way + // always come out lowercase (harmless: HTTP header names are + // case-insensitive). + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, }, + Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -464,7 +472,14 @@ func TestLoad(t *testing.T) { MaxMessageBytes: 1234567, CompressionType: "snappy", }, + PyroscopeEnabled: true, + PyroscopeServerAddress: "http://pyroscope.example.com:4040", + // viper lowercases YAML map keys, so header names configured this way + // always come out lowercase (harmless: HTTP header names are + // case-insensitive). + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, }, + Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -645,7 +660,14 @@ func TestLoad(t *testing.T) { MaxMessageBytes: 1234567, CompressionType: "snappy", }, + PyroscopeEnabled: true, + PyroscopeServerAddress: "http://pyroscope.example.com:4040", + // viper lowercases YAML map keys, so header names configured this way + // always come out lowercase (harmless: HTTP header names are + // case-insensitive). + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, }, + Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -740,6 +762,18 @@ func TestLoad(t *testing.T) { } } +func TestLoad_OtelAndPyroscope(t *testing.T) { + cfg, err := config.Load("testdata/config-1.yaml") + require.NoError(t, err) + + assert.Equal(t, false, cfg.Otel.Enabled) + assert.Equal(t, float64(1), cfg.Otel.SamplingRatio) + assert.Equal(t, false, cfg.Pyroscope.Enabled) + assert.Equal(t, true, cfg.RouterDefaults.PyroscopeEnabled) + assert.Equal(t, "http://pyroscope.example.com:4040", cfg.RouterDefaults.PyroscopeServerAddress) + assert.Equal(t, map[string]string{"authorization": "Bearer token"}, cfg.RouterDefaults.PyroscopeHTTPHeaders) +} + // Reference: // https://github.com/mitchellh/mapstructure/blob/ce2ff0c13ce509e36e9254c08ea0bca90ed5af6c/decode_hooks_test.go#L128 func TestStringToQuantityHookFunc(t *testing.T) { @@ -1058,6 +1092,35 @@ func TestConfigValidate(t *testing.T) { }, wantErr: true, }, + "otel enabled but missing OtlpEndpoint": { + validConfigUpdate: func(validConfig config.Config) config.Config { + validConfig.Otel = config.OtelConfig{Enabled: true} + return validConfig + }, + wantErr: true, + }, + "otel enabled with OtlpEndpoint set": { + validConfigUpdate: func(validConfig config.Config) config.Config { + validConfig.Otel = config.OtelConfig{Enabled: true, OtlpEndpoint: "http://otel-collector.example.com:4318"} + return validConfig + }, + wantErr: false, + }, + "api pyroscope enabled but missing ServerAddress": { + validConfigUpdate: func(validConfig config.Config) config.Config { + validConfig.Pyroscope = config.PyroscopeConfig{Enabled: true} + return validConfig + }, + wantErr: true, + }, + "router defaults pyroscope enabled but missing PyroscopeServerAddress": { + validConfigUpdate: func(validConfig config.Config) config.Config { + validConfig.RouterDefaults.PyroscopeEnabled = true + validConfig.RouterDefaults.PyroscopeServerAddress = "" + return validConfig + }, + wantErr: true, + }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { diff --git a/api/turing/config/example.yaml b/api/turing/config/example.yaml index c6a94e3df..22233ee6c 100644 --- a/api/turing/config/example.yaml +++ b/api/turing/config/example.yaml @@ -149,7 +149,15 @@ RouterDefaults: FiberDebugLogEnabled: false CustomMetricsEnabled: false JaegerEnabled: false - JaegerCollectorEndpoint: http://jaeger-tracing-collector.example.com:14268/api/traces + JaegerCollectorEndpoint: http://otel-collector.example.com:4318 + PyroscopeEnabled: false + PyroscopeServerAddress: http://pyroscope.example.com:4040 + # HTTP headers attached to every profile push request routers make to + # PyroscopeServerAddress, e.g. for auth. Optional, omit entirely if the server + # doesn't require auth. Note: header names are lowercased by the config + # loader; this is harmless since HTTP header names are case-insensitive. + PyroscopeHTTPHeaders: + Authorization: " or "Bearer "> LogLevel: INFO # Fluentd log forwarder configuration that can be used in Turing router @@ -171,6 +179,23 @@ RouterDefaults: # Note that {{.ProjectName}}, {{.ClusterName}}, {{.RouterName}} and {{.Version}} is required MonitoringURLFormat: "https://www.example.com/{{.ClusterName}}/{{.ProjectName}}/{{.RouterName}}/{{.Version}}" +# OpenTelemetry (OTEL) tracing configuration +Otel: + Enabled: false + OtlpEndpoint: http://otel-collector.example.com:4318 + SamplingRatio: 1 + +# Pyroscope profiling service configuration +Pyroscope: + Enabled: false + ServerAddress: http://pyroscope.example.com:4040 + # HTTP headers attached to every profile push request, e.g. for auth. Optional, + # omit entirely if the server doesn't require auth. Note: header names are + # lowercased by the config loader; this is harmless since HTTP header names + # are case-insensitive. + HTTPHeaders: + Authorization: " or "Bearer "> + # Sentry application monitoring service configuration # https://docs.sentry.io/product/sentry-basics/dsn-explainer/ Sentry: diff --git a/api/turing/config/testdata/config-1.yaml b/api/turing/config/testdata/config-1.yaml index 968217574..ecc740181 100644 --- a/api/turing/config/testdata/config-1.yaml +++ b/api/turing/config/testdata/config-1.yaml @@ -55,6 +55,10 @@ RouterDefaults: FluentdConfig: FlushIntervalSeconds: 60 WorkerCount: 2 + PyroscopeEnabled: true + PyroscopeServerAddress: http://pyroscope.example.com:4040 + PyroscopeHTTPHeaders: + Authorization: Bearer token Sentry: Enabled: true Labels: diff --git a/api/turing/models/log_config.go b/api/turing/models/log_config.go index 2114aca8c..c544f69f1 100644 --- a/api/turing/models/log_config.go +++ b/api/turing/models/log_config.go @@ -65,6 +65,8 @@ type LogConfig struct { FiberDebugLogEnabled bool `json:"fiber_debug_log_enabled"` // Enable Jaeger tracing. JaegerEnabled bool `json:"jaeger_enabled"` + // Enable Pyroscope profiling. + PyroscopeEnabled bool `json:"pyroscope_enabled"` // Result Logger type. The associated config must not be null. ResultLoggerType ResultLogger `json:"result_logger_type"` // Configuration necessary to log results to BigQuery. Cannot be empty if diff --git a/api/turing/models/log_config_test.go b/api/turing/models/log_config_test.go index 679464512..abe081adb 100644 --- a/api/turing/models/log_config_test.go +++ b/api/turing/models/log_config_test.go @@ -26,6 +26,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": true, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "pyroscope_enabled": false, "result_logger_type": "nop" }`), }, @@ -44,6 +45,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": false, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "pyroscope_enabled": false, "result_logger_type": "bigquery", "bigquery_config": { "table": "test-table", @@ -67,6 +69,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": false, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "pyroscope_enabled": false, "result_logger_type": "kafka", "kafka_config": { "brokers": "test-brokers", diff --git a/api/turing/server/api.go b/api/turing/server/api.go index fac1ef398..80c23e96c 100644 --- a/api/turing/server/api.go +++ b/api/turing/server/api.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/rs/cors" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "github.com/caraml-dev/turing/api/turing/api" "github.com/caraml-dev/turing/api/turing/config" @@ -59,7 +60,9 @@ func AddAPIRoutesHandler(r *mux.Router, path string, appCtx *api.AppContext, cfg for _, c := range controllers { for _, route := range c.Routes() { // NewRelic handler - _, handler := newrelic.WrapHandle(route.Name(), route.HandlerFunc(validator)) + _, nrHandler := newrelic.WrapHandle(route.Name(), route.HandlerFunc(validator)) + // Wrap with OTel span, alongside (not replacing) the New Relic instrumentation above + handler := otelhttp.NewHandler(nrHandler, route.Name()) apiRouter.Name(route.Name()). Methods(route.Method()). diff --git a/api/turing/server/application.go b/api/turing/server/application.go index 410b51d89..0930e7896 100644 --- a/api/turing/server/application.go +++ b/api/turing/server/application.go @@ -1,6 +1,7 @@ package server import ( + "context" "flag" "net/http" "strings" @@ -74,6 +75,30 @@ func Run() { } defer newrelic.Shutdown(5 * time.Second) + // Initialise OTel tracer + tracerShutdown, err := initTracer(cfg.Otel) + if err != nil { + log.Errorf("Failed to initialize OTel tracer: %s", err) + } + defer func() { + if err := tracerShutdown(context.Background()); err != nil { + log.Errorf("Failed to shut down OTel tracer: %s", err) + } + }() + + // Initialise Pyroscope profiler + profiler, err := initProfiler(cfg.Pyroscope) + if err != nil { + log.Errorf("Failed to initialize Pyroscope profiler: %s", err) + } + if profiler != nil { + defer func() { + if err := profiler.Stop(); err != nil { + log.Errorf("Failed to stop Pyroscope profiler: %s", err) + } + }() + } + // Init app context appCtx, err := api.NewAppContext(db, cfg) if err != nil { diff --git a/api/turing/server/instrumentation.go b/api/turing/server/instrumentation.go new file mode 100644 index 000000000..4c98a0d78 --- /dev/null +++ b/api/turing/server/instrumentation.go @@ -0,0 +1,84 @@ +package server + +import ( + "context" + "fmt" + + "github.com/grafana/pyroscope-go" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + + "github.com/caraml-dev/turing/api/turing/config" +) + +const appName = "turing-api" + +// initTracer initializes the global OpenTelemetry tracer provider for api, exporting +// spans via OTLP HTTP, and returns its shutdown function. When cfg.Enabled is false, +// it returns a no-op shutdown function and leaves the OTel globals untouched. The +// returned shutdown function is always non-nil and safe to call, even when a non-nil +// error is also returned, so callers can unconditionally defer it. +func initTracer(cfg config.OtelConfig) (func(context.Context) error, error) { + noopShutdown := func(context.Context) error { return nil } + + if !cfg.Enabled { + return noopShutdown, nil + } + + ctx := context.Background() + exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(cfg.OtlpEndpoint)) + if err != nil { + return noopShutdown, err + } + + ratio := cfg.SamplingRatio + if ratio <= 0 { + ratio = 1 + } + + res := resource.NewSchemaless(attribute.String("service.name", appName)) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(ratio))), + sdktrace.WithResource(res), + ) + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{}, + )) + + return tp.Shutdown, nil +} + +// initProfiler starts continuous profiling via pyroscope-go if enabled in cfg. Returns +// a nil profiler and nil error when profiling is disabled. pyroscope.Start does not itself +// error on an empty ServerAddress -- it happily constructs a client that fails silently on +// every upload -- so an empty address is rejected explicitly here instead. +func initProfiler(cfg config.PyroscopeConfig) (*pyroscope.Profiler, error) { + if !cfg.Enabled { + return nil, nil + } + if cfg.ServerAddress == "" { + return nil, fmt.Errorf("pyroscope profiling is enabled but ServerAddress is empty") + } + + return pyroscope.Start(pyroscope.Config{ + ApplicationName: appName, + ServerAddress: cfg.ServerAddress, + HTTPHeaders: cfg.HTTPHeaders, + ProfileTypes: []pyroscope.ProfileType{ + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + pyroscope.ProfileGoroutines, + }, + }) +} diff --git a/api/turing/server/instrumentation_test.go b/api/turing/server/instrumentation_test.go new file mode 100644 index 000000000..53e54bb14 --- /dev/null +++ b/api/turing/server/instrumentation_test.go @@ -0,0 +1,65 @@ +package server + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/caraml-dev/turing/api/turing/config" +) + +func TestInitTracer_Disabled(t *testing.T) { + shutdown, err := initTracer(config.OtelConfig{Enabled: false}) + require.NoError(t, err) + require.NotNil(t, shutdown) + assert.NoError(t, shutdown(context.Background())) +} + +func TestInitTracer_Enabled(t *testing.T) { + shutdown, err := initTracer(config.OtelConfig{ + Enabled: true, + OtlpEndpoint: "http://localhost:4318", + SamplingRatio: 0.5, + }) + require.NoError(t, err) + require.NotNil(t, shutdown) + assert.NoError(t, shutdown(context.Background())) +} + +func TestInitProfiler_Disabled(t *testing.T) { + profiler, err := initProfiler(config.PyroscopeConfig{Enabled: false}) + require.NoError(t, err) + assert.Nil(t, profiler) +} + +func TestInitProfiler_Enabled(t *testing.T) { + profiler, err := initProfiler(config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", + }) + require.NoError(t, err) + require.NotNil(t, profiler) + defer func() { _ = profiler.Stop() }() +} + +func TestInitProfiler_EnabledWithHTTPHeaders(t *testing.T) { + profiler, err := initProfiler(config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", + HTTPHeaders: map[string]string{"Authorization": "Bearer token"}, + }) + require.NoError(t, err) + require.NotNil(t, profiler) + defer func() { _ = profiler.Stop() }() +} + +func TestInitProfiler_EnabledEmptyServerAddress(t *testing.T) { + profiler, err := initProfiler(config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "", + }) + require.Error(t, err) + assert.Nil(t, profiler) +} diff --git a/engines/router/.env.development b/engines/router/.env.development index 9e7bd3a9e..477418b1b 100644 --- a/engines/router/.env.development +++ b/engines/router/.env.development @@ -34,9 +34,10 @@ APP_KAFKA_SERIALIZATION_FORMAT=json # Instrumentation APP_CUSTOM_METRICS=true APP_JAEGER_ENABLED=false -APP_JAEGER_COLLECTOR_ENDPOINT= -APP_JAEGER_REPORTER_HOST=localhost -APP_JAEGER_REPORTER_PORT=6831 +APP_JAEGER_COLLECTOR_ENDPOINT=http://localhost:4318 +APP_JAEGER_SAMPLING_RATIO=1 +APP_PYROSCOPE_ENABLED=false +APP_PYROSCOPE_SERVER_ADDRESS=http://localhost:4040 SENTRY_ENABLED=false SENTRY_DSN= diff --git a/engines/router/compose/tracing.yaml b/engines/router/compose/tracing.yaml index be124df3a..48d40daf6 100644 --- a/engines/router/compose/tracing.yaml +++ b/engines/router/compose/tracing.yaml @@ -3,6 +3,14 @@ version: '3.1' services: jaeger: image: jaegertracing/all-in-one:latest + environment: + - COLLECTOR_OTLP_ENABLED=true ports: - - 6831:6831/udp - 16686:16686 + - 4317:4317 + - 4318:4318 + + pyroscope: + image: grafana/pyroscope:latest + ports: + - 4040:4040 diff --git a/engines/router/go.mod b/engines/router/go.mod index 06ab3b3e6..8329b5356 100644 --- a/engines/router/go.mod +++ b/engines/router/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( bou.ke/monkey v1.0.2 - cloud.google.com/go/bigquery v1.44.0 + cloud.google.com/go/bigquery v1.57.1 github.com/buger/jsonparser v1.1.1 github.com/caraml-dev/mlp v1.12.0 github.com/caraml-dev/turing/engines/experiment v0.0.0 @@ -13,55 +13,75 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/gojek/fiber v0.2.1-rc2 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 - github.com/google/go-cmp v0.5.9 - github.com/google/uuid v1.3.0 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.4.0 + github.com/grafana/pyroscope-go v1.2.0 github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 github.com/json-iterator/go v1.1.12 github.com/kelseyhightower/envconfig v1.4.0 - github.com/opentracing/opentracing-go v1.1.0 github.com/pierrec/lz4 v2.4.1+incompatible github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.1 github.com/soheilhy/cmux v0.1.5 - github.com/stretchr/testify v1.8.1 - github.com/uber/jaeger-client-go v2.23.1+incompatible + github.com/stretchr/testify v1.9.0 go.einride.tech/protobuf-bigquery v0.7.0 + go.opentelemetry.io/contrib/propagators/b3 v1.24.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/zap v1.26.0 - google.golang.org/grpc v1.52.3 - google.golang.org/protobuf v1.29.0 + google.golang.org/grpc v1.61.1 + google.golang.org/protobuf v1.32.0 gopkg.in/confluentinc/confluent-kafka-go.v1 v1.4.2 ) require ( - cloud.google.com/go v0.107.0 // indirect - cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go v0.111.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.8.0 // indirect + cloud.google.com/go/iam v1.1.5 // indirect + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/apache/arrow/go/v12 v12.0.0 // indirect + github.com/apache/thrift v0.16.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/confluentinc/confluent-kafka-go v1.4.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/frankban/quicktest v1.8.1 // indirect github.com/getsentry/raven-go v0.2.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator v9.31.0+incompatible // indirect + github.com/goccy/go-json v0.9.11 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v2.0.8+incompatible // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/klauspost/asmfmt v1.3.2 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -69,26 +89,34 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/philhofer/fwd v1.0.0 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tinylib/msgp v1.1.2 // indirect - github.com/uber/jaeger-lib v2.2.0+incompatible // indirect github.com/zaffka/zap-to-hclog v0.10.6 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.9.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.106.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect + google.golang.org/api v0.149.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/errgo.v2 v2.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/engines/router/go.sum b/engines/router/go.sum index b3652f534..08a5cab90 100644 --- a/engines/router/go.sum +++ b/engines/router/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -26,20 +26,20 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.14.0/go.mod h1:5W5fTMEyY+JxNqHenf5atJFqt0lqylyG+pIHyJYH4c8= -cloud.google.com/go/bigquery v1.44.0 h1:Wi4dITi+cf9VYp4VH2T9O41w0kCW0uQTELq2Z6tukN0= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/bigquery v1.57.1 h1:FiULdbbzUxWD0Y4ZGPSVCDLvqRSyCIO6zKV7E2nf5uA= +cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datacatalog v1.8.0 h1:6kZ4RIOW/uT7QWC5SfPfq/G8sYzr/v+UOmOAxy4Z1TE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.19.0 h1:rbYNmHwvAOOwnW2FPXYkaK3Mf1MmGqRzK0mMiIEyLdo= +cloud.google.com/go/datacatalog v1.19.0/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -49,16 +49,24 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= +github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -71,19 +79,20 @@ github.com/caraml-dev/mlp v1.12.0 h1:yb/EgMh+83oKj0C1AhH3R6xt0eSd9GX+incZEOwM7oo github.com/caraml-dev/mlp v1.12.0/go.mod h1:Zdz4bALO9WOHXhOgsoLmCjMCJnDVEZEnQFg8rk+u2cE= github.com/caraml-dev/universal-prediction-interface v0.3.6 h1:G/D4aukfjLECl8armJqFy/R2+0u/f4AiurSFqAo33uQ= github.com/caraml-dev/universal-prediction-interface v0.3.6/go.mod h1:e0qmFOXQxx8HFg5ObYyQO3WVnrqsr5v5JApFmeF7eJo= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40 h1:xvUo53O5MRZhVMJAxWCJcS5HHrqAiAG9SJ1LpMu6aAI= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/confluentinc/confluent-kafka-go v1.4.2 h1:13EK9RTujF7lVkvHQ5Hbu6bM+Yfrq8L0MkJNnjHSd4Q= github.com/confluentinc/confluent-kafka-go v1.4.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -116,6 +125,11 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -127,14 +141,14 @@ github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+Pugk github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gojek/fiber v0.2.1-rc2 h1:hJPaH4jDlIBhfRKQby2CISPnbSh7vx5S996IvuXA34U= github.com/gojek/fiber v0.2.1-rc2/go.mod h1:R5cRkUnXdTLpdchCkm3lmGJS+nfhPhLDOklRq1T65Jg= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -147,6 +161,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -162,10 +177,15 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -177,15 +197,15 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -196,17 +216,23 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 h1:BqHID5W5qnMkug0Z8UmL8tN0gAy4jQ+B4WFt8cCgluU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2/go.mod h1:ZbS3MZTZq/apAfAEHGoB5HbsQQstoqP92SjAqtQ9zeg= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/grafana/pyroscope-go v1.2.0 h1:aILLKjTj8CS8f/24OPMGPewQSYlhmdQMBmol1d3KGj8= +github.com/grafana/pyroscope-go v1.2.0/go.mod h1:2GHr28Nr05bg2pElS+dDsc98f3JTUh2f6Fz1hWXrqwk= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= @@ -237,14 +263,21 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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= @@ -265,6 +298,10 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -283,12 +320,12 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -317,8 +354,9 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -328,8 +366,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -339,20 +378,22 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/uber/jaeger-client-go v2.23.1+incompatible h1:uArBYHQR0HqLFFAypI7RsWTzPSj/bDpmZZuQjMLSg1A= -github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zaffka/zap-to-hclog v0.10.6 h1:dNxbL5drL6sVUDHtCMbokJLWrYn5wSKAWTXjgWFadx0= github.com/zaffka/zap-to-hclog v0.10.6/go.mod h1:wLqRe/Fa1MkfUY9EtnCDiz2CqhTPNZPh0/pRE9lLi04= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.einride.tech/protobuf-bigquery v0.7.0 h1:8OIyEW/vl2BEBCXYek0f0Gy1OLAWbCVOiKjSMgKWzZ4= go.einride.tech/protobuf-bigquery v0.7.0/go.mod h1:Nw3+OrRBOXaIwucZF9kZt8gydxGv7kX+Zb6cXGvHKos= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -363,8 +404,22 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -377,9 +432,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -390,6 +446,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -412,6 +470,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB 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/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -446,9 +507,11 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -457,8 +520,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -469,6 +532,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -515,11 +581,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -528,8 +597,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -579,12 +649,17 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201204162204-73cf035baebf/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -603,16 +678,17 @@ google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSr google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.106.0 h1:ffmW0faWCwKkpbbtvlY/K/8fUl+JKvNS5CVzRoyfCv8= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -648,8 +724,12 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201204160425-06b3db808446/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 h1:p0kMzw6AG0JEzd7Z+kXqOiLhC6gjUQTbtS2zR0Q3DbI= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -666,8 +746,8 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -680,8 +760,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= -google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/engines/router/missionctl/config/config.go b/engines/router/missionctl/config/config.go index 9650e849a..1c1633b6f 100644 --- a/engines/router/missionctl/config/config.go +++ b/engines/router/missionctl/config/config.go @@ -110,13 +110,23 @@ type KafkaConfig struct { CompressionType string `split_words:"true" default:"none"` } -// JaegerConfig captures the settings for tracing using Jaeger client -// Ref: https://pkg.go.dev/github.com/uber/jaeger-client-go/config +// JaegerConfig captures the settings for tracing using OpenTelemetry, exported via OTLP HTTP type JaegerConfig struct { - Enabled bool + Enabled bool + // CollectorEndpoint is the OTLP HTTP endpoint spans are exported to, + // e.g. http://otel-collector:4318 CollectorEndpoint string `split_words:"true"` - ReporterAgentHost string `envconfig:"REPORTER_HOST" split_words:"true"` - ReporterAgentPort int `envconfig:"REPORTER_PORT" split_words:"true"` + // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 1 (sample all). + SamplingRatio float64 `split_words:"true" default:"1"` +} + +// PyroscopeConfig captures the settings for continuous profiling using Pyroscope +type PyroscopeConfig struct { + Enabled bool + ServerAddress string `split_words:"true"` + // HTTPHeaders are attached to every profile push request, e.g. for auth + // (Authorization, X-Scope-OrgID, ...). Optional. + HTTPHeaders map[string]string `split_words:"true"` } // AppConfig is the structure used to the parse the environment configs that correspond @@ -135,6 +145,7 @@ type AppConfig struct { Fluentd *FluentdConfig Kafka *KafkaConfig Jaeger *JaegerConfig + Pyroscope *PyroscopeConfig Sentry sentry.Config } diff --git a/engines/router/missionctl/config/config_test.go b/engines/router/missionctl/config/config_test.go index 2d82cd087..e4ca04676 100644 --- a/engines/router/missionctl/config/config_test.go +++ b/engines/router/missionctl/config/config_test.go @@ -7,6 +7,7 @@ import ( "github.com/caraml-dev/mlp/api/pkg/instrumentation/sentry" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" tu "github.com/caraml-dev/turing/engines/router/missionctl/internal/testutils" ) @@ -59,8 +60,9 @@ var optionalEnvs = map[string]string{ "APP_KAFKA_SERIALIZATION_FORMAT": "json", "APP_JAEGER_ENABLED": "true", "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:5000", - "APP_JAEGER_REPORTER_HOST": "localhost", - "APP_JAEGER_REPORTER_PORT": "5001", + "APP_JAEGER_SAMPLING_RATIO": "0.8", + "APP_PYROSCOPE_ENABLED": "true", + "APP_PYROSCOPE_SERVER_ADDRESS": "http://localhost:4040", "APP_SENTRY_ENABLED": "true", "APP_SENTRY_DSN": "test:dsn", "APP_SENTRY_LABELS": "sentry_key1:value1,sentry_key2:value2", @@ -119,8 +121,11 @@ func TestInitConfigDefaultEnvs(t *testing.T) { Jaeger: &JaegerConfig{ Enabled: false, CollectorEndpoint: "", - ReporterAgentHost: "", - ReporterAgentPort: 0, + SamplingRatio: 1, + }, + Pyroscope: &PyroscopeConfig{ + Enabled: false, + ServerAddress: "", }, Sentry: sentry.Config{ Enabled: false, @@ -183,8 +188,11 @@ func TestInitConfigEnv(t *testing.T) { Jaeger: &JaegerConfig{ Enabled: true, CollectorEndpoint: "http://localhost:5000", - ReporterAgentHost: "localhost", - ReporterAgentPort: 5001, + SamplingRatio: 0.8, + }, + Pyroscope: &PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", }, Sentry: sentry.Config{ Enabled: true, @@ -345,6 +353,35 @@ func TestSerializationFormatDecode(t *testing.T) { } } +func TestInitConfigEnv_JaegerAndPyroscope(t *testing.T) { + env := map[string]string{ + "PORT": "8080", + "ROUTER_CONFIG_FILE": "config.yaml", + "APP_NAME": "test-router", + "APP_ENVIRONMENT": "dev", + "APP_JAEGER_ENABLED": "true", + "APP_JAEGER_COLLECTOR_ENDPOINT": "http://otel-collector:4318", + "APP_JAEGER_SAMPLING_RATIO": "0.5", + "APP_PYROSCOPE_ENABLED": "true", + "APP_PYROSCOPE_SERVER_ADDRESS": "http://pyroscope:4040", + "APP_PYROSCOPE_HTTP_HEADERS": "Authorization:Bearer token,X-Scope-OrgID:tenant1", + } + setupNewEnv(env) + + cfg, err := InitConfigEnv() + require.NoError(t, err) + + assert.Equal(t, true, cfg.AppConfig.Jaeger.Enabled) + assert.Equal(t, "http://otel-collector:4318", cfg.AppConfig.Jaeger.CollectorEndpoint) + assert.Equal(t, 0.5, cfg.AppConfig.Jaeger.SamplingRatio) + assert.Equal(t, true, cfg.AppConfig.Pyroscope.Enabled) + assert.Equal(t, "http://pyroscope:4040", cfg.AppConfig.Pyroscope.ServerAddress) + assert.Equal(t, map[string]string{ + "Authorization": "Bearer token", + "X-Scope-OrgID": "tenant1", + }, cfg.AppConfig.Pyroscope.HTTPHeaders) +} + func setupNewEnv(envMaps ...map[string]string) { os.Clearenv() diff --git a/engines/router/missionctl/fiberapi/fan_in.go b/engines/router/missionctl/fiberapi/fan_in.go index 07cfc802f..55b9550b1 100644 --- a/engines/router/missionctl/fiberapi/fan_in.go +++ b/engines/router/missionctl/fiberapi/fan_in.go @@ -10,7 +10,7 @@ import ( "github.com/gojek/fiber" fiberHttp "github.com/gojek/fiber/http" jsoniter "github.com/json-iterator/go" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation" @@ -113,10 +113,10 @@ func (fanIn *EnsemblingFanIn) Aggregate( // Associate span to context to trace response ensembling, if tracing enabled if tracing.Glob().IsEnabled() { - var sp opentracing.Span + var sp trace.Span sp, _ = tracing.Glob().StartSpanFromContext(ctx, FanInID) if sp != nil { - defer sp.Finish() + defer sp.End() } } return fanIn.collectResponses(responses, experimentResponse) diff --git a/engines/router/missionctl/fiberapi/interceptors.go b/engines/router/missionctl/fiberapi/interceptors.go index b895fed9b..ed7edc7c2 100644 --- a/engines/router/missionctl/fiberapi/interceptors.go +++ b/engines/router/missionctl/fiberapi/interceptors.go @@ -6,7 +6,7 @@ import ( "time" "github.com/gojek/fiber" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation" @@ -199,14 +199,12 @@ func (i *TracingInterceptor) BeforeDispatch( return ctx } -// AfterCompletion retrieves the span from the context, if exists, and finishes the trace +// AfterCompletion retrieves the span from the context and finishes it. If no span was +// associated with the context, this is a no-op (trace.SpanFromContext never returns nil). func (i *TracingInterceptor) AfterCompletion( ctx context.Context, _ fiber.Request, _ fiber.ResponseQueue, ) { - span := opentracing.SpanFromContext(ctx) - if span != nil { - span.Finish() - } + trace.SpanFromContext(ctx).End() } diff --git a/engines/router/missionctl/fiberapi/interceptors_test.go b/engines/router/missionctl/fiberapi/interceptors_test.go index 03efa4250..398e7be66 100644 --- a/engines/router/missionctl/fiberapi/interceptors_test.go +++ b/engines/router/missionctl/fiberapi/interceptors_test.go @@ -10,13 +10,14 @@ import ( "testing" "time" - "bou.ke/monkey" "github.com/gojek/fiber" fiberHttp "github.com/gojek/fiber/http" - "github.com/opentracing/opentracing-go" - opentracingLog "github.com/opentracing/opentracing-go/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/config" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/tracing" @@ -96,50 +97,28 @@ func (*mockMetricsCollector) Inc( return nil } -// mockSpan satisfies the opentracing.Span interface -type mockSpan struct { - mock.Mock -} - -func (s *mockSpan) Finish() { - s.Called() -} -func (*mockSpan) FinishWithOptions(_ opentracing.FinishOptions) {} -func (*mockSpan) Context() opentracing.SpanContext { return nil } -func (*mockSpan) SetOperationName(_ string) opentracing.Span { return nil } -func (*mockSpan) SetTag(_ string, _ interface{}) opentracing.Span { return nil } -func (*mockSpan) LogFields(_ ...opentracingLog.Field) {} -func (*mockSpan) LogKV(_ ...interface{}) {} -func (*mockSpan) SetBaggageItem(_, _ string) opentracing.Span { return nil } -func (*mockSpan) BaggageItem(_ string) string { return "" } -func (*mockSpan) Tracer() opentracing.Tracer { return nil } -func (*mockSpan) LogEvent(_ string) {} -func (*mockSpan) LogEventWithPayload(_ string, _ interface{}) {} -func (*mockSpan) Log(_ opentracing.LogData) {} - // mockTracer implements tracing.Tracer interface type mockTracer struct { mock.Mock } -func (*mockTracer) IsEnabled() bool { return false } -func (*mockTracer) SetEnabled(_ bool) {} +func (*mockTracer) IsEnabled() bool { return false } func (*mockTracer) StartSpanFromRequestHeader( - context.Context, - string, - http.Header, -) (opentracing.Span, context.Context) { - return nil, nil + ctx context.Context, + _ string, + _ http.Header, +) (trace.Span, context.Context) { + return nil, ctx } func (t *mockTracer) StartSpanFromContext( ctx context.Context, name string, -) (opentracing.Span, context.Context) { +) (trace.Span, context.Context) { t.Called(ctx, name) - return nil, nil + return nil, ctx } -func (*mockTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (io.Closer, error) { - return io.NopCloser(nil), nil +func (*mockTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (tracing.ShutdownFunc, error) { + return func(context.Context) error { return nil }, nil } // Test that a startTimeKey has been associated to the context @@ -354,24 +333,19 @@ func TestTracingInterceptorBeforeDispatch(t *testing.T) { } func TestTracingInterceptorAfterCompletion(t *testing.T) { - // Create mock span - mockSp := &mockSpan{} - mockSp.On("Finish").Return(nil) - - // Patch opentracing.SpanFromContext to return the mock span - monkey.Patch(opentracing.SpanFromContext, - func(_ context.Context) opentracing.Span { - return mockSp - }) - defer monkey.Unpatch(opentracing.SpanFromContext) + exporter := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) + tr := tp.Tracer("test") + + ctx, span := tr.Start(context.Background(), "test-span") + _ = span - // Run Test i := NewTracingInterceptor() - _, ctx := opentracing.StartSpanFromContext(context.Background(), "test") i.AfterCompletion(ctx, nil, nil) - // Validate that mockSpan.Finish() has been called - mockSp.AssertCalled(t, "Finish") + spans := exporter.GetSpans() + require.Len(t, spans, 1) + assert.Equal(t, "test-span", spans[0].Name) } func createTestFiberResponseQueue(respStatus int) fiber.ResponseQueue { diff --git a/engines/router/missionctl/instrumentation/profiling/profiling.go b/engines/router/missionctl/instrumentation/profiling/profiling.go new file mode 100644 index 000000000..100303e27 --- /dev/null +++ b/engines/router/missionctl/instrumentation/profiling/profiling.go @@ -0,0 +1,41 @@ +package profiling + +import ( + "fmt" + + "github.com/grafana/pyroscope-go" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" +) + +const applicationName = "turing-router" + +// Start starts continuous profiling via pyroscope-go if enabled in cfg. All router +// deployments report under the same Pyroscope application name and are differentiated +// by the router_name tag. Returns a nil profiler and nil error when profiling is +// disabled or cfg is nil. pyroscope.Start does not itself error on an empty +// ServerAddress -- it happily constructs a client that fails silently on every upload -- +// so an empty address is rejected explicitly here instead. +func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, error) { + if cfg == nil || !cfg.Enabled { + return nil, nil + } + if cfg.ServerAddress == "" { + return nil, fmt.Errorf("pyroscope profiling is enabled but ServerAddress is empty") + } + + return pyroscope.Start(pyroscope.Config{ + ApplicationName: applicationName, + ServerAddress: cfg.ServerAddress, + HTTPHeaders: cfg.HTTPHeaders, + Tags: map[string]string{"router_name": routerName}, + ProfileTypes: []pyroscope.ProfileType{ + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + pyroscope.ProfileGoroutines, + }, + }) +} diff --git a/engines/router/missionctl/instrumentation/profiling/profiling_test.go b/engines/router/missionctl/instrumentation/profiling/profiling_test.go new file mode 100644 index 000000000..a7cdb3b97 --- /dev/null +++ b/engines/router/missionctl/instrumentation/profiling/profiling_test.go @@ -0,0 +1,53 @@ +package profiling_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" + "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/profiling" +) + +func TestStart_Disabled(t *testing.T) { + profiler, err := profiling.Start("test-router", &config.PyroscopeConfig{Enabled: false}) + require.NoError(t, err) + assert.Nil(t, profiler) +} + +func TestStart_NilConfig(t *testing.T) { + profiler, err := profiling.Start("test-router", nil) + require.NoError(t, err) + assert.Nil(t, profiler) +} + +func TestStart_Enabled(t *testing.T) { + profiler, err := profiling.Start("test-router", &config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", + }) + require.NoError(t, err) + require.NotNil(t, profiler) + defer func() { _ = profiler.Stop() }() +} + +func TestStart_EnabledWithHTTPHeaders(t *testing.T) { + profiler, err := profiling.Start("test-router", &config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", + HTTPHeaders: map[string]string{"Authorization": "Bearer token"}, + }) + require.NoError(t, err) + require.NotNil(t, profiler) + defer func() { _ = profiler.Stop() }() +} + +func TestStart_EnabledEmptyServerAddress(t *testing.T) { + profiler, err := profiling.Start("test-router", &config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "", + }) + require.Error(t, err) + assert.Nil(t, profiler) +} diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger.go b/engines/router/missionctl/instrumentation/tracing/jaeger.go deleted file mode 100644 index 3d73ee04a..000000000 --- a/engines/router/missionctl/instrumentation/tracing/jaeger.go +++ /dev/null @@ -1,58 +0,0 @@ -package tracing - -import ( - "fmt" - "io" - - "github.com/opentracing/opentracing-go" - jaegercfg "github.com/uber/jaeger-client-go/config" - "github.com/uber/jaeger-client-go/zipkin" - - "github.com/caraml-dev/turing/engines/router/missionctl/config" -) - -// JaegerTracer implements the Tracer interface using the jaeger client library -type JaegerTracer struct { - *baseTracer -} - -// InitGlobalTracer creates a global tracer using the Jaeger client -func (t *JaegerTracer) InitGlobalTracer(name string, cfg *config.JaegerConfig) (io.Closer, error) { - - // Create a zipkin propagator as the HTTP extractor - zipkinPropagator := zipkin.NewZipkinB3HTTPHeaderPropagator() - // Initialize tracer with the default logger - return buildConfig(cfg).InitGlobalTracer(name, - jaegercfg.Extractor(opentracing.HTTPHeaders, zipkinPropagator)) -} - -// IsEnabled satisfies the Tracer interface, always returning true -func (*JaegerTracer) IsEnabled() bool { - return true -} - -// buildConfig converts the input JaegerConfig into the format that can be interpreted -// by the Jaeger client library, applying const sampling -func buildConfig(jCfg *config.JaegerConfig) jaegercfg.Configuration { - return jaegercfg.Configuration{ - Disabled: !jCfg.Enabled, - Reporter: &jaegercfg.ReporterConfig{ - CollectorEndpoint: jCfg.CollectorEndpoint, - LocalAgentHostPort: fmt.Sprintf("%s:%d", - jCfg.ReporterAgentHost, jCfg.ReporterAgentPort), - LogSpans: true, - }, - // Sample all requests by default. - Sampler: &jaegercfg.SamplerConfig{ - Type: "const", - Param: 1, - }, - } -} - -// newJaegerTracer is a creator for the Jaeger Tracer -func newJaegerTracer() Tracer { - return &JaegerTracer{ - &baseTracer{}, - } -} diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger_test.go b/engines/router/missionctl/instrumentation/tracing/jaeger_test.go deleted file mode 100644 index eef7bd150..000000000 --- a/engines/router/missionctl/instrumentation/tracing/jaeger_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package tracing - -import ( - "context" - "net/http" - "testing" - - "github.com/opentracing/opentracing-go" - "github.com/stretchr/testify/assert" - - "github.com/caraml-dev/turing/engines/router/missionctl/config" -) - -func TestIsEnabled(t *testing.T) { - tr := newJaegerTracer() - assert.Equal(t, true, tr.IsEnabled()) -} - -func TestStartSpanFromRequestHeader(t *testing.T) { - tr := newJaegerTracer() - - // Init global tracer using Jaeger client - _, _ = tr.InitGlobalTracer("test", &config.JaegerConfig{ - Enabled: true, - ReporterAgentHost: "localhost", - ReporterAgentPort: 6832, - }) - - // Set span related attributes to request header - header := http.Header{} - header.Set("X-B3-Sampled", "1") - header.Set("X-B3-Spanid", "a30ec88c39471716") - header.Set("X-B3-Traceid", "950f2de0b8430e9fa30ec88c39471716") - header.Set("X-Request-Id", "26787b1c-bf6e-97a8-8b30-36675e4effa0") - - // Verify that a span can be extracted and a child span created - sp, _ := tr.StartSpanFromRequestHeader(context.Background(), "test", header) - assert.NotNil(t, sp) -} - -func TestStartSpanFromContext(t *testing.T) { - tr := newJaegerTracer() - - // Init global tracer using Jaeger client - _, _ = tr.InitGlobalTracer("test", &config.JaegerConfig{ - Enabled: true, - ReporterAgentHost: "localhost", - ReporterAgentPort: 6832, - }) - - // Associate a new span to a context - _, ctx := opentracing.StartSpanFromContext(context.Background(), "test") - // Verify that a span can be extracted and a child span created - sp, _ := tr.StartSpanFromContext(ctx, "test") - assert.NotNil(t, sp) -} - -func TestBuildConfig(t *testing.T) { - cfg := &config.JaegerConfig{ - Enabled: true, - ReporterAgentHost: "localhost", - ReporterAgentPort: 2000, - CollectorEndpoint: "test_endpoint", - } - - jaegerCfg := buildConfig(cfg) - - // Test jaeger config values - assert.Equal(t, false, jaegerCfg.Disabled) - assert.Equal(t, "test_endpoint", jaegerCfg.Reporter.CollectorEndpoint) - assert.Equal(t, "localhost:2000", jaegerCfg.Reporter.LocalAgentHostPort) - assert.Equal(t, true, jaegerCfg.Reporter.LogSpans) - assert.Equal(t, "const", jaegerCfg.Sampler.Type) - assert.Equal(t, float64(1), jaegerCfg.Sampler.Param) -} diff --git a/engines/router/missionctl/instrumentation/tracing/nop.go b/engines/router/missionctl/instrumentation/tracing/nop.go index 55f5bd928..4f6197cf8 100644 --- a/engines/router/missionctl/instrumentation/tracing/nop.go +++ b/engines/router/missionctl/instrumentation/tracing/nop.go @@ -2,10 +2,9 @@ package tracing import ( "context" - "io" "net/http" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/config" ) @@ -13,9 +12,9 @@ import ( // NopTracer implements the Tracer interface with dummy methods type NopTracer struct{} -// InitGlobalTracer satisfies the Tracer interface and returns a Nop closer -func (*NopTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (io.Closer, error) { - return io.NopCloser(nil), nil +// InitGlobalTracer satisfies the Tracer interface and returns a no-op shutdown func +func (*NopTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (ShutdownFunc, error) { + return func(context.Context) error { return nil }, nil } // IsEnabled satisfies the Tracer interface, always returning false @@ -24,22 +23,22 @@ func (*NopTracer) IsEnabled() bool { } // StartSpanFromRequestHeader satisfies the Tracer interface, returning the context as -// is and an empty span +// is and a no-op span func (*NopTracer) StartSpanFromRequestHeader( ctx context.Context, _ string, _ http.Header, -) (opentracing.Span, context.Context) { - return nil, ctx +) (trace.Span, context.Context) { + return trace.SpanFromContext(ctx), ctx } // StartSpanFromContext satisfies the Tracer interface, returning the context as is -// and an empty span +// and a no-op span func (*NopTracer) StartSpanFromContext( ctx context.Context, _ string, -) (opentracing.Span, context.Context) { - return nil, ctx +) (trace.Span, context.Context) { + return trace.SpanFromContext(ctx), ctx } func newNopTracer() Tracer { diff --git a/engines/router/missionctl/instrumentation/tracing/nop_test.go b/engines/router/missionctl/instrumentation/tracing/nop_test.go index 50980becb..34b60350e 100644 --- a/engines/router/missionctl/instrumentation/tracing/nop_test.go +++ b/engines/router/missionctl/instrumentation/tracing/nop_test.go @@ -2,30 +2,27 @@ package tracing import ( "context" + "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" ) func TestNopMethods(t *testing.T) { tr := newNopTracer() - // Create test context - testCtx := context.Background() - - // Test methods with return values - assert.Equal(t, false, tr.IsEnabled()) - - closer, err := tr.InitGlobalTracer("", nil) - assert.NoError(t, err) - err = closer.Close() + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{}) assert.NoError(t, err) + assert.NoError(t, shutdown(context.Background())) + assert.Equal(t, false, tr.IsEnabled()) - sp, ctx := tr.StartSpanFromRequestHeader(testCtx, "", nil) - assert.Nil(t, sp) - assert.Equal(t, testCtx, ctx) + sp, ctx := tr.StartSpanFromRequestHeader(context.Background(), "test", http.Header{}) + assert.NotNil(t, sp) + assert.NotNil(t, ctx) - sp, ctx = tr.StartSpanFromContext(testCtx, "") - assert.Nil(t, sp) - assert.Equal(t, testCtx, ctx) + sp, ctx = tr.StartSpanFromContext(context.Background(), "test") + assert.NotNil(t, sp) + assert.NotNil(t, ctx) } diff --git a/engines/router/missionctl/instrumentation/tracing/otel.go b/engines/router/missionctl/instrumentation/tracing/otel.go new file mode 100644 index 000000000..935aeb427 --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/otel.go @@ -0,0 +1,108 @@ +package tracing + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" +) + +// OtelTracer implements the Tracer interface using the OpenTelemetry SDK, +// exporting spans via OTLP over HTTP. +type OtelTracer struct { + tracer trace.Tracer + propagator propagation.TextMapPropagator +} + +// InitGlobalTracer creates an OTel TracerProvider exporting to jCfg.CollectorEndpoint +// via OTLP HTTP, registers it (and a composite W3C trace-context/baggage/B3 propagator) as +// the OTel globals, and returns its Shutdown function. +func (t *OtelTracer) InitGlobalTracer(name string, jCfg *config.JaegerConfig) (ShutdownFunc, error) { + ctx := context.Background() + + // Validate the endpoint has a host before handing it to otlptracehttp: WithEndpointURL + // silently falls back to an empty host on a parse error or an empty/host-less string, + // which would construct an exporter that can never successfully connect. + endpointURL, err := url.Parse(jCfg.CollectorEndpoint) + if err != nil { + return nil, err + } + if endpointURL.Host == "" { + return nil, fmt.Errorf("invalid CollectorEndpoint %q: missing host", jCfg.CollectorEndpoint) + } + + exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(jCfg.CollectorEndpoint)) + if err != nil { + return nil, err + } + + ratio := jCfg.SamplingRatio + if ratio <= 0 { + ratio = 1 + } + + res := resource.NewSchemaless(attribute.String("service.name", name)) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(ratio))), + sdktrace.WithResource(res), + ) + + // Register both W3C TraceContext and B3 extractors so the router can join traces from + // callers using either propagation format. B3 is Istio's default, so this matters for + // routers running behind Istio/Knative; W3C TraceContext remains the injection format. + propagator := propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{}, b3.New(), + ) + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagator) + + t.tracer = tp.Tracer(name) + t.propagator = propagator + + return tp.Shutdown, nil +} + +// IsEnabled satisfies the Tracer interface, always returning true +func (*OtelTracer) IsEnabled() bool { + return true +} + +// StartSpanFromRequestHeader extracts a remote span context from the request header +// (via the W3C traceparent/tracestate headers) and starts a child span from it. +func (t *OtelTracer) StartSpanFromRequestHeader( + ctx context.Context, + opName string, + header http.Header, +) (trace.Span, context.Context) { + ctx = t.propagator.Extract(ctx, propagation.HeaderCarrier(header)) + ctx, span := t.tracer.Start(ctx, opName) + return span, ctx +} + +// StartSpanFromContext starts a new / child span associated with the given context. +func (t *OtelTracer) StartSpanFromContext( + ctx context.Context, + opName string, +) (trace.Span, context.Context) { + ctx, span := t.tracer.Start(ctx, opName) + return span, ctx +} + +// newOtelTracer is a creator for the OtelTracer +func newOtelTracer() Tracer { + return &OtelTracer{} +} diff --git a/engines/router/missionctl/instrumentation/tracing/otel_test.go b/engines/router/missionctl/instrumentation/tracing/otel_test.go new file mode 100644 index 000000000..253e651f3 --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/otel_test.go @@ -0,0 +1,124 @@ +package tracing + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" +) + +func TestOtelTracer_IsEnabled(t *testing.T) { + tr := newOtelTracer() + assert.Equal(t, true, tr.IsEnabled()) +} + +func TestOtelTracer_InitGlobalTracer(t *testing.T) { + tr := newOtelTracer() + + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + SamplingRatio: 0.5, + }) + require.NoError(t, err) + require.NotNil(t, shutdown) + + defer func() { _ = shutdown(context.Background()) }() +} + +func TestOtelTracer_InitGlobalTracer_HTTPS(t *testing.T) { + tr := newOtelTracer() + + // CollectorEndpoint using the https scheme should still construct a valid + // exporter/tracer without error. + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "https://localhost:4318", + SamplingRatio: 0.5, + }) + require.NoError(t, err) + require.NotNil(t, shutdown) + + defer func() { _ = shutdown(context.Background()) }() + + span, ctx := tr.StartSpanFromContext(context.Background(), "test-op") + assert.NotNil(t, span) + assert.NotNil(t, ctx) + span.End() +} + +func TestOtelTracer_StartSpanFromContext(t *testing.T) { + tr := newOtelTracer() + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + span, ctx := tr.StartSpanFromContext(context.Background(), "test-op") + assert.NotNil(t, span) + assert.NotNil(t, ctx) + span.End() +} + +func TestOtelTracer_StartSpanFromRequestHeader(t *testing.T) { + tr := newOtelTracer() + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + header := http.Header{} + header.Set("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01") + + span, ctx := tr.StartSpanFromRequestHeader(context.Background(), "test-op", header) + assert.NotNil(t, span) + assert.NotNil(t, ctx) + assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", span.SpanContext().TraceID().String()) + span.End() +} + +// TestOtelTracer_StartSpanFromRequestHeader_B3 confirms that inbound requests carrying B3 +// trace-context headers (Istio/Knative's default propagation format) are joined into the +// same trace, rather than silently producing a disconnected root span. This is the +// counterpart of TestOtelTracer_StartSpanFromRequestHeader, which covers the W3C +// traceparent format. +func TestOtelTracer_StartSpanFromRequestHeader_B3(t *testing.T) { + tr := newOtelTracer() + shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + header := http.Header{} + header.Set("X-B3-Traceid", "4bf92f3577b34da6a3ce929d0e0e4736") + header.Set("X-B3-Spanid", "00f067aa0ba902b7") + header.Set("X-B3-Sampled", "1") + + span, ctx := tr.StartSpanFromRequestHeader(context.Background(), "test-op", header) + assert.NotNil(t, span) + assert.NotNil(t, ctx) + assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", span.SpanContext().TraceID().String()) + assert.True(t, span.SpanContext().IsSampled()) + span.End() +} + +func TestOtelTracer_InitGlobalTracer_MissingHost(t *testing.T) { + tr := newOtelTracer() + + _, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "", + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "missing host") +} diff --git a/engines/router/missionctl/instrumentation/tracing/tracing.go b/engines/router/missionctl/instrumentation/tracing/tracing.go index 1fb41212b..1fec65eb2 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing.go @@ -2,74 +2,57 @@ package tracing import ( "context" - "io" "net/http" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/config" ) +// ShutdownFunc flushes and shuts down the tracer provider created by InitGlobalTracer. +type ShutdownFunc func(context.Context) error + // Tracer represents a generic tracer that supports initialization of a global -// tracing client and creation of opentracing spans +// tracing client and creation of OpenTelemetry spans type Tracer interface { - InitGlobalTracer(string, *config.JaegerConfig) (io.Closer, error) + InitGlobalTracer(string, *config.JaegerConfig) (ShutdownFunc, error) IsEnabled() bool StartSpanFromRequestHeader( context.Context, string, http.Header, - ) (opentracing.Span, context.Context) - StartSpanFromContext(context.Context, string) (opentracing.Span, context.Context) -} - -// baseTracer partially implements the Tracer interface and can be used by all tracers -// to support creation of opentracing spans -type baseTracer struct { + ) (trace.Span, context.Context) + StartSpanFromContext(context.Context, string) (trace.Span, context.Context) } -// StartSpanFromRequestHeader attempts to extract span info from the request header and creates a -// new / child span accordingly, which is associated to the given context.Context object. -func (t *baseTracer) StartSpanFromRequestHeader( - ctx context.Context, - opName string, - header http.Header, -) (opentracing.Span, context.Context) { - tr := opentracing.GlobalTracer() - spanCtx, _ := tr.Extract( - opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(header)) - var sp opentracing.Span - if spanCtx != nil { - // Start child span - sp = opentracing.StartSpan(opName, opentracing.ChildOf(spanCtx)) - if sp != nil { - // A (new / child) span has been created, add it to the context - ctx = opentracing.ContextWithSpan(ctx, sp) - } - } - return sp, ctx -} +// globalTracer is initialised to a Nop tracer, calling InitGlobalTracer will reset this +var globalTracer Tracer = newNopTracer() -// StartSpanFromContext attempts to extract span info from the given context.Context and creates -// a new / child span accordingly, which is associated to the same context object. -func (t *baseTracer) StartSpanFromContext( - ctx context.Context, - opName string, -) (opentracing.Span, context.Context) { - return opentracing.StartSpanFromContext(ctx, opName) -} +// InitGlobalTracer creates a new OTel tracer exporting via OTLP HTTP, and sets it as global +// tracer. The returned ShutdownFunc is always non-nil and safe to call, even when a non-nil +// error is also returned, so callers can unconditionally defer it rather than relying on a +// Fatal-on-error caller to skip the call. +func InitGlobalTracer(name string, jaegerCfg *config.JaegerConfig) (ShutdownFunc, error) { + noopShutdown := func(context.Context) error { return nil } -// globalTracer is initialised to a Nop tracer, calling InitGlobalTracer will reset this -var globalTracer = newNopTracer() + // If jaeger config has not been set or tracing is not enabled, just (re-)initialise + // whatever tracer is currently global (typically the Nop tracer). + if jaegerCfg == nil || !jaegerCfg.Enabled { + return globalTracer.InitGlobalTracer(name, jaegerCfg) + } -// InitGlobalTracer creates a new Jaeger tracer, and sets it as global tracer. -func InitGlobalTracer(name string, jaegerCfg *config.JaegerConfig) (io.Closer, error) { - // If jaeger config has been set and the tracing enabled, initialise the JaegerTracer - if jaegerCfg != nil && jaegerCfg.Enabled { - globalTracer = newJaegerTracer() + // Tracing is enabled: initialise a new OtelTracer, but only swap it in as the + // global tracer if initialisation succeeds. Otherwise leave the previous + // globalTracer (e.g. the Nop tracer) in place, so callers never observe a + // half-initialised OtelTracer whose IsEnabled() returns true but whose + // tracer/propagator fields are nil (which would panic on use). + otelTracer := newOtelTracer() + shutdown, err := otelTracer.InitGlobalTracer(name, jaegerCfg) + if err != nil { + return noopShutdown, err } - // Initialise the tracer - return globalTracer.InitGlobalTracer(name, jaegerCfg) + globalTracer = otelTracer + return shutdown, nil } // Glob returns the global tracer diff --git a/engines/router/missionctl/instrumentation/tracing/tracing_test.go b/engines/router/missionctl/instrumentation/tracing/tracing_test.go index 68331e846..d5f615aa0 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing_test.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing_test.go @@ -1,62 +1,69 @@ package tracing import ( + "context" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/caraml-dev/turing/engines/router/missionctl/config" ) func TestGetGlob(t *testing.T) { - // Save globalTracer in a temp var and reset after the test tempTracer := globalTracer - defer func() { - globalTracer = tempTracer - }() + defer func() { globalTracer = tempTracer }() - // Test globalTracer = &NopTracer{} assert.Equal(t, globalTracer, Glob()) } func TestSetGlob(t *testing.T) { - // Save globalTracer in a temp var and reset after the test tempTracer := globalTracer - defer func() { - globalTracer = tempTracer - }() + defer func() { globalTracer = tempTracer }() - // Test tr := &NopTracer{} SetGlob(tr) assert.Equal(t, tr, globalTracer) } func TestInitGlobalTracerNop(t *testing.T) { - // Save globalTracer in a temp var and reset after the test tempTracer := globalTracer - defer func() { - globalTracer = tempTracer - }() + defer func() { globalTracer = tempTracer }() _, err := InitGlobalTracer("test", &config.JaegerConfig{}) assert.NoError(t, err) assert.Equal(t, false, globalTracer.IsEnabled()) } -func TestInitGlobalTracerJaeger(t *testing.T) { - // Save globalTracer in a temp var and reset after the test +func TestInitGlobalTracerOtel(t *testing.T) { tempTracer := globalTracer - defer func() { - globalTracer = tempTracer - }() + defer func() { globalTracer = tempTracer }() - _, err := InitGlobalTracer("test", &config.JaegerConfig{ + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ Enabled: true, - ReporterAgentHost: "localhost", - ReporterAgentPort: 1000, + CollectorEndpoint: "http://localhost:4318", }) assert.NoError(t, err) assert.Equal(t, true, globalTracer.IsEnabled()) + assert.NoError(t, shutdown(context.Background())) +} + +// TestInitGlobalTracerOtel_ErrorReturnsNonNilShutdown ensures that even when tracer +// initialisation fails (e.g. an invalid CollectorEndpoint), callers get back a safe, +// callable no-op ShutdownFunc rather than nil. Package-level InitGlobalTracer is called +// from application.go, which currently relies on log.Glob().Fatalf to exit the process on +// error, but the contract shouldn't depend on that -- a nil ShutdownFunc would panic any +// caller that unconditionally defers it. +func TestInitGlobalTracerOtel_ErrorReturnsNonNilShutdown(t *testing.T) { + tempTracer := globalTracer + defer func() { globalTracer = tempTracer }() + + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + CollectorEndpoint: "", + }) + assert.Error(t, err) + require.NotNil(t, shutdown) + assert.NoError(t, shutdown(context.Background())) } diff --git a/engines/router/missionctl/server/application.go b/engines/router/missionctl/server/application.go index 11d773027..690750ad0 100644 --- a/engines/router/missionctl/server/application.go +++ b/engines/router/missionctl/server/application.go @@ -1,8 +1,8 @@ package server import ( + "context" "fmt" - "io" "net" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/caraml-dev/turing/engines/router/missionctl/config" "github.com/caraml-dev/turing/engines/router/missionctl/errors" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/metrics" + "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/profiling" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/tracing" "github.com/caraml-dev/turing/engines/router/missionctl/log" "github.com/caraml-dev/turing/engines/router/missionctl/log/resultlog" @@ -128,26 +129,34 @@ func Run() { } } -// initInstrumentation initializes the metrics collector and tracing client +// initInstrumentation initializes the metrics collector, tracing client and profiler func initInstrumentation(cfg *config.Config) func() { - var tracingCloser io.Closer - var err error - // Init metrics collector - err = metrics.InitMetricsCollector(cfg.AppConfig.CustomMetrics) - if err != nil { + if err := metrics.InitMetricsCollector(cfg.AppConfig.CustomMetrics); err != nil { log.Glob().Fatalf("Failed initializing Metrics Collector: %v", err) } // Init tracing client - tracingCloser, err = tracing.InitGlobalTracer(cfg.AppConfig.Name, cfg.AppConfig.Jaeger) + tracingShutdown, err := tracing.InitGlobalTracer(cfg.AppConfig.Name, cfg.AppConfig.Jaeger) if err != nil { log.Glob().Fatalf("Failed initializing Tracer: %v", err) } + + // Init profiler + profiler, err := profiling.Start(cfg.AppConfig.Name, cfg.AppConfig.Pyroscope) + if err != nil { + log.Glob().Fatalf("Failed initializing Profiler: %v", err) + } + // Return closer function return func() { - if err := tracingCloser.Close(); err != nil { - panic(err) + if err := tracingShutdown(context.Background()); err != nil { + log.Glob().Errorf("Failed shutting down tracer: %v", err) + } + if profiler != nil { + if err := profiler.Stop(); err != nil { + log.Glob().Errorf("Failed stopping profiler: %v", err) + } } } } diff --git a/engines/router/missionctl/server/http/handlers/batch_http_handler.go b/engines/router/missionctl/server/http/handlers/batch_http_handler.go index 6e2e3ddd5..f6e56d1ee 100644 --- a/engines/router/missionctl/server/http/handlers/batch_http_handler.go +++ b/engines/router/missionctl/server/http/handlers/batch_http_handler.go @@ -8,7 +8,7 @@ import ( "sync" fiberProtocol "github.com/gojek/fiber/protocol" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation" @@ -69,10 +69,10 @@ func (h *batchHTTPHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) ctxLogger.Debugf("Received batch request for %v", turingReqID) if tracing.Glob().IsEnabled() { - var sp opentracing.Span + var sp trace.Span ctx, sp = h.enableTracingSpan(ctx, req, batchHTTPHandlerID) if sp != nil { - defer sp.Finish() + defer sp.End() } } diff --git a/engines/router/missionctl/server/http/handlers/http_handler.go b/engines/router/missionctl/server/http/handlers/http_handler.go index e3adb6861..ceb5fc3c1 100644 --- a/engines/router/missionctl/server/http/handlers/http_handler.go +++ b/engines/router/missionctl/server/http/handlers/http_handler.go @@ -8,7 +8,7 @@ import ( "time" fiberProtocol "github.com/gojek/fiber/protocol" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation" @@ -56,8 +56,8 @@ func (h *httpHandler) error( // enableTracingSpan associates span to context, if applicable func (h *httpHandler) enableTracingSpan(ctx context.Context, req *http.Request, - httpHandlerID string) (context.Context, opentracing.Span) { - var sp opentracing.Span + httpHandlerID string) (context.Context, trace.Span) { + var sp trace.Span sp, ctx = tracing.Glob().StartSpanFromRequestHeader(ctx, httpHandlerID, req.Header) return ctx, sp } @@ -172,10 +172,10 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { req.Header.Set(constant.TuringReqIDHeaderKey, turingReqID) if tracing.Glob().IsEnabled() { - var sp opentracing.Span + var sp trace.Span ctx, sp = h.enableTracingSpan(ctx, req, httpHandlerID) if sp != nil { - defer sp.Finish() + defer sp.End() } } diff --git a/engines/router/missionctl/server/upi/server.go b/engines/router/missionctl/server/upi/server.go index 0bed03076..5185d29a8 100644 --- a/engines/router/missionctl/server/upi/server.go +++ b/engines/router/missionctl/server/upi/server.go @@ -7,7 +7,7 @@ import ( "os/signal" "syscall" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -117,10 +117,10 @@ func (us *Server) PredictValues(ctx context.Context, req *upiv1.PredictValuesReq md.Append(constant.TuringReqIDHeaderKey, turingReqID) if tracing.Glob().IsEnabled() { - var sp opentracing.Span + var sp trace.Span sp, ctx = tracing.Glob().StartSpanFromContext(ctx, tracingComponentID) if sp != nil { - defer sp.Finish() + defer sp.End() } } diff --git a/sdk/turing/generated/model/router_version_log_config.py b/sdk/turing/generated/model/router_version_log_config.py index c6814c880..95a0701b2 100644 --- a/sdk/turing/generated/model/router_version_log_config.py +++ b/sdk/turing/generated/model/router_version_log_config.py @@ -87,6 +87,7 @@ def openapi_types(): 'custom_metrics_enabled': (bool,), # noqa: E501 'fiber_debug_log_enabled': (bool,), # noqa: E501 'jaeger_enabled': (bool,), # noqa: E501 + 'pyroscope_enabled': (bool,), # noqa: E501 'result_logger_type': (ResultLoggerType,), # noqa: E501 'bigquery_config': (BigQueryConfig,), # noqa: E501 'kafka_config': (KafkaConfig,), # noqa: E501 @@ -102,6 +103,7 @@ def discriminator(): 'custom_metrics_enabled': 'custom_metrics_enabled', # noqa: E501 'fiber_debug_log_enabled': 'fiber_debug_log_enabled', # noqa: E501 'jaeger_enabled': 'jaeger_enabled', # noqa: E501 + 'pyroscope_enabled': 'pyroscope_enabled', # noqa: E501 'result_logger_type': 'result_logger_type', # noqa: E501 'bigquery_config': 'bigquery_config', # noqa: E501 'kafka_config': 'kafka_config', # noqa: E501 @@ -157,6 +159,7 @@ def __init__(self, *args, **kwargs): # noqa: E501 custom_metrics_enabled (bool): [optional] # noqa: E501 fiber_debug_log_enabled (bool): [optional] # noqa: E501 jaeger_enabled (bool): [optional] # noqa: E501 + pyroscope_enabled (bool): [optional] # noqa: E501 result_logger_type (ResultLoggerType): [optional] # noqa: E501 bigquery_config (BigQueryConfig): [optional] # noqa: E501 kafka_config (KafkaConfig): [optional] # noqa: E501 From 0d439f2222a99e9b36eb8acc367e7f816b503db1 Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Sat, 15 Aug 2026 06:07:37 +0700 Subject: [PATCH 2/8] feat: restore classic Jaeger tracer alongside OTel, add additive Otel config to api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores the classic Jaeger/Thrift tracer in engines/router as a deprecated, independently-toggleable backend that runs simultaneously alongside the existing OTel/OTLP backend, since a downstream system still requires Jaeger's native Thrift ingestion. A MultiTracer fans out span creation to both backends when both are enabled, tracking each backend's own parent span chain independently so nested spans stay correctly parented per backend. engines/router: - Restore JaegerConfig (Enabled/CollectorEndpoint/ReporterAgentHost/ ReporterAgentPort, Thrift transport), add a separate OtelConfig (Enabled/CollectorEndpoint/SamplingRatio, OTLP HTTP). - New JaegerTracer adapts the classic jaeger-client-go (OpenTracing API) onto the trace.Tracer/trace.Span API directly via a hand-rolled facade (go.opentelemetry.io/otel/bridge/opentracing bridges the opposite direction and can't be used for this). - New MultiTracer fans out to every enabled backend via a dedicated context key (multiParentsKey) so each backend's nested spans parent from its own prior span, not a shared identity. - Tracer interface drops InitGlobalTracer in favour of typed newOtelTracer/newJaegerTracer constructors, since the two backends take different config types. - Local dev compose/env wired for both backends. api (purely additive — nothing persisted, public, or SDK-facing renamed): - RouterDefaults, models.LogConfig, and the OpenAPI contract gain OtelEnabled/OtelCollectorEndpoint next to the existing (now Deprecated:-commented) Jaeger fields. - servicebuilder injects APP_OTEL_ENABLED/APP_OTEL_COLLECTOR_ENDPOINT alongside the existing Jaeger env vars on every deployed router. - Go and Python generated clients regenerated from the updated spec. Every Jaeger* identifier, config field, env var, and the OpenAPI jaeger_enabled property is marked deprecated in favour of the Otel equivalent, with no removal date yet. Co-Authored-By: Claude Sonnet 5 --- api/.env.development | 4 + api/api/openapi.bundle.yaml | 6 + api/api/specs/routers.yaml | 5 + api/go.mod | 4 + api/go.sum | 8 + api/turing/api/request/request.go | 1 + api/turing/api/request/request_test.go | 3 + api/turing/cluster/servicebuilder/router.go | 30 ++- .../cluster/servicebuilder/router_test.go | 24 ++ api/turing/config/config.go | 17 +- api/turing/config/config_test.go | 22 +- api/turing/config/example.yaml | 7 +- api/turing/config/testdata/config-1.yaml | 2 + api/turing/models/log_config.go | 4 + api/turing/models/log_config_test.go | 3 + engines/router/.env.development | 10 +- engines/router/README.md | 2 +- engines/router/compose/tracing.yaml | 4 + engines/router/go.mod | 4 + engines/router/go.sum | 10 + engines/router/missionctl/config/config.go | 20 +- .../router/missionctl/config/config_test.go | 38 ++- .../missionctl/fiberapi/interceptors_test.go | 4 - .../instrumentation/tracing/jaeger.go | 251 ++++++++++++++++++ .../instrumentation/tracing/jaeger_test.go | 77 ++++++ .../instrumentation/tracing/multi.go | 145 ++++++++++ .../instrumentation/tracing/multi_test.go | 89 +++++++ .../missionctl/instrumentation/tracing/nop.go | 7 - .../instrumentation/tracing/nop_test.go | 5 - .../instrumentation/tracing/otel.go | 38 ++- .../instrumentation/tracing/otel_test.go | 46 ++-- .../instrumentation/tracing/tracing.go | 89 +++++-- .../instrumentation/tracing/tracing_test.go | 59 +++- .../router/missionctl/server/application.go | 2 +- .../model/router_version_log_config.py | 5 +- 35 files changed, 912 insertions(+), 133 deletions(-) create mode 100644 engines/router/missionctl/instrumentation/tracing/jaeger.go create mode 100644 engines/router/missionctl/instrumentation/tracing/jaeger_test.go create mode 100644 engines/router/missionctl/instrumentation/tracing/multi.go create mode 100644 engines/router/missionctl/instrumentation/tracing/multi_test.go diff --git a/api/.env.development b/api/.env.development index 7822788a1..ca65a2dff 100644 --- a/api/.env.development +++ b/api/.env.development @@ -3,8 +3,12 @@ TURING_DATABASE_USER=turing TURING_DATABASE_PASSWORD=turing TURING_DATABASE_NAME=turing TURING_ROUTER_IMAGE=asia.gcr.io/gcp-project-id/turing-router:latest +# Deprecated: classic Jaeger/Thrift backend. Use TURING_ROUTER_OTEL_* instead unless a +# specific downstream backend still requires Thrift ingestion. TURING_ROUTER_JAEGER_ENABLED=false TURING_ROUTER_JAEGER_COLLECTOR_ENDPOINT= +TURING_ROUTER_OTEL_ENABLED=false +TURING_ROUTER_OTEL_COLLECTOR_ENDPOINT= TURING_ROUTER_FLUENTD_IMAGE=asia.gcr.io/gcp-project-id/fluentd-bigquery:0.0.1 TURING_ROUTER_FLUENTD_FLUSH_INTERVAL_SECONDS=10 TURING_ROUTER_LOG_LEVEL=DEBUG diff --git a/api/api/openapi.bundle.yaml b/api/api/openapi.bundle.yaml index e393c1a00..dd78606e3 100644 --- a/api/api/openapi.bundle.yaml +++ b/api/api/openapi.bundle.yaml @@ -2112,6 +2112,7 @@ components: error: error version: 5 log_config: + otel_enabled: true custom_metrics_enabled: true bigquery_config: batch_load: true @@ -3705,6 +3706,7 @@ components: type: object RouterVersion_log_config: example: + otel_enabled: true custom_metrics_enabled: true bigquery_config: batch_load: true @@ -3725,6 +3727,10 @@ components: fiber_debug_log_enabled: type: boolean jaeger_enabled: + description: 'Deprecated: use otel_enabled instead. Will be removed in a + future release.' + type: boolean + otel_enabled: type: boolean pyroscope_enabled: type: boolean diff --git a/api/api/specs/routers.yaml b/api/api/specs/routers.yaml index 3e94dd922..947dbd4c1 100644 --- a/api/api/specs/routers.yaml +++ b/api/api/specs/routers.yaml @@ -573,6 +573,11 @@ components: type: "boolean" jaeger_enabled: type: "boolean" + description: >- + Deprecated: use otel_enabled instead. Will be removed in a + future release. + otel_enabled: + type: "boolean" pyroscope_enabled: type: "boolean" result_logger_type: diff --git a/api/go.mod b/api/go.mod index 4315e86b0..9456225e0 100644 --- a/api/go.mod +++ b/api/go.mod @@ -191,6 +191,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -212,6 +213,8 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.1 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.0.0+incompatible // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.34.0 // indirect github.com/vbatts/tar-split v0.11.3 // indirect @@ -231,6 +234,7 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/api/go.sum b/api/go.sum index 46ed8088c..cffd84f7e 100644 --- a/api/go.sum +++ b/api/go.sum @@ -642,6 +642,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -757,6 +759,10 @@ github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -830,6 +836,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= diff --git a/api/turing/api/request/request.go b/api/turing/api/request/request.go index 4f311d374..c81f00d72 100644 --- a/api/turing/api/request/request.go +++ b/api/turing/api/request/request.go @@ -159,6 +159,7 @@ func (r RouterConfig) BuildRouterVersion( CustomMetricsEnabled: defaults.CustomMetricsEnabled, FiberDebugLogEnabled: defaults.FiberDebugLogEnabled, JaegerEnabled: defaults.JaegerEnabled, + OtelEnabled: defaults.OtelEnabled, PyroscopeEnabled: defaults.PyroscopeEnabled, ResultLoggerType: r.LogConfig.ResultLoggerType, }, diff --git a/api/turing/api/request/request_test.go b/api/turing/api/request/request_test.go index 07495c58a..d166753b3 100644 --- a/api/turing/api/request/request_test.go +++ b/api/turing/api/request/request_test.go @@ -357,6 +357,8 @@ func TestRequestBuildRouterVersionWithDefaultConfig(t *testing.T) { CustomMetricsEnabled: true, JaegerEnabled: true, JaegerCollectorEndpoint: "jaegerendpoint", + OtelEnabled: true, + OtelCollectorEndpoint: "otelendpoint", PyroscopeEnabled: true, LogLevel: "DEBUG", FluentdConfig: &config.FluentdConfig{ @@ -405,6 +407,7 @@ func TestRequestBuildRouterVersionWithDefaultConfig(t *testing.T) { CustomMetricsEnabled: true, FiberDebugLogEnabled: true, JaegerEnabled: true, + OtelEnabled: true, PyroscopeEnabled: true, ResultLoggerType: models.BigQueryLogger, BigQueryConfig: &models.BigQueryConfig{ diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index 5132542c1..43e4cfa1b 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -26,18 +26,22 @@ import ( // Define env var names for the router const ( - envAppName = "APP_NAME" - envAppEnvironment = "APP_ENVIRONMENT" - envRouterTimeout = "ROUTER_TIMEOUT" - envEnricherEndpoint = "ENRICHER_ENDPOINT" - envEnricherTimeout = "ENRICHER_TIMEOUT" - envEnsemblerEndpoint = "ENSEMBLER_ENDPOINT" - envEnsemblerTimeout = "ENSEMBLER_TIMEOUT" - envLogLevel = "APP_LOGLEVEL" - envFiberDebugLog = "APP_FIBER_DEBUG_LOG" - envCustomMetrics = "APP_CUSTOM_METRICS" - envJaegerEnabled = "APP_JAEGER_ENABLED" + envAppName = "APP_NAME" + envAppEnvironment = "APP_ENVIRONMENT" + envRouterTimeout = "ROUTER_TIMEOUT" + envEnricherEndpoint = "ENRICHER_ENDPOINT" + envEnricherTimeout = "ENRICHER_TIMEOUT" + envEnsemblerEndpoint = "ENSEMBLER_ENDPOINT" + envEnsemblerTimeout = "ENSEMBLER_TIMEOUT" + envLogLevel = "APP_LOGLEVEL" + envFiberDebugLog = "APP_FIBER_DEBUG_LOG" + envCustomMetrics = "APP_CUSTOM_METRICS" + // Deprecated: use envOtel* instead. + envJaegerEnabled = "APP_JAEGER_ENABLED" + // Deprecated: use envOtel* instead. envJaegerEndpoint = "APP_JAEGER_COLLECTOR_ENDPOINT" + envOtelEnabled = "APP_OTEL_ENABLED" + envOtelEndpoint = "APP_OTEL_COLLECTOR_ENDPOINT" envPyroscopeEnabled = "APP_PYROSCOPE_ENABLED" envPyroscopeServerAddress = "APP_PYROSCOPE_SERVER_ADDRESS" envPyroscopeHTTPHeaders = "APP_PYROSCOPE_HTTP_HEADERS" @@ -233,13 +237,14 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( ) ([]corev1.EnvVar, error) { envs := sb.getEnvVars(ver.ResourceRequest, nil, nil, "") - // Add app name, router timeout, jaeger collector + // Add app name, router timeout, jaeger collector (deprecated) and otel collector envs = mergeEnvVars(envs, []corev1.EnvVar{ {Name: envAppName, Value: fmt.Sprintf("%s-%d.%s", ver.Router.Name, ver.Version, namespace)}, {Name: envAppEnvironment, Value: environmentType}, {Name: envRouterTimeout, Value: ver.Timeout}, {Name: envJaegerEndpoint, Value: routerDefaults.JaegerCollectorEndpoint}, + {Name: envOtelEndpoint, Value: routerDefaults.OtelCollectorEndpoint}, {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, {Name: envRouterConfigFile, Value: routerConfigMapMountPath + routerConfigFileName}, @@ -283,6 +288,7 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envLogLevel, Value: string(logConfig.LogLevel)}, {Name: envCustomMetrics, Value: strconv.FormatBool(logConfig.CustomMetricsEnabled)}, {Name: envJaegerEnabled, Value: strconv.FormatBool(logConfig.JaegerEnabled)}, + {Name: envOtelEnabled, Value: strconv.FormatBool(logConfig.OtelEnabled)}, {Name: envPyroscopeEnabled, Value: strconv.FormatBool(logConfig.PyroscopeEnabled)}, {Name: envResultLogger, Value: string(logConfig.ResultLoggerType)}, {Name: envFiberDebugLog, Value: strconv.FormatBool(logConfig.FiberDebugLogEnabled)}, diff --git a/api/turing/cluster/servicebuilder/router_test.go b/api/turing/cluster/servicebuilder/router_test.go index 3b3d66490..7e81fd397 100644 --- a/api/turing/cluster/servicebuilder/router_test.go +++ b/api/turing/cluster/servicebuilder/router_test.go @@ -124,6 +124,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -133,6 +134,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -230,6 +232,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -239,6 +242,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -335,6 +339,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -348,6 +353,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -447,6 +453,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -456,6 +463,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -551,6 +559,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -560,6 +569,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -655,6 +665,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -664,6 +675,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -759,6 +771,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -768,6 +781,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "bigquery"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -863,6 +877,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -874,6 +889,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "nop"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -996,6 +1012,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "test-env"}, {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -1005,6 +1022,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "nop"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -1075,6 +1093,7 @@ func TestNewRouterService(t *testing.T) { data.expRawConfig, &config.RouterDefaults{ JaegerCollectorEndpoint: "jaeger-endpoint", + OtelCollectorEndpoint: "otel-endpoint", PyroscopeServerAddress: "pyroscope-address", FluentdConfig: &config.FluentdConfig{Tag: "fluentd-tag"}, }, @@ -1166,6 +1185,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { environmentType: "dev", routerDefaults: &config.RouterDefaults{ JaegerCollectorEndpoint: "", + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", PyroscopeServerAddress: "http://pyroscope.example.com:4040", FluentdConfig: &config.FluentdConfig{Tag: ""}, KafkaConfig: &config.KafkaConfig{ @@ -1200,6 +1220,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: "dev"}, {Name: "ROUTER_TIMEOUT", Value: "10s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "http://otel-collector.example.com:4318"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "http://pyroscope.example.com:4040"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -1209,6 +1230,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_LOGLEVEL", Value: "DEBUG"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "kafka"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, @@ -1248,6 +1270,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_ENVIRONMENT", Value: ""}, {Name: "ROUTER_TIMEOUT", Value: ""}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, + {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: ""}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: ""}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, @@ -1257,6 +1280,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_LOGLEVEL", Value: ""}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, + {Name: "APP_OTEL_ENABLED", Value: "false"}, {Name: "APP_PYROSCOPE_ENABLED", Value: "false"}, {Name: "APP_RESULT_LOGGER", Value: "upi"}, {Name: "APP_FIBER_DEBUG_LOG", Value: "false"}, diff --git a/api/turing/config/config.go b/api/turing/config/config.go index 0db5aa9a4..7ef1f4934 100644 --- a/api/turing/config/config.go +++ b/api/turing/config/config.go @@ -326,10 +326,21 @@ type RouterDefaults struct { // Enable router custom metrics CustomMetricsEnabled bool // Enable Jaeger Tracing + // + // Deprecated: Jaeger's native Thrift ingestion is being phased out; use + // OtelEnabled instead unless a specific downstream backend still requires + // Thrift. This field will be removed in a future release. JaegerEnabled bool - // Jaeger collector endpoint. If JaegerEnabled is true, this value - // must be set. + // Jaeger collector endpoint (Thrift-over-HTTP). If JaegerEnabled is true, this + // value must be set. + // + // Deprecated: use OtelCollectorEndpoint instead. JaegerCollectorEndpoint string + // Enable router tracing via OpenTelemetry, exported over OTLP HTTP + OtelEnabled bool + // OTLP HTTP endpoint routers should export traces to. If OtelEnabled is true, + // this value must be set. + OtelCollectorEndpoint string `validate:"required_if=OtelEnabled True"` // Enable Pyroscope profiling for routers deployed by this instance of the Turing API PyroscopeEnabled bool // Pyroscope server address routers should report profiles to. If PyroscopeEnabled is @@ -641,6 +652,8 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("RouterDefaults::CustomMetricsEnabled", "false") v.SetDefault("RouterDefaults::JaegerEnabled", "false") v.SetDefault("RouterDefaults::JaegerCollectorEndpoint", "") + v.SetDefault("RouterDefaults::OtelEnabled", "false") + v.SetDefault("RouterDefaults::OtelCollectorEndpoint", "") v.SetDefault("RouterDefaults::PyroscopeEnabled", "false") v.SetDefault("RouterDefaults::PyroscopeServerAddress", "") v.SetDefault("RouterDefaults::LogLevel", "INFO") diff --git a/api/turing/config/config_test.go b/api/turing/config/config_test.go index 518ef9339..3b880028d 100644 --- a/api/turing/config/config_test.go +++ b/api/turing/config/config_test.go @@ -307,7 +307,9 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -477,7 +479,9 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -665,7 +669,9 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -772,6 +778,8 @@ func TestLoad_OtelAndPyroscope(t *testing.T) { assert.Equal(t, true, cfg.RouterDefaults.PyroscopeEnabled) assert.Equal(t, "http://pyroscope.example.com:4040", cfg.RouterDefaults.PyroscopeServerAddress) assert.Equal(t, map[string]string{"authorization": "Bearer token"}, cfg.RouterDefaults.PyroscopeHTTPHeaders) + assert.Equal(t, true, cfg.RouterDefaults.OtelEnabled) + assert.Equal(t, "http://otel-collector.example.com:4318", cfg.RouterDefaults.OtelCollectorEndpoint) } // Reference: @@ -1121,6 +1129,14 @@ func TestConfigValidate(t *testing.T) { }, wantErr: true, }, + "router defaults otel enabled but missing OtelCollectorEndpoint": { + validConfigUpdate: func(validConfig config.Config) config.Config { + validConfig.RouterDefaults.OtelEnabled = true + validConfig.RouterDefaults.OtelCollectorEndpoint = "" + return validConfig + }, + wantErr: true, + }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { diff --git a/api/turing/config/example.yaml b/api/turing/config/example.yaml index 22233ee6c..2a1832202 100644 --- a/api/turing/config/example.yaml +++ b/api/turing/config/example.yaml @@ -148,8 +148,13 @@ RouterDefaults: Image: ghcr.io/caraml-dev/turing:latest FiberDebugLogEnabled: false CustomMetricsEnabled: false + # Deprecated: Jaeger's native Thrift ingestion is being phased out; use + # OtelEnabled/OtelCollectorEndpoint instead unless a specific downstream + # backend still requires Thrift. JaegerEnabled: false - JaegerCollectorEndpoint: http://otel-collector.example.com:4318 + JaegerCollectorEndpoint: http://jaeger-collector.example.com:14268/api/traces + OtelEnabled: false + OtelCollectorEndpoint: http://otel-collector.example.com:4318 PyroscopeEnabled: false PyroscopeServerAddress: http://pyroscope.example.com:4040 # HTTP headers attached to every profile push request routers make to diff --git a/api/turing/config/testdata/config-1.yaml b/api/turing/config/testdata/config-1.yaml index ecc740181..fe9617ef8 100644 --- a/api/turing/config/testdata/config-1.yaml +++ b/api/turing/config/testdata/config-1.yaml @@ -59,6 +59,8 @@ RouterDefaults: PyroscopeServerAddress: http://pyroscope.example.com:4040 PyroscopeHTTPHeaders: Authorization: Bearer token + OtelEnabled: true + OtelCollectorEndpoint: http://otel-collector.example.com:4318 Sentry: Enabled: true Labels: diff --git a/api/turing/models/log_config.go b/api/turing/models/log_config.go index c544f69f1..81fc44e66 100644 --- a/api/turing/models/log_config.go +++ b/api/turing/models/log_config.go @@ -64,7 +64,11 @@ type LogConfig struct { // Enable debug logs for Fiber. Defaults to false. FiberDebugLogEnabled bool `json:"fiber_debug_log_enabled"` // Enable Jaeger tracing. + // + // Deprecated: use OtelEnabled instead. Will be removed in a future release. JaegerEnabled bool `json:"jaeger_enabled"` + // Enable OpenTelemetry tracing. + OtelEnabled bool `json:"otel_enabled"` // Enable Pyroscope profiling. PyroscopeEnabled bool `json:"pyroscope_enabled"` // Result Logger type. The associated config must not be null. diff --git a/api/turing/models/log_config_test.go b/api/turing/models/log_config_test.go index abe081adb..1a52251f2 100644 --- a/api/turing/models/log_config_test.go +++ b/api/turing/models/log_config_test.go @@ -26,6 +26,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": true, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "otel_enabled": false, "pyroscope_enabled": false, "result_logger_type": "nop" }`), @@ -45,6 +46,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": false, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "otel_enabled": false, "pyroscope_enabled": false, "result_logger_type": "bigquery", "bigquery_config": { @@ -69,6 +71,7 @@ func TestLogConfigValue(t *testing.T) { "custom_metrics_enabled": false, "fiber_debug_log_enabled": false, "jaeger_enabled": false, + "otel_enabled": false, "pyroscope_enabled": false, "result_logger_type": "kafka", "kafka_config": { diff --git a/engines/router/.env.development b/engines/router/.env.development index 477418b1b..3a87e67b2 100644 --- a/engines/router/.env.development +++ b/engines/router/.env.development @@ -33,9 +33,15 @@ APP_KAFKA_SERIALIZATION_FORMAT=json # Instrumentation APP_CUSTOM_METRICS=true +# Deprecated: classic Jaeger/Thrift backend. Use APP_OTEL_* instead unless a +# specific downstream backend still requires Thrift ingestion. APP_JAEGER_ENABLED=false -APP_JAEGER_COLLECTOR_ENDPOINT=http://localhost:4318 -APP_JAEGER_SAMPLING_RATIO=1 +APP_JAEGER_COLLECTOR_ENDPOINT=http://localhost:14268/api/traces +APP_JAEGER_REPORTER_HOST=localhost +APP_JAEGER_REPORTER_PORT=6831 +APP_OTEL_ENABLED=false +APP_OTEL_COLLECTOR_ENDPOINT=http://localhost:4318 +APP_OTEL_SAMPLING_RATIO=1 APP_PYROSCOPE_ENABLED=false APP_PYROSCOPE_SERVER_ADDRESS=http://localhost:4040 diff --git a/engines/router/README.md b/engines/router/README.md index a72b0ab6d..06e64ffb3 100644 --- a/engines/router/README.md +++ b/engines/router/README.md @@ -66,7 +66,7 @@ __Note:__ The app uses the `in_forward` plugin to write data to a TCP socket. - name: TEST_ENV2 value: value2 ``` -3. Jaeger client is initialised to trace all requests (`const` mode). However, the tracer's `IsEnabled()` methods determine whether the app adds the trace to the requests. To run Jaeger locally, do `make jaeger-local`. Simulating incoming requests with Spans: +3. OpenTelemetry (`APP_OTEL_*`) is now the primary tracer; the classic Jaeger/Thrift backend described below (`APP_JAEGER_*`) is deprecated and kept only for downstream backends that still require Thrift ingestion. Jaeger client is initialised to trace all requests (`const` mode). However, the tracer's `IsEnabled()` methods determine whether the app adds the trace to the requests. To run Jaeger locally, do `make jaeger-local`. Simulating incoming requests with Spans: ``` curl -v -X GET http://localhost:8080/v1/predict \ -H "X-B3-Sampled: 1" \ diff --git a/engines/router/compose/tracing.yaml b/engines/router/compose/tracing.yaml index 48d40daf6..b99a2d854 100644 --- a/engines/router/compose/tracing.yaml +++ b/engines/router/compose/tracing.yaml @@ -6,6 +6,10 @@ services: environment: - COLLECTOR_OTLP_ENABLED=true ports: + # 6831/udp and 14268 are for the classic, deprecated Jaeger/Thrift ingestion + # (used by APP_JAEGER_*). 4317/4318 are OTLP gRPC/HTTP, used by APP_OTEL_*. + - 6831:6831/udp + - 14268:14268 - 16686:16686 - 4317:4317 - 4318:4318 diff --git a/engines/router/go.mod b/engines/router/go.mod index 8329b5356..83b65df81 100644 --- a/engines/router/go.mod +++ b/engines/router/go.mod @@ -19,11 +19,13 @@ require ( github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 github.com/json-iterator/go v1.1.12 github.com/kelseyhightower/envconfig v1.4.0 + github.com/opentracing/opentracing-go v1.2.0 github.com/pierrec/lz4 v2.4.1+incompatible github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.1 github.com/soheilhy/cmux v0.1.5 github.com/stretchr/testify v1.9.0 + github.com/uber/jaeger-client-go v2.30.0+incompatible go.einride.tech/protobuf-bigquery v0.7.0 go.opentelemetry.io/contrib/propagators/b3 v1.24.0 go.opentelemetry.io/otel v1.24.0 @@ -96,12 +98,14 @@ require ( github.com/prometheus/procfs v0.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tinylib/msgp v1.1.2 // indirect + github.com/uber/jaeger-lib v2.0.0+incompatible // indirect github.com/zaffka/zap-to-hclog v0.10.6 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/mod v0.10.0 // indirect diff --git a/engines/router/go.sum b/engines/router/go.sum index 08a5cab90..84f0d9c11 100644 --- a/engines/router/go.sum +++ b/engines/router/go.sum @@ -93,6 +93,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/confluentinc/confluent-kafka-go v1.4.2 h1:13EK9RTujF7lVkvHQ5Hbu6bM+Yfrq8L0MkJNnjHSd4Q= github.com/confluentinc/confluent-kafka-go v1.4.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -320,6 +322,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= @@ -383,6 +387,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -420,6 +428,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/engines/router/missionctl/config/config.go b/engines/router/missionctl/config/config.go index 1c1633b6f..15cbae71e 100644 --- a/engines/router/missionctl/config/config.go +++ b/engines/router/missionctl/config/config.go @@ -110,8 +110,23 @@ type KafkaConfig struct { CompressionType string `split_words:"true" default:"none"` } -// JaegerConfig captures the settings for tracing using OpenTelemetry, exported via OTLP HTTP +// JaegerConfig captures the settings for tracing using the classic Jaeger client +// (OpenTracing API, Thrift transport over a UDP agent or an HTTP collector). +// +// Deprecated: Jaeger's native Thrift ingestion is being phased out; use OtelConfig +// instead unless a specific downstream backend still requires Thrift. This config, +// and the tracer backend it configures, will be removed in a future release. type JaegerConfig struct { + Enabled bool + // CollectorEndpoint is a Thrift-over-HTTP Jaeger collector endpoint, + // e.g. http://jaeger-collector:14268/api/traces + CollectorEndpoint string `split_words:"true"` + ReporterAgentHost string `envconfig:"REPORTER_HOST" split_words:"true"` + ReporterAgentPort int `envconfig:"REPORTER_PORT" split_words:"true"` +} + +// OtelConfig captures the settings for tracing using OpenTelemetry, exported via OTLP HTTP. +type OtelConfig struct { Enabled bool // CollectorEndpoint is the OTLP HTTP endpoint spans are exported to, // e.g. http://otel-collector:4318 @@ -144,7 +159,8 @@ type AppConfig struct { BigQuery *BQConfig `envconfig:"BQ"` Fluentd *FluentdConfig Kafka *KafkaConfig - Jaeger *JaegerConfig + Jaeger *JaegerConfig // Deprecated: see JaegerConfig. + Otel *OtelConfig Pyroscope *PyroscopeConfig Sentry sentry.Config } diff --git a/engines/router/missionctl/config/config_test.go b/engines/router/missionctl/config/config_test.go index e4ca04676..d5714f579 100644 --- a/engines/router/missionctl/config/config_test.go +++ b/engines/router/missionctl/config/config_test.go @@ -59,8 +59,12 @@ var optionalEnvs = map[string]string{ "APP_KAFKA_TOPIC": "kafka_topic", "APP_KAFKA_SERIALIZATION_FORMAT": "json", "APP_JAEGER_ENABLED": "true", - "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:5000", - "APP_JAEGER_SAMPLING_RATIO": "0.8", + "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:14268/api/traces", + "APP_JAEGER_REPORTER_HOST": "localhost", + "APP_JAEGER_REPORTER_PORT": "6831", + "APP_OTEL_ENABLED": "true", + "APP_OTEL_COLLECTOR_ENDPOINT": "http://localhost:5000", + "APP_OTEL_SAMPLING_RATIO": "0.8", "APP_PYROSCOPE_ENABLED": "true", "APP_PYROSCOPE_SERVER_ADDRESS": "http://localhost:4040", "APP_SENTRY_ENABLED": "true", @@ -119,6 +123,12 @@ func TestInitConfigDefaultEnvs(t *testing.T) { }, CustomMetrics: false, Jaeger: &JaegerConfig{ + Enabled: false, + CollectorEndpoint: "", + ReporterAgentHost: "", + ReporterAgentPort: 0, + }, + Otel: &OtelConfig{ Enabled: false, CollectorEndpoint: "", SamplingRatio: 1, @@ -186,6 +196,12 @@ func TestInitConfigEnv(t *testing.T) { }, CustomMetrics: true, Jaeger: &JaegerConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:14268/api/traces", + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }, + Otel: &OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:5000", SamplingRatio: 0.8, @@ -353,15 +369,19 @@ func TestSerializationFormatDecode(t *testing.T) { } } -func TestInitConfigEnv_JaegerAndPyroscope(t *testing.T) { +func TestInitConfigEnv_JaegerOtelAndPyroscope(t *testing.T) { env := map[string]string{ "PORT": "8080", "ROUTER_CONFIG_FILE": "config.yaml", "APP_NAME": "test-router", "APP_ENVIRONMENT": "dev", "APP_JAEGER_ENABLED": "true", - "APP_JAEGER_COLLECTOR_ENDPOINT": "http://otel-collector:4318", - "APP_JAEGER_SAMPLING_RATIO": "0.5", + "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:14268/api/traces", + "APP_JAEGER_REPORTER_HOST": "localhost", + "APP_JAEGER_REPORTER_PORT": "6831", + "APP_OTEL_ENABLED": "true", + "APP_OTEL_COLLECTOR_ENDPOINT": "http://otel-collector:4318", + "APP_OTEL_SAMPLING_RATIO": "0.5", "APP_PYROSCOPE_ENABLED": "true", "APP_PYROSCOPE_SERVER_ADDRESS": "http://pyroscope:4040", "APP_PYROSCOPE_HTTP_HEADERS": "Authorization:Bearer token,X-Scope-OrgID:tenant1", @@ -372,8 +392,12 @@ func TestInitConfigEnv_JaegerAndPyroscope(t *testing.T) { require.NoError(t, err) assert.Equal(t, true, cfg.AppConfig.Jaeger.Enabled) - assert.Equal(t, "http://otel-collector:4318", cfg.AppConfig.Jaeger.CollectorEndpoint) - assert.Equal(t, 0.5, cfg.AppConfig.Jaeger.SamplingRatio) + assert.Equal(t, "http://localhost:14268/api/traces", cfg.AppConfig.Jaeger.CollectorEndpoint) + assert.Equal(t, "localhost", cfg.AppConfig.Jaeger.ReporterAgentHost) + assert.Equal(t, 6831, cfg.AppConfig.Jaeger.ReporterAgentPort) + assert.Equal(t, true, cfg.AppConfig.Otel.Enabled) + assert.Equal(t, "http://otel-collector:4318", cfg.AppConfig.Otel.CollectorEndpoint) + assert.Equal(t, 0.5, cfg.AppConfig.Otel.SamplingRatio) assert.Equal(t, true, cfg.AppConfig.Pyroscope.Enabled) assert.Equal(t, "http://pyroscope:4040", cfg.AppConfig.Pyroscope.ServerAddress) assert.Equal(t, map[string]string{ diff --git a/engines/router/missionctl/fiberapi/interceptors_test.go b/engines/router/missionctl/fiberapi/interceptors_test.go index 398e7be66..00e18d4ab 100644 --- a/engines/router/missionctl/fiberapi/interceptors_test.go +++ b/engines/router/missionctl/fiberapi/interceptors_test.go @@ -19,7 +19,6 @@ import ( "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" - "github.com/caraml-dev/turing/engines/router/missionctl/config" "github.com/caraml-dev/turing/engines/router/missionctl/instrumentation/tracing" tu "github.com/caraml-dev/turing/engines/router/missionctl/internal/testutils" "github.com/caraml-dev/turing/engines/router/missionctl/log" @@ -117,9 +116,6 @@ func (t *mockTracer) StartSpanFromContext( t.Called(ctx, name) return nil, ctx } -func (*mockTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (tracing.ShutdownFunc, error) { - return func(context.Context) error { return nil }, nil -} // Test that a startTimeKey has been associated to the context func TestTimeInterceptorBeforeDispatch(t *testing.T) { diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger.go b/engines/router/missionctl/instrumentation/tracing/jaeger.go new file mode 100644 index 000000000..63d135859 --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/jaeger.go @@ -0,0 +1,251 @@ +package tracing + +import ( + "context" + "fmt" + "net/http" + + ot "github.com/opentracing/opentracing-go" + otext "github.com/opentracing/opentracing-go/ext" + jaeger "github.com/uber/jaeger-client-go" + jaegercfg "github.com/uber/jaeger-client-go/config" + + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" + "go.opentelemetry.io/otel/trace/noop" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" +) + +// JaegerTracer implements the Tracer interface using the classic Jaeger client +// library (OpenTracing API, Thrift transport over a UDP agent or an HTTP +// collector). Every trace.Tracer/trace.Span call made by the rest of this +// codebase is translated into the equivalent ot.Tracer/ot.Span (OpenTracing) +// call by this file, so spans are still physically created by the classic +// Jaeger client and exported over Thrift, while callers keep working against +// the same trace.Span type regardless of which backend is active. +// +// Note: go.opentelemetry.io/otel/bridge/opentracing only bridges in the +// opposite direction to what's needed here -- it forwards OpenTracing-API +// calls onto an OpenTelemetry trace.Tracer of choice (see its NewTracerPair, +// which takes a trace.Tracer, not an ot.Tracer), so it cannot adapt an +// existing ot.Tracer (the classic Jaeger client) into a trace.Tracer. This +// file does that translation directly instead. +// +// Deprecated: this backend exists only to support downstream systems that +// still require Jaeger's native Thrift ingestion; it will be removed in a +// future release. New deployments should use OtelTracer (OtelConfig) instead. +type JaegerTracer struct { + tracer ot.Tracer + propagator propagation.TextMapPropagator +} + +// newJaegerTracer builds a classic Jaeger client from cfg (sampling all requests, +// matching this backend's pre-OTel-migration behaviour, and generating 128-bit +// trace IDs so they line up byte-for-byte with the OTel trace.TraceID format used +// elsewhere in this codebase), and returns a Tracer that adapts it to the +// trace.Tracer/trace.Span API, alongside a ShutdownFunc that closes the underlying +// Jaeger reporter. +func newJaegerTracer(name string, cfg *config.JaegerConfig) (Tracer, ShutdownFunc, error) { + jCfg := jaegercfg.Configuration{ + ServiceName: name, + Disabled: !cfg.Enabled, + Gen128Bit: true, + Reporter: &jaegercfg.ReporterConfig{ + CollectorEndpoint: cfg.CollectorEndpoint, + LocalAgentHostPort: fmt.Sprintf("%s:%d", cfg.ReporterAgentHost, cfg.ReporterAgentPort), + LogSpans: true, + }, + Sampler: &jaegercfg.SamplerConfig{ + Type: "const", + Param: 1, + }, + } + + nativeTracer, closer, err := jCfg.NewTracer() + if err != nil { + return nil, nil, err + } + + t := &JaegerTracer{ + tracer: nativeTracer, + // Reuse the same propagator OtelTracer uses for incoming header extraction, + // so both backends agree on how a remote parent span context is decoded. + propagator: propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{}, b3.New(), + ), + } + + return t, func(context.Context) error { return closer.Close() }, nil +} + +// IsEnabled satisfies the Tracer interface, always returning true +func (*JaegerTracer) IsEnabled() bool { + return true +} + +// StartSpanFromRequestHeader extracts a remote span context from the request header +// and starts a child span from it. +func (t *JaegerTracer) StartSpanFromRequestHeader( + ctx context.Context, + opName string, + header http.Header, +) (trace.Span, context.Context) { + ctx = t.propagator.Extract(ctx, propagation.HeaderCarrier(header)) + return t.startSpan(ctx, opName) +} + +// StartSpanFromContext starts a new / child span associated with the given context. +func (t *JaegerTracer) StartSpanFromContext( + ctx context.Context, + opName string, +) (trace.Span, context.Context) { + return t.startSpan(ctx, opName) +} + +// startSpan starts a native Jaeger span, using whatever OTel trace.SpanContext is +// already present in ctx (either injected by StartSpanFromRequestHeader above, or +// left there by a previous call to this same JaegerTracer) as its ChildOf parent, +// so the classic Jaeger client's own trace/span IDs stay derived from -- and thus +// consistent with -- the trace.SpanContext exposed to the rest of this codebase. +func (t *JaegerTracer) startSpan(ctx context.Context, opName string) (trace.Span, context.Context) { + var opts []ot.StartSpanOption + if parent := trace.SpanContextFromContext(ctx); parent.IsValid() { + if parentSC, err := jaegerSpanContextFrom(parent); err == nil { + opts = append(opts, ot.ChildOf(parentSC)) + } + } + + span := &jaegerSpan{span: t.tracer.StartSpan(opName, opts...)} + return span, trace.ContextWithSpan(ctx, span) +} + +// jaegerSpanContextFrom converts an OTel trace.SpanContext into the equivalent +// jaeger.SpanContext, so a span started from an OTel-style parent (extracted from +// request headers, or carried over from a previous jaegerSpan already in ctx) +// keeps the same trace ID under the classic Jaeger client too. +func jaegerSpanContextFrom(sc trace.SpanContext) (jaeger.SpanContext, error) { + traceID, err := jaeger.TraceIDFromString(sc.TraceID().String()) + if err != nil { + return jaeger.SpanContext{}, err + } + spanID, err := jaeger.SpanIDFromString(sc.SpanID().String()) + if err != nil { + return jaeger.SpanContext{}, err + } + return jaeger.NewSpanContext(traceID, spanID, 0, sc.IsSampled(), nil), nil +} + +// jaegerSpan adapts a native Jaeger (OpenTracing) ot.Span to the OTel trace.Span +// interface, so every backend in this package exposes the same span type to callers. +type jaegerSpan struct { + embedded.Span + + span ot.Span +} + +// End satisfies trace.Span, finishing the underlying native Jaeger span. +func (s *jaegerSpan) End(...trace.SpanEndOption) { + s.span.Finish() +} + +// AddEvent satisfies trace.Span, logging name and any attributes as a structured +// log entry on the underlying native Jaeger span. +func (s *jaegerSpan) AddEvent(name string, options ...trace.EventOption) { + cfg := trace.NewEventConfig(options...) + attrs := cfg.Attributes() + + kv := make([]interface{}, 0, 2+2*len(attrs)) + kv = append(kv, "event", name) + for _, attr := range attrs { + kv = append(kv, string(attr.Key), attr.Value.AsInterface()) + } + s.span.LogKV(kv...) +} + +// IsRecording satisfies trace.Span. The classic Jaeger client does not expose +// whether a given span will actually be sampled/reported until Finish(), so this +// conservatively always returns true. +func (s *jaegerSpan) IsRecording() bool { + return true +} + +// RecordError satisfies trace.Span, tagging the underlying native Jaeger span as +// an error and logging err on it. +func (s *jaegerSpan) RecordError(err error, _ ...trace.EventOption) { + otext.LogError(s.span, err) +} + +// SpanContext satisfies trace.Span, translating the underlying native Jaeger +// span's own SpanContext into the equivalent OTel trace.SpanContext. +func (s *jaegerSpan) SpanContext() trace.SpanContext { + sc, ok := s.span.Context().(jaeger.SpanContext) + if !ok { + return trace.SpanContext{} + } + + // jaeger.TraceID.String() only zero-pads to 32 hex chars when High != 0 (i.e. + // when the trace was actually generated as 128-bit); pad it out so it always + // parses as a valid OTel TraceID. + traceIDHex := sc.TraceID().String() + for len(traceIDHex) < 32 { + traceIDHex = "0" + traceIDHex + } + + traceID, err := trace.TraceIDFromHex(traceIDHex) + if err != nil { + return trace.SpanContext{} + } + spanID, err := trace.SpanIDFromHex(sc.SpanID().String()) + if err != nil { + return trace.SpanContext{} + } + + var flags trace.TraceFlags + if sc.IsSampled() { + flags = trace.FlagsSampled + } + + return trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: flags, + }) +} + +// SetStatus satisfies trace.Span, tagging the underlying native Jaeger span with +// the OTel status code/description. +func (s *jaegerSpan) SetStatus(code codes.Code, description string) { + if code == codes.Error { + s.span.SetTag("error", true) + } + s.span.SetTag("otel.status_code", code.String()) + if description != "" { + s.span.SetTag("otel.status_description", description) + } +} + +// SetName satisfies trace.Span, changing the underlying native Jaeger span's +// operation name. +func (s *jaegerSpan) SetName(name string) { + s.span.SetOperationName(name) +} + +// SetAttributes satisfies trace.Span, setting each attribute as a tag on the +// underlying native Jaeger span. +func (s *jaegerSpan) SetAttributes(kv ...attribute.KeyValue) { + for _, attr := range kv { + s.span.SetTag(string(attr.Key), attr.Value.AsInterface()) + } +} + +// TracerProvider satisfies trace.Span. This backend doesn't construct an OTel +// SDK TracerProvider (spans are created directly against the native Jaeger +// ot.Tracer), so this returns a no-op provider. +func (s *jaegerSpan) TracerProvider() trace.TracerProvider { + return noop.NewTracerProvider() +} diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger_test.go b/engines/router/missionctl/instrumentation/tracing/jaeger_test.go new file mode 100644 index 000000000..4c719a331 --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/jaeger_test.go @@ -0,0 +1,77 @@ +package tracing + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/caraml-dev/turing/engines/router/missionctl/config" +) + +func TestNewJaegerTracer_IsEnabled(t *testing.T) { + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + assert.Equal(t, true, tr.IsEnabled()) +} + +func TestNewJaegerTracer_StartSpanFromContext(t *testing.T) { + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + span, ctx := tr.StartSpanFromContext(context.Background(), "test-op") + assert.NotNil(t, span) + assert.NotNil(t, ctx) + span.End() +} + +func TestNewJaegerTracer_NestedSpans_ShareTraceID(t *testing.T) { + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + outerSpan, ctx := tr.StartSpanFromContext(context.Background(), "outer") + innerSpan, _ := tr.StartSpanFromContext(ctx, "inner") + + assert.Equal(t, outerSpan.SpanContext().TraceID(), innerSpan.SpanContext().TraceID()) + innerSpan.End() + outerSpan.End() +} + +func TestNewJaegerTracer_StartSpanFromRequestHeader(t *testing.T) { + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + + header := http.Header{} + header.Set("X-B3-Traceid", "4bf92f3577b34da6a3ce929d0e0e4736") + header.Set("X-B3-Spanid", "00f067aa0ba902b7") + header.Set("X-B3-Sampled", "1") + + span, ctx := tr.StartSpanFromRequestHeader(context.Background(), "test-op", header) + assert.NotNil(t, span) + assert.NotNil(t, ctx) + assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", span.SpanContext().TraceID().String()) + span.End() +} diff --git a/engines/router/missionctl/instrumentation/tracing/multi.go b/engines/router/missionctl/instrumentation/tracing/multi.go new file mode 100644 index 000000000..fb4696d9e --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/multi.go @@ -0,0 +1,145 @@ +package tracing + +import ( + "context" + "net/http" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" +) + +// MultiTracer fans out span creation to every wrapped Tracer, so a single logical +// operation is exported to all of them. Used when more than one backend is enabled +// simultaneously (e.g. both JaegerConfig and OtelConfig). +type MultiTracer struct { + tracers []Tracer +} + +// newMultiTracer wraps tracers (expected to have at least 2 elements -- for exactly +// one enabled backend, InitGlobalTracer uses it directly instead) in a MultiTracer. +func newMultiTracer(tracers []Tracer) Tracer { + return &MultiTracer{tracers: tracers} +} + +// IsEnabled satisfies the Tracer interface, always returning true (MultiTracer is +// only constructed when at least one wrapped tracer is enabled). +func (*MultiTracer) IsEnabled() bool { + return true +} + +// multiParentsKey is the context key under which each backend's own previous span +// is tracked independently (see multiSpan's doc comment for why a single shared +// composite identity can't be used for this). +type multiParentsKey struct{} + +// StartSpanFromContext starts a span on every wrapped tracer, each parented from +// its OWN previous span (tracked independently per backend via multiParentsKey, +// not the shared composite -- see multiSpan's doc comment for why that matters), +// and returns a composite span representing all of them. +func (m *MultiTracer) StartSpanFromContext(ctx context.Context, opName string) (trace.Span, context.Context) { + parents, _ := ctx.Value(multiParentsKey{}).([]trace.Span) + spans := make([]trace.Span, len(m.tracers)) + for i, t := range m.tracers { + backendCtx := ctx + // Guard against a length mismatch (e.g. if SetGlob swapped the global tracer, + // changing the number of backends, mid-request): fall back to the plain ctx + // for this backend rather than panicking on an out-of-range index. + if parents != nil && len(parents) == len(m.tracers) { + backendCtx = trace.ContextWithSpan(ctx, parents[i]) + } + spans[i], _ = t.StartSpanFromContext(backendCtx, opName) + } + return m.newCompositeCtx(ctx, spans) +} + +// StartSpanFromRequestHeader starts a span on every wrapped tracer, each extracting +// its own remote parent from header, and returns a composite span representing all +// of them. (No multiParentsKey lookup needed here: this is always the root of a +// request, so there's no prior per-backend parent to seed each backendCtx with -- +// each backend independently extracts its own parent straight from header instead.) +func (m *MultiTracer) StartSpanFromRequestHeader( + ctx context.Context, + opName string, + header http.Header, +) (trace.Span, context.Context) { + spans := make([]trace.Span, len(m.tracers)) + for i, t := range m.tracers { + spans[i], _ = t.StartSpanFromRequestHeader(ctx, opName, header) + } + return m.newCompositeCtx(ctx, spans) +} + +// newCompositeCtx wraps spans in a multiSpan and seeds ctx with both the composite +// (for a caller's trace.SpanFromContext(ctx).End() to find) and the raw per-backend +// spans keyed by multiParentsKey (for the next nested call to hand each backend its +// own correct parent, per the doc comment above). +func (m *MultiTracer) newCompositeCtx(ctx context.Context, spans []trace.Span) (trace.Span, context.Context) { + composite := &multiSpan{spans: spans} + ctx = context.WithValue(ctx, multiParentsKey{}, spans) + return composite, trace.ContextWithSpan(ctx, composite) +} + +// multiSpan implements trace.Span by fanning every mutating call out to every +// wrapped span. SpanContext/IsRecording/TracerProvider delegate to the first +// wrapped span only -- but that is now purely about what identity the composite +// itself reports to any external caller that inspects it directly (e.g. logging +// the "current" trace ID). It no longer has anything to do with parent-chaining +// for a later nested StartSpanFromContext call: that lookup goes through +// multiParentsKey instead, precisely because delegating parent-lookup to spans[0] +// unconditionally would give every backend after the first the wrong parent (the +// first backend's span identity, not its own). +type multiSpan struct { + embedded.Span + + spans []trace.Span +} + +func (m *multiSpan) End(options ...trace.SpanEndOption) { + for _, s := range m.spans { + s.End(options...) + } +} + +func (m *multiSpan) AddEvent(name string, options ...trace.EventOption) { + for _, s := range m.spans { + s.AddEvent(name, options...) + } +} + +func (m *multiSpan) IsRecording() bool { + return m.spans[0].IsRecording() +} + +func (m *multiSpan) RecordError(err error, options ...trace.EventOption) { + for _, s := range m.spans { + s.RecordError(err, options...) + } +} + +func (m *multiSpan) SpanContext() trace.SpanContext { + return m.spans[0].SpanContext() +} + +func (m *multiSpan) SetStatus(code codes.Code, description string) { + for _, s := range m.spans { + s.SetStatus(code, description) + } +} + +func (m *multiSpan) SetName(name string) { + for _, s := range m.spans { + s.SetName(name) + } +} + +func (m *multiSpan) SetAttributes(kv ...attribute.KeyValue) { + for _, s := range m.spans { + s.SetAttributes(kv...) + } +} + +func (m *multiSpan) TracerProvider() trace.TracerProvider { + return m.spans[0].TracerProvider() +} diff --git a/engines/router/missionctl/instrumentation/tracing/multi_test.go b/engines/router/missionctl/instrumentation/tracing/multi_test.go new file mode 100644 index 000000000..77f1c1d46 --- /dev/null +++ b/engines/router/missionctl/instrumentation/tracing/multi_test.go @@ -0,0 +1,89 @@ +package tracing + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" +) + +// recordingTracer is a minimal Tracer that records every span it starts via an +// in-memory exporter, so tests can assert on what a MultiTracer produced. +type recordingTracer struct { + tracer trace.Tracer + exporter *tracetest.InMemoryExporter +} + +func newRecordingTracer(name string) *recordingTracer { + exporter := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) + return &recordingTracer{tracer: tp.Tracer(name), exporter: exporter} +} + +func (*recordingTracer) IsEnabled() bool { return true } + +func (r *recordingTracer) StartSpanFromContext(ctx context.Context, opName string) (trace.Span, context.Context) { + ctx, span := r.tracer.Start(ctx, opName) + return span, ctx +} + +func (r *recordingTracer) StartSpanFromRequestHeader( + ctx context.Context, + opName string, + _ http.Header, +) (trace.Span, context.Context) { + return r.StartSpanFromContext(ctx, opName) +} + +func TestMultiTracer_IsEnabled(t *testing.T) { + mt := newMultiTracer([]Tracer{newRecordingTracer("a"), newRecordingTracer("b")}) + assert.Equal(t, true, mt.IsEnabled()) +} + +func TestMultiTracer_StartSpanFromContext_RecordsOnEveryBackend(t *testing.T) { + a := newRecordingTracer("a") + b := newRecordingTracer("b") + mt := newMultiTracer([]Tracer{a, b}) + + span, ctx := mt.StartSpanFromContext(context.Background(), "test-op") + require.NotNil(t, span) + require.NotNil(t, ctx) + span.End() + + require.Len(t, a.exporter.GetSpans(), 1) + require.Len(t, b.exporter.GetSpans(), 1) + assert.Equal(t, "test-op", a.exporter.GetSpans()[0].Name) + assert.Equal(t, "test-op", b.exporter.GetSpans()[0].Name) +} + +func TestMultiTracer_NestedSpans_EachBackendKeepsItsOwnParentChain(t *testing.T) { + a := newRecordingTracer("a") + b := newRecordingTracer("b") + mt := newMultiTracer([]Tracer{a, b}) + + outerSpan, ctx := mt.StartSpanFromContext(context.Background(), "outer") + innerSpan, ctx := mt.StartSpanFromContext(ctx, "inner") + innerSpan.End() + outerSpan.End() + _ = ctx + + for _, exp := range []*tracetest.InMemoryExporter{a.exporter, b.exporter} { + spans := exp.GetSpans() + require.Len(t, spans, 2) + var outer, inner tracetest.SpanStub + for _, s := range spans { + if s.Name == "outer" { + outer = s + } else { + inner = s + } + } + assert.Equal(t, outer.SpanContext.SpanID(), inner.Parent.SpanID()) + assert.Equal(t, outer.SpanContext.TraceID(), inner.SpanContext.TraceID()) + } +} diff --git a/engines/router/missionctl/instrumentation/tracing/nop.go b/engines/router/missionctl/instrumentation/tracing/nop.go index 4f6197cf8..dcdf020c5 100644 --- a/engines/router/missionctl/instrumentation/tracing/nop.go +++ b/engines/router/missionctl/instrumentation/tracing/nop.go @@ -5,18 +5,11 @@ import ( "net/http" "go.opentelemetry.io/otel/trace" - - "github.com/caraml-dev/turing/engines/router/missionctl/config" ) // NopTracer implements the Tracer interface with dummy methods type NopTracer struct{} -// InitGlobalTracer satisfies the Tracer interface and returns a no-op shutdown func -func (*NopTracer) InitGlobalTracer(_ string, _ *config.JaegerConfig) (ShutdownFunc, error) { - return func(context.Context) error { return nil }, nil -} - // IsEnabled satisfies the Tracer interface, always returning false func (*NopTracer) IsEnabled() bool { return false diff --git a/engines/router/missionctl/instrumentation/tracing/nop_test.go b/engines/router/missionctl/instrumentation/tracing/nop_test.go index 34b60350e..65bbcf79e 100644 --- a/engines/router/missionctl/instrumentation/tracing/nop_test.go +++ b/engines/router/missionctl/instrumentation/tracing/nop_test.go @@ -6,16 +6,11 @@ import ( "testing" "github.com/stretchr/testify/assert" - - "github.com/caraml-dev/turing/engines/router/missionctl/config" ) func TestNopMethods(t *testing.T) { tr := newNopTracer() - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{}) - assert.NoError(t, err) - assert.NoError(t, shutdown(context.Background())) assert.Equal(t, false, tr.IsEnabled()) sp, ctx := tr.StartSpanFromRequestHeader(context.Background(), "test", http.Header{}) diff --git a/engines/router/missionctl/instrumentation/tracing/otel.go b/engines/router/missionctl/instrumentation/tracing/otel.go index 935aeb427..92c2af13b 100644 --- a/engines/router/missionctl/instrumentation/tracing/otel.go +++ b/engines/router/missionctl/instrumentation/tracing/otel.go @@ -25,29 +25,29 @@ type OtelTracer struct { propagator propagation.TextMapPropagator } -// InitGlobalTracer creates an OTel TracerProvider exporting to jCfg.CollectorEndpoint -// via OTLP HTTP, registers it (and a composite W3C trace-context/baggage/B3 propagator) as -// the OTel globals, and returns its Shutdown function. -func (t *OtelTracer) InitGlobalTracer(name string, jCfg *config.JaegerConfig) (ShutdownFunc, error) { +// newOtelTracer creates an OTel TracerProvider exporting to cfg.CollectorEndpoint via +// OTLP HTTP, registers it (and a composite W3C trace-context/baggage/B3 propagator) as +// the OTel globals, and returns the resulting Tracer alongside its ShutdownFunc. +func newOtelTracer(name string, cfg *config.OtelConfig) (Tracer, ShutdownFunc, error) { ctx := context.Background() // Validate the endpoint has a host before handing it to otlptracehttp: WithEndpointURL // silently falls back to an empty host on a parse error or an empty/host-less string, // which would construct an exporter that can never successfully connect. - endpointURL, err := url.Parse(jCfg.CollectorEndpoint) + endpointURL, err := url.Parse(cfg.CollectorEndpoint) if err != nil { - return nil, err + return nil, nil, err } if endpointURL.Host == "" { - return nil, fmt.Errorf("invalid CollectorEndpoint %q: missing host", jCfg.CollectorEndpoint) + return nil, nil, fmt.Errorf("invalid CollectorEndpoint %q: missing host", cfg.CollectorEndpoint) } - exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(jCfg.CollectorEndpoint)) + exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(cfg.CollectorEndpoint)) if err != nil { - return nil, err + return nil, nil, err } - ratio := jCfg.SamplingRatio + ratio := cfg.SamplingRatio if ratio <= 0 { ratio = 1 } @@ -61,8 +61,7 @@ func (t *OtelTracer) InitGlobalTracer(name string, jCfg *config.JaegerConfig) (S ) // Register both W3C TraceContext and B3 extractors so the router can join traces from - // callers using either propagation format. B3 is Istio's default, so this matters for - // routers running behind Istio/Knative; W3C TraceContext remains the injection format. + // callers using either propagation format. B3 is Istio's default. propagator := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, b3.New(), ) @@ -70,10 +69,12 @@ func (t *OtelTracer) InitGlobalTracer(name string, jCfg *config.JaegerConfig) (S otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagator) - t.tracer = tp.Tracer(name) - t.propagator = propagator + t := &OtelTracer{ + tracer: tp.Tracer(name), + propagator: propagator, + } - return tp.Shutdown, nil + return t, tp.Shutdown, nil } // IsEnabled satisfies the Tracer interface, always returning true @@ -82,7 +83,7 @@ func (*OtelTracer) IsEnabled() bool { } // StartSpanFromRequestHeader extracts a remote span context from the request header -// (via the W3C traceparent/tracestate headers) and starts a child span from it. +// (via the W3C traceparent/tracestate or B3 headers) and starts a child span from it. func (t *OtelTracer) StartSpanFromRequestHeader( ctx context.Context, opName string, @@ -101,8 +102,3 @@ func (t *OtelTracer) StartSpanFromContext( ctx, span := t.tracer.Start(ctx, opName) return span, ctx } - -// newOtelTracer is a creator for the OtelTracer -func newOtelTracer() Tracer { - return &OtelTracer{} -} diff --git a/engines/router/missionctl/instrumentation/tracing/otel_test.go b/engines/router/missionctl/instrumentation/tracing/otel_test.go index 253e651f3..d0a7b86df 100644 --- a/engines/router/missionctl/instrumentation/tracing/otel_test.go +++ b/engines/router/missionctl/instrumentation/tracing/otel_test.go @@ -11,38 +11,37 @@ import ( "github.com/caraml-dev/turing/engines/router/missionctl/config" ) -func TestOtelTracer_IsEnabled(t *testing.T) { - tr := newOtelTracer() +func TestNewOtelTracer_IsEnabled(t *testing.T) { + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + }) + require.NoError(t, err) + defer func() { _ = shutdown(context.Background()) }() + assert.Equal(t, true, tr.IsEnabled()) } -func TestOtelTracer_InitGlobalTracer(t *testing.T) { - tr := newOtelTracer() - - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ +func TestNewOtelTracer(t *testing.T) { + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:4318", SamplingRatio: 0.5, }) require.NoError(t, err) + require.NotNil(t, tr) require.NotNil(t, shutdown) defer func() { _ = shutdown(context.Background()) }() } -func TestOtelTracer_InitGlobalTracer_HTTPS(t *testing.T) { - tr := newOtelTracer() - - // CollectorEndpoint using the https scheme should still construct a valid - // exporter/tracer without error. - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ +func TestNewOtelTracer_HTTPS(t *testing.T) { + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "https://localhost:4318", SamplingRatio: 0.5, }) require.NoError(t, err) - require.NotNil(t, shutdown) - defer func() { _ = shutdown(context.Background()) }() span, ctx := tr.StartSpanFromContext(context.Background(), "test-op") @@ -52,8 +51,7 @@ func TestOtelTracer_InitGlobalTracer_HTTPS(t *testing.T) { } func TestOtelTracer_StartSpanFromContext(t *testing.T) { - tr := newOtelTracer() - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:4318", }) @@ -67,8 +65,7 @@ func TestOtelTracer_StartSpanFromContext(t *testing.T) { } func TestOtelTracer_StartSpanFromRequestHeader(t *testing.T) { - tr := newOtelTracer() - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:4318", }) @@ -87,12 +84,9 @@ func TestOtelTracer_StartSpanFromRequestHeader(t *testing.T) { // TestOtelTracer_StartSpanFromRequestHeader_B3 confirms that inbound requests carrying B3 // trace-context headers (Istio/Knative's default propagation format) are joined into the -// same trace, rather than silently producing a disconnected root span. This is the -// counterpart of TestOtelTracer_StartSpanFromRequestHeader, which covers the W3C -// traceparent format. +// same trace, rather than silently producing a disconnected root span. func TestOtelTracer_StartSpanFromRequestHeader_B3(t *testing.T) { - tr := newOtelTracer() - shutdown, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:4318", }) @@ -112,10 +106,8 @@ func TestOtelTracer_StartSpanFromRequestHeader_B3(t *testing.T) { span.End() } -func TestOtelTracer_InitGlobalTracer_MissingHost(t *testing.T) { - tr := newOtelTracer() - - _, err := tr.InitGlobalTracer("test", &config.JaegerConfig{ +func TestNewOtelTracer_MissingHost(t *testing.T) { + _, _, err := newOtelTracer("test", &config.OtelConfig{ Enabled: true, CollectorEndpoint: "", }) diff --git a/engines/router/missionctl/instrumentation/tracing/tracing.go b/engines/router/missionctl/instrumentation/tracing/tracing.go index 1fec65eb2..02c86e4a7 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing.go @@ -2,6 +2,7 @@ package tracing import ( "context" + "errors" "net/http" "go.opentelemetry.io/otel/trace" @@ -9,13 +10,14 @@ import ( "github.com/caraml-dev/turing/engines/router/missionctl/config" ) -// ShutdownFunc flushes and shuts down the tracer provider created by InitGlobalTracer. +// ShutdownFunc flushes and shuts down the tracer provider(s) created by InitGlobalTracer. type ShutdownFunc func(context.Context) error -// Tracer represents a generic tracer that supports initialization of a global -// tracing client and creation of OpenTelemetry spans +// Tracer represents a generic tracer that supports creation of OpenTelemetry spans. +// Concrete tracers are constructed via their own typed newXTracer function (see +// newOtelTracer, newJaegerTracer, newMultiTracer) rather than through this interface, +// since different backends take different config types. type Tracer interface { - InitGlobalTracer(string, *config.JaegerConfig) (ShutdownFunc, error) IsEnabled() bool StartSpanFromRequestHeader( context.Context, @@ -28,31 +30,68 @@ type Tracer interface { // globalTracer is initialised to a Nop tracer, calling InitGlobalTracer will reset this var globalTracer Tracer = newNopTracer() -// InitGlobalTracer creates a new OTel tracer exporting via OTLP HTTP, and sets it as global -// tracer. The returned ShutdownFunc is always non-nil and safe to call, even when a non-nil -// error is also returned, so callers can unconditionally defer it rather than relying on a -// Fatal-on-error caller to skip the call. -func InitGlobalTracer(name string, jaegerCfg *config.JaegerConfig) (ShutdownFunc, error) { - noopShutdown := func(context.Context) error { return nil } +// InitGlobalTracer initialises whichever of jaegerCfg/otelCfg are enabled, and sets the +// global tracer to: the Nop tracer if neither is enabled, that single backend's tracer +// if exactly one is enabled, or a MultiTracer fanning out to both if both are enabled. +// The returned ShutdownFunc is always non-nil and safe to call, even when a non-nil +// error is also returned, so callers can unconditionally defer it. +func InitGlobalTracer( + name string, + jaegerCfg *config.JaegerConfig, + otelCfg *config.OtelConfig, +) (ShutdownFunc, error) { + var tracers []Tracer + var shutdowns []ShutdownFunc - // If jaeger config has not been set or tracing is not enabled, just (re-)initialise - // whatever tracer is currently global (typically the Nop tracer). - if jaegerCfg == nil || !jaegerCfg.Enabled { - return globalTracer.InitGlobalTracer(name, jaegerCfg) + // NOTE: order matters here. Otel is appended before Jaeger so that, when both are + // enabled, tracers[0] (and thus spans[0] in the resulting multiSpan) is always the + // Otel tracer/span. multiSpan's SpanContext()/IsRecording()/TracerProvider() delegate + // to spans[0] as the composite's "primary" identity (see multi.go's multiSpan doc + // comment) -- reordering these two blocks would silently flip which backend that + // primary identity comes from, so don't reorder casually. + if otelCfg != nil && otelCfg.Enabled { + t, shutdown, err := newOtelTracer(name, otelCfg) + if err != nil { + return aggregateShutdown(shutdowns), err + } + tracers = append(tracers, t) + shutdowns = append(shutdowns, shutdown) } - // Tracing is enabled: initialise a new OtelTracer, but only swap it in as the - // global tracer if initialisation succeeds. Otherwise leave the previous - // globalTracer (e.g. the Nop tracer) in place, so callers never observe a - // half-initialised OtelTracer whose IsEnabled() returns true but whose - // tracer/propagator fields are nil (which would panic on use). - otelTracer := newOtelTracer() - shutdown, err := otelTracer.InitGlobalTracer(name, jaegerCfg) - if err != nil { - return noopShutdown, err + // Must stay after the Otel block above -- see the ordering note there. + if jaegerCfg != nil && jaegerCfg.Enabled { + t, shutdown, err := newJaegerTracer(name, jaegerCfg) + if err != nil { + return aggregateShutdown(shutdowns), err + } + tracers = append(tracers, t) + shutdowns = append(shutdowns, shutdown) + } + + switch len(tracers) { + case 0: + globalTracer = newNopTracer() + case 1: + globalTracer = tracers[0] + default: + globalTracer = newMultiTracer(tracers) + } + + return aggregateShutdown(shutdowns), nil +} + +// aggregateShutdown returns a ShutdownFunc that always attempts every shutdown func +// in shutdowns (even if an earlier one errors), combining any errors. +func aggregateShutdown(shutdowns []ShutdownFunc) ShutdownFunc { + return func(ctx context.Context) error { + var errs []error + for _, shutdown := range shutdowns { + if err := shutdown(ctx); err != nil { + errs = append(errs, err) + } + } + return errors.Join(errs...) } - globalTracer = otelTracer - return shutdown, nil } // Glob returns the global tracer diff --git a/engines/router/missionctl/instrumentation/tracing/tracing_test.go b/engines/router/missionctl/instrumentation/tracing/tracing_test.go index d5f615aa0..3a3157841 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing_test.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing_test.go @@ -27,39 +27,74 @@ func TestSetGlob(t *testing.T) { assert.Equal(t, tr, globalTracer) } -func TestInitGlobalTracerNop(t *testing.T) { +func TestInitGlobalTracer_Nop(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - _, err := InitGlobalTracer("test", &config.JaegerConfig{}) + _, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{}) assert.NoError(t, err) assert.Equal(t, false, globalTracer.IsEnabled()) } -func TestInitGlobalTracerOtel(t *testing.T) { +func TestInitGlobalTracer_OtelOnly(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ Enabled: true, CollectorEndpoint: "http://localhost:4318", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, true, globalTracer.IsEnabled()) + assert.IsType(t, &OtelTracer{}, globalTracer) assert.NoError(t, shutdown(context.Background())) } -// TestInitGlobalTracerOtel_ErrorReturnsNonNilShutdown ensures that even when tracer -// initialisation fails (e.g. an invalid CollectorEndpoint), callers get back a safe, -// callable no-op ShutdownFunc rather than nil. Package-level InitGlobalTracer is called -// from application.go, which currently relies on log.Glob().Fatalf to exit the process on -// error, but the contract shouldn't depend on that -- a nil ShutdownFunc would panic any -// caller that unconditionally defers it. -func TestInitGlobalTracerOtel_ErrorReturnsNonNilShutdown(t *testing.T) { +// TestInitGlobalTracer_JaegerOnly relies on the classic Jaeger client's UDP agent +// reporter not dialling eagerly -- InitGlobalTracer succeeds even with no agent +// listening at the configured host:port, exactly like the OTel exporter today (see +// TestNewOtelTracer in otel_test.go). +func TestInitGlobalTracer_JaegerOnly(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }, &config.OtelConfig{}) + require.NoError(t, err) + assert.Equal(t, true, globalTracer.IsEnabled()) + assert.IsType(t, &JaegerTracer{}, globalTracer) + assert.NoError(t, shutdown(context.Background())) +} + +func TestInitGlobalTracer_Multi(t *testing.T) { + tempTracer := globalTracer + defer func() { globalTracer = tempTracer }() + + shutdown, err := InitGlobalTracer("test", + &config.JaegerConfig{ + Enabled: true, + ReporterAgentHost: "localhost", + ReporterAgentPort: 6831, + }, + &config.OtelConfig{ + Enabled: true, + CollectorEndpoint: "http://localhost:4318", + }, + ) + require.NoError(t, err) + assert.Equal(t, true, globalTracer.IsEnabled()) + assert.IsType(t, &MultiTracer{}, globalTracer) + assert.NoError(t, shutdown(context.Background())) +} + +func TestInitGlobalTracer_OtelError_ReturnsNonNilShutdown(t *testing.T) { + tempTracer := globalTracer + defer func() { globalTracer = tempTracer }() + + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ Enabled: true, CollectorEndpoint: "", }) diff --git a/engines/router/missionctl/server/application.go b/engines/router/missionctl/server/application.go index 690750ad0..ab5497460 100644 --- a/engines/router/missionctl/server/application.go +++ b/engines/router/missionctl/server/application.go @@ -137,7 +137,7 @@ func initInstrumentation(cfg *config.Config) func() { } // Init tracing client - tracingShutdown, err := tracing.InitGlobalTracer(cfg.AppConfig.Name, cfg.AppConfig.Jaeger) + tracingShutdown, err := tracing.InitGlobalTracer(cfg.AppConfig.Name, cfg.AppConfig.Jaeger, cfg.AppConfig.Otel) if err != nil { log.Glob().Fatalf("Failed initializing Tracer: %v", err) } diff --git a/sdk/turing/generated/model/router_version_log_config.py b/sdk/turing/generated/model/router_version_log_config.py index 95a0701b2..85b78afc6 100644 --- a/sdk/turing/generated/model/router_version_log_config.py +++ b/sdk/turing/generated/model/router_version_log_config.py @@ -87,6 +87,7 @@ def openapi_types(): 'custom_metrics_enabled': (bool,), # noqa: E501 'fiber_debug_log_enabled': (bool,), # noqa: E501 'jaeger_enabled': (bool,), # noqa: E501 + 'otel_enabled': (bool,), # noqa: E501 'pyroscope_enabled': (bool,), # noqa: E501 'result_logger_type': (ResultLoggerType,), # noqa: E501 'bigquery_config': (BigQueryConfig,), # noqa: E501 @@ -103,6 +104,7 @@ def discriminator(): 'custom_metrics_enabled': 'custom_metrics_enabled', # noqa: E501 'fiber_debug_log_enabled': 'fiber_debug_log_enabled', # noqa: E501 'jaeger_enabled': 'jaeger_enabled', # noqa: E501 + 'otel_enabled': 'otel_enabled', # noqa: E501 'pyroscope_enabled': 'pyroscope_enabled', # noqa: E501 'result_logger_type': 'result_logger_type', # noqa: E501 'bigquery_config': 'bigquery_config', # noqa: E501 @@ -158,7 +160,8 @@ def __init__(self, *args, **kwargs): # noqa: E501 log_level (LogLevel): [optional] # noqa: E501 custom_metrics_enabled (bool): [optional] # noqa: E501 fiber_debug_log_enabled (bool): [optional] # noqa: E501 - jaeger_enabled (bool): [optional] # noqa: E501 + jaeger_enabled (bool): Deprecated: use otel_enabled instead. Will be removed in a future release.. [optional] # noqa: E501 + otel_enabled (bool): [optional] # noqa: E501 pyroscope_enabled (bool): [optional] # noqa: E501 result_logger_type (ResultLoggerType): [optional] # noqa: E501 bigquery_config (BigQueryConfig): [optional] # noqa: E501 From 815653b06d56e9d3bfe8ae85efae8f9c0f86cdd7 Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Tue, 18 Aug 2026 14:50:24 +0700 Subject: [PATCH 3/8] feat: tag Pyroscope profiles with pod identity for multi-replica routers Injects POD_NAME/POD_NAMESPACE into the router container via the Kubernetes downward API and reports them as pod_name/pod_namespace Pyroscope tags (alongside the existing router_name tag), so profiles from individual replicas of a router deployment can be told apart. A new RouterDefaults.PyroscopeIncludePodTags flag (default true) lets an operator opt out of the pod tags deployment-wide, e.g. if per-pod label cardinality is undesirable in Pyroscope. It's templated into routers as APP_PYROSCOPE_INCLUDE_POD_TAGS, mirroring how PyroscopeServerAddress/PyroscopeHTTPHeaders are already passed through. Co-Authored-By: Claude Sonnet 5 --- api/turing/cluster/servicebuilder/router.go | 19 +++ .../cluster/servicebuilder/router_test.go | 144 ++++++++++++++++++ api/turing/config/config.go | 6 + api/turing/config/config_test.go | 23 +-- api/turing/config/example.yaml | 4 + engines/router/missionctl/config/config.go | 4 + .../router/missionctl/config/config_test.go | 40 ++--- .../instrumentation/profiling/profiling.go | 39 ++++- .../profiling/profiling_internal_test.go | 31 ++++ 9 files changed, 277 insertions(+), 33 deletions(-) create mode 100644 engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index 43e4cfa1b..0094a9757 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -45,6 +45,7 @@ const ( envPyroscopeEnabled = "APP_PYROSCOPE_ENABLED" envPyroscopeServerAddress = "APP_PYROSCOPE_SERVER_ADDRESS" envPyroscopeHTTPHeaders = "APP_PYROSCOPE_HTTP_HEADERS" + envPyroscopeIncludePodTags = "APP_PYROSCOPE_INCLUDE_POD_TAGS" envSentryEnabled = "APP_SENTRY_ENABLED" envSentryDSN = "APP_SENTRY_DSN" envResultLogger = "APP_RESULT_LOGGER" @@ -66,6 +67,11 @@ const ( envExpGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS_EXPERIMENT_ENGINE" envPluginName = "PLUGIN_NAME" envPluginsDir = "PLUGINS_DIR" + // envPodName and envPodNamespace are set via the Kubernetes downward API so the router + // can tag its Pyroscope profiles with the identity of the individual pod, since a router + // deployment can run multiple replicas behind the same APP_NAME. + envPodName = "POD_NAME" + envPodNamespace = "POD_NAMESPACE" ) // Router service constants @@ -247,10 +253,23 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envOtelEndpoint, Value: routerDefaults.OtelCollectorEndpoint}, {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, + {Name: envPyroscopeIncludePodTags, Value: strconv.FormatBool(routerDefaults.PyroscopeIncludePodTags)}, {Name: envRouterConfigFile, Value: routerConfigMapMountPath + routerConfigFileName}, {Name: envRouterProtocol, Value: string(ver.Protocol)}, {Name: envSentryEnabled, Value: strconv.FormatBool(sentryEnabled)}, {Name: envSentryDSN, Value: sentryDSN}, + { + Name: envPodName, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: envPodNamespace, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, }) // Add enricher / ensembler related env vars, if enabled diff --git a/api/turing/cluster/servicebuilder/router_test.go b/api/turing/cluster/servicebuilder/router_test.go index 7e81fd397..1885f7592 100644 --- a/api/turing/cluster/servicebuilder/router_test.go +++ b/api/turing/cluster/servicebuilder/router_test.go @@ -127,10 +127,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -235,10 +248,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -342,10 +368,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "ENRICHER_ENDPOINT", Value: enrEndpoint}, {Name: "ENRICHER_TIMEOUT", Value: "2s"}, {Name: "ENSEMBLER_ENDPOINT", Value: ensEndpoint}, @@ -456,10 +495,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -562,10 +614,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -668,10 +733,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -774,10 +852,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -880,10 +971,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "GOOGLE_APPLICATION_CREDENTIALS_EXPERIMENT_ENGINE", Value: "/var/secret/exp-engine/exp-engine-service-account.json"}, {Name: "APP_LOGLEVEL", Value: "INFO"}, @@ -1015,10 +1119,23 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "true"}, {Name: "APP_SENTRY_DSN", Value: "sentry-dsn"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "INFO"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -1095,6 +1212,7 @@ func TestNewRouterService(t *testing.T) { JaegerCollectorEndpoint: "jaeger-endpoint", OtelCollectorEndpoint: "otel-endpoint", PyroscopeServerAddress: "pyroscope-address", + PyroscopeIncludePodTags: true, FluentdConfig: &config.FluentdConfig{Tag: "fluentd-tag"}, }, true, @@ -1223,10 +1341,23 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "http://otel-collector.example.com:4318"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "http://pyroscope.example.com:4040"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, {Name: "APP_SENTRY_ENABLED", Value: "false"}, {Name: "APP_SENTRY_DSN", Value: ""}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: "DEBUG"}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, @@ -1273,10 +1404,23 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: ""}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: ""}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, {Name: "APP_SENTRY_ENABLED", Value: "false"}, {Name: "APP_SENTRY_DSN", Value: ""}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, {Name: "APP_LOGLEVEL", Value: ""}, {Name: "APP_CUSTOM_METRICS", Value: "false"}, {Name: "APP_JAEGER_ENABLED", Value: "false"}, diff --git a/api/turing/config/config.go b/api/turing/config/config.go index 7ef1f4934..b6b1b1322 100644 --- a/api/turing/config/config.go +++ b/api/turing/config/config.go @@ -349,6 +349,11 @@ type RouterDefaults struct { // HTTP headers routers should attach to every profile push request they make to // PyroscopeServerAddress, e.g. for auth (Authorization, X-Scope-OrgID, ...). Optional. PyroscopeHTTPHeaders map[string]string + // PyroscopeIncludePodTags controls whether routers tag their Pyroscope profiles with + // pod_name/pod_namespace (from the POD_NAME/POD_NAMESPACE downward API env vars), in + // addition to the always-present router_name tag. Defaults to true; set to false to opt + // out deployment-wide, e.g. if per-pod cardinality is undesirable in Pyroscope. + PyroscopeIncludePodTags bool // Router log level LogLevel string `validate:"required"` // Fluentd config for the router @@ -656,6 +661,7 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("RouterDefaults::OtelCollectorEndpoint", "") v.SetDefault("RouterDefaults::PyroscopeEnabled", "false") v.SetDefault("RouterDefaults::PyroscopeServerAddress", "") + v.SetDefault("RouterDefaults::PyroscopeIncludePodTags", "true") v.SetDefault("RouterDefaults::LogLevel", "INFO") v.SetDefault("RouterDefaults::FluentdConfig::Image", "") v.SetDefault("RouterDefaults::FluentdConfig::Tag", "turing-result.log") diff --git a/api/turing/config/config_test.go b/api/turing/config/config_test.go index 3b880028d..a3469cf65 100644 --- a/api/turing/config/config_test.go +++ b/api/turing/config/config_test.go @@ -185,6 +185,7 @@ func TestLoad(t *testing.T) { MaxMessageBytes: 1048588, CompressionType: "none", }, + PyroscopeIncludePodTags: true, }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{}, @@ -307,9 +308,10 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, - OtelEnabled: true, - OtelCollectorEndpoint: "http://otel-collector.example.com:4318", + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeIncludePodTags: true, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -479,9 +481,10 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, - OtelEnabled: true, - OtelCollectorEndpoint: "http://otel-collector.example.com:4318", + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeIncludePodTags: true, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -669,9 +672,10 @@ func TestLoad(t *testing.T) { // viper lowercases YAML map keys, so header names configured this way // always come out lowercase (harmless: HTTP header names are // case-insensitive). - PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, - OtelEnabled: true, - OtelCollectorEndpoint: "http://otel-collector.example.com:4318", + PyroscopeHTTPHeaders: map[string]string{"authorization": "Bearer token"}, + PyroscopeIncludePodTags: true, + OtelEnabled: true, + OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, Otel: config.OtelConfig{SamplingRatio: 1}, Sentry: sentry.Config{ @@ -778,6 +782,7 @@ func TestLoad_OtelAndPyroscope(t *testing.T) { assert.Equal(t, true, cfg.RouterDefaults.PyroscopeEnabled) assert.Equal(t, "http://pyroscope.example.com:4040", cfg.RouterDefaults.PyroscopeServerAddress) assert.Equal(t, map[string]string{"authorization": "Bearer token"}, cfg.RouterDefaults.PyroscopeHTTPHeaders) + assert.Equal(t, true, cfg.RouterDefaults.PyroscopeIncludePodTags) assert.Equal(t, true, cfg.RouterDefaults.OtelEnabled) assert.Equal(t, "http://otel-collector.example.com:4318", cfg.RouterDefaults.OtelCollectorEndpoint) } diff --git a/api/turing/config/example.yaml b/api/turing/config/example.yaml index 2a1832202..4e543bfc5 100644 --- a/api/turing/config/example.yaml +++ b/api/turing/config/example.yaml @@ -163,6 +163,10 @@ RouterDefaults: # loader; this is harmless since HTTP header names are case-insensitive. PyroscopeHTTPHeaders: Authorization: " or "Bearer "> + # Tag Pyroscope profiles with pod_name/pod_namespace (in addition to the always-present + # router_name tag), so individual pods of a multi-replica router deployment can be told + # apart. Defaults to true; set to false to opt out deployment-wide. + PyroscopeIncludePodTags: true LogLevel: INFO # Fluentd log forwarder configuration that can be used in Turing router diff --git a/engines/router/missionctl/config/config.go b/engines/router/missionctl/config/config.go index 15cbae71e..73215f188 100644 --- a/engines/router/missionctl/config/config.go +++ b/engines/router/missionctl/config/config.go @@ -142,6 +142,10 @@ type PyroscopeConfig struct { // HTTPHeaders are attached to every profile push request, e.g. for auth // (Authorization, X-Scope-OrgID, ...). Optional. HTTPHeaders map[string]string `split_words:"true"` + // IncludePodTags controls whether profiles are additionally tagged with pod_name/ + // pod_namespace (from the POD_NAME/POD_NAMESPACE downward API env vars), on top of the + // always-present router_name tag. Defaults to true. + IncludePodTags bool `split_words:"true" default:"true"` } // AppConfig is the structure used to the parse the environment configs that correspond diff --git a/engines/router/missionctl/config/config_test.go b/engines/router/missionctl/config/config_test.go index d5714f579..7781c231b 100644 --- a/engines/router/missionctl/config/config_test.go +++ b/engines/router/missionctl/config/config_test.go @@ -134,8 +134,9 @@ func TestInitConfigDefaultEnvs(t *testing.T) { SamplingRatio: 1, }, Pyroscope: &PyroscopeConfig{ - Enabled: false, - ServerAddress: "", + Enabled: false, + ServerAddress: "", + IncludePodTags: true, }, Sentry: sentry.Config{ Enabled: false, @@ -207,8 +208,9 @@ func TestInitConfigEnv(t *testing.T) { SamplingRatio: 0.8, }, Pyroscope: &PyroscopeConfig{ - Enabled: true, - ServerAddress: "http://localhost:4040", + Enabled: true, + ServerAddress: "http://localhost:4040", + IncludePodTags: true, }, Sentry: sentry.Config{ Enabled: true, @@ -371,20 +373,21 @@ func TestSerializationFormatDecode(t *testing.T) { func TestInitConfigEnv_JaegerOtelAndPyroscope(t *testing.T) { env := map[string]string{ - "PORT": "8080", - "ROUTER_CONFIG_FILE": "config.yaml", - "APP_NAME": "test-router", - "APP_ENVIRONMENT": "dev", - "APP_JAEGER_ENABLED": "true", - "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:14268/api/traces", - "APP_JAEGER_REPORTER_HOST": "localhost", - "APP_JAEGER_REPORTER_PORT": "6831", - "APP_OTEL_ENABLED": "true", - "APP_OTEL_COLLECTOR_ENDPOINT": "http://otel-collector:4318", - "APP_OTEL_SAMPLING_RATIO": "0.5", - "APP_PYROSCOPE_ENABLED": "true", - "APP_PYROSCOPE_SERVER_ADDRESS": "http://pyroscope:4040", - "APP_PYROSCOPE_HTTP_HEADERS": "Authorization:Bearer token,X-Scope-OrgID:tenant1", + "PORT": "8080", + "ROUTER_CONFIG_FILE": "config.yaml", + "APP_NAME": "test-router", + "APP_ENVIRONMENT": "dev", + "APP_JAEGER_ENABLED": "true", + "APP_JAEGER_COLLECTOR_ENDPOINT": "http://localhost:14268/api/traces", + "APP_JAEGER_REPORTER_HOST": "localhost", + "APP_JAEGER_REPORTER_PORT": "6831", + "APP_OTEL_ENABLED": "true", + "APP_OTEL_COLLECTOR_ENDPOINT": "http://otel-collector:4318", + "APP_OTEL_SAMPLING_RATIO": "0.5", + "APP_PYROSCOPE_ENABLED": "true", + "APP_PYROSCOPE_SERVER_ADDRESS": "http://pyroscope:4040", + "APP_PYROSCOPE_HTTP_HEADERS": "Authorization:Bearer token,X-Scope-OrgID:tenant1", + "APP_PYROSCOPE_INCLUDE_POD_TAGS": "false", } setupNewEnv(env) @@ -404,6 +407,7 @@ func TestInitConfigEnv_JaegerOtelAndPyroscope(t *testing.T) { "Authorization": "Bearer token", "X-Scope-OrgID": "tenant1", }, cfg.AppConfig.Pyroscope.HTTPHeaders) + assert.Equal(t, false, cfg.AppConfig.Pyroscope.IncludePodTags) } func setupNewEnv(envMaps ...map[string]string) { diff --git a/engines/router/missionctl/instrumentation/profiling/profiling.go b/engines/router/missionctl/instrumentation/profiling/profiling.go index 100303e27..1ed6e3aa7 100644 --- a/engines/router/missionctl/instrumentation/profiling/profiling.go +++ b/engines/router/missionctl/instrumentation/profiling/profiling.go @@ -2,20 +2,31 @@ package profiling import ( "fmt" + "os" "github.com/grafana/pyroscope-go" "github.com/caraml-dev/turing/engines/router/missionctl/config" ) -const applicationName = "turing-router" +const ( + applicationName = "turing-router" + // envPodName and envPodNamespace are populated via the Kubernetes downward API by the + // Turing API's servicebuilder. They are absent when running outside a pod (e.g. local dev). + envPodName = "POD_NAME" + envPodNamespace = "POD_NAMESPACE" +) // Start starts continuous profiling via pyroscope-go if enabled in cfg. All router // deployments report under the same Pyroscope application name and are differentiated -// by the router_name tag. Returns a nil profiler and nil error when profiling is -// disabled or cfg is nil. pyroscope.Start does not itself error on an empty -// ServerAddress -- it happily constructs a client that fails silently on every upload -- -// so an empty address is rejected explicitly here instead. +// by the router_name tag, plus -- when cfg.IncludePodTags is true -- pod_name/pod_namespace +// tags populated from the POD_NAME and POD_NAMESPACE downward API env vars, to distinguish +// individual pods within a multi-replica router deployment. The pod tags are also omitted +// when those env vars are unset, so as not to report noisy empty-string tags outside a real +// pod. Returns a nil profiler and nil error when profiling is disabled or cfg is nil. +// pyroscope.Start does not itself error on an empty ServerAddress -- it happily constructs a +// client that fails silently on every upload -- so an empty address is rejected explicitly +// here instead. func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, error) { if cfg == nil || !cfg.Enabled { return nil, nil @@ -28,7 +39,7 @@ func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, ApplicationName: applicationName, ServerAddress: cfg.ServerAddress, HTTPHeaders: cfg.HTTPHeaders, - Tags: map[string]string{"router_name": routerName}, + Tags: buildTags(routerName, cfg.IncludePodTags), ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, @@ -39,3 +50,19 @@ func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, }, }) } + +// buildTags returns the static Pyroscope tags for this process: router_name always, plus +// pod_name/pod_namespace -- when includePodTags is true -- for whichever of the +// corresponding downward API env vars are set. +func buildTags(routerName string, includePodTags bool) map[string]string { + tags := map[string]string{"router_name": routerName} + if includePodTags { + if podName := os.Getenv(envPodName); podName != "" { + tags["pod_name"] = podName + } + if podNamespace := os.Getenv(envPodNamespace); podNamespace != "" { + tags["pod_namespace"] = podNamespace + } + } + return tags +} diff --git a/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go b/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go new file mode 100644 index 000000000..7275246e5 --- /dev/null +++ b/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go @@ -0,0 +1,31 @@ +package profiling + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildTags(t *testing.T) { + t.Run("only router_name when pod env vars are unset", func(t *testing.T) { + assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", true)) + }) + + t.Run("includes pod_name and pod_namespace when set and includePodTags is true", func(t *testing.T) { + t.Setenv(envPodName, "test-router-abc123") + t.Setenv(envPodNamespace, "test-namespace") + + assert.Equal(t, map[string]string{ + "router_name": "test-router", + "pod_name": "test-router-abc123", + "pod_namespace": "test-namespace", + }, buildTags("test-router", true)) + }) + + t.Run("omits pod_name and pod_namespace when includePodTags is false, even if set", func(t *testing.T) { + t.Setenv(envPodName, "test-router-abc123") + t.Setenv(envPodNamespace, "test-namespace") + + assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", false)) + }) +} From a56ee84bb7e6ff1113cccf4bc7723b9667597d01 Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Tue, 18 Aug 2026 23:12:57 +0700 Subject: [PATCH 4/8] fix: unbreak CI after adding OTel/Pyroscope deps to router - Bump Go 1.22 -> 1.25 across api/router/experiment/hardcoded-plugin go.mod files (CI's GO_VERSION, and the golang:1.22-alpine base image in each component's own build Dockerfile): the new OTel OTLP HTTP exporter pulls grpc-gateway v2.19.0 -> kr/pretty v0.3.1, whose fmtsort import only exists in go-internal v1.16.0+, which requires go>=1.25. This only surfaced in engines/experiment (and its example plugin) because both locally `replace` engines/router, so `go mod tidy`/`vendor` must resolve the combined dependency graph. - Bump golangci-lint 1.56.2 -> 2.12.2 (and golangci-lint-action v2/v3 -> v7, migrating the shared .golangci.yml to the v2 config schema): no v1.x golangci-lint release supports Go 1.25's export data format. v2 also merges the `gosimple` linter into `staticcheck`, which by default pulls in the `stylecheck`/quickfix check families this repo never enabled -- restricted staticcheck.checks to keep the same check scope as before. - Add //nolint:staticcheck at every intentional usage of the newly-deprecated Jaeger config fields/types, since the PR's own backward-compat code paths trigger SA1019 on their own deprecated symbols. - Fix vet/lint findings that only surfaced once Go 1.25's govet and golangci-lint v2.12.2 ran over the affected packages: non-constant format strings passed to fmt.Errorf, reflect.Ptr -> reflect.Pointer, a named return shadowing the builtin `error` identifier, and two redundant nil-checks before len() on slices. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/turing.yaml | 12 ++-- .golangci.yml | 57 +++++++++++----- api/Dockerfile | 2 +- api/Makefile | 2 +- api/go.mod | 2 +- api/go.sum | 6 +- api/turing/api/base_controller.go | 6 +- api/turing/api/request/request.go | 2 +- api/turing/cluster/servicebuilder/router.go | 9 ++- api/turing/internal/testutils/validation.go | 4 +- .../service/router_deployment_service_test.go | 4 +- api/turing/validation/validator.go | 2 +- engines/experiment/Makefile | 2 +- .../examples/plugins/hardcoded/Dockerfile | 2 +- .../examples/plugins/hardcoded/go.mod | 24 +++---- .../examples/plugins/hardcoded/go.sum | 66 ++++++++++--------- engines/experiment/go.mod | 29 ++++---- engines/experiment/go.sum | 66 ++++++++++--------- engines/router/Dockerfile | 2 +- engines/router/Makefile | 2 +- engines/router/go.mod | 4 +- engines/router/go.sum | 8 +-- .../router/missionctl/errors/errors_test.go | 5 +- .../instrumentation/tracing/jaeger.go | 2 +- .../instrumentation/tracing/jaeger_test.go | 8 +-- .../instrumentation/tracing/tracing.go | 4 +- .../instrumentation/tracing/tracing_test.go | 10 +-- .../internal/testutils/validation.go | 2 +- .../server/http/handlers/http_handler.go | 2 +- 29 files changed, 186 insertions(+), 160 deletions(-) diff --git a/.github/workflows/turing.yaml b/.github/workflows/turing.yaml index fcbbb3975..5aa90d092 100644 --- a/.github/workflows/turing.yaml +++ b/.github/workflows/turing.yaml @@ -37,8 +37,8 @@ on: env: ARTIFACT_RETENTION_DAYS: 7 - GO_VERSION: "1.22" - GO_LINT_VERSION: v1.56.2 + GO_VERSION: "1.25" + GO_LINT_VERSION: v2.12.2 CLUSTER_NAME: turing-e2e ISTIO_VERSION: 1.9.9 KNATIVE_VERSION: 1.7.4 @@ -257,7 +257,7 @@ jobs: run: make test - name: Lint code - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v7 with: version: ${{ env.GO_LINT_VERSION }} working-directory: api @@ -292,11 +292,10 @@ jobs: run: make benchmark - name: Lint code - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v7 with: version: ${{ env.GO_LINT_VERSION }} working-directory: engines/router - skip-go-installation: true args: --verbose test-engines-experiment: @@ -318,11 +317,10 @@ jobs: run: make test - name: Lint code - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v7 with: version: ${{ env.GO_LINT_VERSION }} working-directory: engines/experiment - skip-go-installation: true args: --verbose test-e2e: diff --git a/.golangci.yml b/.golangci.yml index 151fdea9a..3671e9c39 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,28 +1,49 @@ +version: "2" run: build-tags: - e2e - skip-dirs: - - turing/generated - linters: enable: - bodyclose - - errcheck - gocyclo - - gofmt - - goimports - - gosimple - - govet - - ineffassign - lll - misspell - revive - - staticcheck - - unused - -linters-settings: - gocyclo: - # Min code complexity to report, 30 by default (recommended 10-20) - min-complexity: 25 - lll: - line-length: 120 + settings: + gocyclo: + min-complexity: 25 + lll: + line-length: 120 + staticcheck: + # v1 only ever enabled the `staticcheck` (SA*) and `gosimple` (S1*) linters, never + # `stylecheck` (ST*) or the quickfix suggestions (QF*). v2 merges gosimple into this + # single staticcheck linter, so exclude ST*/QF* here to keep the same check scope + # as before, instead of newly linting the whole codebase against rules it was never + # checked against. + checks: + - all + - -ST* + - -QF* + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - turing/generated + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - turing/generated + - third_party$ + - builtin$ + - examples$ diff --git a/api/Dockerfile b/api/Dockerfile index c1d7940f6..98f8d867a 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -3,7 +3,7 @@ FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine as gke-plugin-builde RUN gcloud components install gke-gcloud-auth-plugin --quiet # Build turing-api binary -FROM golang:1.22-alpine as api-builder +FROM golang:1.25-alpine as api-builder ARG API_BIN_NAME=turing-api ENV GO111MODULE=on \ diff --git a/api/Makefile b/api/Makefile index 778f696af..ea0327a98 100644 --- a/api/Makefile +++ b/api/Makefile @@ -14,7 +14,7 @@ clean: .PHONY: setup setup: @echo "Setting up tools..." - @test -x ${GOPATH}/bin/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 + @test -x ${GOPATH}/bin/golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 @test -x ${GOPATH}/bin/gotest || go install github.com/rakyll/gotest@latest .PHONY: fmt diff --git a/api/go.mod b/api/go.mod index 9456225e0..47c9197ef 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,6 +1,6 @@ module github.com/caraml-dev/turing/api -go 1.22 +go 1.25 require ( bou.ke/monkey v1.0.2 diff --git a/api/go.sum b/api/go.sum index cffd84f7e..7f7c09da3 100644 --- a/api/go.sum +++ b/api/go.sum @@ -158,6 +158,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= @@ -688,8 +690,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= diff --git a/api/turing/api/base_controller.go b/api/turing/api/base_controller.go index 01cc7d95d..835c4c0a4 100644 --- a/api/turing/api/base_controller.go +++ b/api/turing/api/base_controller.go @@ -49,7 +49,7 @@ func (c BaseController) ParseVars(dst interface{}, vars RequestVars) error { return nil } -func (c BaseController) getProjectFromRequestVars(vars RequestVars) (project *mlp.Project, error *Response) { +func (c BaseController) getProjectFromRequestVars(vars RequestVars) (project *mlp.Project, errResponse *Response) { id, err := getIDFromVars(vars, "project_id") if err != nil { return nil, BadRequest("invalid project id", err.Error()) @@ -61,7 +61,7 @@ func (c BaseController) getProjectFromRequestVars(vars RequestVars) (project *ml return project, nil } -func (c BaseController) getRouterFromRequestVars(vars RequestVars) (router *models.Router, error *Response) { +func (c BaseController) getRouterFromRequestVars(vars RequestVars) (router *models.Router, errResponse *Response) { id, err := getIDFromVars(vars, "router_id") if err != nil { return nil, BadRequest("invalid router id", err.Error()) @@ -75,7 +75,7 @@ func (c BaseController) getRouterFromRequestVars(vars RequestVars) (router *mode func (c BaseController) getRouterVersionFromRequestVars( vars RequestVars, -) (routerVersion *models.RouterVersion, error *Response) { +) (routerVersion *models.RouterVersion, errResponse *Response) { routerID, err := getIDFromVars(vars, "router_id") if err != nil { return nil, BadRequest("invalid router id", err.Error()) diff --git a/api/turing/api/request/request.go b/api/turing/api/request/request.go index c81f00d72..13ca75aaf 100644 --- a/api/turing/api/request/request.go +++ b/api/turing/api/request/request.go @@ -158,7 +158,7 @@ func (r RouterConfig) BuildRouterVersion( LogLevel: routerConfig.LogLevel(defaults.LogLevel), CustomMetricsEnabled: defaults.CustomMetricsEnabled, FiberDebugLogEnabled: defaults.FiberDebugLogEnabled, - JaegerEnabled: defaults.JaegerEnabled, + JaegerEnabled: defaults.JaegerEnabled, //nolint:staticcheck OtelEnabled: defaults.OtelEnabled, PyroscopeEnabled: defaults.PyroscopeEnabled, ResultLoggerType: r.LogConfig.ResultLoggerType, diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index 0094a9757..62cf27f0d 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -249,7 +249,7 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envAppName, Value: fmt.Sprintf("%s-%d.%s", ver.Router.Name, ver.Version, namespace)}, {Name: envAppEnvironment, Value: environmentType}, {Name: envRouterTimeout, Value: ver.Timeout}, - {Name: envJaegerEndpoint, Value: routerDefaults.JaegerCollectorEndpoint}, + {Name: envJaegerEndpoint, Value: routerDefaults.JaegerCollectorEndpoint}, //nolint:staticcheck {Name: envOtelEndpoint, Value: routerDefaults.OtelCollectorEndpoint}, {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, @@ -306,7 +306,7 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( envs = mergeEnvVars(envs, []corev1.EnvVar{ {Name: envLogLevel, Value: string(logConfig.LogLevel)}, {Name: envCustomMetrics, Value: strconv.FormatBool(logConfig.CustomMetricsEnabled)}, - {Name: envJaegerEnabled, Value: strconv.FormatBool(logConfig.JaegerEnabled)}, + {Name: envJaegerEnabled, Value: strconv.FormatBool(logConfig.JaegerEnabled)}, //nolint:staticcheck {Name: envOtelEnabled, Value: strconv.FormatBool(logConfig.OtelEnabled)}, {Name: envPyroscopeEnabled, Value: strconv.FormatBool(logConfig.PyroscopeEnabled)}, {Name: envResultLogger, Value: string(logConfig.ResultLoggerType)}, @@ -656,8 +656,7 @@ func buildFiberConfigMap( } if ver.Ensembler != nil && ver.Ensembler.Type == models.EnsemblerStandardType { - if ver.Ensembler.StandardConfig.ExperimentMappings != nil && - len(ver.Ensembler.StandardConfig.ExperimentMappings) != 0 { + if len(ver.Ensembler.StandardConfig.ExperimentMappings) != 0 { propsMap["experiment_mappings"] = ver.Ensembler.StandardConfig.ExperimentMappings } if ver.Ensembler.StandardConfig.RouteNamePath != "" { @@ -680,7 +679,7 @@ func buildFiberConfigMap( // if the version is configured with traffic splitting rules on it, // then define root-level fiber component as a lazy router with // a traffic-splitting strategy based on these rules - if ver.TrafficRules != nil && len(ver.TrafficRules) > 0 { + if len(ver.TrafficRules) > 0 { // TrafficRule struct used requires the name and conditions field to be specified. But // Default Traffic Rule has no name and a hardcoded name can be used instead since // the name field is not used for traffic splitting strategy. Likewise, an empty slice diff --git a/api/turing/internal/testutils/validation.go b/api/turing/internal/testutils/validation.go index a3ed80f0c..b7105a2b3 100644 --- a/api/turing/internal/testutils/validation.go +++ b/api/turing/internal/testutils/validation.go @@ -11,11 +11,11 @@ import ( // If the object types have unexported fields, a custom marshaler is required to be defined. func CompareObjects(actual interface{}, expected interface{}) error { allowUnexportedOn := actual - if reflect.TypeOf(allowUnexportedOn).Kind() == reflect.Ptr { + if reflect.TypeOf(allowUnexportedOn).Kind() == reflect.Pointer { allowUnexportedOn = reflect.ValueOf(actual).Elem().Interface() } if !cmp.Equal(actual, expected, cmp.AllowUnexported(allowUnexportedOn)) { - return fmt.Errorf(cmp.Diff(actual, expected, cmp.AllowUnexported(allowUnexportedOn))) + return fmt.Errorf("%s", cmp.Diff(actual, expected, cmp.AllowUnexported(allowUnexportedOn))) } return nil } diff --git a/api/turing/service/router_deployment_service_test.go b/api/turing/service/router_deployment_service_test.go index ca3975b81..45366ba8a 100644 --- a/api/turing/service/router_deployment_service_test.go +++ b/api/turing/service/router_deployment_service_test.go @@ -119,7 +119,7 @@ func (msb *mockClusterServiceBuilder) NewRouterService( Name: fmt.Sprintf("%s-router-%d", rv.Router.Name, rv.Version), Namespace: project.Name, Envs: []corev1.EnvVar{ - {Name: "JAEGER_EP", Value: routerDefaults.JaegerCollectorEndpoint}, + {Name: "JAEGER_EP", Value: routerDefaults.JaegerCollectorEndpoint}, //nolint:staticcheck {Name: "FLUENTD_TAG", Value: routerDefaults.FluentdConfig.Tag}, {Name: "ENVIRONMENT", Value: envType}, {Name: "SENTRY_ENABLED", Value: strconv.FormatBool(sentryEnabled)}, @@ -306,7 +306,7 @@ func TestDeployEndpoint(t *testing.T) { Name: fmt.Sprintf("%s-router-%d", routerVersion.Router.Name, routerVersion.Version), Namespace: testNamespace, Envs: []corev1.EnvVar{ - {Name: "JAEGER_EP", Value: ds.routerDefaults.JaegerCollectorEndpoint}, + {Name: "JAEGER_EP", Value: ds.routerDefaults.JaegerCollectorEndpoint}, //nolint:staticcheck {Name: "FLUENTD_TAG", Value: ds.routerDefaults.FluentdConfig.Tag}, {Name: "ENVIRONMENT", Value: envType}, {Name: "SENTRY_ENABLED", Value: "true"}, diff --git a/api/turing/validation/validator.go b/api/turing/validation/validator.go index 1071f7bf9..87b0799a5 100644 --- a/api/turing/validation/validator.go +++ b/api/turing/validation/validator.go @@ -377,7 +377,7 @@ func validateRouterConfig(sl validator.StructLevel) { } // Validate dangling routes and traffic rules orthogonality checks - if router.TrafficRules != nil && len(router.TrafficRules) > 0 { + if len(router.TrafficRules) > 0 { checkDanglingRoutes(sl, "Routes", router.Routes, allRuleRoutesSet) validateConditionOrthogonality(sl, "TrafficRules", router.TrafficRules) } diff --git a/engines/experiment/Makefile b/engines/experiment/Makefile index 09582c62f..84f191103 100644 --- a/engines/experiment/Makefile +++ b/engines/experiment/Makefile @@ -7,7 +7,7 @@ default: test setup: @echo "Setting up tools..." @test -x $(shell go env GOPATH)/bin/golangci-lint || \ - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v1.48.0/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.48.0 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v2.12.2/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v2.12.2 .PHONY: tidy tidy: diff --git a/engines/experiment/examples/plugins/hardcoded/Dockerfile b/engines/experiment/examples/plugins/hardcoded/Dockerfile index d95426512..886afabce 100644 --- a/engines/experiment/examples/plugins/hardcoded/Dockerfile +++ b/engines/experiment/examples/plugins/hardcoded/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine as builder +FROM golang:1.25-alpine as builder ARG binary_name="plugin" ARG project_root=github.com/caraml-dev/turing/engines/experiment/examples/plugins/hardcoded/cmd diff --git a/engines/experiment/examples/plugins/hardcoded/go.mod b/engines/experiment/examples/plugins/hardcoded/go.mod index 8ac98aad0..bcd43c091 100644 --- a/engines/experiment/examples/plugins/hardcoded/go.mod +++ b/engines/experiment/examples/plugins/hardcoded/go.mod @@ -1,12 +1,12 @@ module github.com/caraml-dev/turing/engines/experiment/examples/plugins/hardcoded -go 1.22 +go 1.25 require ( github.com/caraml-dev/mlp v1.12.0 github.com/caraml-dev/turing/engines/experiment v1.0.0 github.com/hashicorp/go-hclog v1.5.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.9.0 ) require ( @@ -14,15 +14,15 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/caraml-dev/turing/engines/router v0.0.0 // indirect github.com/caraml-dev/universal-prediction-interface v0.3.6 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator v9.31.0+incompatible // indirect github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -41,12 +41,14 @@ require ( github.com/zaffka/zap-to-hclog v0.10.6 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect - google.golang.org/grpc v1.52.3 // indirect - google.golang.org/protobuf v1.29.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/grpc v1.61.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/errgo.v2 v2.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/engines/experiment/examples/plugins/hardcoded/go.sum b/engines/experiment/examples/plugins/hardcoded/go.sum index 83b2ba911..f50529a40 100644 --- a/engines/experiment/examples/plugins/hardcoded/go.sum +++ b/engines/experiment/examples/plugins/hardcoded/go.sum @@ -19,8 +19,9 @@ github.com/caraml-dev/mlp v1.12.0/go.mod h1:Zdz4bALO9WOHXhOgsoLmCjMCJnDVEZEnQFg8 github.com/caraml-dev/universal-prediction-interface v0.3.6 h1:G/D4aukfjLECl8armJqFy/R2+0u/f4AiurSFqAo33uQ= github.com/caraml-dev/universal-prediction-interface v0.3.6/go.mod h1:e0qmFOXQxx8HFg5ObYyQO3WVnrqsr5v5JApFmeF7eJo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -48,8 +49,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -63,19 +62,19 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/grpc-ecosystem/grpc-gateway/v2 v2.11.2 h1:BqHID5W5qnMkug0Z8UmL8tN0gAy4jQ+B4WFt8cCgluU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2/go.mod h1:ZbS3MZTZq/apAfAEHGoB5HbsQQstoqP92SjAqtQ9zeg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= @@ -97,6 +96,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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= @@ -130,8 +131,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -159,23 +158,22 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zaffka/zap-to-hclog v0.10.6 h1:dNxbL5drL6sVUDHtCMbokJLWrYn5wSKAWTXjgWFadx0= github.com/zaffka/zap-to-hclog v0.10.6/go.mod h1:wLqRe/Fa1MkfUY9EtnCDiz2CqhTPNZPh0/pRE9lLi04= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= @@ -201,8 +199,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -232,13 +230,13 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -250,14 +248,18 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 h1:p0kMzw6AG0JEzd7Z+kXqOiLhC6gjUQTbtS2zR0Q3DbI= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -266,14 +268,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= -google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -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/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/engines/experiment/go.mod b/engines/experiment/go.mod index d1e168ef5..d485a7c2a 100644 --- a/engines/experiment/go.mod +++ b/engines/experiment/go.mod @@ -1,6 +1,6 @@ module github.com/caraml-dev/turing/engines/experiment -go 1.22 +go 1.25 require ( bou.ke/monkey v1.0.2 @@ -14,43 +14,44 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.9.0 github.com/zaffka/zap-to-hclog v0.10.6 go.uber.org/zap v1.26.0 - google.golang.org/grpc v1.52.3 + google.golang.org/grpc v1.61.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/oklog/run v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/rogpeppe/go-internal v1.16.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect - google.golang.org/protobuf v1.29.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/errgo.v2 v2.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/engines/experiment/go.sum b/engines/experiment/go.sum index 83b2ba911..f50529a40 100644 --- a/engines/experiment/go.sum +++ b/engines/experiment/go.sum @@ -19,8 +19,9 @@ github.com/caraml-dev/mlp v1.12.0/go.mod h1:Zdz4bALO9WOHXhOgsoLmCjMCJnDVEZEnQFg8 github.com/caraml-dev/universal-prediction-interface v0.3.6 h1:G/D4aukfjLECl8armJqFy/R2+0u/f4AiurSFqAo33uQ= github.com/caraml-dev/universal-prediction-interface v0.3.6/go.mod h1:e0qmFOXQxx8HFg5ObYyQO3WVnrqsr5v5JApFmeF7eJo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -48,8 +49,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -63,19 +62,19 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/grpc-ecosystem/grpc-gateway/v2 v2.11.2 h1:BqHID5W5qnMkug0Z8UmL8tN0gAy4jQ+B4WFt8cCgluU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2/go.mod h1:ZbS3MZTZq/apAfAEHGoB5HbsQQstoqP92SjAqtQ9zeg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= @@ -97,6 +96,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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= @@ -130,8 +131,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -159,23 +158,22 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zaffka/zap-to-hclog v0.10.6 h1:dNxbL5drL6sVUDHtCMbokJLWrYn5wSKAWTXjgWFadx0= github.com/zaffka/zap-to-hclog v0.10.6/go.mod h1:wLqRe/Fa1MkfUY9EtnCDiz2CqhTPNZPh0/pRE9lLi04= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= @@ -201,8 +199,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -232,13 +230,13 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -250,14 +248,18 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 h1:p0kMzw6AG0JEzd7Z+kXqOiLhC6gjUQTbtS2zR0Q3DbI= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -266,14 +268,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= -google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -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/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/engines/router/Dockerfile b/engines/router/Dockerfile index bc98fd9ee..57121ecb9 100644 --- a/engines/router/Dockerfile +++ b/engines/router/Dockerfile @@ -1,5 +1,5 @@ # Build application binary -FROM golang:1.22-alpine as builder +FROM golang:1.25-alpine as builder ARG BIN_NAME=turing-router ARG VERSION ARG USER diff --git a/engines/router/Makefile b/engines/router/Makefile index 87ad4fae8..29341074a 100644 --- a/engines/router/Makefile +++ b/engines/router/Makefile @@ -21,7 +21,7 @@ clean: .PHONY: setup setup: @echo "Setting up tools..." - @test -x ${GOPATH}/bin/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 + @test -x ${GOPATH}/bin/golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 @test -x $(shell go env GOPATH)/bin/gotest || go install github.com/rakyll/gotest@latest .PHONY: fmt diff --git a/engines/router/go.mod b/engines/router/go.mod index 83b65df81..572668d9d 100644 --- a/engines/router/go.mod +++ b/engines/router/go.mod @@ -1,6 +1,6 @@ module github.com/caraml-dev/turing/engines/router -go 1.22 +go 1.25 require ( bou.ke/monkey v1.0.2 @@ -112,7 +112,7 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/engines/router/go.sum b/engines/router/go.sum index 84f0d9c11..8ce4e9f63 100644 --- a/engines/router/go.sum +++ b/engines/router/go.sum @@ -359,8 +359,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -595,8 +595,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/engines/router/missionctl/errors/errors_test.go b/engines/router/missionctl/errors/errors_test.go index 30636d1b4..8f907396d 100644 --- a/engines/router/missionctl/errors/errors_test.go +++ b/engines/router/missionctl/errors/errors_test.go @@ -2,7 +2,6 @@ package errors import ( "errors" - "fmt" "net/http" "testing" @@ -89,7 +88,7 @@ func TestGetHTTPErrorCode(t *testing.T) { func TestNewHTTPErrorMessage(t *testing.T) { message := "Test Error Message" - err := fmt.Errorf(message) + err := errors.New(message) httpErr := NewTuringError(err, fiberProtocol.HTTP) assert.Equal(t, message, httpErr.Error()) } @@ -114,7 +113,7 @@ func TestNewHTTPErrorStatus(t *testing.T) { t.Run(name, func(t *testing.T) { // Create new error message := "Test Error" - err := fmt.Errorf(message) + err := errors.New(message) // Create new HTTP error httpErr := NewTuringError(err, fiberProtocol.HTTP, data.codes...) // Validate diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger.go b/engines/router/missionctl/instrumentation/tracing/jaeger.go index 63d135859..541bba8e9 100644 --- a/engines/router/missionctl/instrumentation/tracing/jaeger.go +++ b/engines/router/missionctl/instrumentation/tracing/jaeger.go @@ -50,7 +50,7 @@ type JaegerTracer struct { // elsewhere in this codebase), and returns a Tracer that adapts it to the // trace.Tracer/trace.Span API, alongside a ShutdownFunc that closes the underlying // Jaeger reporter. -func newJaegerTracer(name string, cfg *config.JaegerConfig) (Tracer, ShutdownFunc, error) { +func newJaegerTracer(name string, cfg *config.JaegerConfig) (Tracer, ShutdownFunc, error) { //nolint:staticcheck jCfg := jaegercfg.Configuration{ ServiceName: name, Disabled: !cfg.Enabled, diff --git a/engines/router/missionctl/instrumentation/tracing/jaeger_test.go b/engines/router/missionctl/instrumentation/tracing/jaeger_test.go index 4c719a331..9cd19125a 100644 --- a/engines/router/missionctl/instrumentation/tracing/jaeger_test.go +++ b/engines/router/missionctl/instrumentation/tracing/jaeger_test.go @@ -12,7 +12,7 @@ import ( ) func TestNewJaegerTracer_IsEnabled(t *testing.T) { - tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, @@ -24,7 +24,7 @@ func TestNewJaegerTracer_IsEnabled(t *testing.T) { } func TestNewJaegerTracer_StartSpanFromContext(t *testing.T) { - tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, @@ -39,7 +39,7 @@ func TestNewJaegerTracer_StartSpanFromContext(t *testing.T) { } func TestNewJaegerTracer_NestedSpans_ShareTraceID(t *testing.T) { - tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, @@ -56,7 +56,7 @@ func TestNewJaegerTracer_NestedSpans_ShareTraceID(t *testing.T) { } func TestNewJaegerTracer_StartSpanFromRequestHeader(t *testing.T) { - tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ + tr, shutdown, err := newJaegerTracer("test", &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, diff --git a/engines/router/missionctl/instrumentation/tracing/tracing.go b/engines/router/missionctl/instrumentation/tracing/tracing.go index 02c86e4a7..1a407f377 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing.go @@ -28,7 +28,7 @@ type Tracer interface { } // globalTracer is initialised to a Nop tracer, calling InitGlobalTracer will reset this -var globalTracer Tracer = newNopTracer() +var globalTracer = newNopTracer() // InitGlobalTracer initialises whichever of jaegerCfg/otelCfg are enabled, and sets the // global tracer to: the Nop tracer if neither is enabled, that single backend's tracer @@ -37,7 +37,7 @@ var globalTracer Tracer = newNopTracer() // error is also returned, so callers can unconditionally defer it. func InitGlobalTracer( name string, - jaegerCfg *config.JaegerConfig, + jaegerCfg *config.JaegerConfig, //nolint:staticcheck otelCfg *config.OtelConfig, ) (ShutdownFunc, error) { var tracers []Tracer diff --git a/engines/router/missionctl/instrumentation/tracing/tracing_test.go b/engines/router/missionctl/instrumentation/tracing/tracing_test.go index 3a3157841..bc17f57bc 100644 --- a/engines/router/missionctl/instrumentation/tracing/tracing_test.go +++ b/engines/router/missionctl/instrumentation/tracing/tracing_test.go @@ -31,7 +31,7 @@ func TestInitGlobalTracer_Nop(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - _, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{}) + _, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{}) //nolint:staticcheck assert.NoError(t, err) assert.Equal(t, false, globalTracer.IsEnabled()) } @@ -40,7 +40,7 @@ func TestInitGlobalTracer_OtelOnly(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ //nolint:staticcheck Enabled: true, CollectorEndpoint: "http://localhost:4318", }) @@ -58,7 +58,7 @@ func TestInitGlobalTracer_JaegerOnly(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, @@ -74,7 +74,7 @@ func TestInitGlobalTracer_Multi(t *testing.T) { defer func() { globalTracer = tempTracer }() shutdown, err := InitGlobalTracer("test", - &config.JaegerConfig{ + &config.JaegerConfig{ //nolint:staticcheck Enabled: true, ReporterAgentHost: "localhost", ReporterAgentPort: 6831, @@ -94,7 +94,7 @@ func TestInitGlobalTracer_OtelError_ReturnsNonNilShutdown(t *testing.T) { tempTracer := globalTracer defer func() { globalTracer = tempTracer }() - shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ + shutdown, err := InitGlobalTracer("test", &config.JaegerConfig{}, &config.OtelConfig{ //nolint:staticcheck Enabled: true, CollectorEndpoint: "", }) diff --git a/engines/router/missionctl/internal/testutils/validation.go b/engines/router/missionctl/internal/testutils/validation.go index 190cfa09c..265ee06a6 100644 --- a/engines/router/missionctl/internal/testutils/validation.go +++ b/engines/router/missionctl/internal/testutils/validation.go @@ -13,7 +13,7 @@ import ( // If the object types have unexported fields, a custom marshaler is required to be defined. func CompareObjects(actual interface{}, expected interface{}) error { allowUnexportedOn := actual - if reflect.TypeOf(allowUnexportedOn).Kind() == reflect.Ptr { + if reflect.TypeOf(allowUnexportedOn).Kind() == reflect.Pointer { allowUnexportedOn = reflect.ValueOf(actual).Elem().Interface() } if !cmp.Equal(actual, expected, cmp.AllowUnexported(allowUnexportedOn)) { diff --git a/engines/router/missionctl/server/http/handlers/http_handler.go b/engines/router/missionctl/server/http/handlers/http_handler.go index ceb5fc3c1..ba245390e 100644 --- a/engines/router/missionctl/server/http/handlers/http_handler.go +++ b/engines/router/missionctl/server/http/handlers/http_handler.go @@ -116,7 +116,7 @@ func (h *httpHandler) getPrediction( if expResp != nil { var expErr *errors.TuringError if expResp.Error != "" { - expErr = errors.NewTuringError(fmt.Errorf(expResp.Error), fiberProtocol.HTTP) + expErr = errors.NewTuringError(fmt.Errorf("%s", expResp.Error), fiberProtocol.HTTP) } if expResp.Configuration != nil || expErr != nil { h.rl.SendResponseToLogChannel(ctx, respCh, resultlog.ResultLogKeys.Experiment, expResp, expErr) From f37ef039ff770a8e7972aa706257bc8082fd4883 Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Wed, 19 Aug 2026 17:07:43 +0700 Subject: [PATCH 5/8] fix(router): dry-run before setting pod fieldRef env vars, no new RBAC needed Knative's Revision admission webhook rejects env[].valueFrom.fieldRef unless the alpha kubernetes.podspec-fieldref feature gate is set to Enabled in the target cluster (disabled by default), so router deploys were failing outright with: admission webhook "validation.webhook.serving.knative.dev" denied the request: validation failed: must not set the field(s): ...valueFrom.fieldRef Rather than reading Knative's own config-features ConfigMap to detect this (which needs a new RBAC grant per target cluster, separate from whatever Turing already needs there), probe for the capability with a dry-run of the exact deploy request itself, using only the create/update permission on Knative Services that Turing's deploy credential already requires for every deploy. DeployKnativeService now: if the built service has any env[].valueFrom.fieldRef (the router's POD_NAME/POD_NAMESPACE), it dry-runs the Create/Update first. If the cluster rejects it with that specific admission error, the fields are stripped in place and a warning is logged; otherwise the error propagates as a normal deploy failure. The real Create/Update then proceeds as before, with or without those fields depending on the probe. buildRouterEnvs/NewRouterService go back to unconditionally including the fieldRef env vars -- all the capability handling lives in the cluster/controller layer instead. Co-Authored-By: Claude Sonnet 5 --- api/turing/cluster/controller.go | 118 ++++++++++++---- api/turing/cluster/controller_test.go | 188 ++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 26 deletions(-) diff --git a/api/turing/cluster/controller.go b/api/turing/cluster/controller.go index 1757cb07e..5e1efffb9 100644 --- a/api/turing/cluster/controller.go +++ b/api/turing/cluster/controller.go @@ -39,6 +39,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "github.com/caraml-dev/turing/api/turing/config" + logger "github.com/caraml-dev/turing/api/turing/log" ) var ErrNamespaceAlreadyExists = errors.New("namespace already exists") @@ -258,9 +259,6 @@ func (c *controller) DeleteConfigMap(ctx context.Context, name string, namespace // Deploy creates / updates a Kubernetes/Knative service with the given specs func (c *controller) DeployKnativeService(ctx context.Context, svcConf *KnativeService) error { - var existingSvc *knservingv1.Service - var err error - // Build the deployment specs desiredSvc, err := svcConf.BuildKnativeServiceConfig() if err != nil { @@ -270,39 +268,107 @@ func (c *controller) DeployKnativeService(ctx context.Context, svcConf *KnativeS // Init knative ServicesGetter services := c.knServingClient.Services(svcConf.Namespace) - // Check if service already exists. If exists, update it. If not, create. - existingSvc, err = services.Get(ctx, svcConf.Name, metav1.GetOptions{}) + // Some clusters' Knative Serving installations reject env[].valueFrom.fieldRef in the + // container spec outright (the alpha kubernetes.podspec-fieldref feature gate is disabled by + // default). Rather than requiring every cluster to opt into that gate, or requiring a + // separate permission to read Knative's own feature-flag ConfigMap, probe for it with a + // dry-run of this exact request first, and silently drop those env vars if rejected. + if hasFieldRefEnvVars(desiredSvc) { + if err := applyKnativeService(ctx, services, svcConf.Name, desiredSvc, true); err != nil { + if !isFieldRefRejection(err) { + return err + } + logger.Warnf( + "cluster rejected env[].valueFrom.fieldRef for Knative service %s/%s (%s); "+ + "deploying without it", + svcConf.Namespace, svcConf.Name, err.Error(), + ) + stripFieldRefEnvVars(desiredSvc) + } + } + + if err := applyKnativeService(ctx, services, svcConf.Name, desiredSvc, false); err != nil { + return err + } + + // Wait until service ready and return any errors + return c.waitKnativeServiceReady(ctx, svcConf.Name, svcConf.Namespace) +} + +// applyKnativeService creates or updates the Knative service named svcName to match desiredSvc. +// When dryRun is true, no changes are persisted -- this only exercises the API server's admission +// chain, to test whether desiredSvc as given would be accepted. +func applyKnativeService( + ctx context.Context, + services knservingclient.ServiceInterface, + svcName string, + desiredSvc *knservingv1.Service, + dryRun bool, +) error { + var dryRunOpt []string + if dryRun { + dryRunOpt = []string{metav1.DryRunAll} + } + + existingSvc, err := services.Get(ctx, svcName, metav1.GetOptions{}) if err != nil { - if k8serrors.IsNotFound(err) { - // Create new service - _, err = services.Create(ctx, desiredSvc, metav1.CreateOptions{}) - } else { + if !k8serrors.IsNotFound(err) { // Unexpected error, return it return err } - } else { - // Check for differences between current and new specs - if !knServiceSemanticEquals(desiredSvc, existingSvc) { - _, err = kmp.SafeDiff( - desiredSvc.Spec.ConfigurationSpec, - existingSvc.Spec.ConfigurationSpec, - ) - if err != nil { - return fmt.Errorf("Failed to diff Knative Service: %v", err) + // Create new service + _, err = services.Create(ctx, desiredSvc, metav1.CreateOptions{DryRun: dryRunOpt}) + return err + } + + // Check for differences between current and new specs + if knServiceSemanticEquals(desiredSvc, existingSvc) { + return nil + } + if _, err := kmp.SafeDiff(desiredSvc.Spec.ConfigurationSpec, existingSvc.Spec.ConfigurationSpec); err != nil { + return fmt.Errorf("Failed to diff Knative Service: %v", err) + } + // Update the existing service with the new config + existingSvc.Spec.ConfigurationSpec = desiredSvc.Spec.ConfigurationSpec + existingSvc.ObjectMeta.Labels = desiredSvc.ObjectMeta.Labels + _, err = services.Update(ctx, existingSvc, metav1.UpdateOptions{DryRun: dryRunOpt}) + return err +} + +// hasFieldRefEnvVars reports whether any container in the service's pod spec sets an env var via +// the Kubernetes downward API (env[].valueFrom.fieldRef). +func hasFieldRefEnvVars(svc *knservingv1.Service) bool { + for _, container := range svc.Spec.ConfigurationSpec.Template.Spec.Containers { + for _, env := range container.Env { + if env.ValueFrom != nil && env.ValueFrom.FieldRef != nil { + return true } - // Update the existing service with the new config - existingSvc.Spec.ConfigurationSpec = desiredSvc.Spec.ConfigurationSpec - existingSvc.ObjectMeta.Labels = desiredSvc.ObjectMeta.Labels - _, err = services.Update(ctx, existingSvc, metav1.UpdateOptions{}) } } + return false +} - if err != nil { - return err +// stripFieldRefEnvVars removes, in place, every env[].valueFrom.fieldRef entry from the service's +// containers. +func stripFieldRefEnvVars(svc *knservingv1.Service) { + containers := svc.Spec.ConfigurationSpec.Template.Spec.Containers + for i := range containers { + filtered := containers[i].Env[:0] + for _, env := range containers[i].Env { + if env.ValueFrom != nil && env.ValueFrom.FieldRef != nil { + continue + } + filtered = append(filtered, env) + } + containers[i].Env = filtered } +} - // Wait until service ready and return any errors - return c.waitKnativeServiceReady(ctx, svcConf.Name, svcConf.Namespace) +// isFieldRefRejection reports whether err is the admission rejection Knative's Revision webhook +// returns for env[].valueFrom.fieldRef when the cluster's kubernetes.podspec-fieldref feature +// gate is disabled (the default). +func isFieldRefRejection(err error) bool { + return err != nil && strings.Contains(err.Error(), "valueFrom.fieldRef") } // Delete removes the Kubernetes/Knative service and all related artifacts diff --git a/api/turing/cluster/controller_test.go b/api/turing/cluster/controller_test.go index 793e6270d..7dfcee0f3 100644 --- a/api/turing/cluster/controller_test.go +++ b/api/turing/cluster/controller_test.go @@ -191,6 +191,194 @@ func TestDeployKnativeService(t *testing.T) { } } +func TestDeployKnativeServiceFieldRefRejectedFallsBackWithoutIt(t *testing.T) { + testName, testNamespace := "test-name", "test-namespace" + + svcWithFieldRef := &knservingv1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: testName}, + Spec: knservingv1.ServiceSpec{ + ConfigurationSpec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "APP_NAME", Value: "test"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + svcConf := &KnativeService{ + BaseService: &BaseService{ + Name: testName, + Namespace: testNamespace, + }, + } + + getReady := func(_ k8stesting.Action) (bool, runtime.Object, error) { + return true, &knservingv1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: testName}, + Status: knservingv1.ServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue}, + }, + }, + }, + }, nil + } + + fieldRefRejection := errors.New( + `admission webhook "validation.webhook.serving.knative.dev" denied the request: ` + + `validation failed: must not set the field(s): ` + + `spec.template.spec.containers[0].env[1].valueFrom.fieldRef`, + ) + + createCallCount := 0 + + cs := knservingclientset.NewSimpleClientset() + cs.PrependReactor(reactorVerbs.Get, knativeServicesResource, + func(_ k8stesting.Action) (bool, runtime.Object, error) { + return true, nil, k8serrors.NewNotFound(schema.GroupResource{}, testName) + }) + cs.PrependReactor(reactorVerbs.Create, knativeServicesResource, + func(action k8stesting.Action) (bool, runtime.Object, error) { + createCallCount++ + created := action.(k8stesting.CreateAction).GetObject().(*knservingv1.Service) + + if createCallCount == 1 { + // First (dry-run) attempt: still carries the fieldRef env var; cluster rejects it. + assert.True(t, hasFieldRefEnvVars(created), + "expected first Create attempt to still include the fieldRef env var") + return true, nil, fieldRefRejection + } + // Second (real) attempt, after stripping: must no longer include it. + assert.False(t, hasFieldRefEnvVars(created), + "expected fieldRef env var to be stripped before the real Create") + cs.PrependReactor(reactorVerbs.Get, knativeServicesResource, getReady) + return true, created, nil + }) + + monkey.PatchInstanceMethod( + reflect.TypeOf(svcConf), + "BuildKnativeServiceConfig", + func(*KnativeService) (*knservingv1.Service, error) { + return svcWithFieldRef.DeepCopy(), nil + }) + defer monkey.UnpatchAll() + + c := &controller{knServingClient: cs.ServingV1()} + + ctx, cancel := context.WithTimeout(context.Background(), contextTimeoutDuration) + defer cancel() + + err := c.DeployKnativeService(ctx, svcConf) + + assert.NoError(t, err) + assert.Equal(t, 2, createCallCount, "expected exactly two Create attempts: dry-run, then real") +} + +func TestHasFieldRefEnvVars(t *testing.T) { + withFieldRef := &knservingv1.Service{Spec: knservingv1.ServiceSpec{ + ConfigurationSpec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "APP_NAME", Value: "test"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }} + withoutFieldRef := &knservingv1.Service{Spec: knservingv1.ServiceSpec{ + ConfigurationSpec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Env: []corev1.EnvVar{{Name: "APP_NAME", Value: "test"}}}, + }, + }, + }, + }, + }, + }} + + assert.True(t, hasFieldRefEnvVars(withFieldRef)) + assert.False(t, hasFieldRefEnvVars(withoutFieldRef)) +} + +func TestStripFieldRefEnvVars(t *testing.T) { + svc := &knservingv1.Service{Spec: knservingv1.ServiceSpec{ + ConfigurationSpec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "APP_NAME", Value: "test"}, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }} + + stripFieldRefEnvVars(svc) + + assert.Equal(t, []corev1.EnvVar{{Name: "APP_NAME", Value: "test"}}, + svc.Spec.ConfigurationSpec.Template.Spec.Containers[0].Env) +} + +func TestIsFieldRefRejection(t *testing.T) { + assert.True(t, isFieldRefRejection(errors.New( + `admission webhook "validation.webhook.serving.knative.dev" denied the request: `+ + `validation failed: must not set the field(s): `+ + `spec.template.spec.containers[0].env[1].valueFrom.fieldRef`))) + assert.False(t, isFieldRefRejection(errors.New("some other unrelated error"))) + assert.False(t, isFieldRefRejection(nil)) +} + func TestDeployKubernetesService(t *testing.T) { testName, testNamespace := "test-name", "test-namespace" statefulSetResourceItem := schema.GroupVersionResource{ From 5d8cb5c9b6f1e745b9a1c341561c01be68dd38ed Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Wed, 19 Aug 2026 19:00:15 +0700 Subject: [PATCH 6/8] feat(router): expose OTel sampling ratio in RouterDefaults, default to 0.01 The router engine already supported configuring its OTel trace sampling ratio via APP_OTEL_SAMPLING_RATIO (engines/router/missionctl/config, default 1 -- sample everything), but Turing's API had no way to set it: RouterDefaults had no corresponding field, and buildRouterEnvs never set the env var, so every deployed router silently sampled 100% of traces regardless of the API's own Otel.SamplingRatio (which only affects the API server's own self-tracing, not routers it deploys). Add RouterDefaults.OtelSamplingRatio, threaded into buildRouterEnvs as APP_OTEL_SAMPLING_RATIO, so operators can configure this per Turing deployment like the other RouterDefaults fields. Also lower the default sampling ratio from 1 to 0.01 in three places: the API's own Otel.SamplingRatio, the new RouterDefaults.OtelSamplingRatio default, and the router engine's own default when the env var is unset -- sampling everything is rarely necessary and gets expensive at scale. Co-Authored-By: Claude Sonnet 5 --- api/turing/cluster/servicebuilder/router.go | 2 ++ .../cluster/servicebuilder/router_test.go | 11 ++++++++++ api/turing/config/config.go | 8 +++++-- api/turing/config/config_test.go | 22 +++++++++++-------- api/turing/config/example.yaml | 4 +++- engines/router/missionctl/config/config.go | 4 ++-- .../router/missionctl/config/config_test.go | 2 +- 7 files changed, 38 insertions(+), 15 deletions(-) diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index 62cf27f0d..67f98086f 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -42,6 +42,7 @@ const ( envJaegerEndpoint = "APP_JAEGER_COLLECTOR_ENDPOINT" envOtelEnabled = "APP_OTEL_ENABLED" envOtelEndpoint = "APP_OTEL_COLLECTOR_ENDPOINT" + envOtelSamplingRatio = "APP_OTEL_SAMPLING_RATIO" envPyroscopeEnabled = "APP_PYROSCOPE_ENABLED" envPyroscopeServerAddress = "APP_PYROSCOPE_SERVER_ADDRESS" envPyroscopeHTTPHeaders = "APP_PYROSCOPE_HTTP_HEADERS" @@ -251,6 +252,7 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envRouterTimeout, Value: ver.Timeout}, {Name: envJaegerEndpoint, Value: routerDefaults.JaegerCollectorEndpoint}, //nolint:staticcheck {Name: envOtelEndpoint, Value: routerDefaults.OtelCollectorEndpoint}, + {Name: envOtelSamplingRatio, Value: strconv.FormatFloat(routerDefaults.OtelSamplingRatio, 'f', -1, 64)}, {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, {Name: envPyroscopeIncludePodTags, Value: strconv.FormatBool(routerDefaults.PyroscopeIncludePodTags)}, diff --git a/api/turing/cluster/servicebuilder/router_test.go b/api/turing/cluster/servicebuilder/router_test.go index 1885f7592..0b18f8b88 100644 --- a/api/turing/cluster/servicebuilder/router_test.go +++ b/api/turing/cluster/servicebuilder/router_test.go @@ -125,6 +125,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -246,6 +247,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -366,6 +368,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -493,6 +496,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -612,6 +616,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -731,6 +736,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -850,6 +856,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -969,6 +976,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -1117,6 +1125,7 @@ func TestNewRouterService(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "5s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: "jaeger-endpoint"}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "otel-endpoint"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, @@ -1339,6 +1348,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: "10s"}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: "http://otel-collector.example.com:4318"}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "http://pyroscope.example.com:4040"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, @@ -1402,6 +1412,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "ROUTER_TIMEOUT", Value: ""}, {Name: "APP_JAEGER_COLLECTOR_ENDPOINT", Value: ""}, {Name: "APP_OTEL_COLLECTOR_ENDPOINT", Value: ""}, + {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: ""}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, diff --git a/api/turing/config/config.go b/api/turing/config/config.go index b6b1b1322..07fae8410 100644 --- a/api/turing/config/config.go +++ b/api/turing/config/config.go @@ -341,6 +341,9 @@ type RouterDefaults struct { // OTLP HTTP endpoint routers should export traces to. If OtelEnabled is true, // this value must be set. OtelCollectorEndpoint string `validate:"required_if=OtelEnabled True"` + // OtelSamplingRatio is the fraction of traces routers should sample, between 0 and 1. + // Defaults to 0.01. + OtelSamplingRatio float64 // Enable Pyroscope profiling for routers deployed by this instance of the Turing API PyroscopeEnabled bool // Pyroscope server address routers should report profiles to. If PyroscopeEnabled is @@ -381,7 +384,7 @@ type OtelConfig struct { // OtlpEndpoint is the OTLP HTTP endpoint spans are exported to, e.g. http://otel-collector:4318. // If Enabled is true, this value must be set. OtlpEndpoint string `validate:"required_if=Enabled True"` - // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 1 (sample all). + // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 0.01. SamplingRatio float64 } @@ -659,6 +662,7 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("RouterDefaults::JaegerCollectorEndpoint", "") v.SetDefault("RouterDefaults::OtelEnabled", "false") v.SetDefault("RouterDefaults::OtelCollectorEndpoint", "") + v.SetDefault("RouterDefaults::OtelSamplingRatio", "0.01") v.SetDefault("RouterDefaults::PyroscopeEnabled", "false") v.SetDefault("RouterDefaults::PyroscopeServerAddress", "") v.SetDefault("RouterDefaults::PyroscopeIncludePodTags", "true") @@ -676,7 +680,7 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("Otel::Enabled", "false") v.SetDefault("Otel::OtlpEndpoint", "") - v.SetDefault("Otel::SamplingRatio", "1") + v.SetDefault("Otel::SamplingRatio", "0.01") v.SetDefault("Pyroscope::Enabled", "false") v.SetDefault("Pyroscope::ServerAddress", "") diff --git a/api/turing/config/config_test.go b/api/turing/config/config_test.go index a3469cf65..cbe86c4a0 100644 --- a/api/turing/config/config_test.go +++ b/api/turing/config/config_test.go @@ -175,7 +175,8 @@ func TestLoad(t *testing.T) { UserContainerMemoryLimitRequestFactor: 1, }, RouterDefaults: &config.RouterDefaults{ - LogLevel: "INFO", + LogLevel: "INFO", + OtelSamplingRatio: 0.01, FluentdConfig: &config.FluentdConfig{ Tag: "turing-result.log", FlushIntervalSeconds: 90, @@ -187,7 +188,7 @@ func TestLoad(t *testing.T) { }, PyroscopeIncludePodTags: true, }, - Otel: config.OtelConfig{SamplingRatio: 1}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, Sentry: sentry.Config{}, ClusterConfig: config.ClusterConfig{ InClusterConfig: false, @@ -293,7 +294,8 @@ func TestLoad(t *testing.T) { }, }, RouterDefaults: &config.RouterDefaults{ - LogLevel: "INFO", + LogLevel: "INFO", + OtelSamplingRatio: 0.01, FluentdConfig: &config.FluentdConfig{ Tag: "turing-result.log", FlushIntervalSeconds: 60, @@ -313,7 +315,7 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 1}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -451,7 +453,8 @@ func TestLoad(t *testing.T) { }, }, RouterDefaults: &config.RouterDefaults{ - LogLevel: "INFO", + LogLevel: "INFO", + OtelSamplingRatio: 0.01, FluentdConfig: &config.FluentdConfig{ Tag: "turing-result.log", FlushIntervalSeconds: 90, @@ -486,7 +489,7 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 1}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -642,7 +645,8 @@ func TestLoad(t *testing.T) { }, }, RouterDefaults: &config.RouterDefaults{ - LogLevel: "INFO", + LogLevel: "INFO", + OtelSamplingRatio: 0.01, FluentdConfig: &config.FluentdConfig{ Tag: "turing-result.log", FlushIntervalSeconds: 90, @@ -677,7 +681,7 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 1}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -777,7 +781,7 @@ func TestLoad_OtelAndPyroscope(t *testing.T) { require.NoError(t, err) assert.Equal(t, false, cfg.Otel.Enabled) - assert.Equal(t, float64(1), cfg.Otel.SamplingRatio) + assert.Equal(t, float64(0.01), cfg.Otel.SamplingRatio) assert.Equal(t, false, cfg.Pyroscope.Enabled) assert.Equal(t, true, cfg.RouterDefaults.PyroscopeEnabled) assert.Equal(t, "http://pyroscope.example.com:4040", cfg.RouterDefaults.PyroscopeServerAddress) diff --git a/api/turing/config/example.yaml b/api/turing/config/example.yaml index 4e543bfc5..807a1b2b1 100644 --- a/api/turing/config/example.yaml +++ b/api/turing/config/example.yaml @@ -155,6 +155,8 @@ RouterDefaults: JaegerCollectorEndpoint: http://jaeger-collector.example.com:14268/api/traces OtelEnabled: false OtelCollectorEndpoint: http://otel-collector.example.com:4318 + # Fraction of traces routers should sample, between 0 and 1. Defaults to 0.01. + OtelSamplingRatio: 0.01 PyroscopeEnabled: false PyroscopeServerAddress: http://pyroscope.example.com:4040 # HTTP headers attached to every profile push request routers make to @@ -192,7 +194,7 @@ RouterDefaults: Otel: Enabled: false OtlpEndpoint: http://otel-collector.example.com:4318 - SamplingRatio: 1 + SamplingRatio: 0.01 # Pyroscope profiling service configuration Pyroscope: diff --git a/engines/router/missionctl/config/config.go b/engines/router/missionctl/config/config.go index 73215f188..6f6105dcc 100644 --- a/engines/router/missionctl/config/config.go +++ b/engines/router/missionctl/config/config.go @@ -131,8 +131,8 @@ type OtelConfig struct { // CollectorEndpoint is the OTLP HTTP endpoint spans are exported to, // e.g. http://otel-collector:4318 CollectorEndpoint string `split_words:"true"` - // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 1 (sample all). - SamplingRatio float64 `split_words:"true" default:"1"` + // SamplingRatio is the fraction of traces to sample, between 0 and 1. Defaults to 0.01. + SamplingRatio float64 `split_words:"true" default:"0.01"` } // PyroscopeConfig captures the settings for continuous profiling using Pyroscope diff --git a/engines/router/missionctl/config/config_test.go b/engines/router/missionctl/config/config_test.go index 7781c231b..7588c666f 100644 --- a/engines/router/missionctl/config/config_test.go +++ b/engines/router/missionctl/config/config_test.go @@ -131,7 +131,7 @@ func TestInitConfigDefaultEnvs(t *testing.T) { Otel: &OtelConfig{ Enabled: false, CollectorEndpoint: "", - SamplingRatio: 1, + SamplingRatio: 0.01, }, Pyroscope: &PyroscopeConfig{ Enabled: false, From 28df63f0a6466f606a4a7d1c02befaa744758696 Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Wed, 19 Aug 2026 20:09:35 +0700 Subject: [PATCH 7/8] chore: add trailing newline to sdk/.coveragerc Trailing newline only, no content changes. Touches sdk/** so this PR's sdk workflow computes a fresh dev version and re-runs the publish job, after the previous run collided with an already-published PyPI version (0.16.2.post10.dev0 -- the dev version scheme derives its "postN" purely from commit count since the last tag, which is shared across branches, so an unrelated commit elsewhere had already claimed that exact version). Co-Authored-By: Claude Sonnet 5 --- sdk/.coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/.coveragerc b/sdk/.coveragerc index 1ce887926..bbdd3f52e 100644 --- a/sdk/.coveragerc +++ b/sdk/.coveragerc @@ -1,3 +1,3 @@ [run] omit = - turing/generated/* \ No newline at end of file + turing/generated/* From 6ba301d7d6cd05d44471692516661923433ed3ea Mon Sep 17 00:00:00 2001 From: anantadwi13 Date: Thu, 20 Aug 2026 15:48:51 +0700 Subject: [PATCH 8/8] feat: support custom Pyroscope profile tags via RouterDefaults Adds RouterDefaults.PyroscopeCustomTags (map[string]string), serialized into a single APP_PYROSCOPE_CUSTOM_TAGS env var the same way PyroscopeHTTPHeaders already is. The router merges these into its Pyroscope tags alongside the built-in router_name/pod_name/ pod_namespace tags, which always win on key collision. Also adds the same CustomTags support to the Turing API's own Pyroscope config (config.PyroscopeConfig, api/turing/server/ instrumentation.go), which profiles the API server itself and previously had no way to attach custom tags. It further adds Pyroscope.IncludePodTags (default true), tagging API profiles with pod_name/pod_namespace so individual replicas can be told apart -- the POD_NAME/POD_NAMESPACE env vars are left for the deployer to populate via the Kubernetes downward API (e.g. turing.extraEnvs in the Helm chart), since the API's own deployment isn't templated by this codebase the way routers are. Co-Authored-By: Claude Sonnet 5 --- api/turing/cluster/servicebuilder/router.go | 17 ++++--- .../cluster/servicebuilder/router_test.go | 44 +++++++++++------ api/turing/config/config.go | 12 +++++ api/turing/config/config_test.go | 14 ++++-- api/turing/config/example.yaml | 13 +++++ api/turing/server/instrumentation.go | 31 ++++++++++++ api/turing/server/instrumentation_test.go | 49 +++++++++++++++++++ engines/router/missionctl/config/config.go | 5 ++ .../instrumentation/profiling/profiling.go | 21 +++++--- .../profiling/profiling_internal_test.go | 26 ++++++++-- 10 files changed, 193 insertions(+), 39 deletions(-) diff --git a/api/turing/cluster/servicebuilder/router.go b/api/turing/cluster/servicebuilder/router.go index 67f98086f..0adf9fe87 100644 --- a/api/turing/cluster/servicebuilder/router.go +++ b/api/turing/cluster/servicebuilder/router.go @@ -46,6 +46,7 @@ const ( envPyroscopeEnabled = "APP_PYROSCOPE_ENABLED" envPyroscopeServerAddress = "APP_PYROSCOPE_SERVER_ADDRESS" envPyroscopeHTTPHeaders = "APP_PYROSCOPE_HTTP_HEADERS" + envPyroscopeCustomTags = "APP_PYROSCOPE_CUSTOM_TAGS" envPyroscopeIncludePodTags = "APP_PYROSCOPE_INCLUDE_POD_TAGS" envSentryEnabled = "APP_SENTRY_ENABLED" envSentryDSN = "APP_SENTRY_DSN" @@ -217,19 +218,18 @@ func (sb *clusterSvcBuilder) GetRouterServiceName(routerVersion *models.RouterVe return GetComponentName(routerVersion, ComponentTypes.Router) } -// formatHTTPHeaders serializes headers into the "Key1:Val1,Key2:Val2" format -// expected by the router's envconfig-based map decoding, with keys sorted for -// deterministic output. -func formatHTTPHeaders(headers map[string]string) string { - keys := make([]string, 0, len(headers)) - for k := range headers { +// formatMapEnvVar serializes a map into the "Key1:Val1,Key2:Val2" format expected by the +// router's envconfig-based map decoding, with keys sorted for deterministic output. +func formatMapEnvVar(m map[string]string) string { + keys := make([]string, 0, len(m)) + for k := range m { keys = append(keys, k) } sort.Strings(keys) pairs := make([]string, 0, len(keys)) for _, k := range keys { - pairs = append(pairs, fmt.Sprintf("%s:%s", k, headers[k])) + pairs = append(pairs, fmt.Sprintf("%s:%s", k, m[k])) } return strings.Join(pairs, ",") } @@ -254,7 +254,8 @@ func (sb *clusterSvcBuilder) buildRouterEnvs( {Name: envOtelEndpoint, Value: routerDefaults.OtelCollectorEndpoint}, {Name: envOtelSamplingRatio, Value: strconv.FormatFloat(routerDefaults.OtelSamplingRatio, 'f', -1, 64)}, {Name: envPyroscopeServerAddress, Value: routerDefaults.PyroscopeServerAddress}, - {Name: envPyroscopeHTTPHeaders, Value: formatHTTPHeaders(routerDefaults.PyroscopeHTTPHeaders)}, + {Name: envPyroscopeHTTPHeaders, Value: formatMapEnvVar(routerDefaults.PyroscopeHTTPHeaders)}, + {Name: envPyroscopeCustomTags, Value: formatMapEnvVar(routerDefaults.PyroscopeCustomTags)}, {Name: envPyroscopeIncludePodTags, Value: strconv.FormatBool(routerDefaults.PyroscopeIncludePodTags)}, {Name: envRouterConfigFile, Value: routerConfigMapMountPath + routerConfigFileName}, {Name: envRouterProtocol, Value: string(ver.Protocol)}, diff --git a/api/turing/cluster/servicebuilder/router_test.go b/api/turing/cluster/servicebuilder/router_test.go index 0b18f8b88..b4f0d9152 100644 --- a/api/turing/cluster/servicebuilder/router_test.go +++ b/api/turing/cluster/servicebuilder/router_test.go @@ -128,6 +128,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -250,6 +251,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, @@ -371,6 +373,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -499,6 +502,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -619,6 +623,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -739,6 +744,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -859,6 +865,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -979,6 +986,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -1128,6 +1136,7 @@ func TestNewRouterService(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "pyroscope-address"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "true"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -1314,6 +1323,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { JaegerCollectorEndpoint: "", OtelCollectorEndpoint: "http://otel-collector.example.com:4318", PyroscopeServerAddress: "http://pyroscope.example.com:4040", + PyroscopeCustomTags: map[string]string{"team": "fraud"}, FluentdConfig: &config.FluentdConfig{Tag: ""}, KafkaConfig: &config.KafkaConfig{ MaxMessageBytes: 123, @@ -1351,6 +1361,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: "http://pyroscope.example.com:4040"}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: "team:fraud"}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.HTTP)}, @@ -1415,6 +1426,7 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { {Name: "APP_OTEL_SAMPLING_RATIO", Value: "0"}, {Name: "APP_PYROSCOPE_SERVER_ADDRESS", Value: ""}, {Name: "APP_PYROSCOPE_HTTP_HEADERS", Value: ""}, + {Name: "APP_PYROSCOPE_CUSTOM_TAGS", Value: ""}, {Name: "APP_PYROSCOPE_INCLUDE_POD_TAGS", Value: "false"}, {Name: "ROUTER_CONFIG_FILE", Value: "/app/config/fiber.yml"}, {Name: "ROUTER_PROTOCOL", Value: string(routerConfig.UPI)}, @@ -1465,30 +1477,30 @@ func TestBuildRouterEnvsResultLogger(t *testing.T) { } } -func TestFormatHTTPHeaders(t *testing.T) { +func TestFormatMapEnvVar(t *testing.T) { tests := []struct { - name string - headers map[string]string - want string + name string + m map[string]string + want string }{ { - name: "nil", - headers: nil, - want: "", + name: "nil", + m: nil, + want: "", }, { - name: "empty", - headers: map[string]string{}, - want: "", + name: "empty", + m: map[string]string{}, + want: "", }, { - name: "single header", - headers: map[string]string{"Authorization": "Bearer token"}, - want: "Authorization:Bearer token", + name: "single entry", + m: map[string]string{"Authorization": "Bearer token"}, + want: "Authorization:Bearer token", }, { - name: "multiple headers sorted by key regardless of map order", - headers: map[string]string{ + name: "multiple entries sorted by key regardless of map order", + m: map[string]string{ "X-Scope-OrgID": "tenant1", "Authorization": "Bearer token", "X-Custom": "value", @@ -1501,7 +1513,7 @@ func TestFormatHTTPHeaders(t *testing.T) { // Run repeatedly since Go map iteration order is randomized per run, // to catch any accidental reliance on iteration order. for i := 0; i < 5; i++ { - assert.Equal(t, tt.want, formatHTTPHeaders(tt.headers)) + assert.Equal(t, tt.want, formatMapEnvVar(tt.m)) } }) } diff --git a/api/turing/config/config.go b/api/turing/config/config.go index 07fae8410..fe8c6670e 100644 --- a/api/turing/config/config.go +++ b/api/turing/config/config.go @@ -352,6 +352,10 @@ type RouterDefaults struct { // HTTP headers routers should attach to every profile push request they make to // PyroscopeServerAddress, e.g. for auth (Authorization, X-Scope-OrgID, ...). Optional. PyroscopeHTTPHeaders map[string]string + // PyroscopeCustomTags are additional static tags routers should attach to every profile, + // on top of the built-in router_name/pod_name/pod_namespace tags. If a key collides with + // one of those, the built-in value wins. Optional. + PyroscopeCustomTags map[string]string // PyroscopeIncludePodTags controls whether routers tag their Pyroscope profiles with // pod_name/pod_namespace (from the POD_NAME/POD_NAMESPACE downward API env vars), in // addition to the always-present router_name tag. Defaults to true; set to false to opt @@ -397,6 +401,12 @@ type PyroscopeConfig struct { // HTTPHeaders are attached to every profile push request, e.g. for auth // (Authorization, X-Scope-OrgID, ...). Optional. HTTPHeaders map[string]string + // CustomTags are additional static tags attached to every profile. Optional. + CustomTags map[string]string + // IncludePodTags controls whether profiles are additionally tagged with pod_name/ + // pod_namespace (from the POD_NAME/POD_NAMESPACE downward API env vars), to distinguish + // individual replicas of the Turing API deployment. Defaults to true. + IncludePodTags bool } // FluentdConfig captures the defaults used by the Turing Router when Fluentd is enabled @@ -665,6 +675,7 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("RouterDefaults::OtelSamplingRatio", "0.01") v.SetDefault("RouterDefaults::PyroscopeEnabled", "false") v.SetDefault("RouterDefaults::PyroscopeServerAddress", "") + v.SetDefault("RouterDefaults::PyroscopeCustomTags", map[string]interface{}{}) v.SetDefault("RouterDefaults::PyroscopeIncludePodTags", "true") v.SetDefault("RouterDefaults::LogLevel", "INFO") v.SetDefault("RouterDefaults::FluentdConfig::Image", "") @@ -684,6 +695,7 @@ func setDefaultValues(v *viper.Viper) { v.SetDefault("Pyroscope::Enabled", "false") v.SetDefault("Pyroscope::ServerAddress", "") + v.SetDefault("Pyroscope::IncludePodTags", "true") v.SetDefault("TuringEncryptionKey", "") diff --git a/api/turing/config/config_test.go b/api/turing/config/config_test.go index cbe86c4a0..7e69e734a 100644 --- a/api/turing/config/config_test.go +++ b/api/turing/config/config_test.go @@ -188,8 +188,9 @@ func TestLoad(t *testing.T) { }, PyroscopeIncludePodTags: true, }, - Otel: config.OtelConfig{SamplingRatio: 0.01}, - Sentry: sentry.Config{}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, + Pyroscope: config.PyroscopeConfig{IncludePodTags: true}, + Sentry: sentry.Config{}, ClusterConfig: config.ClusterConfig{ InClusterConfig: false, }, @@ -315,7 +316,8 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 0.01}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, + Pyroscope: config.PyroscopeConfig{IncludePodTags: true}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -489,7 +491,8 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 0.01}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, + Pyroscope: config.PyroscopeConfig{IncludePodTags: true}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, @@ -681,7 +684,8 @@ func TestLoad(t *testing.T) { OtelEnabled: true, OtelCollectorEndpoint: "http://otel-collector.example.com:4318", }, - Otel: config.OtelConfig{SamplingRatio: 0.01}, + Otel: config.OtelConfig{SamplingRatio: 0.01}, + Pyroscope: config.PyroscopeConfig{IncludePodTags: true}, Sentry: sentry.Config{ Enabled: true, Labels: map[string]string{"foo": "bar"}, diff --git a/api/turing/config/example.yaml b/api/turing/config/example.yaml index 807a1b2b1..8504ac48c 100644 --- a/api/turing/config/example.yaml +++ b/api/turing/config/example.yaml @@ -165,6 +165,11 @@ RouterDefaults: # loader; this is harmless since HTTP header names are case-insensitive. PyroscopeHTTPHeaders: Authorization: " or "Bearer "> + # Additional static tags attached to every profile, on top of the built-in + # router_name/pod_name/pod_namespace tags. If a key collides with one of those, the + # built-in value wins. Optional, omit entirely if not needed. + PyroscopeCustomTags: + team: fraud # Tag Pyroscope profiles with pod_name/pod_namespace (in addition to the always-present # router_name tag), so individual pods of a multi-replica router deployment can be told # apart. Defaults to true; set to false to opt out deployment-wide. @@ -206,6 +211,14 @@ Pyroscope: # are case-insensitive. HTTPHeaders: Authorization: " or "Bearer "> + # Additional static tags attached to every profile. Optional, omit entirely if not needed. + CustomTags: + team: fraud + # Tag Pyroscope profiles with pod_name/pod_namespace, so individual replicas of the + # Turing API deployment can be told apart. Requires the POD_NAME/POD_NAMESPACE env vars + # to be populated, e.g. via the Kubernetes downward API (turing.extraEnvs in the Helm + # chart). Defaults to true; set to false to opt out. + IncludePodTags: true # Sentry application monitoring service configuration # https://docs.sentry.io/product/sentry-basics/dsn-explainer/ diff --git a/api/turing/server/instrumentation.go b/api/turing/server/instrumentation.go index 4c98a0d78..53f7e32f3 100644 --- a/api/turing/server/instrumentation.go +++ b/api/turing/server/instrumentation.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + "os" "github.com/grafana/pyroscope-go" "go.opentelemetry.io/otel" @@ -17,6 +18,14 @@ import ( const appName = "turing-api" +// envPodName and envPodNamespace are expected to be populated via the Kubernetes downward +// API, e.g. through turing.extraEnvs in the Helm chart. They are absent when running +// outside a pod (e.g. local dev) or when not configured. +const ( + envPodName = "POD_NAME" + envPodNamespace = "POD_NAMESPACE" +) + // initTracer initializes the global OpenTelemetry tracer provider for api, exporting // spans via OTLP HTTP, and returns its shutdown function. When cfg.Enabled is false, // it returns a no-op shutdown function and leaves the OTel globals untouched. The @@ -72,6 +81,7 @@ func initProfiler(cfg config.PyroscopeConfig) (*pyroscope.Profiler, error) { ApplicationName: appName, ServerAddress: cfg.ServerAddress, HTTPHeaders: cfg.HTTPHeaders, + Tags: buildTags(cfg), ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, @@ -82,3 +92,24 @@ func initProfiler(cfg config.PyroscopeConfig) (*pyroscope.Profiler, error) { }, }) } + +// buildTags returns the static Pyroscope tags for this process: cfg.CustomTags first, then +// pod_name/pod_namespace -- when cfg.IncludePodTags is true -- for whichever of the +// corresponding downward API env vars are set, to distinguish individual replicas of the +// Turing API deployment. pod_name/pod_namespace are applied last, so they always win over +// a colliding custom tag key. +func buildTags(cfg config.PyroscopeConfig) map[string]string { + tags := make(map[string]string, len(cfg.CustomTags)+2) + for k, v := range cfg.CustomTags { + tags[k] = v + } + if cfg.IncludePodTags { + if podName := os.Getenv(envPodName); podName != "" { + tags["pod_name"] = podName + } + if podNamespace := os.Getenv(envPodNamespace); podNamespace != "" { + tags["pod_namespace"] = podNamespace + } + } + return tags +} diff --git a/api/turing/server/instrumentation_test.go b/api/turing/server/instrumentation_test.go index 53e54bb14..576f40891 100644 --- a/api/turing/server/instrumentation_test.go +++ b/api/turing/server/instrumentation_test.go @@ -55,6 +55,17 @@ func TestInitProfiler_EnabledWithHTTPHeaders(t *testing.T) { defer func() { _ = profiler.Stop() }() } +func TestInitProfiler_EnabledWithCustomTags(t *testing.T) { + profiler, err := initProfiler(config.PyroscopeConfig{ + Enabled: true, + ServerAddress: "http://localhost:4040", + CustomTags: map[string]string{"team": "fraud"}, + }) + require.NoError(t, err) + require.NotNil(t, profiler) + defer func() { _ = profiler.Stop() }() +} + func TestInitProfiler_EnabledEmptyServerAddress(t *testing.T) { profiler, err := initProfiler(config.PyroscopeConfig{ Enabled: true, @@ -63,3 +74,41 @@ func TestInitProfiler_EnabledEmptyServerAddress(t *testing.T) { require.Error(t, err) assert.Nil(t, profiler) } + +func TestBuildTags(t *testing.T) { + t.Run("empty when pod env vars are unset and no custom tags", func(t *testing.T) { + assert.Equal(t, map[string]string{}, buildTags(config.PyroscopeConfig{IncludePodTags: true})) + }) + + t.Run("includes pod_name and pod_namespace when set and IncludePodTags is true", func(t *testing.T) { + t.Setenv(envPodName, "turing-api-abc123") + t.Setenv(envPodNamespace, "test-namespace") + + assert.Equal(t, map[string]string{ + "pod_name": "turing-api-abc123", + "pod_namespace": "test-namespace", + }, buildTags(config.PyroscopeConfig{IncludePodTags: true})) + }) + + t.Run("omits pod_name and pod_namespace when IncludePodTags is false, even if set", func(t *testing.T) { + t.Setenv(envPodName, "turing-api-abc123") + t.Setenv(envPodNamespace, "test-namespace") + + assert.Equal(t, map[string]string{}, buildTags(config.PyroscopeConfig{IncludePodTags: false})) + }) + + t.Run("merges in CustomTags", func(t *testing.T) { + assert.Equal(t, map[string]string{"team": "fraud", "env": "staging"}, buildTags(config.PyroscopeConfig{ + CustomTags: map[string]string{"team": "fraud", "env": "staging"}, + })) + }) + + t.Run("built-in pod tags win over a colliding CustomTags key", func(t *testing.T) { + t.Setenv(envPodName, "turing-api-abc123") + + assert.Equal(t, map[string]string{"pod_name": "turing-api-abc123"}, buildTags(config.PyroscopeConfig{ + IncludePodTags: true, + CustomTags: map[string]string{"pod_name": "should-be-overridden"}, + })) + }) +} diff --git a/engines/router/missionctl/config/config.go b/engines/router/missionctl/config/config.go index 6f6105dcc..37d0a24bd 100644 --- a/engines/router/missionctl/config/config.go +++ b/engines/router/missionctl/config/config.go @@ -142,6 +142,11 @@ type PyroscopeConfig struct { // HTTPHeaders are attached to every profile push request, e.g. for auth // (Authorization, X-Scope-OrgID, ...). Optional. HTTPHeaders map[string]string `split_words:"true"` + // CustomTags are additional static tags attached to every profile, on top of the + // always-present router_name tag (and pod_name/pod_namespace, when IncludePodTags is + // true). If a key collides with one of those built-in tags, the built-in value wins. + // Optional. + CustomTags map[string]string `split_words:"true"` // IncludePodTags controls whether profiles are additionally tagged with pod_name/ // pod_namespace (from the POD_NAME/POD_NAMESPACE downward API env vars), on top of the // always-present router_name tag. Defaults to true. diff --git a/engines/router/missionctl/instrumentation/profiling/profiling.go b/engines/router/missionctl/instrumentation/profiling/profiling.go index 1ed6e3aa7..ed26769d5 100644 --- a/engines/router/missionctl/instrumentation/profiling/profiling.go +++ b/engines/router/missionctl/instrumentation/profiling/profiling.go @@ -23,7 +23,9 @@ const ( // tags populated from the POD_NAME and POD_NAMESPACE downward API env vars, to distinguish // individual pods within a multi-replica router deployment. The pod tags are also omitted // when those env vars are unset, so as not to report noisy empty-string tags outside a real -// pod. Returns a nil profiler and nil error when profiling is disabled or cfg is nil. +// pod. cfg.CustomTags are merged in on top of those, though router_name/pod_name/ +// pod_namespace always win on key collision. Returns a nil profiler and nil error when +// profiling is disabled or cfg is nil. // pyroscope.Start does not itself error on an empty ServerAddress -- it happily constructs a // client that fails silently on every upload -- so an empty address is rejected explicitly // here instead. @@ -39,7 +41,7 @@ func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, ApplicationName: applicationName, ServerAddress: cfg.ServerAddress, HTTPHeaders: cfg.HTTPHeaders, - Tags: buildTags(routerName, cfg.IncludePodTags), + Tags: buildTags(routerName, cfg.CustomTags, cfg.IncludePodTags), ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, @@ -51,11 +53,16 @@ func Start(routerName string, cfg *config.PyroscopeConfig) (*pyroscope.Profiler, }) } -// buildTags returns the static Pyroscope tags for this process: router_name always, plus -// pod_name/pod_namespace -- when includePodTags is true -- for whichever of the -// corresponding downward API env vars are set. -func buildTags(routerName string, includePodTags bool) map[string]string { - tags := map[string]string{"router_name": routerName} +// buildTags returns the static Pyroscope tags for this process: customTags first, then +// router_name always, plus pod_name/pod_namespace -- when includePodTags is true -- for +// whichever of the corresponding downward API env vars are set. router_name/pod_name/ +// pod_namespace are applied last, so they always win over a colliding customTags key. +func buildTags(routerName string, customTags map[string]string, includePodTags bool) map[string]string { + tags := make(map[string]string, len(customTags)+3) + for k, v := range customTags { + tags[k] = v + } + tags["router_name"] = routerName if includePodTags { if podName := os.Getenv(envPodName); podName != "" { tags["pod_name"] = podName diff --git a/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go b/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go index 7275246e5..3a9dfbcce 100644 --- a/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go +++ b/engines/router/missionctl/instrumentation/profiling/profiling_internal_test.go @@ -8,7 +8,7 @@ import ( func TestBuildTags(t *testing.T) { t.Run("only router_name when pod env vars are unset", func(t *testing.T) { - assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", true)) + assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", nil, true)) }) t.Run("includes pod_name and pod_namespace when set and includePodTags is true", func(t *testing.T) { @@ -19,13 +19,33 @@ func TestBuildTags(t *testing.T) { "router_name": "test-router", "pod_name": "test-router-abc123", "pod_namespace": "test-namespace", - }, buildTags("test-router", true)) + }, buildTags("test-router", nil, true)) }) t.Run("omits pod_name and pod_namespace when includePodTags is false, even if set", func(t *testing.T) { t.Setenv(envPodName, "test-router-abc123") t.Setenv(envPodNamespace, "test-namespace") - assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", false)) + assert.Equal(t, map[string]string{"router_name": "test-router"}, buildTags("test-router", nil, false)) + }) + + t.Run("merges in customTags", func(t *testing.T) { + assert.Equal(t, map[string]string{ + "router_name": "test-router", + "team": "fraud", + "env": "staging", + }, buildTags("test-router", map[string]string{"team": "fraud", "env": "staging"}, true)) + }) + + t.Run("built-in tags win over a colliding customTags key", func(t *testing.T) { + t.Setenv(envPodName, "test-router-abc123") + + assert.Equal(t, map[string]string{ + "router_name": "test-router", + "pod_name": "test-router-abc123", + }, buildTags("test-router", map[string]string{ + "router_name": "should-be-overridden", + "pod_name": "should-also-be-overridden", + }, true)) }) }