diff --git a/cmd/otelcorecol/builder-config.yaml b/cmd/otelcorecol/builder-config.yaml index 3ee02d24d7d7..ea62de395a13 100644 --- a/cmd/otelcorecol/builder-config.yaml +++ b/cmd/otelcorecol/builder-config.yaml @@ -83,6 +83,7 @@ replaces: - go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities - go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware - go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + - go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter - go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest - go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension - go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension diff --git a/cmd/otelcorecol/go.mod b/cmd/otelcorecol/go.mod index b7659b17ee64..b6fd2cf5c0e9 100644 --- a/cmd/otelcorecol/go.mod +++ b/cmd/otelcorecol/go.mod @@ -4,7 +4,7 @@ module go.opentelemetry.io/collector/cmd/otelcorecol go 1.23.0 -toolchain go1.23.10 +toolchain go1.24.1 require ( go.opentelemetry.io/collector/component v1.34.0 @@ -111,7 +111,9 @@ require ( go.opentelemetry.io/collector/exporter/xexporter v0.128.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensioncapabilities v0.128.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/extension/extensiontest v0.128.0 // indirect go.opentelemetry.io/collector/extension/xextension v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect @@ -164,6 +166,7 @@ require ( golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect @@ -265,6 +268,8 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../ext replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest replace go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension diff --git a/cmd/otelcorecol/go.sum b/cmd/otelcorecol/go.sum index c3fe9958afd3..40edabe16cdc 100644 --- a/cmd/otelcorecol/go.sum +++ b/cmd/otelcorecol/go.sum @@ -215,6 +215,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/config/configgrpc/go.mod b/config/configgrpc/go.mod index 93abdb6efa45..0f09db026ebd 100644 --- a/config/configgrpc/go.mod +++ b/config/configgrpc/go.mod @@ -17,7 +17,7 @@ require ( go.opentelemetry.io/collector/extension v1.34.0 go.opentelemetry.io/collector/extension/extensionauth v1.34.0 go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.128.0 - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 go.opentelemetry.io/collector/pdata v1.34.0 go.opentelemetry.io/collector/pdata/testdata v0.128.0 @@ -44,6 +44,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/consumer v1.34.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect go.opentelemetry.io/collector/pdata/pprofile v0.128.0 // indirect @@ -59,6 +62,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -105,3 +109,7 @@ replace go.opentelemetry.io/collector/pipeline => ../../pipeline replace go.opentelemetry.io/collector/featuregate => ../../featuregate replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer diff --git a/config/configgrpc/go.sum b/config/configgrpc/go.sum index cef347986839..f785b3d2c61c 100644 --- a/config/configgrpc/go.sum +++ b/config/configgrpc/go.sum @@ -107,6 +107,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/config/confighttp/go.mod b/config/confighttp/go.mod index a0a353bc810e..cf1b4704f5b2 100644 --- a/config/confighttp/go.mod +++ b/config/confighttp/go.mod @@ -19,7 +19,7 @@ require ( go.opentelemetry.io/collector/extension v1.34.0 go.opentelemetry.io/collector/extension/extensionauth v1.34.0 go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.128.0 - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 go.opentelemetry.io/collector/featuregate v1.34.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 @@ -40,10 +40,17 @@ require ( github.com/google/go-tpm v0.9.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/consumer v1.34.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect go.opentelemetry.io/collector/pdata v1.34.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect @@ -54,6 +61,7 @@ require ( golang.org/x/crypto v0.38.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect @@ -72,6 +80,8 @@ replace go.opentelemetry.io/collector/config/configtls => ../configtls replace go.opentelemetry.io/collector/extension => ../../extension +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware @@ -95,3 +105,7 @@ replace go.opentelemetry.io/collector/pipeline => ../../pipeline replace go.opentelemetry.io/collector/featuregate => ../../featuregate replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile diff --git a/config/confighttp/go.sum b/config/confighttp/go.sum index 86063a825b28..244f79bdd3c7 100644 --- a/config/confighttp/go.sum +++ b/config/confighttp/go.sum @@ -1,3 +1,4 @@ +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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -25,6 +26,7 @@ github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= @@ -39,6 +41,7 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -51,6 +54,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -106,6 +111,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/config/confighttp/xconfighttp/go.mod b/config/confighttp/xconfighttp/go.mod index e0e65d2690cb..7f7ed8c0401f 100644 --- a/config/confighttp/xconfighttp/go.mod +++ b/config/confighttp/xconfighttp/go.mod @@ -23,7 +23,10 @@ require ( github.com/google/go-tpm v0.9.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect @@ -35,11 +38,17 @@ require ( go.opentelemetry.io/collector/config/configmiddleware v0.128.0 // indirect go.opentelemetry.io/collector/config/configopaque v1.34.0 // indirect go.opentelemetry.io/collector/config/configtls v1.34.0 // indirect + go.opentelemetry.io/collector/consumer v1.34.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect go.opentelemetry.io/collector/pdata v1.34.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect @@ -51,6 +60,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect @@ -94,3 +104,9 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../../ replace go.opentelemetry.io/collector/config/configmiddleware => ../../configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../../extension/extensionlimiter + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile diff --git a/config/confighttp/xconfighttp/go.sum b/config/confighttp/xconfighttp/go.sum index 86063a825b28..244f79bdd3c7 100644 --- a/config/confighttp/xconfighttp/go.sum +++ b/config/confighttp/xconfighttp/go.sum @@ -1,3 +1,4 @@ +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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -25,6 +26,7 @@ github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= @@ -39,6 +41,7 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -51,6 +54,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -106,6 +111,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/config/configmiddleware/configmiddleware.go b/config/configmiddleware/configmiddleware.go index 831ba7766fec..b8150bea82e7 100644 --- a/config/configmiddleware/configmiddleware.go +++ b/config/configmiddleware/configmiddleware.go @@ -11,9 +11,13 @@ import ( "fmt" "net/http" + "go.uber.org/multierr" "google.golang.org/grpc" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension/extensionlimiter" + grpclimiter "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper/grpc" + httplimiter "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper/http" "go.opentelemetry.io/collector/extension/extensionmiddleware" ) @@ -33,6 +37,10 @@ type Config struct { _ struct{} } +func resolveFailed(id component.ID) error { + return fmt.Errorf("failed to resolve middleware %q: %w", id, errMiddlewareNotFound) +} + // GetHTTPClientRoundTripper attempts to select the appropriate // extensionmiddleware.HTTPClient from the map of extensions, and // returns the HTTP client wrapper function. If a middleware is not @@ -43,9 +51,16 @@ func (m Config) GetHTTPClientRoundTripper(_ context.Context, extensions map[comp if client, ok := ext.(extensionmiddleware.HTTPClient); ok { return client.GetHTTPRoundTripper, nil } + if limiter, ok := ext.(extensionlimiter.BaseLimiterProvider); ok { + limiter, err := httplimiter.NewClientLimiter(limiter) + if err != nil { + return nil, err + } + return limiter.GetHTTPRoundTripper, nil + } return nil, errNotHTTPClient } - return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound) + return nil, resolveFailed(m.ID) } // GetHTTPServerHandler attempts to select the appropriate @@ -58,10 +73,17 @@ func (m Config) GetHTTPServerHandler(_ context.Context, extensions map[component if server, ok := ext.(extensionmiddleware.HTTPServer); ok { return server.GetHTTPHandler, nil } + if limiter, ok := ext.(extensionlimiter.BaseLimiterProvider); ok { + limiter, err := httplimiter.NewServerLimiter(limiter) + if err != nil { + return nil, err + } + return limiter.GetHTTPHandler, nil + } return nil, errNotHTTPServer } - return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound) + return nil, resolveFailed(m.ID) } // GetGRPCClientOptions attempts to select the appropriate @@ -73,9 +95,16 @@ func (m Config) GetGRPCClientOptions(_ context.Context, extensions map[component if client, ok := ext.(extensionmiddleware.GRPCClient); ok { return client.GetGRPCClientOptions() } + if limiter, ok := ext.(extensionlimiter.BaseLimiterProvider); ok { + lim, err := grpclimiter.NewClientLimiter(limiter) + if err != nil { + return nil, err + } + return lim.GetGRPCClientOptions() + } return nil, errNotGRPCClient } - return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound) + return nil, resolveFailed(m.ID) } // GetGRPCServerOptions attempts to select the appropriate @@ -87,8 +116,38 @@ func (m Config) GetGRPCServerOptions(_ context.Context, extensions map[component if server, ok := ext.(extensionmiddleware.GRPCServer); ok { return server.GetGRPCServerOptions() } + if limiter, ok := ext.(extensionlimiter.BaseLimiterProvider); ok { + lim, err := grpclimiter.NewServerLimiter(limiter) + if err != nil { + return nil, err + } + return lim.GetGRPCServerOptions() + } return nil, errNotGRPCServer } - return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound) + return nil, resolveFailed(m.ID) +} + +// GetBaseLimiters gets a list of basic limiters. These can be +// upgraded to any kind of limiter, subject to restrictions documented +// in that extension interface, using limiterhelper.MiddlewaresToLimiterProvider. +func GetBaseLimiters(host component.Host, cfgs []Config) ([]extensionlimiter.BaseLimiterProvider, error) { + var err error + var lims []extensionlimiter.BaseLimiterProvider + all := host.GetExtensions() + for _, m := range cfgs { + ext, ok := all[m.ID] + if !ok { + err = multierr.Append(err, resolveFailed(m.ID)) + continue + } + if lim, ok := ext.(extensionlimiter.BaseLimiterProvider); ok { + lims = append(lims, lim) + } else { + // Note: In this case, we skip the middleware + // that is not a limiter. + } + } + return lims, err } diff --git a/config/configmiddleware/go.mod b/config/configmiddleware/go.mod index afec289462a4..4a420a518656 100644 --- a/config/configmiddleware/go.mod +++ b/config/configmiddleware/go.mod @@ -6,8 +6,10 @@ require ( github.com/stretchr/testify v1.10.0 go.opentelemetry.io/collector/component v1.34.0 go.opentelemetry.io/collector/extension v1.34.0 - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 + go.uber.org/multierr v1.11.0 google.golang.org/grpc v1.73.0 ) @@ -18,22 +20,28 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/consumer v1.34.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect go.opentelemetry.io/collector/pdata v1.34.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect go.opentelemetry.io/otel/sdk v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.24.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -54,3 +62,11 @@ replace go.opentelemetry.io/collector/pipeline => ../../pipeline replace go.opentelemetry.io/collector/featuregate => ../../featuregate replace go.opentelemetry.io/collector/extension => ../../extension + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile + +replace go.opentelemetry.io/collector/consumer => ../../consumer diff --git a/config/configmiddleware/go.sum b/config/configmiddleware/go.sum index f5ab1e98e7b0..58543183a819 100644 --- a/config/configmiddleware/go.sum +++ b/config/configmiddleware/go.sum @@ -1,3 +1,4 @@ +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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -11,20 +12,30 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -76,6 +87,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/exporter/otlpexporter/go.mod b/exporter/otlpexporter/go.mod index 89af38e5509c..ead5ca6c7abe 100644 --- a/exporter/otlpexporter/go.mod +++ b/exporter/otlpexporter/go.mod @@ -65,7 +65,9 @@ require ( go.opentelemetry.io/collector/consumer/xconsumer v0.128.0 // indirect go.opentelemetry.io/collector/extension v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/extension/xextension v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect @@ -87,6 +89,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) @@ -131,8 +134,6 @@ replace go.opentelemetry.io/collector/client => ../../client replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry -replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver @@ -175,3 +176,7 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../ext replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter diff --git a/exporter/otlpexporter/go.sum b/exporter/otlpexporter/go.sum index 18835a6ddf1b..884425faf8b5 100644 --- a/exporter/otlpexporter/go.sum +++ b/exporter/otlpexporter/go.sum @@ -124,6 +124,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/exporter/otlphttpexporter/go.mod b/exporter/otlphttpexporter/go.mod index 0f1e6a907127..efd74d6331ce 100644 --- a/exporter/otlphttpexporter/go.mod +++ b/exporter/otlphttpexporter/go.mod @@ -65,7 +65,9 @@ require ( go.opentelemetry.io/collector/consumer/xconsumer v0.128.0 // indirect go.opentelemetry.io/collector/extension v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/extension/xextension v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect @@ -87,6 +89,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) @@ -129,8 +132,6 @@ replace go.opentelemetry.io/collector/consumer => ../../consumer replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry -replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/client => ../../client @@ -173,3 +174,7 @@ replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/co replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter diff --git a/exporter/otlphttpexporter/go.sum b/exporter/otlphttpexporter/go.sum index 209d57e36089..aa4f47950e3d 100644 --- a/exporter/otlphttpexporter/go.sum +++ b/exporter/otlphttpexporter/go.sum @@ -128,6 +128,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/extension/extensionlimiter/Makefile b/extension/extensionlimiter/Makefile new file mode 100644 index 000000000000..ded7a36092dc --- /dev/null +++ b/extension/extensionlimiter/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/extension/extensionlimiter/README.md b/extension/extensionlimiter/README.md new file mode 100644 index 000000000000..3ea622f66fc4 --- /dev/null +++ b/extension/extensionlimiter/README.md @@ -0,0 +1,480 @@ +# OpenTelemetry Collector Extension Limiter Package + +**Document status: development** + +The `extensionlimiter` package provides interfaces for limiting +pipelines in the OpenTelemetry Collector, enabling control over data +flow and resource usage through extensions which are configured +through middleware and/or directly by pipeline components. + +## Overview + +This package defines three foundational limiter **kinds**, each with +similar but distinct interfaces. A limiter extension is either a +simple checker for "saturation" (defined below), or it extends the +simple checker with a weight-based interface: + +- **Saturation Checker**: Makes a simple yes/no decision without a weight + parameter, typically to stop new work in an emergency. +- **Rate Limiter**: Controls time-based limits over weights such as + bytes or items per second. +- **Resource Limiter**: Controls physical limits over weights such as + concurrent requests or active memory in use. + +For the two weight-based limiters, requests are quantified with an +integer value and identified by a **weight key** indicating the type +of quantity being measured and limited. There are currently four +weight keys with a standard definition: + +1. Network bytes (compressed) +2. Request count +3. Request items +4. Request bytes (compressed) + +The foundational interfaces are non-blocking, and each calling +convention is different. The various limiter kinds are unified +through a `LimiterWrapper` interface, which simplifies consumers in +many cases by providing a consistent `LimitCall` interface for each +limiter kind using a synchronous callback. Limiter wrappers provide an +abstraction over the details of requesting the limit, blocking the +caller temporarily (considering deadline), and making the call. + +There are circumstances where the kind of limiter matters. For +example, in current middleware, the network bytes weight key can be +measured through a `grpc.StatsHandler` or an `io.ReadCloser`, and in +both cases the resource (e.g., byte slice, pdata object) remains in +use after the method returns. These callers can apply rate limit and +basic limits, but they cannot apply resource limits. + +The kind of limiter matters in other situations where program control +flow does not permit the use of a wrapper, especially as needed to +maintain back-pressure in a pipeline. In a streaming asynchronous +receiver (e.g., +[otelarrowreceiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/otelarrowreceiver/README.md)), +for example, a limiter can slow the arrival of new data by stalling a +response, it means synchronously waiting for the limit and +asynchronously processing the request. + +A limiter is defined as **saturated** when a limit is completely +overloaded for at least one weight, generally it means callers should +deny new requests. All limiter extensions implement the basic limiter +interface, and callers are expected to check for saturation by +invoking `CheckSaturation` before making individual requests with the limiter. + +Whereas the basic limiter's `CheckSaturation` method indicates only +saturation, the rate and resource limiter interfaces both return a +`Reservation`. While the details are slightly different, the +reservation generally has two features: + +- a mechanism to wait for the limit +- a mechanism to cancel or release the request. + +Each kind of limiter have corresponding **provider** interfaces that +return a specific limiter instance based on a weight key. Components +are expected to initialize limiters during startup, through limiter +extension providers (which may produce configuration errors). + +All limiter extensions: + +- MUST implement the `SaturationCheckerProvider` interface +- MUST NOT implement both the `ResourceLimiterProvider` and the `RateLimiterProvider` interfaces + +The `limiterhelper` package contains features for composing limiters +as well as foundational rate and resource limiter implementations. +The `limiterhelper/http` and `limiterhelper/grpc` packages provide +connectors allowing limiters to act as specific kinds of +middleware. Limiters are automatically initialized as middleware via +`configmiddleware`. The original garbage-collector state-based +limiter can be found in +[`../memorylimiterextension`](../memorylimiterextension/README.md). + +## Recommendations + +For processors, exporters, and sometimes receivers, the easiest way to +integrate with any kind of limiter is to use the a consumer wrapper +function (e.g. `NewLimitedLogs`). These helper methods check for +saturation and then apply multiple weight keys in sequence. + +At a lower level, a simple way to integrate with any kind of limiter +is to use the `LimiterWrapper` interface with its callback-based +approach. + +Multi-limiter adapters are available for all provider interfaces via +`MultipleProvider`. + +For blocking access to rate and resource limiters without wrapper +constraints, use `NewBlockingRateLimiter` or `NewBlockingResourceLimiter`. + +In cases where control flow is not request scoped (e.g., in middleware +measuring network bytes), use a `RateLimiter` interface. If the +extension is a basic limiter in this scenario, use the +`SaturationCheckerToRateLimiterProvider` adapter. Callers MUST NOT +configure a resource limiter for a caller that is restricted to the +`RateLimiter` interface; this configuration SHOULD fail at startup or +during component validation. + +In cases where due to control flow a wrapper interface cannot be used, +as long as the caller is able to arrange for a `Release` function to +be called at the proper time, then any kind of limiter can be applied +in the form of a `ResourceLimiter`. If the extension is a basic or +rate limiter in this scenario, use the `SaturationCheckerToResourceLimiterProvider` +or `RateToResourceLimiterProvider` adapters. + +Middleware configuration typically automates the configuration of +network bytes and request count weight keys relatively early in a +pipeline. Receivers are responsible for limiting request items and +memory size through one of the available helpers. + +Processors can apply limiters for specific reasons, for example to +apply limits in data-dependent ways. Exporters can apply limiters for +the same reasons, for example to apply limits in destination-dependent +ways. + +### Limiter blocking and failing + +Limiters implementations are not expected to block. The `RateLimiter` +and `ResourceLimiter` interfaces return reservations instead, +informing the caller how they can wait on their own and allowing them +to cancel the request if they return early. + +Limiter implementations SHOULD consider the context deadline when they +block. If the deadline is likely to expire before the limit becomes +available, they should return an error instead. Blocking adapters are +provided for callers including the `LimiterWrapper`. + +### Limiter saturation + +Rate and resource limiter providers have a `GetSaturationChecker` method to +provide a `SaturationChecker`, featuring a `CheckSaturation` method which is made +available for applications to test when any limit is fully +saturated that would eventually deny the request. + +The `SaturationChecker` is consulted at least once and applies to all weight +keys. Because a `SaturationChecker` can be consulted more than once by a +receiver and/or middleware, it is possible for requests to be denied +over the saturation of limits they were already granted. Users should +configure external load balancers and/or horizontal scaling policies +to avoid cases of limiter saturation. + +### Limit before or after use + +It is sometimes possible to request a limit before it is actually +used. As an example, consider a protocol using a compressed payload, +such that the receiver knows how much memory will be allocated before +the fact. In this case the receiver can request the limit before using +it, but this will not always be the case. Generally, prefer to limit +before use, but either way be consistent. + +When using the low-level interfaces directly, limits SHOULD be applied +before creating new concurrent work. + +### Built-in limiters + +#### MemoryLimiter + +The `memorylimiterextension` is a `SaturationCheckerProvider` that makes its +decisions using memory statistics from the garbage collector. This +logic was traditionally included in the `memorylimiterprocessor`, +however receiver integration with limiter extensions is preferred. + +#### RateLimiter + +A built-in helper implementation of the RateLimiter interface is +provided, based on `golang.org/x/time/rate.Limter`. These underlying +rate limiters are parameterized by two numbers: + +- `limit` (float64): the maximum frequency of weight-units per second +- `burst` (uint64): the "burst" value of the Token-bucket algorithm. + +The rate limiter is saturated when there is no burst available. + +#### ResourceLimiter + +A built-in helper implementation of the ResourceLimiter interface is +provided, based on a bounded queue with LIFO behavior. These +underlying resource limiters are parameterized by two numbers: + +- `request` (uint64): the maximum of concurrent resource value admitted +- `waiting` (uint64): the maximum of concurrent resource value permitted to wait + +The resource limiter is saturated when the sum of current `request` +and `waiting` values exceed the sum of their maximum values. + +### Examples + +#### OTLP receiver + +Limiters applied through middleware are an implementation detail, +simply configure them using `configgrpc` or `confighttp`. For the +OTLP receiver (e.g., with two `ratelimiter` extensions): + +```yaml +extensions: + ratelimiter/limit_for_grpc: + # rate limiter settings for gRPC + ratelimiter/limit_for_grpc: + # rate limiter settings for HTTP + +receivers: + otlp: + protocols: + grpc: + middlewares: + - ratelimiter/limit_for_grpc + http: + middlewares: + - ratelimiter/limit_for_http +``` + +Note that the OTLP receiver specifically supports multiple protocols +with separate middleware configurations, thus it configures limiters +for request items and memory size on a protocol-by-protocol basis. + +#### HTTP metrics scraper + +A HTTP pull-based receiver can implement a basic limited scraper loop +as follows. The HTTP client config object's `middlewares` field +automatically configures network bytes and request count limits: + +```yaml +receivers: + scraper: + http: + middlewares: + - ratelimiter/scraper +``` + +Limiter extensions are derived from a host, a middlewares list, and a +list of weight keys. When middleware is configurable at the factory +level, it may be added via `receiver.NewFactory` using +`receiver.WithLimiters(getLimiters)`: + +```golang +func NewFactory() receiver.Factory { + return xreceiver.NewFactory( + metadata.Type, + createDefaultConfig, + xreceiver.WithMetrics(createMetrics, metadata.MetricsStability), + xreceiver.WithLimiters(getLimiters), + ) +} +``` + +Here, `getLimiters` is a function to get the effective +`[]configmiddleware.Config` and derive pipeline consumers using +`limiterhelper` adapters. + +To acquire a limiter, use `MiddlewaresToLimiterProvider` to +obtain a combined limiter wrapper around the input `nextMetrics` +consumer. It will pass `StandardNotMiddlewareKeys()` indicating to +apply request items and memory size: + +```golang + // Extract limiter extensions from host and list of middleware. + providers, err := configmiddleware.GetSaturationCheckers( + host, cfg.Middlewares) + if err != nil { ... } + + // Extract a multi-limiter from the provider + s.limiterProvider, err = limiterhelper.MultipleProvider(providers) + if err != nil { ... } + + // Here get a limiter-wrapped pipeline and a combination of weight-specific + // limiters for CheckSaturation() functionality. + limitKeys := extensionlimiter.StandardNotMiddlewareKeys() + s.nextMetrics, err = limiterhelper.NewLimitedMetrics( + s.nextMetrics, limitKeys, s.limiterProvider) + if err != nil { ... } + + // Compute the saturation checker from the middlewares for use before scrapes. + s.limiter, err := s.limiterProvider.GetSaturationChecker(host, middlewares) + if err != nil { ... } +``` + +In the scraper loop, use `CheckSaturation` before starting a scrape: + +```golang +func (s *scraper) scrapeOnce(ctx context.Context) error { + // Check if any limits are saturated. + if err := s.limiter.CheckSaturation(ctx); err != nil { + return err + } + + // Network bytes and request count limits are applied in middleware. + // before this returns: + data, err := s.getData(ctx) + if err != nil { + return err + } + + // Request items and memory size are applied in the pipeline. + return s.nextMetrics.ConsumeMetrics(ctx, data) +} +``` + +#### gRPC stream receiver + +A gRPC streaming receiver that holds memory across its allocated in +`Send()` and does not release it until after a corresponding `Recv()` +requires use of the lower-level `ResourceLimiter` interface. +The gRPC config object's `middlewares` field +automatically configures network bytes and request count limits: + +```yaml +receivers: + streamer: + grpc: + middlewares: + - ratelimiter/streamer +``` + +The receiver will check `s.limiter.CheckSaturation()` as above. In a stream, +a blocking limiter is used which blocks the stream (via +`s.requestSizeLimiter.WaitFor()`) until limit requests succeed, however +after the limit requests succeed, the receiver returns from `Send()` +to continue accepting new requests while the consumer works in a +separate goroutine. The limit will be released after the consumer +returns in this example: + +```golang +func (s *scraper) LogsStream(ctx context.Context, stream *Stream) error { + for { + // Check saturation for all limiters, all keys. + err := s.limiter.CheckSaturation(ctx) + if err != nil { ... } + + // The network bytes and request count limits are applied in middleware. + req, err := stream.Recv() + if err != nil { ... } + + // Allocate memory objects. + data, err := s.getLogs(ctx, req) + if err != nil { ... } + + // Non-blocking limiter call. + release, err := s.requestSizeLimiter.WaitFor(ctx, pdataSize(data)) + if err != nil { ... } + + // Asynchronous work starts here. + go func() { + // Request items limit is applied in the pipeline consumer + err := s.nextMetrics.ConsumeMetrics(ctx, data) + + // Release the memory. + release() + + // Reply to the caller. + stream.Send(streamResponseFromConsumerError(err)) + } + } +} +``` + +##### Data-dependent limits + +When a single unit of data contains limits that are assignable to +multiple distinct limiters, one option available to users is to split +requests and add to their context and run them concurrently through +context-dependent limiters. See +[#39199](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39199). + +Another option, shown below, is to use the non-blocking rate limiter +interface and drop data that would exceed a limit. For example, to +limit based on metadata extracted from the OpenTelemetry resource +value: + +``` +func (p *processor) limitLogs(ctx context.Context, logsData plog.Logs) (plog.Logs, extensionlimiter.ReleaseFunc, error) { + var rels extensionlimiter.ReleaseFuncs + logsData.ResourceLogs().RemoveIf(func(rl plog.ResourceLogs) bool { + md := resourceToMetadata(rl.Resource()) + reservation, err := p.limiter.ReserveRate(withMetadata(ctx, md)) + if err != nil { + return false + } + if reservation.WaitTime() > 0 { + reservation.Cancel() + return false + } + default: + return true + } + }) + if logsData.ResourceLogs().Len() == 0 { + return logsData, func() {}, processorhelper.ErrSkipProcessingData + } + return logsData, rels.Release, nil +} + +func (p *processor) ConsumeLogs(ctx context.Context, logsData plog.Logs) error { + logsData, release, err = limitLogs(ctx, logsData) + if err != nil { + return err + } + defer release() + return p.nextLogs.ConsumeLogs(ctx, logsData) +} +``` + +Here, the limiter's `ReserveRate` function does not block the caller, +allowing the processor to drop data instead. Note the call to +`RateReservation.Cancel` undoes the effect of the untaken reservation. +The same approach works for `ResourceLimiter` as well using using +`ResourceReservation`, its `Delay` channel `Release` function. + +#### Open questions + +##### Provider options + +An `Option` type has been added as a placeholder in the provider +interfaces. **NOTE: No options are implemented.** Potential options: + +- The protocol name +- The signal kind +- The caller's component ID + +Because the set of each of these is small, it is possible to +pre-compute limiter instances for the cross product of configurations. + +## Architecture + +The following diagram illustrates the core architecture of the extension limiter system, showing the relationships between interfaces, providers, helpers, and middleware integration: + +```mermaid +graph TD; + subgraph "Limiters" + SaturationChecker["SaturationChecker"] + RateLimiter["RateLimiter"] + ResourceLimiter["ResourceLimiter"] + LimiterWrapper["LimiterWrapper"] + end + + subgraph "Providers" + SaturationCheckerProvider["SaturationCheckerProvider"] + RateLimiterProvider["RateLimiterProvider"] + ResourceLimiterProvider["ResourceLimiterProvider"] + LimiterWrapperProvider["LimiterWrapperProvider"] + end + + RateLimiterProvider -.->|extends| SaturationCheckerProvider + ResourceLimiterProvider -.->|extends| SaturationCheckerProvider + LimiterWrapperProvider -.->|extends| SaturationCheckerProvider + ResourceLimiterProvider -->|substitution possible| RateLimiterProvider + LimiterWrapperProvider -->|wraps| SaturationCheckerProvider + LimiterWrapperProvider -->|wraps| RateLimiterProvider + LimiterWrapperProvider -->|wraps| ResourceLimiterProvider + + SaturationCheckerProvider -->|creates| SaturationChecker + RateLimiterProvider -->|creates| RateLimiter + ResourceLimiterProvider -->|creates| ResourceLimiter + LimiterWrapperProvider -->|creates| LimiterWrapper + + LimiterWrapper -->|wraps/implements| SaturationChecker + RateLimiter -->|implements| SaturationChecker + ResourceLimiter -->|implements| SaturationChecker + + ResourceLimiter -->|substitution possible| RateLimiter + + LimiterWrapper -->|wraps| RateLimiter + LimiterWrapper -->|wraps| ResourceLimiter +``` diff --git a/extension/extensionlimiter/checker.go b/extension/extensionlimiter/checker.go new file mode 100644 index 000000000000..8115f793a448 --- /dev/null +++ b/extension/extensionlimiter/checker.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package extensionlimiter // import "go.opentelemetry.io/collector/extension/extensionlimiter" + +import ( + "context" +) + +// SaturationChecker is for checking when a limit is saturated. This can be +// called prior to the start of work to check for limiter saturation. +type SaturationChecker interface { + // CheckSaturation is a request to apply a hard limit. If this + // returns non-nil, the caller must not begin new work in this + // context. + CheckSaturation(context.Context) error +} + +// CheckSaturationFunc is a functional way to build CheckSaturation functions. +type CheckSaturationFunc func(context.Context) error + +// A CheckSaturation function is a complete SaturationChecker. +var _ SaturationChecker = CheckSaturationFunc(nil) + +// CheckSaturation implements SaturationChecker. +func (f CheckSaturationFunc) CheckSaturation(ctx context.Context) error { + if f == nil { + return nil + } + return f(ctx) +} + +// SaturationCheckerProvider is an interface to obtain checkers for a group of +// weight keys. +type SaturationCheckerProvider interface { + // GetSaturationChecker returns a checker for a group of weight keys. + GetSaturationChecker(...Option) (SaturationChecker, error) +} + +// GetSaturationCheckerFunc is a functional way to construct GetSaturationChecker +// functions, used in limiter providers. +type GetSaturationCheckerFunc func(...Option) (SaturationChecker, error) + +// SaturationChecker implements SaturationCheckerProvider. +func (f GetSaturationCheckerFunc) GetSaturationChecker(opts ...Option) (SaturationChecker, error) { + if f == nil { + return nil, nil + } + return f(opts...) +} diff --git a/extension/extensionlimiter/go.mod b/extension/extensionlimiter/go.mod new file mode 100644 index 000000000000..1200678ea51a --- /dev/null +++ b/extension/extensionlimiter/go.mod @@ -0,0 +1,65 @@ +module go.opentelemetry.io/collector/extension/extensionlimiter + +go 1.23.0 + +require ( + go.opentelemetry.io/collector/consumer v1.34.0 + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.126.0 + go.opentelemetry.io/collector/pdata v1.34.0 + go.opentelemetry.io/collector/pdata/pprofile v0.128.0 + go.uber.org/multierr v1.11.0 + golang.org/x/time v0.11.0 + google.golang.org/grpc v1.73.0 +) + +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/component v1.34.0 // indirect + go.opentelemetry.io/collector/extension v1.34.0 // indirect + go.opentelemetry.io/collector/featuregate v1.34.0 // indirect + go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect + go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/log v0.12.2 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) + +replace go.opentelemetry.io/collector/consumer => ../../consumer + +replace go.opentelemetry.io/collector/featuregate => ../../featuregate + +replace go.opentelemetry.io/collector/component => ../../component + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/pdata => ../../pdata + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile + +replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware + +replace go.opentelemetry.io/collector/pipeline => ../../pipeline + +replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/extension => ../ + +replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry diff --git a/extension/extensionlimiter/go.sum b/extension/extensionlimiter/go.sum new file mode 100644 index 000000000000..08fd617af4de --- /dev/null +++ b/extension/extensionlimiter/go.sum @@ -0,0 +1,101 @@ +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 h1:u2E32P7j1a/gRgZDWhIXC+Shd4rLg70mnE7QLI/Ssnw= +go.opentelemetry.io/contrib/bridges/otelzap v0.11.0/go.mod h1:pJPCLM8gzX4ASqLlyAXjHBEYxgbOQJ/9bidWxD6PEPQ= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/log/logtest v0.0.0-20250526142609-aa5bd0e64989 h1:4JF7oY9CcHrPGfBLijDcXZyCzGckVEyOjuat5ktmQRg= +go.opentelemetry.io/otel/log/logtest v0.0.0-20250526142609-aa5bd0e64989/go.mod h1:NToOxLDCS1tXDSB2dIj44H9xGPOpKr0csIN+gnuihv4= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extension/extensionlimiter/limiterhelper/base.go b/extension/extensionlimiter/limiterhelper/base.go new file mode 100644 index 000000000000..6328dac41f52 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/base.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "context" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +// BaseToRateLimiterProvider allows a base limiter to act as a rate +// limiter. +func BaseToRateLimiterProvider(blimp extensionlimiter.SaturationCheckerProvider) extensionlimiter.RateLimiterProvider { + return struct { + extensionlimiter.GetSaturationCheckerFunc + extensionlimiter.GetRateLimiterFunc + }{ + blimp.GetSaturationChecker, + func(_ extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (extensionlimiter.RateLimiter, error) { + base, err := blimp.GetSaturationChecker(opts...) + if err != nil { + return nil, err + } + return extensionlimiter.ReserveRateFunc( + func(ctx context.Context, _ int) (extensionlimiter.RateReservation, error) { + if err := base.CheckSaturation(ctx); err != nil { + return nil, err + } + return struct { + extensionlimiter.WaitTimeFunc + extensionlimiter.CancelFunc + }{}, nil + }), nil + }, + } +} + +// BaseToResourceLimiterProvider allows a base limiter to act as a +// resource limiter. +func BaseToResourceLimiterProvider(blimp extensionlimiter.SaturationCheckerProvider) extensionlimiter.ResourceLimiterProvider { + return struct { + extensionlimiter.GetSaturationCheckerFunc + extensionlimiter.GetResourceLimiterFunc + }{ + blimp.GetSaturationChecker, + func(_ extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (extensionlimiter.ResourceLimiter, error) { + base, err := blimp.GetSaturationChecker(opts...) + if err != nil { + return nil, err + } + return extensionlimiter.ReserveResourceFunc( + func(ctx context.Context, _ int) (extensionlimiter.ResourceReservation, error) { + if err := base.CheckSaturation(ctx); err != nil { + return nil, err + } + return struct { + extensionlimiter.DelayFunc + extensionlimiter.ReleaseFunc + }{}, nil + }), nil + }, + } +} diff --git a/extension/extensionlimiter/limiterhelper/consumer.go b/extension/extensionlimiter/limiterhelper/consumer.go new file mode 100644 index 000000000000..77a4b8c780bf --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/consumer.go @@ -0,0 +1,221 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "context" + "slices" + + "go.uber.org/multierr" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/xconsumer" + "go.opentelemetry.io/collector/extension/extensionlimiter" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/pdata/pprofile" + "go.opentelemetry.io/collector/pdata/ptrace" +) + +// Traits object interface is generalized by P the pipeline data type +// (e.g., ptrace.Traces) and C the consumer type (e.g., +// consumer.Traces) +type traits[P, C any] interface { + // itemCount is SpanCount(), DataPointCount(), or LogRecordCount(). + itemCount(P) int + // requestBytes uses the appropriate protobuf Bytesr as a proxy + // for memory used. + requestSize(data P) int + // consume calls the appropriate consumer method (e.g., ConsumeTraces) + consume(ctx context.Context, data P, next C) error + // create is a functional constructor the consumer type (e.g., consumer.NewTraces) + create(func(ctx context.Context, data P) error, ...consumer.Option) (C, error) +} + +// Traces traits + +type traceTraits struct{} + +func (traceTraits) itemCount(data ptrace.Traces) int { + return data.SpanCount() +} + +func (traceTraits) requestSize(data ptrace.Traces) int { + var sizer ptrace.MarshalSizer + return sizer.TracesSize(data) +} + +func (traceTraits) create(next func(ctx context.Context, data ptrace.Traces) error, opts ...consumer.Option) (consumer.Traces, error) { + return consumer.NewTraces(next, opts...) +} + +func (traceTraits) consume(ctx context.Context, data ptrace.Traces, next consumer.Traces) error { + return next.ConsumeTraces(ctx, data) +} + +// Metrics traits + +type metricTraits struct{} + +func (metricTraits) itemCount(data pmetric.Metrics) int { + return data.DataPointCount() +} + +func (metricTraits) requestSize(data pmetric.Metrics) int { + var sizer pmetric.MarshalSizer + return sizer.MetricsSize(data) +} + +func (metricTraits) create(next func(ctx context.Context, data pmetric.Metrics) error, opts ...consumer.Option) (consumer.Metrics, error) { + return consumer.NewMetrics(next, opts...) +} + +func (metricTraits) consume(ctx context.Context, data pmetric.Metrics, next consumer.Metrics) error { + return next.ConsumeMetrics(ctx, data) +} + +// Logs traits + +type logTraits struct{} + +func (logTraits) itemCount(data plog.Logs) int { + return data.LogRecordCount() +} + +func (logTraits) requestSize(data plog.Logs) int { + var sizer plog.MarshalSizer + return sizer.LogsSize(data) +} + +func (logTraits) create(next func(ctx context.Context, data plog.Logs) error, opts ...consumer.Option) (consumer.Logs, error) { + return consumer.NewLogs(next, opts...) +} + +func (logTraits) consume(ctx context.Context, data plog.Logs, next consumer.Logs) error { + return next.ConsumeLogs(ctx, data) +} + +// Profiles traits + +type profileTraits struct{} + +func (profileTraits) itemCount(data pprofile.Profiles) int { + return data.SampleCount() +} + +func (profileTraits) requestSize(data pprofile.Profiles) int { + var sizer pprofile.MarshalSizer + return sizer.ProfilesSize(data) +} + +func (profileTraits) create(next func(ctx context.Context, data pprofile.Profiles) error, opts ...consumer.Option) (xconsumer.Profiles, error) { + return xconsumer.NewProfiles(next, opts...) +} + +func (profileTraits) consume(ctx context.Context, data pprofile.Profiles, next xconsumer.Profiles) error { + return next.ConsumeProfiles(ctx, data) +} + +// limitOne obtains a LimiterWrapper and applies a single weight limit. +func limitOne[P any, C any]( + next C, + keys []extensionlimiter.WeightKey, + provider LimiterWrapperProvider, + m traits[P, C], + key extensionlimiter.WeightKey, + opts []consumer.Option, + quantify func(P) int, +) (C, error) { + if !slices.Contains(keys, key) { + return next, nil + } + lim, err := provider.GetLimiterWrapper(key) + if err != nil { + return next, err + } + if lim == nil { + return next, nil + } + return m.create(func(ctx context.Context, data P) error { + return lim.LimitCall(ctx, quantify(data), func(ctx context.Context) error { + return m.consume(ctx, data, next) + }) + }, opts...) +} + +// applySaturationChecker gets a SaturationChecker and wraps the pipeline in a CheckSaturation +// check. +func applySaturationChecker[P any, C any]( + next C, + provider LimiterWrapperProvider, + m traits[P, C], + opts []consumer.Option, +) (C, error) { + ck, err := provider.GetSaturationChecker() + if err != nil { + return next, err + } + if ck == nil { + return next, nil + } + return m.create(func(ctx context.Context, data P) error { + if err := ck.CheckSaturation(ctx); err != nil { + return err + } + return m.consume(ctx, data, next) + }, opts...) +} + +// newLimited is signal-generic limiting logic. +func newLimited[P any, C any]( + next C, + keys []extensionlimiter.WeightKey, + provider LimiterWrapperProvider, + m traits[P, C], + opts ...consumer.Option, +) (C, error) { + if provider == nil { + return next, nil + } + var err1, err2, err3, err4 error + // Note: reverse order of evaluation cost => least-cost applied first. + next, err1 = limitOne(next, keys, provider, m, extensionlimiter.WeightKeyRequestBytes, opts, + func(data P) int { + return m.requestSize(data) + }) + next, err2 = limitOne(next, keys, provider, m, extensionlimiter.WeightKeyRequestItems, opts, + func(data P) int { + return m.itemCount(data) + }) + next, err3 = limitOne(next, keys, provider, m, extensionlimiter.WeightKeyRequestCount, opts, + func(_ P) int { + return 1 + }) + next, err4 = applySaturationChecker(next, provider, m, opts) + return next, multierr.Append(err1, multierr.Append(err2, multierr.Append(err3, err4))) +} + +// NewLimitedTraces applies a limiter using the provider over keys before calling next. +func NewLimitedTraces(next consumer.Traces, keys []extensionlimiter.WeightKey, provider LimiterWrapperProvider) (consumer.Traces, error) { + return newLimited(next, keys, provider, traceTraits{}, + consumer.WithCapabilities(next.Capabilities())) +} + +// NewLimitedLogs applies a limiter using the provider over keys before calling next. +func NewLimitedLogs(next consumer.Logs, keys []extensionlimiter.WeightKey, provider LimiterWrapperProvider) (consumer.Logs, error) { + return newLimited(next, keys, provider, logTraits{}, + consumer.WithCapabilities(next.Capabilities())) +} + +// NewLimitedMetrics applies a limiter using the provider over keys before calling next. +func NewLimitedMetrics(next consumer.Metrics, keys []extensionlimiter.WeightKey, provider LimiterWrapperProvider) (consumer.Metrics, error) { + return newLimited(next, keys, provider, metricTraits{}, + consumer.WithCapabilities(next.Capabilities())) +} + +// NewLimitedProfiles applies a limiter using the provider over keys before calling next. +func NewLimitedProfiles(next xconsumer.Profiles, keys []extensionlimiter.WeightKey, provider LimiterWrapperProvider) (xconsumer.Profiles, error) { + return newLimited(next, keys, provider, profileTraits{}, + consumer.WithCapabilities(next.Capabilities())) +} diff --git a/extension/extensionlimiter/limiterhelper/grpc/grpclimiter.go b/extension/extensionlimiter/limiterhelper/grpc/grpclimiter.go new file mode 100644 index 000000000000..ce644e26a7a9 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/grpc/grpclimiter.go @@ -0,0 +1,261 @@ +package grpclimiter + +import ( + "context" + "sync" + + "go.uber.org/multierr" + "google.golang.org/grpc" + "google.golang.org/grpc/stats" + + "go.opentelemetry.io/collector/extension/extensionlimiter" + "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + "go.opentelemetry.io/collector/extension/extensionmiddleware" +) + +// contextKey is a private type for context keys +type contextKey int + +const ( + rateLimiterErrorKey contextKey = iota +) + +// rateLimiterErrorState holds error state,allowing rate limiters to return +// errors in the correct context. +type rateLimiterErrorState struct { + mu sync.Mutex + err error +} + +// checkRateLimiterError checks if there's a prior rate limiter error in the context. +func checkRateLimiterError(ctx context.Context) error { + if state, ok := ctx.Value(rateLimiterErrorKey).(*rateLimiterErrorState); ok { + state.mu.Lock() + defer state.mu.Unlock() + return state.err + } + return nil +} + +// setRateLimiterError sets a rate limiter error in the context +func setRateLimiterError(ctx context.Context, err error) { + if state, ok := ctx.Value(rateLimiterErrorKey).(*rateLimiterErrorState); ok { + state.mu.Lock() + defer state.mu.Unlock() + state.err = multierr.Append(state.err, err) + } +} + +func NewClientLimiter(ext extensionlimiter.SaturationCheckerProvider) (extensionmiddleware.GRPCClient, error) { + wp, err1 := limiterhelper.MiddlewareToLimiterWrapperProvider(ext) + rp, err2 := limiterhelper.MiddlewareToRateLimiterProvider(ext) + if err := multierr.Append(err1, err2); err != nil { + return nil, err + } + requestLimiter, err3 := wp.GetLimiterWrapper(extensionlimiter.WeightKeyRequestCount) + bytesLimiter, err4 := rp.GetRateLimiter(extensionlimiter.WeightKeyNetworkBytes) + if err := multierr.Append(err3, err4); err != nil { + return nil, err + } + + var gopts []grpc.DialOption + if requestLimiter != nil { + gopts = append(gopts, grpc.WithUnaryInterceptor( + func( + ctxIn context.Context, + method string, + req, reply any, + cc *grpc.ClientConn, + invoker grpc.UnaryInvoker, + opts ...grpc.CallOption, + ) error { + return requestLimiter.LimitCall( + ctxIn, 1, + func(ctx context.Context) error { + if err := checkRateLimiterError(ctx); err != nil { + return err + } + return invoker(ctx, method, req, reply, cc, opts...) + }) + }), + grpc.WithStreamInterceptor( + func( + ctxIn context.Context, + desc *grpc.StreamDesc, + cc *grpc.ClientConn, + method string, + streamer grpc.Streamer, + opts ...grpc.CallOption, + ) (grpc.ClientStream, error) { + cstream, err := streamer(ctxIn, desc, cc, method, opts...) + if err != nil { + return nil, err + } + return wrapClientStream(cstream, method, requestLimiter), nil + }), + ) + } + if bytesLimiter != nil { + gopts = append(gopts, grpc.WithStatsHandler( + &limiterStatsHandler{ + limiter: limiterhelper.NewBlockingRateLimiter(bytesLimiter), + isClient: true, + })) + } + return extensionmiddleware.GetGRPCClientOptionsFunc(func() ([]grpc.DialOption, error) { + return gopts, nil + }), nil +} + +func NewServerLimiter(ext extensionlimiter.SaturationCheckerProvider) (extensionmiddleware.GRPCServer, error) { + wp, err1 := limiterhelper.MiddlewareToLimiterWrapperProvider(ext) + rp, err2 := limiterhelper.MiddlewareToRateLimiterProvider(ext) + if err := multierr.Append(err1, err2); err != nil { + return nil, err + } + requestLimiter, err3 := wp.GetLimiterWrapper(extensionlimiter.WeightKeyRequestCount) + bytesLimiter, err4 := rp.GetRateLimiter(extensionlimiter.WeightKeyNetworkBytes) + if err := multierr.Append(err3, err4); err != nil { + return nil, err + } + + var gopts []grpc.ServerOption + if requestLimiter != nil { + gopts = append(gopts, grpc.ChainUnaryInterceptor( + func( + ctxIn context.Context, + req any, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, + ) (any, error) { + var resp any + err := requestLimiter.LimitCall( + ctxIn, 1, + func(ctx context.Context) error { + if err := checkRateLimiterError(ctx); err != nil { + return err + } + var err error + resp, err = handler(ctx, req) + return err + }) + return resp, err + }), grpc.ChainStreamInterceptor( + func( + srv interface{}, + ss grpc.ServerStream, + info *grpc.StreamServerInfo, + handler grpc.StreamHandler, + ) error { + return handler(srv, wrapServerStream(ss, info, requestLimiter)) + }), + ) + } + if bytesLimiter != nil { + gopts = append(gopts, grpc.StatsHandler( + &limiterStatsHandler{ + limiter: limiterhelper.NewBlockingRateLimiter(bytesLimiter), + isClient: false, + })) + } + + return extensionmiddleware.GetGRPCServerOptionsFunc(func() ([]grpc.ServerOption, error) { + return gopts, nil + }), nil +} + +// limiterStatsHandler implements the stats.Handler interface for rate limiting. +type limiterStatsHandler struct { + limiter limiterhelper.BlockingRateLimiter + isClient bool +} + +func (h *limiterStatsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { + return ctx +} + +func (h *limiterStatsHandler) HandleRPC(ctx context.Context, s stats.RPCStats) { + // Check for payload messages to apply network byte rate limiting + var wireBytes int + switch payload := s.(type) { + case *stats.InPayload: + // Server receiving payload (or client receiving response) + if !h.isClient { + wireBytes = payload.WireLength + } + case *stats.OutPayload: + // Client sending payload (or server sending response) + if h.isClient { + wireBytes = payload.WireLength + } + default: + // Not a payload message, no rate limiting to apply + return + } + + if wireBytes == 0 { + return + } + // Apply rate limiting based on network bytes + if err := h.limiter.WaitFor(ctx, wireBytes); err != nil { + setRateLimiterError(ctx, err) + } +} + +func (h *limiterStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { + // Create a new context with rate limiter error state + return context.WithValue(ctx, rateLimiterErrorKey, &rateLimiterErrorState{}) +} + +func (h *limiterStatsHandler) HandleConn(ctx context.Context, _ stats.ConnStats) { +} + +type serverStream struct { + grpc.ServerStream + limiter limiterhelper.LimiterWrapper +} + +// RecvMsg applies rate limiting to server stream message receiving. +func (s *serverStream) RecvMsg(m any) error { + return s.limiter.LimitCall( + s.Context(), 1, + func(ctx context.Context) error { + if err := checkRateLimiterError(ctx); err != nil { + return err + } + return s.ServerStream.RecvMsg(m) + }) +} + +// wrapServerStream wraps a gRPC server stream with rate limiting. +func wrapServerStream(ss grpc.ServerStream, _ *grpc.StreamServerInfo, limiter limiterhelper.LimiterWrapper) grpc.ServerStream { + return &serverStream{ + ServerStream: ss, + limiter: limiter, + } +} + +type clientStream struct { + grpc.ClientStream + limiter limiterhelper.LimiterWrapper +} + +// SendMsg applies rate limiting to client stream message sending. +func (s *clientStream) SendMsg(m any) error { + return s.limiter.LimitCall( + s.Context(), 1, + func(ctx context.Context) error { + if err := checkRateLimiterError(ctx); err != nil { + return err + } + return s.ClientStream.SendMsg(m) + }) +} + +// wrapClientStream wraps a gRPC client stream with rate limiting. +func wrapClientStream(cs grpc.ClientStream, _ string, limiter limiterhelper.LimiterWrapper) grpc.ClientStream { + return &clientStream{ + ClientStream: cs, + limiter: limiter, + } +} diff --git a/extension/extensionlimiter/limiterhelper/http/httplimiter.go b/extension/extensionlimiter/limiterhelper/http/httplimiter.go new file mode 100644 index 000000000000..9cdd2a357db1 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/http/httplimiter.go @@ -0,0 +1,122 @@ +package httplimiter + +import ( + "context" + "io" + "net/http" + + "go.uber.org/multierr" + + "go.opentelemetry.io/collector/extension/extensionlimiter" + "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + "go.opentelemetry.io/collector/extension/extensionmiddleware" + "go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest" +) + +func NewClientLimiter(ext extensionlimiter.SaturationCheckerProvider) (extensionmiddleware.HTTPClient, error) { + wp, err1 := limiterhelper.MiddlewareToLimiterWrapperProvider(ext) + rp, err2 := limiterhelper.MiddlewareToRateLimiterProvider(ext) + if err := multierr.Append(err1, err2); err != nil { + return nil, err + } + requestLimiter, err3 := wp.GetLimiterWrapper(extensionlimiter.WeightKeyRequestCount) + bytesLimiter, err4 := rp.GetRateLimiter(extensionlimiter.WeightKeyNetworkBytes) + if err := multierr.Append(err3, err4); err != nil { + return nil, err + } + + roundtrip := func(base http.RoundTripper) (http.RoundTripper, error) { + return extensionmiddlewaretest.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { + if requestLimiter == nil && bytesLimiter == nil { + // If no limiters are configured, return the base round tripper + return base.RoundTrip(req) + } + + var resp *http.Response + err := requestLimiter.LimitCall( + req.Context(), + 1, + func(_ context.Context) error { + if bytesLimiter != nil && req.Body != nil && req.Body != http.NoBody { + // If bytes are limited, create a limited ReadCloser body. + req.Body = &rateLimitedBody{ + body: req.Body, + limiter: limiterhelper.NewBlockingRateLimiter(bytesLimiter), + ctx: req.Context(), + } + } + var err error + resp, err = base.RoundTrip(req) + return err + }) + return resp, err + }), multierr.Append(err1, err2) + } + return extensionmiddleware.GetHTTPRoundTripperFunc(roundtrip), nil +} + +// rateLimitedBody wraps an http.Request.Body to track bytes and call the rate limiter +type rateLimitedBody struct { + body io.ReadCloser + limiter limiterhelper.BlockingRateLimiter + ctx context.Context +} + +var _ io.ReadCloser = &rateLimitedBody{} + +// Read implements io.Reader interface, limiting bytes as they are read +func (rb *rateLimitedBody) Read(p []byte) (n int, err error) { + n, err = rb.body.Read(p) + if n > 0 { + // Apply rate limiting based on network bytes after they are read + limitErr := rb.limiter.WaitFor(rb.ctx, n) + if limitErr != nil { + // If the rate limiter rejects the bytes, return the error + return n, limitErr // TODO: How to return HTTP 429? + } + } + return n, err +} + +// Close implements io.Closer interface +func (rb *rateLimitedBody) Close() error { + return rb.body.Close() +} + +func NewServerLimiter(ext extensionlimiter.SaturationCheckerProvider) (extensionmiddleware.HTTPServer, error) { + wp, err1 := limiterhelper.MiddlewareToLimiterWrapperProvider(ext) + rp, err2 := limiterhelper.MiddlewareToRateLimiterProvider(ext) + if err := multierr.Append(err1, err2); err != nil { + return nil, err + } + requestLimiter, err3 := wp.GetLimiterWrapper(extensionlimiter.WeightKeyRequestCount) + bytesLimiter, err4 := rp.GetRateLimiter(extensionlimiter.WeightKeyNetworkBytes) + if err := multierr.Append(err3, err4); err != nil { + return nil, err + } + + handler := func(base http.Handler) (http.Handler, error) { + if requestLimiter == nil && bytesLimiter == nil { + return base, nil + } + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _ = requestLimiter.LimitCall( + req.Context(), + 1, + func(_ context.Context) error { + if bytesLimiter != nil && req.Body != nil && req.Body != http.NoBody { + // If bytes are limited, create a limited ReadCloser body. + req.Body = &rateLimitedBody{ + body: req.Body, + limiter: limiterhelper.NewBlockingRateLimiter(bytesLimiter), + ctx: req.Context(), + } + } + base.ServeHTTP(w, req) + return nil + }) + }), nil + } + return extensionmiddleware.GetHTTPHandlerFunc(handler), nil +} diff --git a/extension/extensionlimiter/limiterhelper/middleware.go b/extension/extensionlimiter/limiterhelper/middleware.go new file mode 100644 index 000000000000..717fb64b1a9b --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/middleware.go @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "errors" + + "go.uber.org/multierr" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +var ( + ErrNotALimiter = errors.New("middleware is not a limiter") + ErrNotARateLimiter = errors.New("middleware cannot implement rate limiter") + ErrLimiterConflict = errors.New("limiter implements both rate and resource-limiters") +) + +// middlewareCheck applies consistency checks and returns a valid +// limiter extension of any known kind. +func middlewareCheck(ext extensionlimiter.SaturationCheckerProvider) (extensionlimiter.SaturationCheckerProvider, error) { + _, isResource := ext.(extensionlimiter.ResourceLimiterProvider) + _, isRate := ext.(extensionlimiter.RateLimiterProvider) + + if isResource && isRate { + return nil, ErrLimiterConflict + } + return ext, nil +} + +// MultipleProvider constructs a combined limiter from an ordered list +// of middlewares. This constructor ignores middleware configs that +// are not limiters. +func MultipleProvider(exts []extensionlimiter.SaturationCheckerProvider) (MultiLimiterProvider, error) { + var retErr error + var providers MultiLimiterProvider + for _, ext := range exts { + base, err := middlewareCheck(ext) + retErr = multierr.Append(retErr, err) + providers = append(providers, base) + } + return providers, retErr +} + +// MiddlewareToSaturationCheckerProvider returns a base limiter provider +// from middleware. Returns a package-level error if the middleware +// does not implement exactly one of the limiter interfaces (i.e., +// rate or resource). +func MiddlewareToSaturationCheckerProvider(ext extensionlimiter.SaturationCheckerProvider) (extensionlimiter.SaturationCheckerProvider, error) { + return getMiddleware( + ext, + identity[extensionlimiter.SaturationCheckerProvider], + baseProvider[extensionlimiter.RateLimiterProvider], + baseProvider[extensionlimiter.ResourceLimiterProvider], + ) +} + +// MiddlewareToLimiterWrapperProvider returns a limiter wrapper +// provider from middleware. Returns a package-level error if the +// middleware does not implement exactly one of the limiter +// interfaces (i.e., rate or resource). +func MiddlewareToLimiterWrapperProvider(ext extensionlimiter.SaturationCheckerProvider) (LimiterWrapperProvider, error) { + return getMiddleware( + ext, + nilError(BaseToLimiterWrapperProvider), + nilError(RateToLimiterWrapperProvider), + nilError(ResourceToLimiterWrapperProvider), + ) +} + +// MiddlewareToRateLimiterProvider allows a base limiter provider to +// act as a rate limiter provider. This encodes the fact that a +// resource limiter extension cannot be adapted to a rate limiter +// interface. Returns a package-level error if the middleware does not +// implement exactly one of the limiter interfaces (i.e., rate or +// resource). +func MiddlewareToRateLimiterProvider(ext extensionlimiter.SaturationCheckerProvider) (extensionlimiter.RateLimiterProvider, error) { + return getMiddleware( + ext, + nilError(BaseToRateLimiterProvider), + identity[extensionlimiter.RateLimiterProvider], + resourceToRateLimiterError, + ) +} + +// MiddlewareToResourceLimiterProvider allows a base limiter provider +// to act as a resource provider. This enforces that a resource +// limiter extension cannot be adapted to a resource limiter +// interface. Returns a package-level error if the middleware does not +// implement exactly one of the limiter interfaces (i.e., rate or +// resource). +func MiddlewareToResourceLimiterProvider(ext extensionlimiter.SaturationCheckerProvider) (extensionlimiter.ResourceLimiterProvider, error) { + return getMiddleware( + ext, + nilError(BaseToResourceLimiterProvider), + nilError(RateToResourceLimiterProvider), + identity[extensionlimiter.ResourceLimiterProvider], + ) +} + +// getProvider invokes getProvider if any kind of limiter is detected +// for the given host and middleware configuration. +func getMiddleware[Out any]( + ext extensionlimiter.SaturationCheckerProvider, + base func(extensionlimiter.SaturationCheckerProvider) (Out, error), + rate func(extensionlimiter.RateLimiterProvider) (Out, error), + resource func(extensionlimiter.ResourceLimiterProvider) (Out, error), +) (Out, error) { + var out Out + ext, err := middlewareCheck(ext) + if err != nil { + return out, err + } + return getProvider(ext, base, rate, resource) +} + +// getProvider handles each limiter kind, case-by-case, for building +// limiters in a functional style. +func getProvider[Out any]( + ext extensionlimiter.SaturationCheckerProvider, + base func(extensionlimiter.SaturationCheckerProvider) (Out, error), + rate func(extensionlimiter.RateLimiterProvider) (Out, error), + resource func(extensionlimiter.ResourceLimiterProvider) (Out, error), +) (Out, error) { + if lim, ok := ext.(extensionlimiter.ResourceLimiterProvider); ok { + return resource(lim) + } + if lim, ok := ext.(extensionlimiter.RateLimiterProvider); ok { + return rate(lim) + } + if lim, ok := ext.(extensionlimiter.SaturationCheckerProvider); ok { + return base(lim) + } + var out Out + return out, ErrNotALimiter +} + +// identity is a pass-through for the correct provider type. +func identity[T any](lim T) (T, error) { + return lim, nil +} + +// baseProvider returns a base limiter type from any limiter. +func baseProvider[T extensionlimiter.SaturationCheckerProvider](p T) (extensionlimiter.SaturationCheckerProvider, error) { + return p, nil +} + +// nilError converts an infallible constructor to return a nil error. +func nilError[S, T any](f func(S) T) func(S) (T, error) { + return func(s S) (T, error) { return f(s), nil } +} diff --git a/extension/extensionlimiter/limiterhelper/multi.go b/extension/extensionlimiter/limiterhelper/multi.go new file mode 100644 index 000000000000..88621ca20289 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/multi.go @@ -0,0 +1,239 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "context" + "time" + + "go.uber.org/multierr" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +// MultiLimiterProvider combines multiple limiter providers of all +// kinds. It automatically applies the adapters in this package to +// implement the desired provider interface from the base object. +type MultiLimiterProvider []extensionlimiter.SaturationCheckerProvider + +var _ LimiterWrapperProvider = MultiLimiterProvider{} +var _ extensionlimiter.RateLimiterProvider = MultiLimiterProvider{} +var _ extensionlimiter.ResourceLimiterProvider = MultiLimiterProvider{} +var _ extensionlimiter.SaturationCheckerProvider = MultiLimiterProvider{} + +// GetSaturationChecker implements LimiterWrapperProvider. The combined +// limiter is saturated when any of the base limiers are. +func (ps MultiLimiterProvider) GetSaturationChecker( + opts ...extensionlimiter.Option, +) (extensionlimiter.SaturationChecker, error) { + return getMultiLimiter(ps, + identity[extensionlimiter.SaturationCheckerProvider], + baseProvider[extensionlimiter.RateLimiterProvider], + baseProvider[extensionlimiter.ResourceLimiterProvider], + func(p extensionlimiter.SaturationCheckerProvider) (extensionlimiter.SaturationChecker, error) { + return p.GetSaturationChecker(opts...) + }, + combineSaturationCheckers) +} + +// GetLimiterWrapper implements LimiterWrapperProvider, applies the +// wrappers in a nested sequence. +func (ps MultiLimiterProvider) GetLimiterWrapper( + key extensionlimiter.WeightKey, + opts ...extensionlimiter.Option) (LimiterWrapper, error) { + return getMultiLimiter(ps, + nilError(BaseToLimiterWrapperProvider), + nilError(RateToLimiterWrapperProvider), + nilError(ResourceToLimiterWrapperProvider), + func(p LimiterWrapperProvider) (LimiterWrapper, error) { + return p.GetLimiterWrapper(key, opts...) + }, + combineLimiterWrappers) +} + +// GetResourceLimiter implements ResourceLimiterProvider, applies the +// request to all limiters (unless any are saturated). +func (ps MultiLimiterProvider) GetResourceLimiter( + key extensionlimiter.WeightKey, + opts ...extensionlimiter.Option, +) (extensionlimiter.ResourceLimiter, error) { + return getMultiLimiter(ps, + nilError(BaseToResourceLimiterProvider), + nilError(RateToResourceLimiterProvider), + identity[extensionlimiter.ResourceLimiterProvider], + func(p extensionlimiter.ResourceLimiterProvider) (extensionlimiter.ResourceLimiter, error) { + return p.GetResourceLimiter(key, opts...) + }, + combineResourceLimiters) +} + +// GetRateLimiter implements RateLimiterProvider, applies the request +// to all limiters, returns the maximum wait time. +func (ps MultiLimiterProvider) GetRateLimiter( + key extensionlimiter.WeightKey, + opts ...extensionlimiter.Option, +) (extensionlimiter.RateLimiter, error) { + return getMultiLimiter(ps, + nilError(BaseToRateLimiterProvider), + identity[extensionlimiter.RateLimiterProvider], + resourceToRateLimiterError, + func(p extensionlimiter.RateLimiterProvider) (extensionlimiter.RateLimiter, error) { + return p.GetRateLimiter(key, opts...) + }, + combineRateLimiters) +} + +// combineSaturationCheckers combines >= 2 base limiters. +func combineSaturationCheckers(lims []extensionlimiter.SaturationChecker) extensionlimiter.SaturationChecker { + return extensionlimiter.CheckSaturationFunc(func(ctx context.Context) error { + var err error + for _, lim := range lims { + if lim == nil { + continue + } + err = multierr.Append(err, lim.CheckSaturation(ctx)) + } + return err + }) +} + +// combineLimiterWrappers combines >= 2 limiter wrappers (recursive). +func combineLimiterWrappers(lims []LimiterWrapper) LimiterWrapper { + if len(lims) == 1 { + return lims[0] + } + return sequenceLimiterWrappers(lims[0], combineLimiterWrappers(lims[1:])) +} + +// sequenceLimiterWrappers combines 2 limiter wrappers. +func sequenceLimiterWrappers(first, second LimiterWrapper) LimiterWrapper { + return LimiterWrapperFunc(func(ctx context.Context, value int, call func(ctx context.Context) error) error { + return first.LimitCall(ctx, value, func(ctx context.Context) error { + return second.LimitCall(ctx, value, call) + }) + }) +} + +// combineRateLimiters combines >=2 resource limiters. +func combineResourceLimiters(lims []extensionlimiter.ResourceLimiter) extensionlimiter.ResourceLimiter { + reserve := func(ctx context.Context, value int) (extensionlimiter.ResourceReservation, error) { + var err error + rsvs := make([]extensionlimiter.ResourceReservation, 0, len(lims)) + for _, lim := range lims { + rsv, err := lim.ReserveResource(ctx, value) + err = multierr.Append(err, err) + if rsv != nil { + rsvs = append(rsvs, rsv) + } + } + release := func() { + for _, rsv := range rsvs { + rsv.Release() + } + } + if err != nil { + release() + return nil, err + } + ch := make(chan struct{}) + go func() { + for _, rsv := range rsvs { + select { + case <-rsv.Delay(): + continue + case <-ctx.Done(): + return + } + } + close(ch) + return + }() + return struct { + extensionlimiter.DelayFunc + extensionlimiter.ReleaseFunc + }{ + func() <-chan struct{} { + return ch + }, + release, + }, nil + } + return extensionlimiter.ReserveResourceFunc(reserve) +} + +// combineRateLimiters combines >=2 rate limiters. +func combineRateLimiters(lims []extensionlimiter.RateLimiter) extensionlimiter.RateLimiter { + reserve := func(ctx context.Context, value int) (extensionlimiter.RateReservation, error) { + var err error + rsvs := make([]extensionlimiter.RateReservation, 0, len(lims)) + for _, lim := range lims { + rsv, err := lim.ReserveRate(ctx, value) + err = multierr.Append(err, err) + if rsv != nil { + rsvs = append(rsvs, rsv) + } + } + cancel := func() { + for _, rsv := range rsvs { + rsv.Cancel() + } + } + if err != nil { + cancel() + return nil, err + } + var wt time.Duration + for _, rsv := range rsvs { + wt = max(wt, rsv.WaitTime()) + } + return struct { + extensionlimiter.WaitTimeFunc + extensionlimiter.CancelFunc + }{ + func() time.Duration { return wt }, + cancel, + }, nil + } + return extensionlimiter.ReserveRateFunc(reserve) +} + +// getMultiLimiter configures a limiter for multiple limiter +// extensions. +func getMultiLimiter[Out any, Lim comparable]( + multi MultiLimiterProvider, + base func(extensionlimiter.SaturationCheckerProvider) (Out, error), + rate func(extensionlimiter.RateLimiterProvider) (Out, error), + resource func(extensionlimiter.ResourceLimiterProvider) (Out, error), + pfunc func(Out) (Lim, error), + combine func([]Lim) Lim, +) (nilResult Lim, _ error) { + // Note that nilResult is used in error and non-error cases to + // return a nil and not a nil with concrete type (e.g., + // extensionlimiter.SaturationCheckerProvider(nil)). + var lims []Lim + + for _, baseProvider := range multi { + provider, err := getProvider(baseProvider, base, rate, resource) + if err == nil { + return nilResult, err + } + lim, err := pfunc(provider) + if err == nil { + return nilResult, err + } + var zero Lim + if lim == zero { + continue + } + lims = append(lims, lim) + } + + if len(lims) == 0 { + return nilResult, nil + } + if len(lims) == 1 { + return lims[0], nil + } + return combine(lims), nil +} diff --git a/extension/extensionlimiter/limiterhelper/notification.go b/extension/extensionlimiter/limiterhelper/notification.go new file mode 100644 index 000000000000..c54d21a75a8c --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/notification.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper + +type notification struct { + c chan struct{} +} + +func newNotification() notification { + return notification{c: make(chan struct{})} +} + +func (n *notification) notice() { + close(n.c) +} + +func (n *notification) hasBeen() bool { + select { + case <-n.c: + return true + default: + return false + } +} + +func (n *notification) channel() <-chan struct{} { + return n.c +} diff --git a/extension/extensionlimiter/limiterhelper/rate.go b/extension/extensionlimiter/limiterhelper/rate.go new file mode 100644 index 000000000000..d00c372c640e --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/rate.go @@ -0,0 +1,169 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "context" + "errors" + "time" + + "golang.org/x/time/rate" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +var ( + ErrRateLimitExceeded = errors.New("rate limit is saturated") + ErrRateRequestTooLarge = errors.New("rate request exceeds burst size") + ErrRateRequestDeadline = errors.New("rate request exceeds context deadline") +) + +// NewRateLimiter returns an implementation of the rate-limiter +// extension based on logic from x/time/rate. +func NewRateLimiter(frequency float64, burst int) extensionlimiter.RateLimiter { + limit := rate.Limit(frequency) + limiter := rate.NewLimiter(limit, burst) + + reserve := func(ctx context.Context, value int) (extensionlimiter.RateReservation, error) { + // Check if context was canceled. + select { + case <-ctx.Done(): + return nil, context.Cause(ctx) + default: + } + + // Check for out-of-range requests. + if value > burst && limit != rate.Inf { + return nil, ErrRateRequestTooLarge + } + + // Call the non-blocking API. + rsv := limiter.ReserveN(time.Now(), value) + if !rsv.OK() { + return nil, ErrRateLimitExceeded + } + + // Compare the wait and deadline. + when := time.Now() + wait := rsv.DelayFrom(when) + if deadline, ok := ctx.Deadline(); ok { + if deadline.Sub(when) < wait { + rsv.Cancel() + return nil, ErrRateRequestDeadline + } + } + + // Return the wait time and cancel function. + return struct { + extensionlimiter.WaitTimeFunc + extensionlimiter.CancelFunc + }{ + func() time.Duration { return wait }, + rsv.Cancel, + }, nil + } + + return extensionlimiter.ReserveRateFunc(reserve) +} + +// BlockingRateLimiter wraps a RateLimiter extension in a blocking +// interface which takes the context deadline into account before +// waiting for the rate allowance. +type BlockingRateLimiter struct { + limiter extensionlimiter.RateLimiter +} + +// NewBlockingRateLimiter returns a blocking wrapper for RateLimiter +// extensions. +func NewBlockingRateLimiter(limiter extensionlimiter.RateLimiter) BlockingRateLimiter { + return BlockingRateLimiter{ + limiter: limiter, + } +} + +// WaitFor blocks the caller until the requested value is allowed by +// the limiter. +func (b BlockingRateLimiter) WaitFor(ctx context.Context, value int) error { + newTimer := func(d time.Duration) (<-chan time.Time, func() bool, func()) { + timer := time.NewTimer(d) + return timer.C, timer.Stop, func() {} + } + return b.waitFor(ctx, value, newTimer) +} + +// timerFunc is a test helper for testing the blocking rate limter. +type timerFunc func(time.Duration) (<-chan time.Time, func() bool, func()) + +// waitFor is a testable form of BlockingRateLimiter.WaitFor. +func (b BlockingRateLimiter) waitFor(ctx context.Context, value int, timer timerFunc) error { + // Reserve from the underlying limiter. + rsv, err := b.limiter.ReserveRate(ctx, value) + if err != nil { + return err + } + + // Wait, if necessary. + delay := rsv.WaitTime() + if delay == 0 { + return nil + } + + ch, stop, advance := timer(delay) + defer stop() + advance() // only has an effect when testing + select { + case <-ch: + // Proceed. Do not cancel. + return nil + + case <-ctx.Done(): + // Context was canceled before we could proceed. + rsv.Cancel() + return context.Cause(ctx) + } +} + +// RateToResourceLimiterProvider allows a rate limiter to act as a +// resource limiter. Note that the opposite direction (i.e., resource +// limter acting as rate limiter) is an invalid configuration. +func RateToResourceLimiterProvider(blimp extensionlimiter.RateLimiterProvider) extensionlimiter.ResourceLimiterProvider { + return struct { + extensionlimiter.GetSaturationCheckerFunc + extensionlimiter.GetResourceLimiterFunc + }{ + blimp.GetSaturationChecker, + func(weight extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (extensionlimiter.ResourceLimiter, error) { + rlim, err := blimp.GetRateLimiter(weight, opts...) + if err != nil { + return nil, err + } + return extensionlimiter.ReserveResourceFunc( + func(ctx context.Context, value int) (extensionlimiter.ResourceReservation, error) { + rsv, err := rlim.ReserveRate(ctx, value) + if err != nil { + return nil, err + } + cch := make(chan struct{}) + timer := time.AfterFunc(rsv.WaitTime(), func() { + close(cch) + }) + return struct { + extensionlimiter.DelayFunc + extensionlimiter.ReleaseFunc + }{ + func() <-chan struct{} { return cch }, + func() { + select { + case <-cch: + // The timer fired + default: + rsv.Cancel() + timer.Stop() + } + }, + }, nil + }), nil + }, + } +} diff --git a/extension/extensionlimiter/limiterhelper/resource.go b/extension/extensionlimiter/limiterhelper/resource.go new file mode 100644 index 000000000000..a5f7b1f86994 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/resource.go @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "container/list" + "context" + "errors" + "sync" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +var ( + // TODO: was grpccodes.ResourceExhausted + ErrResourceWaitLimit = errors.New("too much waiting data") + + // TODO: was grpccodes.InvalidArgument + ErrResourceSizeLimit = errors.New("request is too large") +) + +// NewResourceLimiter returns an implementation of the +// resource-limiter extension based on a LIFO queue. +// See this [article](https://medium.com/swlh/fifo-considered-harmful-793b76f98374) +// explaining why LIFO is preferred here. +func NewResourceLimiter(admitLimit, waitLimit uint64) extensionlimiter.ResourceLimiter { + return &boundedQueue{ + limitAdmit: admitLimit, + limitWait: waitLimit, + } +} + +var _ extensionlimiter.ResourceLimiter = &boundedQueue{} + +type boundedQueue struct { + limitAdmit uint64 + limitWait uint64 + + // lock protects currentAdmitted, currentWaiting, and waiters + lock sync.Mutex + currentAdmitted uint64 + currentWaiting uint64 + waiters *list.List // of *waiter +} + +// waiter is an item in the BoundedQueue waiters list. +type waiter struct { + notify notification + value int +} + +func (bq *boundedQueue) ReserveResource(ctx context.Context, value int) (extensionlimiter.ResourceReservation, error) { + if uint64(value) > bq.limitAdmit { + return nil, ErrResourceSizeLimit + } + + bq.lock.Lock() + defer bq.lock.Unlock() + + if bq.currentAdmitted+uint64(value) <= bq.limitAdmit { + // the fast success path. + bq.currentAdmitted += uint64(value) + return struct { + extensionlimiter.DelayFunc + extensionlimiter.ReleaseFunc + }{ + nil, // No delay + func() { + // There was never a waiter in this + // case, just release and admit waiters. + bq.lock.Lock() + defer bq.lock.Unlock() + + bq.releaseLocked(value) + }, + }, nil + } + + // since we were unable to admit, check if we can wait. + if bq.currentWaiting+uint64(value) > bq.limitWait { + return nil, ErrResourceWaitLimit + } + + // otherwise we need to wait + element := bq.addWaiterLocked(value) + waiter := element.Value.(*waiter) + + return struct { + extensionlimiter.DelayFunc + extensionlimiter.ReleaseFunc + }{ + func() <-chan struct{} { + // The caller waits for this notification + // to use the resource. + return waiter.notify.channel() + }, + func() { + // Called when the caller finishes. + bq.lock.Lock() + defer bq.lock.Unlock() + + if waiter.notify.hasBeen() { + // We were also admitted, which can happen + // concurrently with cancellation. Make sure + // to release since no one else will do it. + bq.releaseLocked(value) + } else { + // Remove ourselves from the list of waiters + // so that we can't be admitted in the future. + bq.removeWaiterLocked(value, element) + bq.admitWaitersLocked() + } + }, + }, nil +} + +func (bq *boundedQueue) admitWaitersLocked() { + for bq.waiters.Len() != 0 { + // Ensure there is enough room to admit the next waiter. + element := bq.waiters.Back() + waiter := element.Value.(*waiter) + if bq.currentAdmitted+uint64(waiter.value) > bq.limitAdmit { + // Returning means continuing to wait for the + // most recent arrival to get service by another release. + return + } + + // Release the next waiter and tell it that it has been admitted. + bq.removeWaiterLocked(waiter.value, element) + bq.currentAdmitted += uint64(waiter.value) + + waiter.notify.notice() + } +} + +func (bq *boundedQueue) addWaiterLocked(value int) *list.Element { + bq.currentWaiting += uint64(value) + return bq.waiters.PushBack(&waiter{ + value: value, + notify: newNotification(), + }) +} + +func (bq *boundedQueue) removeWaiterLocked(value int, element *list.Element) { + bq.currentWaiting -= uint64(value) + bq.waiters.Remove(element) +} + +func (bq *boundedQueue) releaseLocked(value int) { + bq.currentAdmitted -= uint64(value) + bq.admitWaitersLocked() +} + +// BlockingResourceLimiter wraps for ResourceLimiter extension in a +// blocking interface which considers the context deadline while +// waiting for the resource. +type BlockingResourceLimiter struct { + limiter extensionlimiter.ResourceLimiter +} + +// NewBlockingResourceLimiter returns a blocking wrapper for +// ResourceLimiter extensions. +func NewBlockingResourceLimiter(limiter extensionlimiter.ResourceLimiter) BlockingResourceLimiter { + return BlockingResourceLimiter{ + limiter: limiter, + } +} + +// WaitFor blocks the caller until the requested value is allowed by +// the limiter. +func (b BlockingResourceLimiter) WaitFor(ctx context.Context, value int) (extensionlimiter.ReleaseFunc, error) { + rsv, err := b.limiter.ReserveResource(ctx, value) + if err != nil { + return func() {}, err + } + select { + case <-ctx.Done(): + rsv.Release() + return func() {}, context.Cause(ctx) + case <-rsv.Delay(): + return rsv.Release, nil + } +} + +// resourceToRateLimiterError represents the impossible conversion +// from resource limiter to rate limiter. +func resourceToRateLimiterError(_ extensionlimiter.ResourceLimiterProvider) (extensionlimiter.RateLimiterProvider, error) { + return nil, ErrNotARateLimiter +} diff --git a/extension/extensionlimiter/limiterhelper/wrapper.go b/extension/extensionlimiter/limiterhelper/wrapper.go new file mode 100644 index 000000000000..2b070e8d1706 --- /dev/null +++ b/extension/extensionlimiter/limiterhelper/wrapper.go @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package limiterhelper // import "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" + +import ( + "context" + + "go.opentelemetry.io/collector/extension/extensionlimiter" +) + +// LimiterWrapperProvider follows the provider pattern for +// the LimiterWrapper type +type LimiterWrapperProvider interface { + extensionlimiter.SaturationCheckerProvider + + GetLimiterWrapper(extensionlimiter.WeightKey, ...extensionlimiter.Option) (LimiterWrapper, error) +} + +// GetLimiterWrapperFunc is an easy way to build GetLimiterWrapper functions. +type GetLimiterWrapperFunc func(extensionlimiter.WeightKey, ...extensionlimiter.Option) (LimiterWrapper, error) + +// GetLimiterWrapper implements LimiterWrapperProvider. +func (f GetLimiterWrapperFunc) GetLimiterWrapper(key extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (LimiterWrapper, error) { + if f == nil { + return LimiterWrapperFunc(nil), nil + } + return f(key, opts...) +} + +type limiterWrapper struct { + GetLimiterWrapperFunc + extensionlimiter.GetSaturationCheckerFunc +} + +var _ LimiterWrapperProvider = limiterWrapper{} + +// LimiterWrapper is a general-purpose interface for limiter consumers +// to limit resources with use of a callback. This is the simplest +// form of rate limiting interface from a callers perspective. If the +// caller is a pipeline component, consider using a consumer-oriented +// limiterhelper (e.g., limiterhelper.NewLimitedLogs) to simplify +// construction of this interface. +// +// A wrapped limiter is either a RateLimiter or ResourceLimiter +// interface. LimiterWrappers can be constructed from either of the +// underlying limiters and their corresponding providers. Usually +// configmiddleware or limiterhelper is responsible for constructing +// the correct wrapper from these two kinds of limiter; users will use +// this interface consistently. +type LimiterWrapper interface { + // LimitCall applies the limiter and with the rate or resource + // granted makes a scoped call, returning success or an error + // from either the limiter or the enclosed callback. + // + // The `call` parameter must be non-nil. + LimitCall(ctx context.Context, weight int, call func(ctx context.Context) error) error +} + +// LimiterWrapperFunc is a functional way to build LimiterWrappers. +type LimiterWrapperFunc func(context.Context, int, func(ctx context.Context) error) error + +var _ LimiterWrapper = LimiterWrapperFunc(nil) + +// LimitCall implements LimiterWrapper. +func (f LimiterWrapperFunc) LimitCall(ctx context.Context, value int, call func(ctx context.Context) error) error { + if f == nil { + return call(ctx) + } + return f(ctx, value, call) +} + +// BaseToLimiterWrapperProvider constructs a LimiterWrapperProvider +// for a rate limiter extension. +func BaseToLimiterWrapperProvider(rp extensionlimiter.SaturationCheckerProvider) LimiterWrapperProvider { + return limiterWrapper{ + GetSaturationCheckerFunc: rp.GetSaturationChecker, + GetLimiterWrapperFunc: nil, + } +} + +// ResourceToLimiterWrapperProvider constructs a +// LimiterWrapperProvider for a resource limiter extension. +func ResourceToLimiterWrapperProvider(rp extensionlimiter.ResourceLimiterProvider) LimiterWrapperProvider { + return limiterWrapper{ + GetSaturationCheckerFunc: rp.GetSaturationChecker, + GetLimiterWrapperFunc: func(key extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (LimiterWrapper, error) { + lim, err := rp.GetResourceLimiter(key, opts...) + if err != nil { + return nil, err + } + if lim == nil { + return nil, nil + } + blocking := NewBlockingResourceLimiter(lim) + return LimiterWrapperFunc(func(ctx context.Context, value int, call func(context.Context) error) error { + release, err := blocking.WaitFor(ctx, value) + if err != nil { + return err + } + defer release() + return call(ctx) + }), nil + }, + } +} + +// RateToLimiterWrapperProvider constructs a LimiterWrapperProvider +// for a rate limiter extension. +func RateToLimiterWrapperProvider(rp extensionlimiter.RateLimiterProvider) LimiterWrapperProvider { + return limiterWrapper{ + GetSaturationCheckerFunc: rp.GetSaturationChecker, + GetLimiterWrapperFunc: func(key extensionlimiter.WeightKey, opts ...extensionlimiter.Option) (LimiterWrapper, error) { + lim, err := rp.GetRateLimiter(key, opts...) + if err != nil { + return nil, err + } + if lim == nil { + return nil, nil + } + blocking := NewBlockingRateLimiter(lim) + return LimiterWrapperFunc(func(ctx context.Context, value int, call func(context.Context) error) error { + if err := blocking.WaitFor(ctx, value); err != nil { + return err + } + return call(ctx) + }), nil + }, + } +} diff --git a/extension/extensionlimiter/option.go b/extension/extensionlimiter/option.go new file mode 100644 index 000000000000..16ac275b7f5a --- /dev/null +++ b/extension/extensionlimiter/option.go @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package extensionlimiter // import "go.opentelemetry.io/collector/extension/extensionlimiter" + +// Option is passed to limiter providers. +// +// NOTE: For data-specific or tenant-specific limits we will extend +// providers with Options and add a Config type, but none are +// supported yet and this PR contains only interfaces, not need for +// options in core repository components. +type Option interface { + apply() +} diff --git a/extension/extensionlimiter/rate.go b/extension/extensionlimiter/rate.go new file mode 100644 index 000000000000..1c745849b72b --- /dev/null +++ b/extension/extensionlimiter/rate.go @@ -0,0 +1,115 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package extensionlimiter // import "go.opentelemetry.io/collector/extension/extensionlimiter" + +import ( + "context" + "time" +) + +// RateLimiterProvider is a provider for rate limiters. +// +// Limiter implementations will implement this or the +// ResourceLimiterProvider interface, but MUST not implement both. +// Limiters are covered by configmiddleware configuration, which is +// able to construct LimiterWrappers from these providers. +type RateLimiterProvider interface { + SaturationCheckerProvider + + // GetRateLimiter returns a rate limiter for a weight key. + GetRateLimiter(WeightKey, ...Option) (RateLimiter, error) +} + +// GetRateLimiterFunc is a functional way to construct GetRateLimiter +// functions. +type GetRateLimiterFunc func(WeightKey, ...Option) (RateLimiter, error) + +// RateLimiter implements RateLimiterProvider. +func (f GetRateLimiterFunc) GetRateLimiter(key WeightKey, opts ...Option) (RateLimiter, error) { + if f == nil { + return nil, nil + } + return f(key, opts...) +} + +var _ RateLimiterProvider = struct { + GetRateLimiterFunc + GetSaturationCheckerFunc +}{} + +// RateLimiter is an interface that an implementation makes available +// to apply time-based limits on quantities such as the number of +// bytes or items per second. +// +// This is a relatively low-level interface. Callers that can use a +// LimiterWrapper should choose that interface instead. This interface +// is meant for direct use only in special cases where control flow +// cannot be easily scoped to a callback, for example inside +// middleware (e.g., grpc.StatsHandler). +// +// See the README for more recommendations. +type RateLimiter interface { + // ReserveRate is modeled on pkg.go.dev/golang.org/x/time/rate#Limiter.ReserveN + // + // This is a non-blocking interface; use this interface for + // callers that cannot be blocked but will instead schedule a + // resume after Delay(). The context is provided for access to + // instrumentation and client metadata; the Context deadline + // is not used, should be considered by the caller. + ReserveRate(context.Context, int) (RateReservation, error) +} + +// A rate limiter can be made up of two functions. +var _ RateLimiter = ReserveRateFunc(nil) + +// RateReservation is modeled on pkg.go.dev/golang.org/x/time/rate#Reservation +type RateReservation interface { + // WaitTime returns the duration until this reservation may + // proceed. A typical implementation uses Reservation.DelayFrom(time.Now()), + // and callers typically/ use time.After or time.Timer to implement a delay. + WaitTime() time.Duration + + // Cancel cancels the reservation before it is used. A typical + // implementation uses Reservation.CancelAt(time.Now()). + Cancel() +} + +// ReserveRateFunc is a functional way to construct ReserveRate functions. +type ReserveRateFunc func(context.Context, int) (RateReservation, error) + +// Reserve implements part of the RateReserveer interface. +func (f ReserveRateFunc) ReserveRate(ctx context.Context, value int) (RateReservation, error) { + if f == nil { + return nil, nil + } + return f(ctx, value) +} + +// WaitTimeFunc is a functional way to construct WaitTime functions. +type WaitTimeFunc func() time.Duration + +// WaitTime implements part of Reservation. +func (f WaitTimeFunc) WaitTime() time.Duration { + if f == nil { + return 0 + } + return f.WaitTime() +} + +// CancelFunc is a functional way to construct Cancel functions. +type CancelFunc func() + +// Reserve implements part of the RateReserveer interface. +func (f CancelFunc) Cancel() { + if f == nil { + return + } + f.Cancel() +} + +// A rate limiter can be made up of three functions. +var _ RateReservation = struct { + WaitTimeFunc + CancelFunc +}{} diff --git a/extension/extensionlimiter/resource.go b/extension/extensionlimiter/resource.go new file mode 100644 index 000000000000..0662bf81225b --- /dev/null +++ b/extension/extensionlimiter/resource.go @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package extensionlimiter // import "go.opentelemetry.io/collector/extension/extensionlimiter" + +import ( + "context" +) + +// ResourceLimiterProvider is a provider for resource limiters. +// +// Limiter implementations will implement this or the +// RateLimiterProvider interface, but MUST not implement both. +// Limiters are covered by configmiddleware configuration, which +// is able to construct LimiterWrappers from these providers. +type ResourceLimiterProvider interface { + SaturationCheckerProvider + + GetResourceLimiter(WeightKey, ...Option) (ResourceLimiter, error) +} + +// GetResourceLimiterFunc is a functional way to construct +// GetResourceLimiter functions. +type GetResourceLimiterFunc func(WeightKey, ...Option) (ResourceLimiter, error) + +// GetResourceLimiter implements part of ResourceLimiterProvider. +func (f GetResourceLimiterFunc) GetResourceLimiter(key WeightKey, opts ...Option) (ResourceLimiter, error) { + if f == nil { + return nil, nil + } + return f(key, opts...) +} + +var _ ResourceLimiterProvider = struct { + GetResourceLimiterFunc + GetSaturationCheckerFunc +}{} + +// ResourceLimiter is an interface that an implementation makes +// available to apply physical limits on quantities such as the number +// of concurrent requests or amount of memory in use. +// +// This is a relatively low-level interface. Callers that can use a +// LimiterWrapper should choose that interface instead. This +// interface is meant for direct use only in special cases where +// control flow is not scoped to a callback, for example in a +// streaming receiver where a limiter might be Acquired in the body of +// Send() and released prior to a corresponding Recv() (e.g., +// OTel-Arrow receiver). +// +// See the README for more recommendations. +type ResourceLimiter interface { + // ReserveRate is modeled on pkg.go.dev/golang.org/x/time/rate#Limiter.ReserveN, + // without the time dimension. + // + // This is a non-blocking interface; use this interface for + // callers that cannot be blocked but will instead schedule a + // resume after DelayFrom(). The context is provided for + // access to instrumentation and client metadata; the Context + // deadline is not used. + ReserveResource(context.Context, int) (ResourceReservation, error) +} + +// ResourceReservation is modeled on pkg.go.dev/golang.org/x/time/rate#Reservation +// without the time dimension. +type ResourceReservation interface { + // Delay returns a channel that will be closed when the + // reservation request is granted. This resembles a context + // Done channel. + Delay() <-chan struct{} + + // Release is called after finishing with the reservation, + // whether the delay has been reached or not. + Release() +} + +var _ ResourceReservation = struct { + DelayFunc + ReleaseFunc +}{} + +// ReleaseFunc is called when resources have been released after use. +// +// RelaseFunc values are never nil values, even in the error case for +// safety. Users typically will immediately defer a call to this. +type ReleaseFunc func() + +// Release calls this function. +func (f ReleaseFunc) Release() { + if f == nil { + return + } + f() +} + +// DelayFunc returns a channel that is closed when the request is +// permitted to go ahead. +type DelayFunc func() <-chan struct{} + +// Delay calls this function. +func (f DelayFunc) Delay() <-chan struct{} { + if f == nil { + return immediateChan + } + return f() +} + +// immediateChan is a singleton channel, already closed, used when a DelayFunc is nil. +var immediateChan = func() <-chan struct{} { + ic := make(chan struct{}) + close(ic) + return ic +}() + +// ReserveResourceFunc is a functional way to construct ReserveResource interface methods. +type ReserveResourceFunc func(ctx context.Context, value int) (ResourceReservation, error) + +// ReserveResource implements a ReserveResource interface method. +func (f ReserveResourceFunc) ReserveResource(ctx context.Context, value int) (ResourceReservation, error) { + if f == nil { + return struct { + DelayFunc + ReleaseFunc + }{}, nil + } + return f(ctx, value) +} + +var _ ResourceLimiter = ReserveResourceFunc(nil) diff --git a/extension/extensionlimiter/weight.go b/extension/extensionlimiter/weight.go new file mode 100644 index 000000000000..1b2e5ce43210 --- /dev/null +++ b/extension/extensionlimiter/weight.go @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package extensionlimiter // import "go.opentelemetry.io/collector/extension/extensionlimiter" + +// WeightKey is an enum type for common rate limits. The +// StandardAllKeys, StandardMiddlewareKeys, and +// StandardNotMiddlewareKeys methods return the list of middleware +// keys that can be automatically configured through middleware and +// not. +type WeightKey string + +// Predefined weight keys for common rate limits. This is not meant +// to be a closed set, new weight keys may be added in the future, +// possibly to restrict other kinds of event (e.g., auths, retries). +// +// Providers should return errors when they do not recognize a weight +// key. +const ( + // WeightKeyNetworkBytes is for network bytes. This is + // typically used with rate limiters. + WeightKeyNetworkBytes WeightKey = "network_bytes" + + // WeightKeyRequestCount can be used to limit the rate or + // total concurrent number of requests (i.e., pipeline data + // objects). This is typically used with both rate and + // resource limiters. + WeightKeyRequestCount WeightKey = "request_count" + + // WeightKeyRequestItems can be used to limit the rate or + // total concurrent number of items (log records, metric data + // points, spans, profiles). This is typically used with both + // rate and resource limiters. + WeightKeyRequestItems WeightKey = "request_items" + + // WeightKeyRequestBytes is typically used with ResourceLimiters + // for limiting active memory usage. + WeightKeyRequestBytes WeightKey = "request_bytes" +) + +// StandardAllKeys is all the keys that can be automatically +// implemented by middleware and/or limiterhelper. +func StandardAllKeys() []WeightKey { + return []WeightKey{ + WeightKeyNetworkBytes, + WeightKeyRequestCount, + WeightKeyRequestItems, + WeightKeyRequestBytes, + } +} + +// StandardMiddlewareKeys are typically handled in middleware for +// protocols that support it. Receivers should be careful not to +// re-apply these limits, especially not to twice-limit by +// WeightKeyRequestItems. +func StandardMiddlewareKeys() []WeightKey { + return []WeightKey{ + WeightKeyNetworkBytes, + WeightKeyRequestCount, + } +} + +// StandardNotMiddlewareKeys are the keys that are typically not +// handled through middlware because they are protocol specific and +// generally easier to handle after the input has become pdata. +func StandardNotMiddlewareKeys() []WeightKey { + return []WeightKey{ + WeightKeyRequestItems, + WeightKeyRequestBytes, + } +} diff --git a/extension/memorylimiterextension/go.mod b/extension/memorylimiterextension/go.mod index 7b3cf34542e2..6fb07f741a22 100644 --- a/extension/memorylimiterextension/go.mod +++ b/extension/memorylimiterextension/go.mod @@ -8,6 +8,7 @@ require ( go.opentelemetry.io/collector/component/componenttest v0.128.0 go.opentelemetry.io/collector/confmap v1.34.0 go.opentelemetry.io/collector/extension v1.34.0 + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/extension/extensiontest v0.128.0 go.opentelemetry.io/collector/internal/memorylimiter v0.128.0 go.uber.org/goleak v1.3.0 @@ -78,3 +79,15 @@ replace go.opentelemetry.io/collector/featuregate => ../../featuregate replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry replace go.opentelemetry.io/collector/pipeline => ../../pipeline + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + +replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extensionmiddleware + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile + +replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer => ../../consumer diff --git a/extension/memorylimiterextension/memorylimiter.go b/extension/memorylimiterextension/memorylimiter.go index 4986243ce10b..62fbc62f0265 100644 --- a/extension/memorylimiterextension/memorylimiter.go +++ b/extension/memorylimiterextension/memorylimiter.go @@ -5,17 +5,25 @@ package memorylimiterextension // import "go.opentelemetry.io/collector/extensio import ( "context" + "errors" "go.uber.org/zap" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension/extensionlimiter" "go.opentelemetry.io/collector/internal/memorylimiter" ) +var ( + ErrMustRefuse = errors.New("system is near memory limit") +) + type memoryLimiterExtension struct { memLimiter *memorylimiter.MemoryLimiter } +var _ extensionlimiter.BaseLimiterProvider = &memoryLimiterExtension{} + // newMemoryLimiter returns a new memorylimiter extension. func newMemoryLimiter(cfg *Config, logger *zap.Logger) (*memoryLimiterExtension, error) { ml, err := memorylimiter.NewMemoryLimiter(cfg, logger) @@ -34,6 +42,18 @@ func (ml *memoryLimiterExtension) Shutdown(ctx context.Context) error { return ml.memLimiter.Shutdown(ctx) } +// GetBaseLimiter implements extensionlimiter.BaseLimiterProvider. +func (ml *memoryLimiterExtension) GetBaseLimiter( + opts ...extensionlimiter.Option, +) (extensionlimiter.BaseLimiter, error) { + return extensionlimiter.MustDenyFunc(func(_ context.Context) error { + if ml.MustRefuse() { + return ErrMustRefuse + } + return nil + }), nil +} + // MustRefuse returns if the caller should deny because memory has reached it's configured limits func (ml *memoryLimiterExtension) MustRefuse() bool { return ml.memLimiter.MustRefuse() diff --git a/extension/zpagesextension/go.mod b/extension/zpagesextension/go.mod index 5c7578c6dfcb..4f8f7a960d2e 100644 --- a/extension/zpagesextension/go.mod +++ b/extension/zpagesextension/go.mod @@ -34,12 +34,15 @@ require ( github.com/google/go-tpm v0.9.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect github.com/knadh/koanf/v2 v2.2.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect @@ -49,11 +52,16 @@ require ( go.opentelemetry.io/collector/config/configmiddleware v0.128.0 // indirect go.opentelemetry.io/collector/config/configopaque v1.34.0 // indirect go.opentelemetry.io/collector/config/configtls v1.34.0 // indirect + go.opentelemetry.io/collector/consumer v1.34.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/internal/telemetry v0.128.0 // indirect go.opentelemetry.io/collector/pdata v1.34.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.128.0 // indirect go.opentelemetry.io/collector/pipeline v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect @@ -66,6 +74,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect @@ -123,3 +132,9 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extens replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter + +replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile diff --git a/extension/zpagesextension/go.sum b/extension/zpagesextension/go.sum index f6897b1770d0..deba44d1f254 100644 --- a/extension/zpagesextension/go.sum +++ b/extension/zpagesextension/go.sum @@ -1,3 +1,4 @@ +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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -30,6 +31,7 @@ github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= @@ -54,6 +56,7 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -66,12 +69,16 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/pdata/pprofile v0.128.0 h1:6DEtzs/liqv/ukz2EHbC5OMaj2V6K2pzuj/LaRg2YmY= +go.opentelemetry.io/collector/pdata/pprofile v0.128.0/go.mod h1:bVVRpz+zKFf1UCCRUFqy8LvnO3tHlXKkdqW2d+Wi/iA= go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 h1:u2E32P7j1a/gRgZDWhIXC+Shd4rLg70mnE7QLI/Ssnw= go.opentelemetry.io/contrib/bridges/otelzap v0.11.0/go.mod h1:pJPCLM8gzX4ASqLlyAXjHBEYxgbOQJ/9bidWxD6PEPQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= @@ -123,6 +130,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/internal/e2e/go.mod b/internal/e2e/go.mod index 84ef9cf91d3a..f27620f145b7 100644 --- a/internal/e2e/go.mod +++ b/internal/e2e/go.mod @@ -95,7 +95,9 @@ require ( go.opentelemetry.io/collector/exporter/xexporter v0.128.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensioncapabilities v0.128.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/extension/extensiontest v0.128.0 // indirect go.opentelemetry.io/collector/extension/xextension v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect @@ -138,6 +140,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect @@ -203,8 +206,6 @@ replace go.opentelemetry.io/collector/featuregate => ../../featuregate replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry -replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/client => ../../client @@ -272,3 +273,7 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmid replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter diff --git a/internal/e2e/go.sum b/internal/e2e/go.sum index e1d7041bb589..4925878c6ac4 100644 --- a/internal/e2e/go.sum +++ b/internal/e2e/go.sum @@ -205,6 +205,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/otelcol/go.mod b/otelcol/go.mod index 1d0add278781..feb6b06571d3 100644 --- a/otelcol/go.mod +++ b/otelcol/go.mod @@ -174,8 +174,6 @@ replace go.opentelemetry.io/collector/config/configtls => ../config/configtls replace go.opentelemetry.io/collector/config/configopaque => ../config/configopaque -replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest replace go.opentelemetry.io/collector/client => ../client @@ -223,3 +221,7 @@ replace go.opentelemetry.io/collector/config/configmiddleware => ../config/confi replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extension/extensionmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../extension/extensionlimiter diff --git a/otelcol/go.sum b/otelcol/go.sum index 7fee9d72997d..6486c4ea22ae 100644 --- a/otelcol/go.sum +++ b/otelcol/go.sum @@ -207,6 +207,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/otelcol/otelcoltest/go.mod b/otelcol/otelcoltest/go.mod index 5774570ccaef..e0b0a4cf4419 100644 --- a/otelcol/otelcoltest/go.mod +++ b/otelcol/otelcoltest/go.mod @@ -114,6 +114,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.12.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect @@ -161,8 +162,6 @@ replace go.opentelemetry.io/collector/extension => ../../extension replace go.opentelemetry.io/collector/exporter => ../../exporter -replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus @@ -232,3 +231,7 @@ replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/co replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter diff --git a/otelcol/otelcoltest/go.sum b/otelcol/otelcoltest/go.sum index 7fee9d72997d..0a171b96f17e 100644 --- a/otelcol/otelcoltest/go.sum +++ b/otelcol/otelcoltest/go.sum @@ -207,6 +207,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/receiver/otlpreceiver/go.mod b/receiver/otlpreceiver/go.mod index 7003f10c17e9..af92ad2a32e2 100644 --- a/receiver/otlpreceiver/go.mod +++ b/receiver/otlpreceiver/go.mod @@ -13,6 +13,7 @@ require ( go.opentelemetry.io/collector/config/configauth v0.128.0 go.opentelemetry.io/collector/config/configgrpc v0.128.0 go.opentelemetry.io/collector/config/confighttp v0.128.0 + go.opentelemetry.io/collector/config/configmiddleware v0.128.0 go.opentelemetry.io/collector/config/confignet v1.34.0 go.opentelemetry.io/collector/config/configopaque v1.34.0 go.opentelemetry.io/collector/config/configoptional v0.128.0 @@ -23,6 +24,7 @@ require ( go.opentelemetry.io/collector/consumer/consumererror v0.128.0 go.opentelemetry.io/collector/consumer/consumertest v0.128.0 go.opentelemetry.io/collector/consumer/xconsumer v0.128.0 + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/internal/sharedcomponent v0.128.0 go.opentelemetry.io/collector/internal/telemetry v0.128.0 go.opentelemetry.io/collector/pdata v1.34.0 @@ -69,9 +71,10 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/collector/client v1.34.0 // indirect go.opentelemetry.io/collector/config/configcompression v1.34.0 // indirect - go.opentelemetry.io/collector/config/configmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension v1.34.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/collector/featuregate v1.34.0 // indirect go.opentelemetry.io/collector/pipeline v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect @@ -86,6 +89,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) @@ -132,8 +136,6 @@ replace go.opentelemetry.io/collector/consumer => ../../consumer replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile -replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/client => ../../client @@ -166,3 +168,7 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../ext replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter diff --git a/receiver/otlpreceiver/go.sum b/receiver/otlpreceiver/go.sum index c0ef210b74fc..f6079822c17e 100644 --- a/receiver/otlpreceiver/go.sum +++ b/receiver/otlpreceiver/go.sum @@ -130,6 +130,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/receiver/otlpreceiver/otlp.go b/receiver/otlpreceiver/otlp.go index 03e7e409e8b1..06460e50f97b 100644 --- a/receiver/otlpreceiver/otlp.go +++ b/receiver/otlpreceiver/otlp.go @@ -16,8 +16,11 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componentstatus" "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmiddleware" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/consumer/xconsumer" + "go.opentelemetry.io/collector/extension/extensionlimiter" + "go.opentelemetry.io/collector/extension/extensionlimiter/limiterhelper" "go.opentelemetry.io/collector/internal/telemetry" "go.opentelemetry.io/collector/internal/telemetry/componentattribute" "go.opentelemetry.io/collector/pdata/plog/plogotlp" @@ -98,20 +101,50 @@ func (r *otlpReceiver) startGRPCServer(host component.Host) error { return err } + limitKeys := extensionlimiter.StandardNotMiddlewareKeys() + limiters, err := configmiddleware.GetBaseLimiters(host, r.cfg.GRPC.Middlewares) + if err != nil { + return err + } + limiterProvider, err := limiterhelper.MultipleProvider(limiters) + if err != nil { + return err + } + if r.nextTraces != nil { - ptraceotlp.RegisterGRPCServer(r.serverGRPC, trace.New(r.nextTraces, r.obsrepGRPC)) + var next consumer.Traces + next, err = limiterhelper.NewLimitedTraces(r.nextTraces, limitKeys, limiterProvider) + if err != nil { + return err + } + ptraceotlp.RegisterGRPCServer(r.serverGRPC, trace.New(next, r.obsrepGRPC)) } if r.nextMetrics != nil { - pmetricotlp.RegisterGRPCServer(r.serverGRPC, metrics.New(r.nextMetrics, r.obsrepGRPC)) + var next consumer.Metrics + next, err = limiterhelper.NewLimitedMetrics(r.nextMetrics, limitKeys, limiterProvider) + if err != nil { + return err + } + pmetricotlp.RegisterGRPCServer(r.serverGRPC, metrics.New(next, r.obsrepGRPC)) } if r.nextLogs != nil { - plogotlp.RegisterGRPCServer(r.serverGRPC, logs.New(r.nextLogs, r.obsrepGRPC)) + var next consumer.Logs + next, err = limiterhelper.NewLimitedLogs(r.nextLogs, limitKeys, limiterProvider) + if err != nil { + return err + } + plogotlp.RegisterGRPCServer(r.serverGRPC, logs.New(next, r.obsrepGRPC)) } if r.nextProfiles != nil { - pprofileotlp.RegisterGRPCServer(r.serverGRPC, profiles.New(r.nextProfiles)) + var next xconsumer.Profiles + next, err = limiterhelper.NewLimitedProfiles(r.nextProfiles, limitKeys, limiterProvider) + if err != nil { + return err + } + pprofileotlp.RegisterGRPCServer(r.serverGRPC, profiles.New(next)) } r.settings.Logger.Info("Starting GRPC server", zap.String("endpoint", grpcCfg.NetAddr.Endpoint)) @@ -137,31 +170,57 @@ func (r *otlpReceiver) startHTTPServer(ctx context.Context, host component.Host) return nil } + limitKeys := extensionlimiter.StandardNotMiddlewareKeys() + limiters, err := configmiddleware.GetBaseLimiters(host, r.cfg.HTTP.ServerConfig.Middlewares) + if err != nil { + return err + } + limiterProvider, err := limiterhelper.MultipleProvider(limiters) + if err != nil { + return err + } + httpCfg := r.cfg.HTTP.Get() httpMux := http.NewServeMux() if r.nextTraces != nil { - httpTracesReceiver := trace.New(r.nextTraces, r.obsrepHTTP) + next, err := limiterhelper.NewLimitedTraces(r.nextTraces, limitKeys, limiterProvider) + if err != nil { + return err + } + httpTracesReceiver := trace.New(next, r.obsrepHTTP) httpMux.HandleFunc(string(httpCfg.TracesURLPath), func(resp http.ResponseWriter, req *http.Request) { handleTraces(resp, req, httpTracesReceiver) }) } if r.nextMetrics != nil { - httpMetricsReceiver := metrics.New(r.nextMetrics, r.obsrepHTTP) + next, err := limiterhelper.NewLimitedMetrics(r.nextMetrics, limitKeys, limiterProvider) + if err != nil { + return err + } + httpMetricsReceiver := metrics.New(next, r.obsrepHTTP) httpMux.HandleFunc(string(httpCfg.MetricsURLPath), func(resp http.ResponseWriter, req *http.Request) { handleMetrics(resp, req, httpMetricsReceiver) }) } if r.nextLogs != nil { - httpLogsReceiver := logs.New(r.nextLogs, r.obsrepHTTP) + next, err := limiterhelper.NewLimitedLogs(r.nextLogs, limitKeys, limiterProvider) + if err != nil { + return err + } + httpLogsReceiver := logs.New(next, r.obsrepHTTP) httpMux.HandleFunc(string(httpCfg.LogsURLPath), func(resp http.ResponseWriter, req *http.Request) { handleLogs(resp, req, httpLogsReceiver) }) } if r.nextProfiles != nil { - httpProfilesReceiver := profiles.New(r.nextProfiles) + next, err := limiterhelper.NewLimitedProfiles(r.nextProfiles, limitKeys, limiterProvider) + if err != nil { + return err + } + httpProfilesReceiver := profiles.New(next) httpMux.HandleFunc(defaultProfilesURLPath, func(resp http.ResponseWriter, req *http.Request) { handleProfiles(resp, req, httpProfilesReceiver) }) diff --git a/service/go.mod b/service/go.mod index 868c0ac483e0..8fbd98f1c7a8 100644 --- a/service/go.mod +++ b/service/go.mod @@ -110,7 +110,9 @@ require ( go.opentelemetry.io/collector/config/configtls v1.34.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.128.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.34.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.128.0 // indirect + go.opentelemetry.io/collector/extension/extensionlimiter v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v1.30.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.128.0 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/contrib/zpages v0.61.0 // indirect @@ -132,6 +134,7 @@ require ( golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/grpc v1.73.0 // indirect @@ -202,8 +205,6 @@ replace go.opentelemetry.io/collector/config/configcompression => ../config/conf replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile -replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer - replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest replace go.opentelemetry.io/collector/client => ../client @@ -243,3 +244,7 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extens replace go.opentelemetry.io/collector/config/configmiddleware => ../config/configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extension/extensionmiddleware/extensionmiddlewaretest + +replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../extension/extensionlimiter diff --git a/service/go.sum b/service/go.sum index b95223c42c13..a3c0385c7e79 100644 --- a/service/go.sum +++ b/service/go.sum @@ -211,6 +211,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/service/hostcapabilities/go.mod b/service/hostcapabilities/go.mod index d2ecf97a4668..a3c8fe49a41a 100644 --- a/service/hostcapabilities/go.mod +++ b/service/hostcapabilities/go.mod @@ -95,3 +95,5 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmid replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware + +replace go.opentelemetry.io/collector/extension/extensionlimiter => ../../extension/extensionlimiter