From aaa6d68cafbd5a7be75bc5464ef3140eb5908182 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 5 Oct 2024 03:10:06 -0700 Subject: [PATCH] Remove xray sampler (#6187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve #5554 This package was deprecated in release [v0.22.0](https://pkg.go.dev/go.opentelemetry.io/contrib/samplers/aws/xray@v0.22.0) on 2024-07-03. It is no longer supported and is being removed. --------- Co-authored-by: Robert PajÄ…k --- CHANGELOG.md | 2 +- CODEOWNERS | 1 - samplers/aws/xray/fallback_sampler.go | 91 - samplers/aws/xray/fallback_sampler_test.go | 63 - samplers/aws/xray/go.mod | 22 - samplers/aws/xray/go.sum | 29 - samplers/aws/xray/internal/client.go | 187 -- samplers/aws/xray/internal/client_test.go | 310 --- samplers/aws/xray/internal/clock.go | 32 - samplers/aws/xray/internal/manifest.go | 394 ---- samplers/aws/xray/internal/manifest_test.go | 1845 ----------------- samplers/aws/xray/internal/match.go | 60 - samplers/aws/xray/internal/match_test.go | 105 - samplers/aws/xray/internal/reservoir.go | 105 - samplers/aws/xray/internal/reservoir_test.go | 259 --- samplers/aws/xray/internal/rule.go | 238 --- samplers/aws/xray/internal/rule_test.go | 651 ------ samplers/aws/xray/rand.go | 30 - samplers/aws/xray/remote_sampler.go | 180 -- samplers/aws/xray/remote_sampler_config.go | 89 - .../aws/xray/remote_sampler_config_test.go | 99 - samplers/aws/xray/remote_sampler_test.go | 18 - samplers/aws/xray/timer.go | 34 - samplers/aws/xray/version.go | 17 - versions.yaml | 1 - 25 files changed, 1 insertion(+), 4861 deletions(-) delete mode 100644 samplers/aws/xray/fallback_sampler.go delete mode 100644 samplers/aws/xray/fallback_sampler_test.go delete mode 100644 samplers/aws/xray/go.mod delete mode 100644 samplers/aws/xray/go.sum delete mode 100644 samplers/aws/xray/internal/client.go delete mode 100644 samplers/aws/xray/internal/client_test.go delete mode 100644 samplers/aws/xray/internal/clock.go delete mode 100644 samplers/aws/xray/internal/manifest.go delete mode 100644 samplers/aws/xray/internal/manifest_test.go delete mode 100644 samplers/aws/xray/internal/match.go delete mode 100644 samplers/aws/xray/internal/match_test.go delete mode 100644 samplers/aws/xray/internal/reservoir.go delete mode 100644 samplers/aws/xray/internal/reservoir_test.go delete mode 100644 samplers/aws/xray/internal/rule.go delete mode 100644 samplers/aws/xray/internal/rule_test.go delete mode 100644 samplers/aws/xray/rand.go delete mode 100644 samplers/aws/xray/remote_sampler.go delete mode 100644 samplers/aws/xray/remote_sampler_config.go delete mode 100644 samplers/aws/xray/remote_sampler_config_test.go delete mode 100644 samplers/aws/xray/remote_sampler_test.go delete mode 100644 samplers/aws/xray/timer.go delete mode 100644 samplers/aws/xray/version.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a7bb3da4e..8dcd57e796a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,12 +23,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed - Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965) -- Non-200 HTTP status codes when retrieving sampling rules in `go.opentelemetry.io/contrib/samplers/aws/xray` now return an error. (#5718) ### Removed - The `Minimum` field of the `LogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` is removed. Use `NewLogProcessor` to configure this setting. (#6116) +- The deprecated `go.opentelemetry.io/contrib/samplers/aws/xray` package is removed. (#6187) diff --git a/CODEOWNERS b/CODEOWNERS index 20a8c847c5c..47b50e98c1a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -67,7 +67,6 @@ propagators/jaeger/ @open-te propagators/opencensus/ @open-telemetry/go-approvers @dashpole propagators/ot/ @open-telemetry/go-approvers @pellared -samplers/aws/xray/ @open-telemetry/go-approvers samplers/jaegerremote/ @open-telemetry/go-approvers @yurishkuro samplers/probability/consistent/ @open-telemetry/go-approvers diff --git a/samplers/aws/xray/fallback_sampler.go b/samplers/aws/xray/fallback_sampler.go deleted file mode 100644 index 2b23704aac8..00000000000 --- a/samplers/aws/xray/fallback_sampler.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -import ( - "sync" - "time" - - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/trace" -) - -// FallbackSampler does the sampling at a rate of 1 req/sec and 5% of additional requests. -type FallbackSampler struct { - lastTick time.Time - quotaBalance float64 - defaultSampler sdktrace.Sampler - mu sync.RWMutex -} - -// Compile time assertion that remoteSampler implements the Sampler interface. -var _ sdktrace.Sampler = (*FallbackSampler)(nil) - -// NewFallbackSampler returns a FallbackSampler which samples 1 req/sec and additional 5% of requests using traceIDRatioBasedSampler. -func NewFallbackSampler() *FallbackSampler { - return &FallbackSampler{ - defaultSampler: sdktrace.TraceIDRatioBased(0.05), - quotaBalance: 1.0, - } -} - -// ShouldSample implements the logic of borrowing 1 req/sec and then use traceIDRatioBasedSampler to sample 5% of additional requests. -func (fs *FallbackSampler) ShouldSample(parameters sdktrace.SamplingParameters) sdktrace.SamplingResult { - // borrowing one request every second - if fs.take(time.Now(), 1.0) { - return sdktrace.SamplingResult{ - Tracestate: trace.SpanContextFromContext(parameters.ParentContext).TraceState(), - Decision: sdktrace.RecordAndSample, - } - } - - // traceIDRatioBasedSampler to sample 5% of additional requests every second - return fs.defaultSampler.ShouldSample(parameters) -} - -// Description returns description of the sampler being used. -func (fs *FallbackSampler) Description() string { - return "FallbackSampler{fallback sampling with sampling config of 1 req/sec and 5% of additional requests}" -} - -// take consumes quota from reservoir, if any remains, then returns true. False otherwise. -func (fs *FallbackSampler) take(now time.Time, itemCost float64) bool { //nolint:unparam - fs.mu.Lock() - defer fs.mu.Unlock() - - if fs.lastTick.IsZero() { - fs.lastTick = now - } - - if fs.quotaBalance >= itemCost { - fs.quotaBalance -= itemCost - return true - } - - // update quota balance based on elapsed time - fs.refreshQuotaBalanceLocked(now) - - if fs.quotaBalance >= itemCost { - fs.quotaBalance -= itemCost - return true - } - - return false -} - -// refreshQuotaBalanceLocked refreshes the quotaBalance considering elapsedTime. -// It is assumed the lock is held when calling this. -func (fs *FallbackSampler) refreshQuotaBalanceLocked(now time.Time) { - elapsedTime := now.Sub(fs.lastTick) - fs.lastTick = now - - // when elapsedTime is higher than 1 even then we need to keep quotaBalance - // near to 1 so making elapsedTime to 1 for only borrowing 1 per second case - if elapsedTime.Seconds() > 1.0 { - fs.quotaBalance += 1.0 - } else { - // calculate how much credit have we accumulated since the last tick - fs.quotaBalance += elapsedTime.Seconds() - } -} diff --git a/samplers/aws/xray/fallback_sampler_test.go b/samplers/aws/xray/fallback_sampler_test.go deleted file mode 100644 index d912449fd01..00000000000 --- a/samplers/aws/xray/fallback_sampler_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/sdk/trace" -) - -// assert sampling using fallback sampler. -func TestSampleUsingFallbackSampler(t *testing.T) { - fs := NewFallbackSampler() - assert.NotEmpty(t, fs.defaultSampler) - assert.Equal(t, 1.0, fs.quotaBalance) - - sd := fs.ShouldSample(trace.SamplingParameters{}) - assert.Equal(t, trace.RecordAndSample, sd.Decision) -} - -// assert that we only borrow 1 req/sec. -func TestBorrowOnePerSecond(t *testing.T) { - fs := NewFallbackSampler() - borrowed := fs.take(time.Unix(1500000000, 0), 1.0) - - // assert that borrowing one per second - assert.True(t, borrowed) - - borrowed = fs.take(time.Unix(1500000000, 0), 1.0) - - // assert that borrowing again is false during that second - assert.False(t, borrowed) - - borrowed = fs.take(time.Unix(1500000001, 0), 1.0) - - // assert that borrowing again in next second - assert.True(t, borrowed) -} - -// assert that when elapsedTime is high quotaBalance should still be close to 1. -func TestBorrowWithLargeElapsedTime(t *testing.T) { - fs := NewFallbackSampler() - borrowed := fs.take(time.Unix(1500000000, 0), 1.0) - - // assert that borrowing one per second - assert.True(t, borrowed) - - // Increase the time by 9 seconds - borrowed = fs.take(time.Unix(1500000009, 0), 1.0) - assert.True(t, borrowed) - assert.Equal(t, 0.0, fs.quotaBalance) -} - -// assert fallback sampling description. -func TestFallbackSamplerDescription(t *testing.T) { - fs := NewFallbackSampler() - s := fs.Description() - assert.Equal(t, "FallbackSampler{fallback sampling with sampling config of 1 req/sec and 5% of additional requests}", s) -} diff --git a/samplers/aws/xray/go.mod b/samplers/aws/xray/go.mod deleted file mode 100644 index 2bc97864f8d..00000000000 --- a/samplers/aws/xray/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -// Deprecated: xray has no Code Owner. -module go.opentelemetry.io/contrib/samplers/aws/xray - -go 1.22 - -require ( - github.com/go-logr/logr v1.4.2 - github.com/go-logr/stdr v1.2.2 - github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/sdk v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - golang.org/x/sys v0.25.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/samplers/aws/xray/go.sum b/samplers/aws/xray/go.sum deleted file mode 100644 index 27548315030..00000000000 --- a/samplers/aws/xray/go.sum +++ /dev/null @@ -1,29 +0,0 @@ -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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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/samplers/aws/xray/internal/client.go b/samplers/aws/xray/internal/client.go deleted file mode 100644 index d7f0d345b53..00000000000 --- a/samplers/aws/xray/internal/client.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" -) - -// getSamplingRulesOutput is used to store parsed json sampling rules. -type getSamplingRulesOutput struct { - SamplingRuleRecords []*samplingRuleRecords `json:"SamplingRuleRecords"` -} - -type samplingRuleRecords struct { - SamplingRule *ruleProperties `json:"SamplingRule"` -} - -// ruleProperties is the base set of properties that define a sampling rule. -type ruleProperties struct { - RuleName string `json:"RuleName"` - ServiceType string `json:"ServiceType"` - ResourceARN string `json:"ResourceARN"` - Attributes map[string]string `json:"Attributes"` - ServiceName string `json:"ServiceName"` - Host string `json:"Host"` - HTTPMethod string `json:"HTTPMethod"` - URLPath string `json:"URLPath"` - ReservoirSize float64 `json:"ReservoirSize"` - FixedRate float64 `json:"FixedRate"` - Priority int64 `json:"Priority"` - Version int64 `json:"Version"` -} - -type getSamplingTargetsInput struct { - SamplingStatisticsDocuments []*samplingStatisticsDocument -} - -// samplingStatisticsDocument is used to store current state of sampling data. -type samplingStatisticsDocument struct { - // A unique identifier for the service in hexadecimal. - ClientID *string - - // The name of the sampling rule. - RuleName *string - - // The number of requests that matched the rule. - RequestCount *int64 - - // The number of requests borrowed. - BorrowCount *int64 - - // The number of requests sampled using the rule. - SampledCount *int64 - - // The current time. - Timestamp *int64 -} - -// getSamplingTargetsOutput is used to store parsed json sampling targets. -type getSamplingTargetsOutput struct { - LastRuleModification *float64 `json:"LastRuleModification,omitempty"` - SamplingTargetDocuments []*samplingTargetDocument `json:"SamplingTargetDocuments,omitempty"` - UnprocessedStatistics []*unprocessedStatistic `json:"UnprocessedStatistics,omitempty"` -} - -// samplingTargetDocument contains updated targeted information retrieved from X-Ray service. -type samplingTargetDocument struct { - // The percentage of matching requests to instrument, after the reservoir is - // exhausted. - FixedRate *float64 `json:"FixedRate,omitempty"` - - // The number of seconds for the service to wait before getting sampling targets - // again. - Interval *int64 `json:"Interval,omitempty"` - - // The number of requests per second that X-Ray allocated this service. - ReservoirQuota *float64 `json:"ReservoirQuota,omitempty"` - - // The reservoir quota expires. - ReservoirQuotaTTL *float64 `json:"ReservoirQuotaTTL,omitempty"` - - // The name of the sampling rule. - RuleName *string `json:"RuleName,omitempty"` -} - -type unprocessedStatistic struct { - ErrorCode *string `json:"ErrorCode,omitempty"` - Message *string `json:"Message,omitempty"` - RuleName *string `json:"RuleName,omitempty"` -} - -type xrayClient struct { - // HTTP client for sending sampling requests to the collector. - httpClient *http.Client - - // Resolved URL to call getSamplingRules API. - samplingRulesURL string - - // Resolved URL to call getSamplingTargets API. - samplingTargetsURL string -} - -// newClient returns an HTTP client with proxy endpoint. -func newClient(endpoint url.URL) (client *xrayClient, err error) { //nolint:unparam - // Construct resolved URLs for getSamplingRules and getSamplingTargets API calls. - endpoint.Path = "/GetSamplingRules" - samplingRulesURL := endpoint - - endpoint.Path = "/SamplingTargets" - samplingTargetsURL := endpoint - - return &xrayClient{ - httpClient: &http.Client{}, - samplingRulesURL: samplingRulesURL.String(), - samplingTargetsURL: samplingTargetsURL.String(), - }, nil -} - -// getSamplingRules calls the collector(aws proxy enabled) for sampling rules. -func (c *xrayClient) getSamplingRules(ctx context.Context) (*getSamplingRulesOutput, error) { - emptySamplingRulesInputJSON := []byte(`{"NextToken": null}`) - - body := bytes.NewReader(emptySamplingRulesInputJSON) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.samplingRulesURL, body) - if err != nil { - return nil, fmt.Errorf("unable to retrieve sampling rules, error on http request: %w", err) - } - - output, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, error on http request: %w", err) - } - defer output.Body.Close() - - if output.StatusCode != http.StatusOK { - return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, expected response status code 200, got: %d", output.StatusCode) - } - - var samplingRulesOutput *getSamplingRulesOutput - if err := json.NewDecoder(output.Body).Decode(&samplingRulesOutput); err != nil { - return nil, fmt.Errorf("xray client: unable to retrieve sampling rules, unable to unmarshal the response body: %w", err) - } - - return samplingRulesOutput, nil -} - -// getSamplingTargets calls the collector(aws proxy enabled) for sampling targets. -func (c *xrayClient) getSamplingTargets(ctx context.Context, s []*samplingStatisticsDocument) (*getSamplingTargetsOutput, error) { - statistics := getSamplingTargetsInput{ - SamplingStatisticsDocuments: s, - } - - statisticsByte, err := json.Marshal(statistics) - if err != nil { - return nil, err - } - body := bytes.NewReader(statisticsByte) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.samplingTargetsURL, body) - if err != nil { - return nil, fmt.Errorf("xray client: failed to create http request: %w", err) - } - - output, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, error on http request: %w", err) - } - defer output.Body.Close() - - if output.StatusCode != http.StatusOK { - return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, expected response status code 200, got: %d", output.StatusCode) - } - - var samplingTargetsOutput *getSamplingTargetsOutput - if err := json.NewDecoder(output.Body).Decode(&samplingTargetsOutput); err != nil { - return nil, fmt.Errorf("xray client: unable to retrieve sampling targets, unable to unmarshal the response body: %w", err) - } - - return samplingTargetsOutput, nil -} diff --git a/samplers/aws/xray/internal/client_test.go b/samplers/aws/xray/internal/client_test.go deleted file mode 100644 index 464302ba75c..00000000000 --- a/samplers/aws/xray/internal/client_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func createTestClient(t *testing.T, body []byte) *xrayClient { - return createTestClientWithStatusCode(t, http.StatusOK, body) -} - -func createTestClientWithStatusCode(t *testing.T, status int, body []byte) *xrayClient { - testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { - res.WriteHeader(status) - _, err := res.Write(body) - assert.NoError(t, err) - })) - t.Cleanup(testServer.Close) - - u, err := url.Parse(testServer.URL) - require.NoError(t, err) - - client, err := newClient(*u) - require.NoError(t, err) - return client -} - -func TestGetSamplingRules(t *testing.T) { - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/Default", - "RuleName": "Default", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - }, - { - "CreatedAt": 1637691613, - "ModifiedAt": 1643748669, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.09, - "HTTPMethod": "GET", - "Host": "*", - "Priority": 1, - "ReservoirSize": 3, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/test-rule", - "RuleName": "test-rule", - "ServiceName": "test-rule", - "ServiceType": "local", - "URLPath": "/aws-sdk-call", - "Version": 1 - } - }, - { - "CreatedAt": 1639446197, - "ModifiedAt": 1639446197, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.09, - "HTTPMethod": "*", - "Host": "*", - "Priority": 100, - "ReservoirSize": 100, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/test-rule-1", - "RuleName": "test-rule-1", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - ctx := context.Background() - - client := createTestClient(t, body) - - samplingRules, err := client.getSamplingRules(ctx) - require.NoError(t, err) - - assert.Equal(t, "Default", samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[0].SamplingRule.ServiceType) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[0].SamplingRule.Host) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[0].SamplingRule.URLPath) - assert.Equal(t, 60.0, samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize) - assert.Equal(t, 0.5, samplingRules.SamplingRuleRecords[0].SamplingRule.FixedRate) - - assert.Equal(t, "test-rule", samplingRules.SamplingRuleRecords[1].SamplingRule.RuleName) - assert.Equal(t, "local", samplingRules.SamplingRuleRecords[1].SamplingRule.ServiceType) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[1].SamplingRule.Host) - assert.Equal(t, "/aws-sdk-call", samplingRules.SamplingRuleRecords[1].SamplingRule.URLPath) - assert.Equal(t, 3.0, samplingRules.SamplingRuleRecords[1].SamplingRule.ReservoirSize) - assert.Equal(t, 0.09, samplingRules.SamplingRuleRecords[1].SamplingRule.FixedRate) - - assert.Equal(t, "test-rule-1", samplingRules.SamplingRuleRecords[2].SamplingRule.RuleName) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[2].SamplingRule.ServiceType) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[2].SamplingRule.Host) - assert.Equal(t, "*", samplingRules.SamplingRuleRecords[2].SamplingRule.URLPath) - assert.Equal(t, 100.0, samplingRules.SamplingRuleRecords[2].SamplingRule.ReservoirSize) - assert.Equal(t, 0.09, samplingRules.SamplingRuleRecords[2].SamplingRule.FixedRate) -} - -func TestGetSamplingRulesWithMissingValues(t *testing.T) { - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/Default", - "RuleName": "Default", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - ctx := context.Background() - - client := createTestClient(t, body) - - samplingRules, err := client.getSamplingRules(ctx) - require.NoError(t, err) - - // Priority and ReservoirSize are missing in API response so they are assigned as nil - assert.Equal(t, int64(0), samplingRules.SamplingRuleRecords[0].SamplingRule.Priority) - assert.Equal(t, 0.0, samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize) - - // other values are stored as expected - assert.Equal(t, "Default", samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName) -} - -func TestGetSamplingTargets(t *testing.T) { - body := []byte(`{ - "LastRuleModification": 123456, - "SamplingTargetDocuments": [ - { - "FixedRate": 5, - "Interval": 5, - "ReservoirQuota": 3, - "ReservoirQuotaTTL": 456789, - "RuleName": "r1" - } - ], - "UnprocessedStatistics": [ - { - "ErrorCode": "200", - "Message": "ok", - "RuleName": "r1" - } - ] -}`) - - ctx := context.Background() - - client := createTestClient(t, body) - - samplingTragets, err := client.getSamplingTargets(ctx, nil) - require.NoError(t, err) - - assert.Equal(t, float64(123456), *samplingTragets.LastRuleModification) - assert.Equal(t, float64(5), *samplingTragets.SamplingTargetDocuments[0].FixedRate) - assert.Equal(t, int64(5), *samplingTragets.SamplingTargetDocuments[0].Interval) - assert.Equal(t, 3.0, *samplingTragets.SamplingTargetDocuments[0].ReservoirQuota) - assert.Equal(t, float64(456789), *samplingTragets.SamplingTargetDocuments[0].ReservoirQuotaTTL) - assert.Equal(t, "r1", *samplingTragets.SamplingTargetDocuments[0].RuleName) - assert.Equal(t, "r1", *samplingTragets.UnprocessedStatistics[0].RuleName) - assert.Equal(t, "200", *samplingTragets.UnprocessedStatistics[0].ErrorCode) - assert.Equal(t, "ok", *samplingTragets.UnprocessedStatistics[0].Message) -} - -func TestGetSamplingTargetsMissingValues(t *testing.T) { - body := []byte(`{ - "LastRuleModification": 123456, - "SamplingTargetDocuments": [ - { - "FixedRate": 5, - "ReservoirQuotaTTL": 456789, - "RuleName": "r1" - } - ], - "UnprocessedStatistics": [ - { - "ErrorCode": "200", - "Message": "ok", - "RuleName": "r1" - } - ] -}`) - - ctx := context.Background() - - client := createTestClient(t, body) - - samplingTargets, err := client.getSamplingTargets(ctx, nil) - require.NoError(t, err) - - assert.Nil(t, samplingTargets.SamplingTargetDocuments[0].Interval) - assert.Nil(t, samplingTargets.SamplingTargetDocuments[0].ReservoirQuota) -} - -func TestNewClient(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1:2020") - require.NoError(t, err) - - xrayClient, err := newClient(*endpoint) - require.NoError(t, err) - - assert.Equal(t, "http://127.0.0.1:2020/GetSamplingRules", xrayClient.samplingRulesURL) - assert.Equal(t, "http://127.0.0.1:2020/SamplingTargets", xrayClient.samplingTargetsURL) -} - -func TestEndpointIsNotReachable(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1:2020") - require.NoError(t, err) - - client, err := newClient(*endpoint) - require.NoError(t, err) - - actualRules, err := client.getSamplingRules(context.Background()) - assert.Error(t, err) - assert.ErrorContains(t, err, "xray client: unable to retrieve sampling rules, error on http request: ") - assert.Nil(t, actualRules) - - actualTargets, err := client.getSamplingTargets(context.Background(), nil) - assert.Error(t, err) - assert.ErrorContains(t, err, "xray client: unable to retrieve sampling targets, error on http request: ") - assert.Nil(t, actualTargets) -} - -func TestRespondsWithErrorStatusCode(t *testing.T) { - client := createTestClientWithStatusCode(t, http.StatusForbidden, []byte("{}")) - - actualRules, err := client.getSamplingRules(context.Background()) - assert.Error(t, err) - assert.EqualError(t, err, fmt.Sprintf("xray client: unable to retrieve sampling rules, expected response status code 200, got: %d", http.StatusForbidden)) - assert.Nil(t, actualRules) - - actualTargets, err := client.getSamplingTargets(context.Background(), nil) - assert.Error(t, err) - assert.EqualError(t, err, fmt.Sprintf("xray client: unable to retrieve sampling targets, expected response status code 200, got: %d", http.StatusForbidden)) - assert.Nil(t, actualTargets) -} - -func TestInvalidResponseBody(t *testing.T) { - type scenarios struct { - name string - response string - } - for _, scenario := range []scenarios{ - { - name: "empty response", - response: "", - }, - { - name: "malformed json", - response: "", - }, - } { - t.Run(scenario.name, func(t *testing.T) { - client := createTestClient(t, []byte(scenario.response)) - - actualRules, err := client.getSamplingRules(context.TODO()) - - assert.Error(t, err) - assert.Nil(t, actualRules) - assert.ErrorContains(t, err, "xray client: unable to retrieve sampling rules, unable to unmarshal the response body:"+scenario.response) - - actualTargets, err := client.getSamplingTargets(context.TODO(), nil) - assert.Error(t, err) - assert.Nil(t, actualTargets) - assert.ErrorContains(t, err, "xray client: unable to retrieve sampling targets, unable to unmarshal the response body: "+scenario.response) - }) - } -} diff --git a/samplers/aws/xray/internal/clock.go b/samplers/aws/xray/internal/clock.go deleted file mode 100644 index 55f8355d17c..00000000000 --- a/samplers/aws/xray/internal/clock.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "time" -) - -// clock represents a time keeper that returns its version of the current time. -type clock interface { - now() time.Time -} - -// defaultClock wraps the standard time package. -type defaultClock struct{} - -// now returns current time according to the standard time package. -func (t *defaultClock) now() time.Time { - return time.Now() -} - -// mockClock is a time keeper that returns a fixed time. -type mockClock struct { - nowTime int64 - nowNanos int64 -} - -// now function returns the fixed time value stored in c. -func (c *mockClock) now() time.Time { - return time.Unix(c.nowTime, c.nowNanos) -} diff --git a/samplers/aws/xray/internal/manifest.go b/samplers/aws/xray/internal/manifest.go deleted file mode 100644 index 78b5f20588a..00000000000 --- a/samplers/aws/xray/internal/manifest.go +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "context" - crypto "crypto/rand" - "fmt" - "math" - "net/url" - "reflect" - "sort" - "strings" - "sync" - "time" - - "github.com/go-logr/logr" - - sdktrace "go.opentelemetry.io/otel/sdk/trace" -) - -const ( - manifestTTL = 3600 - version = 1 -) - -// Manifest represents a full sampling ruleset and provides -// options for configuring Logger, Clock and xrayClient. -type Manifest struct { - Rules []Rule - SamplingTargetsPollingInterval time.Duration - refreshedAt time.Time - xrayClient *xrayClient - clientID *string - logger logr.Logger - clock clock - mu sync.RWMutex -} - -// NewManifest return manifest object configured the passed with logging and an xrayClient -// configured to address addr. -func NewManifest(addr url.URL, logger logr.Logger) (*Manifest, error) { - // Generate client for getSamplingRules and getSamplingTargets API call. - client, err := newClient(addr) - if err != nil { - return nil, err - } - - // Generate clientID for sampling statistics. - clientID, err := generateClientID() - if err != nil { - return nil, err - } - - return &Manifest{ - xrayClient: client, - clock: &defaultClock{}, - logger: logger, - SamplingTargetsPollingInterval: 10 * time.Second, - clientID: clientID, - }, nil -} - -// Expired returns true if the manifest has not been successfully refreshed in -// manifestTTL seconds. -func (m *Manifest) Expired() bool { - m.mu.RLock() - defer m.mu.RUnlock() - - manifestLiveTime := m.refreshedAt.Add(time.Second * manifestTTL) - return m.clock.now().After(manifestLiveTime) -} - -// MatchAgainstManifestRules returns a Rule and boolean flag set as true -// if rule has been match against span attributes, otherwise nil and false. -func (m *Manifest) MatchAgainstManifestRules(parameters sdktrace.SamplingParameters, serviceName string, cloudPlatform string) (*Rule, bool, error) { - m.mu.RLock() - rules := m.Rules - m.mu.RUnlock() - - for index := range rules { - isRuleMatch, err := rules[index].appliesTo(parameters, serviceName, cloudPlatform) - if err != nil { - return nil, isRuleMatch, err - } - - if isRuleMatch { - return &rules[index], true, nil - } - } - - return nil, false, nil -} - -// RefreshManifestRules writes sampling rule properties to the manifest object. -func (m *Manifest) RefreshManifestRules(ctx context.Context) (err error) { - // Get sampling rules from AWS X-Ray console. - rules, err := m.xrayClient.getSamplingRules(ctx) - if err != nil { - return err - } - - // Update the retrieved sampling rules to manifest object. - m.updateRules(rules) - - return -} - -// RefreshManifestTargets updates sampling targets (statistics) for each rule. -func (m *Manifest) RefreshManifestTargets(ctx context.Context) (refresh bool, err error) { - // Deep copy manifest object. - manifest := m.deepCopy() - - // Generate sampling statistics based on the data in temporary manifest. - statistics, err := manifest.snapshots() - if err != nil { - return false, err - } - - // Return if no statistics to report. - if len(statistics) == 0 { - m.logger.V(5).Info("no statistics to report and not refreshing sampling targets") - return false, nil - } - - // Get sampling targets (statistics) for every expired rule from AWS X-Ray. - targets, err := m.xrayClient.getSamplingTargets(ctx, statistics) - if err != nil { - return false, fmt.Errorf("refreshTargets: error occurred while getting sampling targets: %w", err) - } - - m.logger.V(5).Info("successfully fetched sampling targets") - - // Update temporary manifest with retrieved targets (statistics) for each rule. - refresh, err = manifest.updateTargets(targets) - if err != nil { - return refresh, err - } - - // Find next polling interval for targets. - minPoll := manifest.minimumPollingInterval() - if minPoll > 0 { - m.SamplingTargetsPollingInterval = minPoll - } - - // Update centralized manifest object. - m.mu.Lock() - m.Rules = manifest.Rules - m.mu.Unlock() - - return -} - -func (m *Manifest) updateRules(rules *getSamplingRulesOutput) { - tempManifest := Manifest{ - Rules: []Rule{}, - } - - for _, records := range rules.SamplingRuleRecords { - if records.SamplingRule.RuleName == "" { - m.logger.V(5).Info("sampling rule without rule name is not supported") - continue - } - - if records.SamplingRule.Version != version { - m.logger.V(5).Info("sampling rule without Version 1 is not supported", "RuleName", records.SamplingRule.RuleName) - continue - } - - // Create the rule and store it in temporary manifest to avoid thread safety issues. - tempManifest.createRule(*records.SamplingRule) - } - - // Re-sort to fix matching priorities. - tempManifest.sort() - - currentRuleMap := make(map[string]Rule) - - m.mu.Lock() - for _, rule := range m.Rules { - currentRuleMap[rule.ruleProperties.RuleName] = rule - } - - // Preserve entire Rule if newRule.ruleProperties == curRule.ruleProperties - for i, newRule := range tempManifest.Rules { - if curRule, ok := currentRuleMap[newRule.ruleProperties.RuleName]; ok { - if reflect.DeepEqual(newRule.ruleProperties, curRule.ruleProperties) { - tempManifest.Rules[i] = curRule - } - } - } - - m.Rules = tempManifest.Rules - m.refreshedAt = m.clock.now() - m.mu.Unlock() -} - -func (m *Manifest) createRule(ruleProp ruleProperties) { - cr := reservoir{ - capacity: ruleProp.ReservoirSize, - } - - csr := Rule{ - reservoir: &cr, - ruleProperties: ruleProp, - samplingStatistics: &samplingStatistics{}, - } - - m.Rules = append(m.Rules, csr) -} - -func (m *Manifest) updateTargets(targets *getSamplingTargetsOutput) (refresh bool, err error) { - // Update sampling targets for each rule. - for _, t := range targets.SamplingTargetDocuments { - if err := m.updateReservoir(t); err != nil { - return false, err - } - } - - // Consume unprocessed statistics messages. - for _, s := range targets.UnprocessedStatistics { - m.logger.V(5).Info( - "error occurred updating sampling target for rule, code and message", "RuleName", s.RuleName, "ErrorCode", - s.ErrorCode, - "Message", s.Message, - ) - - // Do not set any flags if error is unknown. - if s.ErrorCode == nil || s.RuleName == nil { - continue - } - - // Set batch failure if any sampling statistics returned 5xx. - if strings.HasPrefix(*s.ErrorCode, "5") { - return false, fmt.Errorf("sampling statistics returned 5xx") - } - - // Set refresh flag if any sampling statistics returned 4xx. - if strings.HasPrefix(*s.ErrorCode, "4") { - refresh = true - } - } - - // Set refresh flag if modifiedAt timestamp from remote is greater than ours. - if remote := targets.LastRuleModification; remote != nil { - // Convert unix timestamp to time.Time. - lastRuleModification := time.Unix(int64(*targets.LastRuleModification), 0) - - if lastRuleModification.After(m.refreshedAt) { - refresh = true - } - } - - return -} - -func (m *Manifest) updateReservoir(t *samplingTargetDocument) (err error) { - if t.RuleName == nil { - return fmt.Errorf("invalid sampling targe: missing rule name") - } - - if t.FixedRate == nil { - return fmt.Errorf("invalid sampling target for rule %s: missing fixed rate", *t.RuleName) - } - - for index := range m.Rules { - if m.Rules[index].ruleProperties.RuleName == *t.RuleName { - m.Rules[index].reservoir.refreshedAt = m.clock.now() - - // Update non-optional attributes from response - m.Rules[index].ruleProperties.FixedRate = *t.FixedRate - - // Update optional attributes from response - if t.ReservoirQuota != nil { - m.Rules[index].reservoir.quota = *t.ReservoirQuota - } - if t.ReservoirQuotaTTL != nil { - m.Rules[index].reservoir.expiresAt = time.Unix(int64(*t.ReservoirQuotaTTL), 0) - } - if t.Interval != nil { - m.Rules[index].reservoir.interval = time.Duration(*t.Interval) - } - } - } - - return -} - -// snapshots takes a snapshot of sampling statistics from all rules, resetting -// statistics counters in the process. -func (m *Manifest) snapshots() ([]*samplingStatisticsDocument, error) { //nolint:unparam - statistics := make([]*samplingStatisticsDocument, 0, len(m.Rules)) - - // Generate sampling statistics for user-defined rules. - for index := range m.Rules { - if m.Rules[index].stale(m.clock.now()) { - s := m.Rules[index].snapshot(m.clock.now()) - s.ClientID = m.clientID - - statistics = append(statistics, s) - } - } - - return statistics, nil -} - -// deepCopy copies the m to another manifest object. -func (m *Manifest) deepCopy() *Manifest { - m.mu.RLock() - defer m.mu.RUnlock() - - manifest := Manifest{ - Rules: []Rule{}, - } - - for _, rule := range m.Rules { - // Deep copying rules. - var tempRule Rule - tempRule.ruleProperties = rule.ruleProperties - - // Deep copying reservoir (copying each fields of reservoir because we want to initialize new mutex values for each rule). - var tempRes reservoir - - rule.reservoir.mu.RLock() - tempRes.expiresAt = rule.reservoir.expiresAt - tempRes.quota = rule.reservoir.quota - tempRes.quotaBalance = rule.reservoir.quotaBalance - tempRes.capacity = rule.reservoir.capacity - tempRes.refreshedAt = rule.reservoir.refreshedAt - tempRes.interval = rule.reservoir.interval - tempRes.lastTick = rule.reservoir.lastTick - rule.reservoir.mu.RUnlock() - - tempRule.reservoir = &tempRes - - // Shallow copying sampling statistics. - tempRule.samplingStatistics = rule.samplingStatistics - - manifest.Rules = append(manifest.Rules, tempRule) - } - - // Copying other manifest fields. - manifest.SamplingTargetsPollingInterval = m.SamplingTargetsPollingInterval - manifest.refreshedAt = m.refreshedAt - manifest.xrayClient = m.xrayClient - manifest.clientID = m.clientID - manifest.logger = m.logger - manifest.clock = m.clock - - return &manifest -} - -// sort sorts the Rules of m first by priority and then by name. -func (m *Manifest) sort() { // nolint: revive // method names are scoped by receiver. - less := func(i, j int) bool { - if m.Rules[i].ruleProperties.Priority == m.Rules[j].ruleProperties.Priority { - return strings.Compare(m.Rules[i].ruleProperties.RuleName, m.Rules[j].ruleProperties.RuleName) < 0 - } - return m.Rules[i].ruleProperties.Priority < m.Rules[j].ruleProperties.Priority - } - - sort.Slice(m.Rules, less) -} - -// minimumPollingInterval finds the minimum polling interval for all the targets of m's Rules. -func (m *Manifest) minimumPollingInterval() time.Duration { - if len(m.Rules) == 0 { - return time.Duration(0) - } - - minPoll := time.Duration(math.MaxInt64) - for _, rules := range m.Rules { - if minPoll >= rules.reservoir.interval { - minPoll = rules.reservoir.interval - } - } - - return minPoll * time.Second -} - -// generateClientID generates random client ID. -func generateClientID() (*string, error) { - var r [12]byte - - _, err := crypto.Read(r[:]) - if err != nil { - return nil, fmt.Errorf("unable to generate client ID: %w", err) - } - - id := fmt.Sprintf("%02x", r) - - return &id, err -} diff --git a/samplers/aws/xray/internal/manifest_test.go b/samplers/aws/xray/internal/manifest_test.go deleted file mode 100644 index 8840cdc7875..00000000000 --- a/samplers/aws/xray/internal/manifest_test.go +++ /dev/null @@ -1,1845 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "context" - "net/url" - "sync" - "testing" - "time" - - "go.opentelemetry.io/otel/attribute" - - "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - - sdktrace "go.opentelemetry.io/otel/sdk/trace" - - "github.com/stretchr/testify/assert" -) - -func createSamplingTargetDocument(name string, interval int64, rate, quota, ttl float64) *samplingTargetDocument { //nolint:unparam - return &samplingTargetDocument{ - FixedRate: &rate, - Interval: &interval, - ReservoirQuota: "a, - ReservoirQuotaTTL: &ttl, - RuleName: &name, - } -} - -// assert that new manifest has certain non-nil attributes. -func TestNewManifest(t *testing.T) { - logger := testr.New(t) - - endpoint, err := url.Parse("http://127.0.0.1:2020") - require.NoError(t, err) - - m, err := NewManifest(*endpoint, logger) - require.NoError(t, err) - - assert.NotEmpty(t, m.logger) - assert.NotEmpty(t, m.clientID) - assert.NotEmpty(t, m.SamplingTargetsPollingInterval) - - assert.NotNil(t, m.xrayClient) -} - -// assert that manifest is expired. -func TestExpiredManifest(t *testing.T) { - clock := &mockClock{ - nowTime: 10000, - } - - m := &Manifest{ - clock: clock, - refreshedAt: time.Unix(3700, 0), - } - - assert.True(t, m.Expired()) -} - -// assert that if collector is not enabled at specified endpoint, returns an error. -func TestRefreshManifestError(t *testing.T) { - // collector is not running at port 2020 so expect error - endpoint, err := url.Parse("http://127.0.0.1:2020") - require.NoError(t, err) - - client, err := newClient(*endpoint) - require.NoError(t, err) - - m := &Manifest{ - xrayClient: client, - } - - err = m.RefreshManifestRules(context.Background()) - assert.Error(t, err) -} - -// assert that manifest rule r2 is a match for sampling. -func TestMatchAgainstManifestRules(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - Priority: 100, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 6, - FixedRate: 0.5, - Version: 1, - ServiceName: "test", - ResourceARN: "*", - ServiceType: "local", - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - m := &Manifest{ - Rules: []Rule{r1, r2}, - } - - exp, match, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{}, "test", "local") - require.True(t, match) - require.NoError(t, err) - - // assert that manifest rule r2 is a match - assert.Equal(t, *exp, r2) -} - -// assert that if rules has attribute and span has those attribute with same value then matching will happen. -func TestMatchAgainstManifestRulesAttributeMatch(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelB": "raspberry", - }, - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - exp, match, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{Attributes: commonLabels}, "test", "local") - require.True(t, match) - require.NoError(t, err) - - // assert that manifest rule r1 is a match - assert.Equal(t, *exp, r1) -} - -// assert that wildcard attributes will match. -func TestMatchAgainstManifestRulesAttributeWildCardMatch(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "*", - Attributes: map[string]string{ - "labelA": "choco*", - "labelB": "rasp*", - }, - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - exp, match, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{Attributes: commonLabels}, "test", "local") - require.True(t, match) - require.NoError(t, err) - - // assert that manifest rule r1 is a match - assert.NoError(t, err) - assert.Equal(t, *exp, r1) -} - -// assert that when no known rule is match then returned rule is nil, -// matched flag is false. -func TestMatchAgainstManifestRulesNoMatch(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "test-no-match", - ResourceARN: "*", - ServiceType: "local", - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - rule, isMatch, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{}, "test", "local") - - // assert that when no known rule is match then returned rule is nil - require.NoError(t, err) - assert.False(t, isMatch) - assert.Nil(t, rule) -} - -func TestRefreshManifestRules(t *testing.T) { - ctx := context.Background() - - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r1", - "RuleName": "r1", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - }, - { - "CreatedAt": 1637691613, - "ModifiedAt": 1643748669, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.09, - "HTTPMethod": "GET", - "Host": "*", - "Priority": 1, - "ReservoirSize": 3, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r2", - "RuleName": "r2", - "ServiceName": "test-rule", - "ServiceType": "*", - "URLPath": "/aws-sdk-call", - "Version": 1 - } - }, - { - "CreatedAt": 1639446197, - "ModifiedAt": 1639446197, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.09, - "HTTPMethod": "*", - "Host": "*", - "Priority": 100, - "ReservoirSize": 100, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r3", - "RuleName": "r3", - "ServiceName": "*", - "ServiceType": "local", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - - m := &Manifest{ - Rules: []Rule{}, - xrayClient: createTestClient(t, body), - clock: &defaultClock{}, - } - - err := m.RefreshManifestRules(ctx) - require.NoError(t, err) - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - Version: 1, - FixedRate: 0.5, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "*", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 60, - }, - samplingStatistics: &samplingStatistics{}, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - Priority: 1, - Host: "*", - HTTPMethod: "GET", - URLPath: "/aws-sdk-call", - ReservoirSize: 3, - FixedRate: 0.09, - Version: 1, - ServiceName: "test-rule", - ResourceARN: "*", - ServiceType: "*", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 3, - }, - samplingStatistics: &samplingStatistics{}, - } - - r3 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r3", - Priority: 100, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 100, - FixedRate: 0.09, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "local", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 100, - }, - samplingStatistics: &samplingStatistics{}, - } - - require.Len(t, m.Rules, 3) - - // Assert on sorting order - assert.Equal(t, r2, m.Rules[0]) - assert.Equal(t, r3, m.Rules[1]) - assert.Equal(t, r1, m.Rules[2]) -} - -// assert that rule with no ServiceName updates manifest successfully with empty values. -func TestRefreshManifestMissingServiceName(t *testing.T) { - ctx := context.Background() - - // rule with no ServiceName - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "XYZ", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r1", - "RuleName": "r1", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - - m := &Manifest{ - Rules: []Rule{}, - xrayClient: createTestClient(t, body), - clock: &defaultClock{}, - } - - err := m.RefreshManifestRules(ctx) - require.NoError(t, err) - - // assert on rule gets added - require.Len(t, m.Rules, 1) -} - -// assert that rule with no RuleName does not update to the manifest. -func TestRefreshManifestMissingRuleName(t *testing.T) { - ctx := context.Background() - - // rule with no RuleName - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "XYZ", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r1", - "ServiceName": "test", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - - m := &Manifest{ - Rules: []Rule{}, - xrayClient: createTestClient(t, body), - clock: &defaultClock{}, - logger: testr.New(t), - } - - err := m.RefreshManifestRules(ctx) - require.NoError(t, err) - - // assert on rule not added - require.Empty(t, m.Rules) -} - -// assert that rule with version greater than one does not update to the manifest. -func TestRefreshManifestIncorrectVersion(t *testing.T) { - ctx := context.Background() - - // rule with Version 5 - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "XYZ", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r1", - "RuleName": "r1", - "ServiceName": "test", - "ServiceType": "*", - "URLPath": "*", - "Version": 5 - } - } - ] -}`) - - m := &Manifest{ - Rules: []Rule{}, - xrayClient: createTestClient(t, body), - clock: &defaultClock{}, - logger: testr.New(t), - } - - err := m.RefreshManifestRules(ctx) - require.NoError(t, err) - - // assert on rule not added - require.Empty(t, m.Rules) -} - -// assert that 1 valid and 1 invalid rule update only valid rule gets stored to the manifest. -func TestRefreshManifestAddOneInvalidRule(t *testing.T) { - ctx := context.Background() - - // RuleName is missing from r2 - body := []byte(`{ - "NextToken": null, - "SamplingRuleRecords": [ - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r1", - "RuleName": "r1", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - }, - { - "CreatedAt": 0, - "ModifiedAt": 1639517389, - "SamplingRule": { - "Attributes": {"a":"b"}, - "FixedRate": 0.5, - "HTTPMethod": "*", - "Host": "*", - "Priority": 10000, - "ReservoirSize": 60, - "ResourceARN": "*", - "RuleARN": "arn:aws:xray:us-west-2:xxxxxxx:sampling-rule/r2", - "ServiceName": "*", - "ServiceType": "*", - "URLPath": "*", - "Version": 1 - } - } - ] -}`) - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "*", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 60, - }, - samplingStatistics: &samplingStatistics{}, - } - - m := &Manifest{ - Rules: []Rule{}, - xrayClient: createTestClient(t, body), - clock: &defaultClock{}, - logger: testr.New(t), - } - - err := m.RefreshManifestRules(ctx) - require.NoError(t, err) - - require.Len(t, m.Rules, 1) - - // assert on r1 - assert.Equal(t, r1, m.Rules[0]) -} - -// assert that inactive rule so return early without doing getSamplingTargets call. -func TestRefreshManifestTargetNoSnapShot(t *testing.T) { - clock := &mockClock{ - nowTime: 15000000, - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r3", - Priority: 100, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 100, - FixedRate: 0.09, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "local", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 100, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: int64(0), - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - clock: clock, - logger: testr.New(t), - } - - refresh, err := m.RefreshManifestTargets(context.Background()) - assert.False(t, refresh) - assert.NoError(t, err) -} - -// assert that refresh manifest targets successfully updates reservoir value for a rule. -func TestRefreshManifestTargets(t *testing.T) { - // RuleName is missing from r2 - body := []byte(`{ - "LastRuleModification": 17000000, - "SamplingTargetDocuments": [ - { - "FixedRate": 0.06, - "Interval": 25, - "ReservoirQuota": 23, - "ReservoirQuotaTTL": 15000000, - "RuleName": "r1" - } - ], - "UnprocessedStatistics": [ - { - "ErrorCode": "200", - "Message": "Ok", - "RuleName": "r1" - } - ] -}`) - - clock := &mockClock{ - nowTime: 150, - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 100, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 100, - FixedRate: 0.09, - Version: 1, - ServiceName: "*", - ResourceARN: "*", - ServiceType: "local", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 100, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: int64(5), - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - clock: clock, - logger: testr.New(t), - xrayClient: createTestClient(t, body), - refreshedAt: time.Unix(18000000, 0), - } - - refresh, err := m.RefreshManifestTargets(context.Background()) - assert.False(t, refresh) - require.NoError(t, err) - - // assert target updates - require.Len(t, m.Rules, 1) - assert.Equal(t, 0.06, m.Rules[0].ruleProperties.FixedRate) - assert.Equal(t, 23.0, m.Rules[0].reservoir.quota) - assert.Equal(t, m.Rules[0].reservoir.expiresAt, time.Unix(15000000, 0)) - assert.Equal(t, m.Rules[0].reservoir.interval, time.Duration(25)) -} - -// assert that refresh manifest targets successfully updates samplingTargetsPollingInterval. -func TestRefreshManifestTargetsPollIntervalUpdateTest(t *testing.T) { - body := []byte(`{ - "LastRuleModification": 17000000, - "SamplingTargetDocuments": [ - { - "FixedRate": 0.06, - "Interval": 15, - "ReservoirQuota": 23, - "ReservoirQuotaTTL": 15000000, - "RuleName": "r1" - }, - { - "FixedRate": 0.06, - "Interval": 5, - "ReservoirQuota": 23, - "ReservoirQuotaTTL": 15000000, - "RuleName": "r2" - }, - { - "FixedRate": 0.06, - "Interval": 25, - "ReservoirQuota": 23, - "ReservoirQuotaTTL": 15000000, - "RuleName": "r3" - } - ], - "UnprocessedStatistics": [ - { - "ErrorCode": "200", - "Message": "Ok", - "RuleName": "r3" - } - ] -}`) - - clock := &mockClock{ - nowTime: 150, - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: int64(5), - }, - reservoir: &reservoir{}, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - }, - samplingStatistics: &samplingStatistics{}, - reservoir: &reservoir{}, - } - - r3 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r3", - }, - samplingStatistics: &samplingStatistics{}, - reservoir: &reservoir{}, - } - - m := &Manifest{ - Rules: []Rule{r1, r2, r3}, - clock: clock, - logger: testr.New(t), - xrayClient: createTestClient(t, body), - refreshedAt: time.Unix(18000000, 0), - } - - _, err := m.RefreshManifestTargets(context.Background()) - require.NoError(t, err) - - // assert that sampling rules polling interval is minimum of all target intervals min(15, 5, 25) - assert.Equal(t, 5*time.Second, m.SamplingTargetsPollingInterval) -} - -// assert that a valid sampling target updates its rule. -func TestUpdateTargets(t *testing.T) { - targets := &getSamplingTargetsOutput{ - SamplingTargetDocuments: []*samplingTargetDocument{createSamplingTargetDocument("r1", 0, 0.05, 10, 1500000060)}, - } - - // sampling rule about to be updated with new target - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.10, - }, - reservoir: &reservoir{ - quota: 8, - refreshedAt: time.Unix(1499999990, 0), - expiresAt: time.Unix(1500000010, 0), - capacity: 50, - }, - } - - clock := &mockClock{ - nowTime: 1500000000, - } - - m := &Manifest{ - Rules: []Rule{r1}, - clock: clock, - } - - refresh, err := m.updateTargets(targets) - require.NoError(t, err) - - // assert refresh is false - assert.False(t, refresh) - - // Updated sampling rule - exp := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.05, - }, - reservoir: &reservoir{ - quota: 10, - refreshedAt: time.Unix(1500000000, 0), - expiresAt: time.Unix(1500000060, 0), - capacity: 50, - }, - } - - // assert that updated the rule targets of rule r1 - assert.Equal(t, exp, m.Rules[0]) -} - -// assert that when last rule modification time is greater than manifest refresh time we need to update manifest -// out of band (async). -func TestUpdateTargetsRefreshFlagTest(t *testing.T) { - targetLastRuleModifiedTime := float64(1500000020) - targets := &getSamplingTargetsOutput{ - SamplingTargetDocuments: []*samplingTargetDocument{createSamplingTargetDocument("r1", 0, 0.05, 10, 1500000060)}, - LastRuleModification: &targetLastRuleModifiedTime, - } - - // sampling rule about to be updated with new target - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.10, - }, - reservoir: &reservoir{ - quota: 8, - refreshedAt: time.Unix(1499999990, 0), - expiresAt: time.Unix(1500000010, 0), - capacity: 50, - }, - } - - clock := &mockClock{ - nowTime: 1500000000, - } - - m := &Manifest{ - Rules: []Rule{r1}, - refreshedAt: clock.now(), - clock: clock, - } - - refresh, err := m.updateTargets(targets) - require.NoError(t, err) - - // assert refresh is false - assert.True(t, refresh) - - // Updated sampling rule - exp := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.05, - }, - reservoir: &reservoir{ - quota: 10, - refreshedAt: time.Unix(1500000000, 0), - expiresAt: time.Unix(1500000060, 0), - capacity: 50, - }, - } - - // assert that updated the rule targets of rule r1 - assert.Equal(t, exp, m.Rules[0]) -} - -// unprocessed statistics error code is 5xx then updateTargets returns an error, if 4xx refresh flag set to true. -func TestUpdateTargetsUnprocessedStatistics(t *testing.T) { - // case for 5xx - ruleName := "r1" - errorCode500 := "500" - unprocessedStats5xx := unprocessedStatistic{ - ErrorCode: &errorCode500, - RuleName: &ruleName, - } - - targets5xx := &getSamplingTargetsOutput{ - SamplingTargetDocuments: []*samplingTargetDocument{createSamplingTargetDocument(ruleName, 0, 0.05, 10, 1500000060)}, - UnprocessedStatistics: []*unprocessedStatistic{&unprocessedStats5xx}, - } - - clock := &mockClock{ - nowTime: 1500000000, - } - - m := &Manifest{ - clock: clock, - logger: testr.New(t), - } - - refresh, err := m.updateTargets(targets5xx) - // assert error happened since unprocessed stats has returned 5xx error code - require.Error(t, err) - - // assert refresh is false - assert.False(t, refresh) - - // case for 4xx - errorCode400 := "400" - unprocessedStats4xx := unprocessedStatistic{ - ErrorCode: &errorCode400, - RuleName: &ruleName, - } - - targets4xx := &getSamplingTargetsOutput{ - SamplingTargetDocuments: []*samplingTargetDocument{createSamplingTargetDocument(ruleName, 0, 0.05, 10, 1500000060)}, - UnprocessedStatistics: []*unprocessedStatistic{&unprocessedStats4xx}, - } - - refresh, err = m.updateTargets(targets4xx) - // assert that no error happened since unprocessed stats has returned 4xx error code - require.NoError(t, err) - - // assert refresh is true - assert.True(t, refresh) - - // case when rule error code is unknown do not set any flag - unprocessedStats := unprocessedStatistic{ - ErrorCode: nil, - RuleName: nil, - } - - targets := &getSamplingTargetsOutput{ - SamplingTargetDocuments: []*samplingTargetDocument{createSamplingTargetDocument(ruleName, 0, 0.05, 10, 1500000060)}, - UnprocessedStatistics: []*unprocessedStatistic{&unprocessedStats}, - } - - m = &Manifest{ - clock: clock, - logger: testr.New(t), - } - - refresh, err = m.updateTargets(targets) - require.NoError(t, err) - - // assert refresh is false - assert.False(t, refresh) -} - -// assert that a missing sampling rule in manifest does not update it's reservoir values. -func TestUpdateReservoir(t *testing.T) { - // manifest only has rule r2 but not rule with r1 which targets just received - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - FixedRate: 0.10, - }, - reservoir: &reservoir{ - quota: 8, - refreshedAt: time.Unix(1499999990, 0), - expiresAt: time.Unix(1500000010, 0), - capacity: 50, - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - err := m.updateReservoir(createSamplingTargetDocument("r1", 0, 0.05, 10, 1500000060)) - require.NoError(t, err) - - // assert that rule reservoir value does not get updated and still same as r1 - assert.Equal(t, m.Rules[0], r1) -} - -// assert that a sampling target with missing Fixed Rate returns an error. -func TestUpdateReservoirMissingFixedRate(t *testing.T) { - // manifest rule which we're trying to update with above target st - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - FixedRate: 0.10, - }, - reservoir: &reservoir{ - quota: 8, - refreshedAt: time.Unix(1499999990, 0), - expiresAt: time.Unix(1500000010, 0), - capacity: 50, - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - st := createSamplingTargetDocument("r1", 0, 0, 10, 1500000060) - st.FixedRate = nil - err := m.updateReservoir(st) - require.Error(t, err) -} - -// assert that a sampling target with missing Rule Name returns an error. -func TestUpdateReservoirMissingRuleName(t *testing.T) { - // manifest rule which we're trying to update with above target st - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - FixedRate: 0.10, - }, - reservoir: &reservoir{ - quota: 8, - refreshedAt: time.Unix(1499999990, 0), - expiresAt: time.Unix(1500000010, 0), - capacity: 50, - }, - } - - m := &Manifest{ - Rules: []Rule{r1}, - } - - st := createSamplingTargetDocument("r1", 0, 0, 10, 1500000060) - st.RuleName = nil - err := m.updateReservoir(st) - require.Error(t, err) -} - -// assert that snapshots returns an array of valid sampling statistics. -func TestSnapshots(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - time1 := clock.now().Unix() - - name1 := "r1" - requests1 := int64(1000) - sampled1 := int64(100) - borrowed1 := int64(5) - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: name1, - }, - reservoir: &reservoir{ - interval: 10, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: requests1, - sampledRequests: sampled1, - borrowedRequests: borrowed1, - }, - } - - name2 := "r2" - requests2 := int64(500) - sampled2 := int64(10) - borrowed2 := int64(0) - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: name2, - }, - reservoir: &reservoir{ - interval: 10, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: requests2, - sampledRequests: sampled2, - borrowedRequests: borrowed2, - }, - } - - id := "c1" - m := &Manifest{ - Rules: []Rule{r1, r2}, - clientID: &id, - clock: clock, - } - - // Expected SamplingStatistics structs - ss1 := samplingStatisticsDocument{ - ClientID: &id, - RequestCount: &requests1, - RuleName: &name1, - SampledCount: &sampled1, - BorrowCount: &borrowed1, - Timestamp: &time1, - } - - ss2 := samplingStatisticsDocument{ - ClientID: &id, - RequestCount: &requests2, - RuleName: &name2, - SampledCount: &sampled2, - BorrowCount: &borrowed2, - Timestamp: &time1, - } - - statistics, err := m.snapshots() - require.NoError(t, err) - - // match time - *statistics[0].Timestamp = 1500000000 - *statistics[1].Timestamp = 1500000000 - - assert.Equal(t, ss1, *statistics[0]) - assert.Equal(t, ss2, *statistics[1]) -} - -// assert that fresh and inactive rules are not included in a snapshot. -func TestMixedSnapshots(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - id := "c1" - time1 := clock.now().Unix() - - // stale and active rule - name1 := "r1" - requests1 := int64(1000) - sampled1 := int64(100) - borrowed1 := int64(5) - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: name1, - }, - reservoir: &reservoir{ - interval: 20, - refreshedAt: time.Unix(1499999970, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: requests1, - sampledRequests: sampled1, - borrowedRequests: borrowed1, - }, - } - - // fresh and inactive rule - name2 := "r2" - requests2 := int64(0) - sampled2 := int64(0) - borrowed2 := int64(0) - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: name2, - }, - reservoir: &reservoir{ - interval: 20, - refreshedAt: time.Unix(1499999990, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: requests2, - sampledRequests: sampled2, - borrowedRequests: borrowed2, - }, - } - - // fresh rule - name3 := "r3" - requests3 := int64(1000) - sampled3 := int64(100) - borrowed3 := int64(5) - - r3 := Rule{ - ruleProperties: ruleProperties{ - RuleName: name3, - }, - reservoir: &reservoir{ - interval: 20, - refreshedAt: time.Unix(1499999990, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: requests3, - sampledRequests: sampled3, - borrowedRequests: borrowed3, - }, - } - - rules := []Rule{r1, r2, r3} - - m := &Manifest{ - clientID: &id, - clock: clock, - Rules: rules, - } - - ss1 := samplingStatisticsDocument{ - ClientID: &id, - RequestCount: &requests1, - RuleName: &name1, - SampledCount: &sampled1, - BorrowCount: &borrowed1, - Timestamp: &time1, - } - - statistics, err := m.snapshots() - require.NoError(t, err) - - // assert that only inactive rules are added to the statistics - require.Len(t, statistics, 1) - assert.Equal(t, ss1, *statistics[0]) -} - -// assert that deep copy creates a new manifest object with new address space. -func TestDeepCopy(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 100, - Host: "http://127.0.0.0.1:2020", - HTTPMethod: "POST", - URLPath: "/test", - ReservoirSize: 100, - FixedRate: 0.09, - Version: 1, - ServiceName: "openTelemetry", - ResourceARN: "*", - ServiceType: "local", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 100, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: int64(5), - borrowedRequests: int64(1), - sampledRequests: int64(3), - }, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - Priority: 10, - Host: "http://127.0.0.0.1:2020", - HTTPMethod: "GET", - URLPath: "/test/path", - ReservoirSize: 100, - FixedRate: 0.09, - Version: 1, - ServiceName: "x-ray", - ResourceARN: "*", - ServiceType: "local", - Attributes: map[string]string{}, - }, - reservoir: &reservoir{ - capacity: 100, - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: int64(5), - borrowedRequests: int64(1), - sampledRequests: int64(3), - }, - } - - clock := &mockClock{ - nowTime: 1500000000, - } - - m := &Manifest{ - Rules: []Rule{r1, r2}, - SamplingTargetsPollingInterval: 10 * time.Second, - refreshedAt: time.Unix(1500000, 0), - xrayClient: createTestClient(t, []byte(`hello world!`)), - logger: testr.New(t), - clock: clock, - } - - manifest := m.deepCopy() - - require.Len(t, m.Rules, 2) - require.Len(t, manifest.Rules, 2) - - assert.Equal(t, &m.xrayClient, &manifest.xrayClient) - - assert.NotSame(t, &m.clock, &manifest.clock) - assert.NotSame(t, &m.refreshedAt, &manifest.refreshedAt) - assert.NotSame(t, &m.SamplingTargetsPollingInterval, &manifest.SamplingTargetsPollingInterval) - assert.NotSame(t, &m.logger, &manifest.logger) - assert.NotSame(t, &m.mu, &manifest.mu) - - // rule properties has different address space in m and manifest - assert.NotSame(t, &m.Rules[0].ruleProperties.RuleName, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.ServiceName, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.ServiceType, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.Host, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.HTTPMethod, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.URLPath, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.FixedRate, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.ReservoirSize, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.ResourceARN, &manifest.Rules[0].ruleProperties.RuleName) - assert.NotSame(t, &m.Rules[0].ruleProperties.Priority, &manifest.Rules[0].ruleProperties.Priority) - assert.NotSame(t, &m.Rules[0].ruleProperties.Version, &manifest.Rules[0].ruleProperties.Version) - assert.NotSame(t, &m.Rules[0].ruleProperties.Attributes, &manifest.Rules[0].ruleProperties.Attributes) - - // reservoir has different address space in m and manifest - assert.NotSame(t, &m.Rules[0].reservoir.refreshedAt, &manifest.Rules[0].reservoir.refreshedAt) - assert.NotSame(t, &m.Rules[0].reservoir.expiresAt, &manifest.Rules[0].reservoir.expiresAt) - assert.NotSame(t, &m.Rules[0].reservoir.lastTick, &manifest.Rules[0].reservoir.lastTick) - assert.NotSame(t, &m.Rules[0].reservoir.interval, &manifest.Rules[0].reservoir.interval) - assert.NotSame(t, &m.Rules[0].reservoir.capacity, &manifest.Rules[0].reservoir.capacity) - assert.NotSame(t, &m.Rules[0].reservoir.quota, &manifest.Rules[0].reservoir.quota) - assert.NotSame(t, &m.Rules[0].reservoir.quotaBalance, &manifest.Rules[0].reservoir.quotaBalance) - - // samplings statistics has same address space since it is a pointer - assert.Equal(t, &m.Rules[0].samplingStatistics, &manifest.Rules[0].samplingStatistics) -} - -// assert that sorting an unsorted array results in a sorted array - check priority. -func TestSortBasedOnPriority(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 5, - }, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - Priority: 6, - }, - } - - r3 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r3", - Priority: 7, - }, - } - - // Unsorted rules array - rules := []Rule{r2, r1, r3} - - m := &Manifest{ - Rules: rules, - } - - // Sort array - m.sort() - - // Assert on order - assert.Equal(t, r1, m.Rules[0]) - assert.Equal(t, r2, m.Rules[1]) - assert.Equal(t, r3, m.Rules[2]) -} - -// assert that sorting an unsorted array results in a sorted array - check priority and rule name. -func TestSortBasedOnRuleName(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 5, - }, - } - - r2 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r2", - Priority: 5, - }, - } - - r3 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r3", - Priority: 7, - }, - } - - // Unsorted rules array - rules := []Rule{r2, r1, r3} - - m := &Manifest{ - Rules: rules, - } - - // Sort array - m.sort() - - // Assert on order - assert.Equal(t, r1, m.Rules[0]) - assert.Equal(t, r2, m.Rules[1]) - assert.Equal(t, r3, m.Rules[2]) -} - -// asserts the minimum value of all the targets. -func TestMinPollInterval(t *testing.T) { - r1 := Rule{reservoir: &reservoir{interval: time.Duration(10)}} - r2 := Rule{reservoir: &reservoir{interval: time.Duration(5)}} - r3 := Rule{reservoir: &reservoir{interval: time.Duration(25)}} - - rules := []Rule{r1, r2, r3} - m := &Manifest{Rules: rules} - - minPoll := m.minimumPollingInterval() - - assert.Equal(t, 5*time.Second, minPoll) -} - -// asserts the minimum value of all the targets when some targets has 0 interval. -func TestMinPollIntervalZeroCase(t *testing.T) { - r1 := Rule{reservoir: &reservoir{interval: time.Duration(0)}} - r2 := Rule{reservoir: &reservoir{interval: time.Duration(0)}} - r3 := Rule{reservoir: &reservoir{interval: time.Duration(5)}} - - rules := []Rule{r1, r2, r3} - m := &Manifest{Rules: rules} - - minPoll := m.minimumPollingInterval() - - assert.Equal(t, 0*time.Second, minPoll) -} - -// asserts the minimum value of all the targets when some targets has negative interval. -func TestMinPollIntervalNegativeCase(t *testing.T) { - r1 := Rule{reservoir: &reservoir{interval: time.Duration(-5)}} - r2 := Rule{reservoir: &reservoir{interval: time.Duration(0)}} - r3 := Rule{reservoir: &reservoir{interval: time.Duration(0)}} - - rules := []Rule{r1, r2, r3} - m := &Manifest{Rules: rules} - - minPoll := m.minimumPollingInterval() - - assert.Equal(t, -5*time.Second, minPoll) -} - -// asserts that manifest with empty rules return 0. -func TestMinPollIntervalNoRules(t *testing.T) { - var rules []Rule - m := &Manifest{Rules: rules} - - minPoll := m.minimumPollingInterval() - - assert.Equal(t, 0*time.Second, minPoll) -} - -// assert that able to successfully generate the client ID. -func TestGenerateClientID(t *testing.T) { - clientID, err := generateClientID() - require.NoError(t, err) - assert.NotEmpty(t, clientID) -} - -// validate no data race is happening when updating rule properties in manifest while matching. -func TestUpdatingRulesWhileMatchingConcurrentSafe(t *testing.T) { - // getSamplingRules response - ruleRecords := samplingRuleRecords{ - SamplingRule: &ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - } - - s := &getSamplingRulesOutput{ - SamplingRuleRecords: []*samplingRuleRecords{&ruleRecords}, - } - - // existing rule in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "test", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - expiresAt: time.Unix(14050, 0), - }, - } - - rules := []Rule{r1} - - clock := &mockClock{ - nowTime: 1500000000, - } - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - // async rule updates - done := make(chan struct{}) - go func() { - defer close(done) - for i := 0; i < 100; i++ { - m.updateRules(s) - time.Sleep(time.Millisecond) - } - }() - - // matching logic - for i := 0; i < 100; i++ { - _, match, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{}, "helios", "macos") - require.NoError(t, err) - require.False(t, match) - } - <-done -} - -// validate no data race is happening when updating rule properties and rule targets in manifest while matching. -func TestUpdatingRulesAndTargetsWhileMatchingConcurrentSafe(t *testing.T) { - // getSamplingRules response to update existing manifest rule - ruleRecords := samplingRuleRecords{ - SamplingRule: &ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - } - - // existing rule already present in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "test", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - refreshedAt: time.Unix(13000000, 0), - }, - } - clock := &mockClock{ - nowTime: 1500000000, - } - - rules := []Rule{r1} - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - var wg sync.WaitGroup - - // async rule updates - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < 100; i++ { - m.updateRules(&getSamplingRulesOutput{ - SamplingRuleRecords: []*samplingRuleRecords{&ruleRecords}, - }) - time.Sleep(time.Millisecond) - } - }() - - // async target updates - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < 100; i++ { - manifest := m.deepCopy() - - err := manifest.updateReservoir(createSamplingTargetDocument("r1", 0, 0.05, 10, 13000000)) - assert.NoError(t, err) - time.Sleep(time.Millisecond) - - m.mu.Lock() - m.Rules = manifest.Rules - m.mu.Unlock() - } - }() - - // matching logic - for i := 0; i < 100; i++ { - _, match, err := m.MatchAgainstManifestRules(sdktrace.SamplingParameters{}, "helios", "macos") - require.NoError(t, err) - require.False(t, match) - time.Sleep(time.Millisecond) - } - - wg.Wait() -} - -// Validate Rules are preserved when a rule is updated with the same ruleProperties. -func TestPreserveRulesWithSameRuleProperties(t *testing.T) { - // getSamplingRules response to update existing manifest rule, with matching ruleProperties - ruleRecords := samplingRuleRecords{ - SamplingRule: &ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - } - - // existing rule already present in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - capacity: 100, - quota: 100, - quotaBalance: 80, - refreshedAt: time.Unix(13000000, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: 500, - sampledRequests: 10, - borrowedRequests: 0, - }, - } - clock := &mockClock{ - nowTime: 1500000000, - } - - rules := []Rule{r1} - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - // Update rules - m.updateRules(&getSamplingRulesOutput{ - SamplingRuleRecords: []*samplingRuleRecords{&ruleRecords}, - }) - - require.Equal(t, r1.reservoir, m.Rules[0].reservoir) - require.Equal(t, r1.samplingStatistics, m.Rules[0].samplingStatistics) -} - -// Validate Rules are NOT preserved when a rule is updated with a different ruleProperties with the same RuleName. -func TestDoNotPreserveRulesWithDifferentRuleProperties(t *testing.T) { - // getSamplingRules response to update existing manifest rule, with different ruleProperties - ruleRecords := samplingRuleRecords{ - SamplingRule: &ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - } - - // existing rule already present in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10001, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - capacity: 100, - quota: 100, - quotaBalance: 80, - refreshedAt: time.Unix(13000000, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: 500, - sampledRequests: 10, - borrowedRequests: 0, - }, - } - clock := &mockClock{ - nowTime: 1500000000, - } - - rules := []Rule{r1} - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - // Update rules - m.updateRules(&getSamplingRulesOutput{ - SamplingRuleRecords: []*samplingRuleRecords{&ruleRecords}, - }) - - require.Equal(t, 0.0, m.Rules[0].reservoir.quota) - require.Equal(t, 0.0, m.Rules[0].reservoir.quotaBalance) - require.Equal(t, samplingStatistics{ - matchedRequests: 0, - sampledRequests: 0, - borrowedRequests: 0, - }, *m.Rules[0].samplingStatistics) -} - -// validate no data race is when capturing sampling statistics in manifest while sampling. -func TestUpdatingSamplingStatisticsWhenSamplingConcurrentSafe(t *testing.T) { - // existing rule already present in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "test", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - refreshedAt: time.Unix(15000000, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: 5, - borrowedRequests: 0, - sampledRequests: 0, - }, - } - clock := &mockClock{ - nowTime: 18000000, - } - - rules := []Rule{r1} - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - // async snapshot updates - done := make(chan struct{}) - go func() { - defer close(done) - for i := 0; i < 100; i++ { - manifest := m.deepCopy() - - _, err := manifest.snapshots() - assert.NoError(t, err) - - m.mu.Lock() - m.Rules = manifest.Rules - m.mu.Unlock() - time.Sleep(time.Millisecond) - } - }() - - // sampling logic - for i := 0; i < 100; i++ { - _ = r1.Sample(sdktrace.SamplingParameters{}, time.Unix(clock.nowTime+int64(i), 0)) - time.Sleep(time.Millisecond) - } - <-done -} diff --git a/samplers/aws/xray/internal/match.go b/samplers/aws/xray/internal/match.go deleted file mode 100644 index bf90b573ccf..00000000000 --- a/samplers/aws/xray/internal/match.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "fmt" - "regexp" - "strings" -) - -// wildcardMatch returns true if text matches pattern at the given case-sensitivity; returns false otherwise. -func wildcardMatch(pattern, text string) (bool, error) { - patternLen := len(pattern) - textLen := len(text) - if patternLen == 0 { - return textLen == 0, nil - } - - if pattern == "*" { - return true, nil - } - - pattern = strings.ToLower(pattern) - text = strings.ToLower(text) - - match, err := regexp.MatchString(toRegexPattern(pattern), text) - if err != nil { - return false, fmt.Errorf("wildcardMatch: unable to perform regex matching: %w", err) - } - - return match, nil -} - -func toRegexPattern(pattern string) string { - tokenStart := -1 - var result strings.Builder - for i, char := range pattern { - if string(char) == "*" || string(char) == "?" { - if tokenStart != -1 { - _, _ = result.WriteString(regexp.QuoteMeta(pattern[tokenStart:i])) - tokenStart = -1 - } - - if string(char) == "*" { - _, _ = result.WriteString(".*") - } else { - _, _ = result.WriteString(".") - } - } else { - if tokenStart == -1 { - tokenStart = i - } - } - } - if tokenStart != -1 { - _, _ = result.WriteString(regexp.QuoteMeta(pattern[tokenStart:])) - } - return result.String() -} diff --git a/samplers/aws/xray/internal/match_test.go b/samplers/aws/xray/internal/match_test.go deleted file mode 100644 index c1734e97cf5..00000000000 --- a/samplers/aws/xray/internal/match_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "math/rand" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// assert wildcard match is positive. -func TestWildCardMatchPositive(t *testing.T) { - tests := []struct { - pattern string - text string - }{ - // wildcard positive test set - {"*", ""}, - {"foo", "foo"}, - {"foo*bar*?", "foodbaris"}, - {"?o?", "foo"}, - {"*oo", "foo"}, - {"foo*", "foo"}, - {"*o?", "foo"}, - {"*", "boo"}, - {"", ""}, - {"a", "a"}, - {"*a", "a"}, - {"*a", "ba"}, - {"a*", "a"}, - {"a*", "ab"}, - {"a*a", "aa"}, - {"a*a", "aba"}, - {"a*a*", "aaaaaaaaaaaaaaaaaaaaaaa"}, - { - "a*b*a*b*a*b*a*b*a*", - "akljd9gsdfbkjhaabajkhbbyiaahkjbjhbuykjakjhabkjhbabjhkaabbabbaaakljdfsjklababkjbsdabab", - }, - {"a*na*ha", "anananahahanahana"}, - {"***a", "a"}, - {"**a**", "a"}, - {"a**b", "ab"}, - {"*?", "a"}, - {"*??", "aa"}, - {"*?", "a"}, - {"*?*a*", "ba"}, - {"?at", "bat"}, - {"?at", "cat"}, - {"?o?se", "horse"}, - {"?o?se", "mouse"}, - {"*s", "horse"}, - {"J*", "Jeep"}, - {"J*", "jeep"}, - {"*/foo", "/bar/foo"}, - } - - for _, test := range tests { - match, err := wildcardMatch(test.pattern, test.text) - require.NoError(t, err) - assert.True(t, match, test.text) - } -} - -// assert wildcard match is negative. -func TestWildCardMatchNegative(t *testing.T) { - tests := []struct { - pattern string - text string - }{ - // wildcard negative test set - {"", "whatever"}, - {"foo", "bar"}, - {"f?o", "boo"}, - {"f??", "boo"}, - {"fo*", "boo"}, - {"f?*", "boo"}, - {"abcd", "abc"}, - {"??", "a"}, - {"??", "a"}, - {"*?*a", "a"}, - } - - for _, test := range tests { - match, err := wildcardMatch(test.pattern, test.text) - require.NoError(t, err) - assert.False(t, match) - } -} - -func TestLongStrings(t *testing.T) { - chars := []byte{'a', 'b', 'c', 'd'} - text := bytes.NewBufferString("a") - for i := 0; i < 8192; i++ { - _, _ = text.WriteString(string(chars[rand.Intn(len(chars))])) - } - _, _ = text.WriteString("b") - - match, err := wildcardMatch("a*b", text.String()) - require.NoError(t, err) - assert.True(t, match) -} diff --git a/samplers/aws/xray/internal/reservoir.go b/samplers/aws/xray/internal/reservoir.go deleted file mode 100644 index 62412f6e51d..00000000000 --- a/samplers/aws/xray/internal/reservoir.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "sync" - "time" -) - -// reservoir represents a sampling statistics for a given rule and populate it's value from -// the response getSamplingTargets API which sends information on sampling statistics in real-time. -type reservoir struct { - // Quota expiration timestamp. - expiresAt time.Time - - // Quota assigned to client to consume per second. - quota float64 - - // Current balance of quota. - quotaBalance float64 - - // Total size of reservoir consumption per second. - capacity float64 - - // Quota refresh timestamp. - refreshedAt time.Time - - // Polling interval for quota. - interval time.Duration - - // Stores reservoir ticks. - lastTick time.Time - - mu sync.RWMutex -} - -// expired returns true if current time is past expiration timestamp. Otherwise, false is returned if no quota remains. -func (r *reservoir) expired(now time.Time) bool { - r.mu.RLock() - defer r.mu.RUnlock() - - return now.After(r.expiresAt) -} - -// take consumes quota from reservoir, if any remains, then returns true. False otherwise. -func (r *reservoir) take(now time.Time, borrowed bool, itemCost float64) bool { // nolint:unparam,revive // borrowed is not a control flag. - r.mu.Lock() - defer r.mu.Unlock() - - if r.capacity == 0 { - return false - } - - if r.lastTick.IsZero() { - r.lastTick = now - - if borrowed { - r.quotaBalance = 1.0 - } else { - r.quotaBalance = r.quota - } - } - - if r.quotaBalance >= itemCost { - r.quotaBalance -= itemCost - return true - } - - // update quota balance based on elapsed time - r.refreshQuotaBalanceLocked(now, borrowed) - - if r.quotaBalance >= itemCost { - r.quotaBalance -= itemCost - return true - } - - return false -} - -// refreshQuotaBalanceLocked refreshes the quotaBalance. If borrowed is true then add to the quota balance 1 by every second, -// otherwise add to the quota balance based on assigned quota by X-Ray service. -// It is assumed the lock is held when calling this. -func (r *reservoir) refreshQuotaBalanceLocked(now time.Time, borrowed bool) { // nolint: revive // borrowed is not a control flag. - elapsedTime := now.Sub(r.lastTick) - r.lastTick = now - - // Calculate how much credit have we accumulated since the last tick. - if borrowed { - // In borrowing case since we want to enforce sample one req every second, no need to accumulate - // quotaBalance based on elapsedTime when elapsedTime is greater than 1. - if elapsedTime.Seconds() > 1.0 { - r.quotaBalance += 1.0 - } else { - r.quotaBalance += elapsedTime.Seconds() - } - } else { - elapsedSeconds := elapsedTime.Seconds() - r.quotaBalance += elapsedSeconds * r.quota - - if r.quotaBalance > r.quota { - r.quotaBalance = r.quota - } - } -} diff --git a/samplers/aws/xray/internal/reservoir_test.go b/samplers/aws/xray/internal/reservoir_test.go deleted file mode 100644 index e334f5b26c2..00000000000 --- a/samplers/aws/xray/internal/reservoir_test.go +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// assert that reservoir quota is expired. -func TestExpiredReservoir(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000001, - } - - expiresAt := time.Unix(1500000000, 0) - r := &reservoir{ - expiresAt: expiresAt, - } - - expired := r.expired(clock.now()) - - assert.True(t, expired) -} - -// assert that reservoir quota is still expired since now time is equal to expiresAt time. -func TestExpiredReservoirSameAsClockTime(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - expiresAt := time.Unix(1500000000, 0) - - r := &reservoir{ - expiresAt: expiresAt, - } - - assert.False(t, r.expired(clock.now())) -} - -// assert that borrow only 1 req/sec. -func TestBorrowEverySecond(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - r := &reservoir{ - capacity: 10, - } - - s := r.take(clock.now(), true, 1.0) - assert.True(t, s) - - s = r.take(clock.now(), true, 1.0) - assert.False(t, s) - - // Increment clock by 1 - clock = &mockClock{ - nowTime: 1500000001, - } - - s = r.take(clock.now(), true, 1.0) - assert.True(t, s) -} - -// assert that when reservoir is expired we consume from quota is 1 and then -// when reservoir is not expired consume from assigned quota by X-Ray service. -func TestConsumeFromBorrowConsumeFromQuota(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - r := &reservoir{ - quota: 2, - capacity: 10, - } - - s := r.take(clock.now(), true, 1.0) - assert.True(t, s) - - s = r.take(clock.now(), true, 1.0) - assert.False(t, s) - - // Increment clock by 1 - clock = &mockClock{ - nowTime: 1500000001, - } - - s = r.take(clock.now(), true, 1.0) - assert.True(t, s) - - // Increment clock by 1 - clock = &mockClock{ - nowTime: 1500000002, - } - - s = r.take(clock.now(), false, 1.0) - assert.True(t, s) - - s = r.take(clock.now(), false, 1.0) - assert.True(t, s) - - s = r.take(clock.now(), false, 1.0) - assert.False(t, s) -} - -// assert that we can still borrowing from reservoir is possible since assigned quota is available to consume -// and it will increase used count. -func TestConsumeFromReservoir(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - r := &reservoir{ - quota: 2, - capacity: 100, - } - - // reservoir updates the quotaBalance for new second and allows to consume - // quota balance is 0 because we are consuming from reservoir for the first time - assert.Equal(t, 0.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 1.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 0.0, r.quotaBalance) - // once assigned quota is consumed reservoir does not allow to consume in that second - assert.False(t, r.take(clock.now(), false, 1.0)) - - // increase the clock by 1 - clock.nowTime = 1500000001 - - // reservoir updates the quotaBalance for new second and allows to consume - assert.Equal(t, 0.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 1.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 0.0, r.quotaBalance) - // once assigned quota is consumed reservoir does not allow to consume in that second - assert.False(t, r.take(clock.now(), false, 1.0)) - - // increase the clock by 4 - clock.nowTime = 1500000005 - - // reservoir updates the quotaBalance with one second worth of quota (even though 4 seconds have passed) and allows to consume - assert.Equal(t, 0.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 1.0, r.quotaBalance) - assert.True(t, r.take(clock.now(), false, 1.0)) - assert.Equal(t, 0.0, r.quotaBalance) -} - -func TestZeroCapacityFailBorrow(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - r := &reservoir{ - quota: 0, - capacity: 0, - } - - // start with no quota balance - assert.Equal(t, 0.0, r.quotaBalance) - // attempt to borrow from reservoir, and should fail since there is no capacity - assert.False(t, r.take(clock.now(), true, 1.0)) - - // increase the clock by 5 - clock.nowTime = 1500000005 - - // validate there is still no quota balance - assert.Equal(t, 0.0, r.quotaBalance) - // again, attempt to borrow from reservoir, and should fail since there is no capacity - assert.False(t, r.take(clock.now(), true, 1.0)) -} - -func TestResetQuotaUsageRotation(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000000, - } - - r := &reservoir{ - quota: 5, - capacity: 100, - } - - // consume quota for second - for i := 0; i < 5; i++ { - assert.True(t, r.take(clock.now(), false, 1.0)) - } - - // take() should be false since no unused quota left - taken := r.take(clock.now(), false, 1.0) - assert.False(t, taken) - - // increment epoch to reset unused quota - clock = &mockClock{ - nowTime: 1500000001, - } - - // take() should be true since unused quota is available - assert.True(t, r.take(clock.now(), false, 1.0)) -} - -// assert that when quotaBalance is assigned the correct value after a portion of a second. -func TestQuotaBalanceAfterPortionOfSecond(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000002, - } - - r := &reservoir{ - quota: 6, - capacity: 6, - lastTick: time.Unix(1500000001, 500000000), - } - - r.refreshQuotaBalanceLocked(clock.now(), false) - - // assert that after half a second, quotaBalance is now quota*0.5 = 3 - assert.Equal(t, 3.0, r.quotaBalance) -} - -// assert quotaBalance and capacity of borrowing case. -func TestQuotaBalanceBorrow(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000001, - } - - r := &reservoir{ - quota: 6, - capacity: 5, - lastTick: time.Unix(1500000000, 0), - } - - r.refreshQuotaBalanceLocked(clock.now(), true) - - // assert that if quotaBalance exceeds capacity then total capacity would be new quotaBalance - assert.Equal(t, 1.0, r.quotaBalance) -} - -// assert that when borrow is true and elapsedTime is greater than 1, then we only increase the quota balance by 1. -func TestQuotaBalanceIncreaseByOneBorrowCase(t *testing.T) { - clock := &mockClock{ - nowTime: 1500000002, - } - - r := &reservoir{ - quota: 6, - capacity: 5, - quotaBalance: 0.25, - lastTick: time.Unix(1500000000, 0), - } - - r.refreshQuotaBalanceLocked(clock.now(), true) - - assert.Equal(t, 1.25, r.quotaBalance) -} diff --git a/samplers/aws/xray/internal/rule.go b/samplers/aws/xray/internal/rule.go deleted file mode 100644 index 003ea6fc233..00000000000 --- a/samplers/aws/xray/internal/rule.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - -import ( - "sync/atomic" - "time" - - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/trace" -) - -// Rule represents a sampling rule which contains rule properties and reservoir which keeps tracks of sampling statistics of a rule. -type Rule struct { - samplingStatistics *samplingStatistics - - // reservoir has equivalent fields to store what we receive from service API getSamplingTargets. - // https://docs.aws.amazon.com/xray/latest/api/API_GetSamplingTargets.html - reservoir *reservoir - - // ruleProperty is equivalent to what we receive from service API getSamplingRules. - // https://docs.aws.amazon.com/cli/latest/reference/xray/get-sampling-rules.html - ruleProperties ruleProperties -} - -type samplingStatistics struct { - // matchedRequests is the number of requests matched against specific rule. - matchedRequests int64 - - // sampledRequests is the number of requests sampled using specific rule. - sampledRequests int64 - - // borrowedRequests is the number of requests borrowed using specific rule. - borrowedRequests int64 -} - -// stale checks if targets (sampling stats) for a given rule is expired or not. -func (r *Rule) stale(now time.Time) bool { - matchedRequests := atomic.LoadInt64(&r.samplingStatistics.matchedRequests) - - reservoirRefreshTime := r.reservoir.refreshedAt.Add(r.reservoir.interval * time.Second) - return matchedRequests != 0 && now.After(reservoirRefreshTime) -} - -// snapshot takes a snapshot of the sampling statistics counters, returning -// samplingStatisticsDocument. It also resets statistics counters. -func (r *Rule) snapshot(now time.Time) *samplingStatisticsDocument { - name := r.ruleProperties.RuleName - - matchedRequests := atomic.SwapInt64(&r.samplingStatistics.matchedRequests, int64(0)) - sampledRequests := atomic.SwapInt64(&r.samplingStatistics.sampledRequests, int64(0)) - borrowedRequest := atomic.SwapInt64(&r.samplingStatistics.borrowedRequests, int64(0)) - - timeStamp := now.Unix() - return &samplingStatisticsDocument{ - RequestCount: &matchedRequests, - SampledCount: &sampledRequests, - BorrowCount: &borrowedRequest, - RuleName: &name, - Timestamp: &timeStamp, - } -} - -// Sample uses sampling targets of a given rule to decide -// which sampling should be done and returns a SamplingResult. -func (r *Rule) Sample(parameters sdktrace.SamplingParameters, now time.Time) sdktrace.SamplingResult { - sd := sdktrace.SamplingResult{ - Tracestate: trace.SpanContextFromContext(parameters.ParentContext).TraceState(), - } - - atomic.AddInt64(&r.samplingStatistics.matchedRequests, int64(1)) - - // Fallback sampling logic if quota for a given rule is expired. - if r.reservoir.expired(now) { - // Borrowing one request every second. - if r.reservoir.take(now, true, 1.0) { - atomic.AddInt64(&r.samplingStatistics.borrowedRequests, int64(1)) - - sd.Decision = sdktrace.RecordAndSample - return sd - } - - // Using traceIDRatioBased sampler to sample using fixed rate. - sd = sdktrace.TraceIDRatioBased(r.ruleProperties.FixedRate).ShouldSample(parameters) - - if sd.Decision == sdktrace.RecordAndSample { - atomic.AddInt64(&r.samplingStatistics.sampledRequests, int64(1)) - } - - return sd - } - - // Take from reservoir quota, if quota is available for that second. - if r.reservoir.take(now, false, 1.0) { - atomic.AddInt64(&r.samplingStatistics.sampledRequests, int64(1)) - sd.Decision = sdktrace.RecordAndSample - - return sd - } - - // using traceIDRatioBased sampler to sample using fixed rate - sd = sdktrace.TraceIDRatioBased(r.ruleProperties.FixedRate).ShouldSample(parameters) - - if sd.Decision == sdktrace.RecordAndSample { - atomic.AddInt64(&r.samplingStatistics.sampledRequests, int64(1)) - } - - return sd -} - -// appliesTo performs a matching against rule properties to see -// if a given rule does match with any of the rule set on AWS X-Ray console. -func (r *Rule) appliesTo(parameters sdktrace.SamplingParameters, serviceName string, cloudPlatform string) (bool, error) { - var httpTarget string - var httpURL string - var httpHost string - var httpMethod string - var HTTPURLPathMatcher bool - - if parameters.Attributes != nil { - for _, attrs := range parameters.Attributes { - if attrs.Key == "http.target" { - httpTarget = attrs.Value.AsString() - } - if attrs.Key == "http.url" { - httpURL = attrs.Value.AsString() - } - if attrs.Key == "http.host" { - httpHost = attrs.Value.AsString() - } - if attrs.Key == "http.method" { - httpMethod = attrs.Value.AsString() - } - } - } - - // Attributes and other HTTP span attributes matching. - attributeMatcher, err := r.attributeMatching(parameters) - if err != nil { - return attributeMatcher, err - } - - if !attributeMatcher { - return attributeMatcher, nil - } - - serviceNameMatcher, err := wildcardMatch(r.ruleProperties.ServiceName, serviceName) - if err != nil { - return serviceNameMatcher, err - } - - if !serviceNameMatcher { - return serviceNameMatcher, nil - } - - serviceTypeMatcher, err := wildcardMatch(r.ruleProperties.ServiceType, cloudPlatform) - if err != nil { - return serviceTypeMatcher, err - } - - if !serviceTypeMatcher { - return serviceTypeMatcher, nil - } - - HTTPMethodMatcher, err := wildcardMatch(r.ruleProperties.HTTPMethod, httpMethod) - if err != nil { - return HTTPMethodMatcher, err - } - - if !HTTPMethodMatcher { - return HTTPMethodMatcher, nil - } - - HTTPHostMatcher, err := wildcardMatch(r.ruleProperties.Host, httpHost) - if err != nil { - return HTTPHostMatcher, err - } - - if !HTTPHostMatcher { - return HTTPHostMatcher, nil - } - - if httpURL != "" { - HTTPURLPathMatcher, err = wildcardMatch(r.ruleProperties.URLPath, httpURL) - if err != nil { - return HTTPURLPathMatcher, err - } - - if !HTTPURLPathMatcher { - return HTTPURLPathMatcher, nil - } - } else { - HTTPURLPathMatcher, err = wildcardMatch(r.ruleProperties.URLPath, httpTarget) - if err != nil { - return HTTPURLPathMatcher, err - } - - if !HTTPURLPathMatcher { - return HTTPURLPathMatcher, nil - } - } - - return true, nil -} - -// attributeMatching performs a match on attributes set by users on AWS X-Ray console. -func (r *Rule) attributeMatching(parameters sdktrace.SamplingParameters) (bool, error) { - match := false - var err error - - if len(r.ruleProperties.Attributes) == 0 { - return true, nil - } - - for key, value := range r.ruleProperties.Attributes { - unmatchedCounter := 0 - for _, attrs := range parameters.Attributes { - if key == string(attrs.Key) { - match, err = wildcardMatch(value, attrs.Value.AsString()) - if err != nil { - return false, err - } - - if !match { - return false, nil - } - } else { - unmatchedCounter++ - } - } - if unmatchedCounter == len(parameters.Attributes) { - return false, nil - } - } - - return match, nil -} diff --git a/samplers/aws/xray/internal/rule_test.go b/samplers/aws/xray/internal/rule_test.go deleted file mode 100644 index a4ce856ec66..00000000000 --- a/samplers/aws/xray/internal/rule_test.go +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/trace" - - "github.com/stretchr/testify/assert" -) - -// assert that rule is active but stale due to quota is expired. -func TestStaleRule(t *testing.T) { - refreshedAt := time.Unix(1500000000, 0) - r1 := Rule{ - samplingStatistics: &samplingStatistics{ - matchedRequests: 5, - }, - reservoir: &reservoir{ - refreshedAt: refreshedAt, - interval: 10, - }, - } - - now := time.Unix(1500000020, 0) - s := r1.stale(now) - assert.True(t, s) -} - -// assert that rule is active and not stale. -func TestFreshRule(t *testing.T) { - refreshedAt := time.Unix(1500000000, 0) - r1 := Rule{ - samplingStatistics: &samplingStatistics{ - matchedRequests: 5, - }, - reservoir: &reservoir{ - refreshedAt: refreshedAt, - interval: 10, - }, - } - - now := time.Unix(1500000009, 0) - s := r1.stale(now) - assert.False(t, s) -} - -// assert that rule is inactive but not stale. -func TestInactiveRule(t *testing.T) { - refreshedAt := time.Unix(1500000000, 0) - r1 := Rule{ - samplingStatistics: &samplingStatistics{ - matchedRequests: 0, - }, - reservoir: &reservoir{ - refreshedAt: refreshedAt, - interval: 10, - }, - } - - now := time.Unix(1500000011, 0) - s := r1.stale(now) - assert.False(t, s) -} - -// assert on snapshot of sampling statistics counters. -func TestSnapshot(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: 100, - sampledRequests: 12, - borrowedRequests: 2, - }, - } - - now := time.Unix(1500000000, 0) - ss := r1.snapshot(now) - - // assert counters were reset - assert.Equal(t, int64(0), r1.samplingStatistics.matchedRequests) - assert.Equal(t, int64(0), r1.samplingStatistics.sampledRequests) - assert.Equal(t, int64(0), r1.samplingStatistics.borrowedRequests) - - // assert on SamplingStatistics counters - assert.Equal(t, int64(100), *ss.RequestCount) - assert.Equal(t, int64(12), *ss.SampledCount) - assert.Equal(t, int64(2), *ss.BorrowCount) - assert.Equal(t, "r1", *ss.RuleName) -} - -// assert that reservoir is expired, borrowing 1 req during that second. -func TestExpiredReservoirBorrowSample(t *testing.T) { - r1 := Rule{ - reservoir: &reservoir{ - expiresAt: time.Unix(1500000060, 0), - capacity: 10, - }, - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.06, - }, - samplingStatistics: &samplingStatistics{}, - } - - now := time.Unix(1500000062, 0) - sd := r1.Sample(trace.SamplingParameters{}, now) - - assert.Equal(t, trace.RecordAndSample, sd.Decision) - assert.Equal(t, int64(1), r1.samplingStatistics.borrowedRequests) - assert.Equal(t, int64(0), r1.samplingStatistics.sampledRequests) - assert.Equal(t, int64(1), r1.samplingStatistics.matchedRequests) -} - -// assert that reservoir is expired, borrowed 1 req during that second so now using traceIDRatioBased sampler. -func TestExpiredReservoirTraceIDRationBasedSample(t *testing.T) { - r1 := Rule{ - reservoir: &reservoir{ - expiresAt: time.Unix(1500000060, 0), - capacity: 10, - lastTick: time.Unix(1500000061, 0), - }, - ruleProperties: ruleProperties{ - RuleName: "r1", - FixedRate: 0.06, - }, - samplingStatistics: &samplingStatistics{}, - } - - now := time.Unix(1500000061, 0) - sd := r1.Sample(trace.SamplingParameters{}, now) - - assert.NotEmpty(t, sd.Decision) - assert.Equal(t, int64(0), r1.samplingStatistics.borrowedRequests) - assert.Equal(t, int64(1), r1.samplingStatistics.sampledRequests) - assert.Equal(t, int64(1), r1.samplingStatistics.matchedRequests) -} - -// assert that reservoir is not expired, quota is available so consuming from quota. -func TestConsumeFromReservoirSample(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - }, - reservoir: &reservoir{ - capacity: 10, - quota: 10, - expiresAt: time.Unix(1500000060, 0), - }, - samplingStatistics: &samplingStatistics{}, - } - - now := time.Unix(1500000000, 0) - sd := r1.Sample(trace.SamplingParameters{}, now) - - assert.Equal(t, trace.RecordAndSample, sd.Decision) - assert.Equal(t, int64(1), r1.samplingStatistics.sampledRequests) - assert.Equal(t, int64(0), r1.samplingStatistics.borrowedRequests) - assert.Equal(t, int64(1), r1.samplingStatistics.matchedRequests) -} - -// assert that sampling using traceIDRationBasedSampler when reservoir quota is consumed. -func TestTraceIDRatioBasedSamplerReservoirIsConsumedSample(t *testing.T) { - r1 := Rule{ - reservoir: &reservoir{ - quota: 10, - expiresAt: time.Unix(1500000060, 0), - lastTick: time.Unix(1500000000, 0), - }, - ruleProperties: ruleProperties{ - FixedRate: 0.05, - RuleName: "r1", - }, - samplingStatistics: &samplingStatistics{}, - } - - now := time.Unix(1500000000, 0) - sd := r1.Sample(trace.SamplingParameters{}, now) - - assert.NotEmpty(t, sd.Decision) - assert.Equal(t, int64(1), r1.samplingStatistics.sampledRequests) - assert.Equal(t, int64(0), r1.samplingStatistics.borrowedRequests) - assert.Equal(t, int64(1), r1.samplingStatistics.matchedRequests) -} - -// assert that when fixed rate is 0 traceIDRatioBased sampler will not sample the trace. -func TestTraceIDRatioBasedSamplerFixedRateZero(t *testing.T) { - r1 := Rule{ - reservoir: &reservoir{ - quota: 10, - expiresAt: time.Unix(1500000060, 0), - lastTick: time.Unix(1500000000, 0), - }, - ruleProperties: ruleProperties{ - FixedRate: 0, - RuleName: "r1", - }, - samplingStatistics: &samplingStatistics{}, - } - - now := time.Unix(1500000000, 0) - sd := r1.Sample(trace.SamplingParameters{}, now) - - assert.Equal(t, trace.Drop, sd.Decision) -} - -func TestAppliesToMatchingWithAllAttrs(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "localhost", - HTTPMethod: "GET", - URLPath: "http://127.0.0.1:2000", - }, - } - - httpAttrs := []attribute.KeyValue{ - attribute.String("http.host", "localhost"), - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:2000"), - } - - match, err := r1.appliesTo(trace.SamplingParameters{Attributes: httpAttrs}, "test-service", "EC2") - require.NoError(t, err) - assert.True(t, match) -} - -// assert that matching will happen when rules has all the HTTP attrs set as '*' and -// span has any attribute values. -func TestAppliesToMatchingWithStarHTTPAttrs(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - }, - } - - httpAttrs := []attribute.KeyValue{ - attribute.String("http.host", "localhost"), - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:2000"), - } - - match, err := r1.appliesTo(trace.SamplingParameters{Attributes: httpAttrs}, "test-service", "EC2") - require.NoError(t, err) - assert.True(t, match) -} - -// assert that matching will not happen when rules has all the HTTP attrs set as non '*' values and -// span has no HTTP attributes. -func TestAppliesToMatchingWithHTTPAttrsNoSpanAttrs(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "localhost", - HTTPMethod: "GET", - URLPath: "http://127.0.0.1:2000", - }, - } - - match, err := r1.appliesTo(trace.SamplingParameters{}, "test-service", "EC2") - require.NoError(t, err) - assert.False(t, match) -} - -// assert that matching will happen when rules has all the HTTP attrs set as '*' values and -// span has no HTTP attributes. -func TestAppliesToMatchingWithStarHTTPAttrsNoSpanAttrs(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - }, - } - - match, err := r1.appliesTo(trace.SamplingParameters{}, "test-service", "EC2") - require.NoError(t, err) - assert.True(t, match) -} - -// assert that matching will not happen when rules has some HTTP attrs set as non '*' values and -// span has no HTTP attributes. -func TestAppliesToMatchingWithPartialHTTPAttrsNoSpanAttrs(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "*", - HTTPMethod: "GET", - URLPath: "*", - }, - } - - match, err := r1.appliesTo(trace.SamplingParameters{}, "test-service", "EC2") - require.NoError(t, err) - assert.False(t, match) -} - -// assert that matching will not happen when rule and span ServiceType attr value is different. -func TestAppliesToNoMatching(t *testing.T) { - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "EC2", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - }, - } - - match, err := r1.appliesTo(trace.SamplingParameters{}, "test-service", "ECS") - require.NoError(t, err) - assert.False(t, match) -} - -// assert that when attribute has http.url is empty, uses http.target wildcard matching. -func TestAppliesToHTTPTargetMatching(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("http.target", "target"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "ECS", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - }, - } - - match, err := r1.appliesTo(trace.SamplingParameters{Attributes: commonLabels}, "test-service", "ECS") - require.NoError(t, err) - assert.True(t, match) -} - -// assert early exit when rule properties retrieved from AWS X-Ray console does not match with span attributes. -func TestAppliesToExitEarlyNoMatch(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelC", "fudge"), - } - - noServiceNameMatch := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "local", - ServiceType: "*", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "fudge", - }, - }, - } - - noServiceTypeMatch := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "*", - ServiceType: "ECS", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "fudge", - }, - }, - } - - noHTTPMethodMatcher := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "*", - ServiceType: "*", - Host: "*", - HTTPMethod: "GET", - URLPath: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "fudge", - }, - }, - } - - noHTTPHostMatcher := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "*", - ServiceType: "*", - Host: "http://localhost:2022", - HTTPMethod: "*", - URLPath: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "fudge", - }, - }, - } - - noHTTPURLPathMatcher := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "*", - ServiceType: "*", - Host: "*", - HTTPMethod: "*", - URLPath: "/test/path", - Attributes: map[string]string{}, - }, - } - - noAttributeMatcher := &Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - ServiceName: "test-service", - ServiceType: "*", - Host: "*", - HTTPMethod: "*", - URLPath: "*", - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "vanilla", - }, - }, - } - - tests := []struct { - rules *Rule - }{ - {noServiceNameMatch}, - {noServiceTypeMatch}, - {noHTTPMethodMatcher}, - {noHTTPHostMatcher}, - {noHTTPURLPathMatcher}, - {noAttributeMatcher}, - } - - for _, test := range tests { - match, err := test.rules.appliesTo(trace.SamplingParameters{Attributes: commonLabels}, "test-service", "local") - require.NoError(t, err) - require.False(t, match) - } -} - -// assert that if rules has attribute and span has those attribute with same value then matching will happen. -func TestAttributeMatching(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - Attributes: map[string]string{ - "labelA": "chocolate", - "labelB": "raspberry", - }, - }, - } - - match, err := r1.attributeMatching(trace.SamplingParameters{Attributes: commonLabels}) - require.NoError(t, err) - assert.True(t, match) -} - -// assert that if rules has no attributes then matching will happen. -func TestAttributeMatchingNoRuleAttrs(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - Attributes: map[string]string{}, - }, - } - - match, err := r1.attributeMatching(trace.SamplingParameters{Attributes: commonLabels}) - require.NoError(t, err) - assert.True(t, match) -} - -// assert that wildcard attributes will match. -func TestAttributeWildCardMatching(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - Attributes: map[string]string{ - "labelA": "choco*", - "labelB": "rasp*", - }, - }, - } - - match, err := r1.attributeMatching(trace.SamplingParameters{Attributes: commonLabels}) - require.NoError(t, err) - assert.True(t, match) -} - -// assert that if some of the rules attributes are not present in span attributes then matching -// will not happen. -func TestMatchAgainstManifestRulesNoAttributeMatch(t *testing.T) { - commonLabels := []attribute.KeyValue{ - attribute.String("labelA", "chocolate"), - attribute.String("labelB", "raspberry"), - } - - r1 := Rule{ - ruleProperties: ruleProperties{ - Attributes: map[string]string{ - "labelA": "chocolate", - "labelC": "fudge", - }, - }, - } - - match, err := r1.attributeMatching(trace.SamplingParameters{Attributes: commonLabels}) - require.NoError(t, err) - assert.False(t, match) -} - -// validate no data race is happening when updating rule properties and rule targets in manifest while sampling. -func TestUpdatingRulesAndTargetsWhileSamplingConcurrentSafe(t *testing.T) { - // getSamplingRules response to update existing manifest rule - ruleRecords := samplingRuleRecords{ - SamplingRule: &ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "localhost", - HTTPMethod: "*", - URLPath: "/test/path", - ReservoirSize: 40, - FixedRate: 0.9, - Version: 1, - ServiceName: "helios", - ResourceARN: "*", - ServiceType: "*", - }, - } - - // sampling target document to update existing manifest rule - rate := 0.05 - quota := float64(10) - ttl := float64(18000000) - name := "r1" - - st := samplingTargetDocument{ - FixedRate: &rate, - ReservoirQuota: "a, - ReservoirQuotaTTL: &ttl, - RuleName: &name, - } - - // existing rule already present in manifest - r1 := Rule{ - ruleProperties: ruleProperties{ - RuleName: "r1", - Priority: 10000, - Host: "*", - HTTPMethod: "*", - URLPath: "*", - ReservoirSize: 60, - FixedRate: 0.5, - Version: 1, - ServiceName: "test", - ResourceARN: "*", - ServiceType: "*", - }, - reservoir: &reservoir{ - refreshedAt: time.Unix(18000000, 0), - }, - samplingStatistics: &samplingStatistics{ - matchedRequests: 0, - borrowedRequests: 0, - sampledRequests: 0, - }, - } - clock := &mockClock{ - nowTime: 1500000000, - } - - rules := []Rule{r1} - - m := &Manifest{ - Rules: rules, - clock: clock, - } - - // async rule updates - go func() { - for i := 0; i < 100; i++ { - m.updateRules(&getSamplingRulesOutput{ - SamplingRuleRecords: []*samplingRuleRecords{&ruleRecords}, - }) - time.Sleep(time.Millisecond) - } - }() - - // async target updates - go func() { - for i := 0; i < 100; i++ { - manifest := m.deepCopy() - - err := manifest.updateReservoir(&st) - assert.NoError(t, err) - time.Sleep(time.Millisecond) - - m.mu.Lock() - m.Rules = manifest.Rules - m.mu.Unlock() - } - }() - - // sampling logic - for i := 0; i < 100; i++ { - _ = r1.Sample(trace.SamplingParameters{}, time.Unix(clock.nowTime+int64(i), 0)) - time.Sleep(time.Millisecond) - } -} diff --git a/samplers/aws/xray/rand.go b/samplers/aws/xray/rand.go deleted file mode 100644 index 888140dd88d..00000000000 --- a/samplers/aws/xray/rand.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -import ( - crand "crypto/rand" - "encoding/binary" - "math/rand" - "time" -) - -func newSeed() int64 { - var seed int64 - if err := binary.Read(crand.Reader, binary.BigEndian, &seed); err != nil { - // fallback to timestamp - seed = time.Now().UnixNano() - } - return seed -} - -var seed = newSeed() - -func newGlobalRand() *rand.Rand { - src := rand.NewSource(seed) - if src64, ok := src.(rand.Source64); ok { - return rand.New(src64) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. - } - return rand.New(src) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. -} diff --git a/samplers/aws/xray/remote_sampler.go b/samplers/aws/xray/remote_sampler.go deleted file mode 100644 index e3c277a5303..00000000000 --- a/samplers/aws/xray/remote_sampler.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package xray provide an OpenTelemetry sampler for the AWS XRAY platform. -// -// Deprecated: xray has no Code Owner. -// After August 21, 2024, it may no longer be supported and may stop -// receiving new releases unless a new Code Owner is found. See -// [this issue] if you would like to become the Code Owner of this module. -// -// [this issue]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5554 -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -import ( - "context" - "time" - - "go.opentelemetry.io/contrib/samplers/aws/xray/internal" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - - "github.com/go-logr/logr" -) - -// remoteSampler is a sampler for AWS X-Ray which polls sampling rules and sampling targets -// to make a sampling decision based on rules set by users on AWS X-Ray console. -type remoteSampler struct { - // manifest is the list of known centralized sampling rules. - manifest *internal.Manifest - - // pollerStarted, if true represents rule and target pollers are started. - pollerStarted bool - - // samplingRulesPollingInterval, default is 300 seconds. - samplingRulesPollingInterval time.Duration - - serviceName string - - cloudPlatform string - - fallbackSampler *FallbackSampler - - // logger for logging. - logger logr.Logger -} - -// Compile time assertion that remoteSampler implements the Sampler interface. -var _ sdktrace.Sampler = (*remoteSampler)(nil) - -// NewRemoteSampler returns a sampler which decides to sample a given request or not -// based on the sampling rules set by users on AWS X-Ray console. Sampler also periodically polls -// sampling rules and sampling targets. -// NOTE: ctx passed in NewRemoteSampler API is being used in background go routine. Cancellation to this context can kill the background go routine. -// serviceName refers to the name of the service equivalent to the one set in the AWS X-Ray console when adding sampling rules and -// cloudPlatform refers to the cloud platform the service is running on ("ec2", "ecs", "eks", "lambda", etc). -// Guide on AWS X-Ray remote sampling implementation (https://aws-otel.github.io/docs/getting-started/remote-sampling#otel-remote-sampling-implementation-caveats). -func NewRemoteSampler(ctx context.Context, serviceName string, cloudPlatform string, opts ...Option) (sdktrace.Sampler, error) { - // Create new config based on options or set to default values. - cfg, err := newConfig(opts...) - if err != nil { - return nil, err - } - - // create manifest with config - m, err := internal.NewManifest(cfg.endpoint, cfg.logger) - if err != nil { - return nil, err - } - - remoteSampler := &remoteSampler{ - manifest: m, - samplingRulesPollingInterval: cfg.samplingRulesPollingInterval, - fallbackSampler: NewFallbackSampler(), - serviceName: serviceName, - cloudPlatform: cloudPlatform, - logger: cfg.logger, - } - - remoteSampler.start(ctx) - - return remoteSampler, nil -} - -// ShouldSample matches span attributes with retrieved sampling rules and returns a sampling result. -// If the sampling parameters do not match or the manifest is expired then the fallback sampler is used. -func (rs *remoteSampler) ShouldSample(parameters sdktrace.SamplingParameters) sdktrace.SamplingResult { - if rs.manifest.Expired() { - // Use fallback sampler if manifest is expired. - rs.logger.V(5).Info("manifest is expired so using fallback sampling strategy") - - return rs.fallbackSampler.ShouldSample(parameters) - } - - r, match, err := rs.manifest.MatchAgainstManifestRules(parameters, rs.serviceName, rs.cloudPlatform) - if err != nil { - rs.logger.Error(err, "rule matching error, using fallback sampler") - return rs.fallbackSampler.ShouldSample(parameters) - } - - if match { - // Remote sampling based on rule match. - return r.Sample(parameters, time.Now()) - } - - // Use fallback sampler if sampling rules does not match against manifest. - rs.logger.V(5).Info("span does not match rules from manifest(or it is expired), using fallback sampler") - return rs.fallbackSampler.ShouldSample(parameters) -} - -// Description returns description of the sampler being used. -func (rs *remoteSampler) Description() string { - return "AWSXRayRemoteSampler{remote sampling with AWS X-Ray}" -} - -func (rs *remoteSampler) start(ctx context.Context) { - if !rs.pollerStarted { - rs.pollerStarted = true - go rs.startPoller(ctx) - } -} - -// startPoller starts the rule and target poller in a single go routine which runs periodically -// to refresh the manifest and targets. -func (rs *remoteSampler) startPoller(ctx context.Context) { - // jitter = 5s, default duration 300 seconds. - rulesTicker := newTicker(rs.samplingRulesPollingInterval, 5*time.Second) - defer rulesTicker.tick.Stop() - - // jitter = 100ms, default duration 10 seconds. - targetTicker := newTicker(rs.manifest.SamplingTargetsPollingInterval, 100*time.Millisecond) - defer targetTicker.tick.Stop() - - // Fetch sampling rules to kick start the remote sampling. - rs.refreshManifest(ctx) - - for { - select { - case _, more := <-rulesTicker.c(): - if !more { - return - } - - rs.refreshManifest(ctx) - continue - case _, more := <-targetTicker.c(): - if !more { - return - } - - refresh := rs.refreshTargets(ctx) - - // If LastRuleModification time is more recent than manifest refresh time, - // then we explicitly perform refreshing the manifest. - if refresh { - rs.refreshManifest(ctx) - } - continue - case <-ctx.Done(): - return - } - } -} - -// refreshManifest refreshes the manifest retrieved via getSamplingRules API. -func (rs *remoteSampler) refreshManifest(ctx context.Context) { - if err := rs.manifest.RefreshManifestRules(ctx); err != nil { - rs.logger.Error(err, "error occurred while refreshing sampling rules") - } else { - rs.logger.V(5).Info("successfully fetched sampling rules") - } -} - -// refreshTarget refreshes the sampling targets in manifest retrieved via getSamplingTargets API. -func (rs *remoteSampler) refreshTargets(ctx context.Context) bool { - refresh := false - var err error - if refresh, err = rs.manifest.RefreshManifestTargets(ctx); err != nil { - rs.logger.Error(err, "error occurred while refreshing sampling rule targets") - } - return refresh -} diff --git a/samplers/aws/xray/remote_sampler_config.go b/samplers/aws/xray/remote_sampler_config.go deleted file mode 100644 index bfc4114e617..00000000000 --- a/samplers/aws/xray/remote_sampler_config.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -import ( - "fmt" - "log" - "math" - "net/url" - "os" - "time" - - "github.com/go-logr/logr" - "github.com/go-logr/stdr" -) - -const ( - defaultPollingInterval = 300 -) - -type config struct { - endpoint url.URL - samplingRulesPollingInterval time.Duration - logger logr.Logger -} - -// Option sets configuration on the sampler. -type Option interface { - apply(*config) *config -} - -type optionFunc func(*config) *config - -func (f optionFunc) apply(cfg *config) *config { - return f(cfg) -} - -// WithEndpoint sets custom proxy endpoint. -// If this option is not provided the default endpoint used will be http://127.0.0.1:2000. -func WithEndpoint(endpoint url.URL) Option { - return optionFunc(func(cfg *config) *config { - cfg.endpoint = endpoint - return cfg - }) -} - -// WithSamplingRulesPollingInterval sets polling interval for sampling rules. -// If this option is not provided the default samplingRulesPollingInterval used will be 300 seconds. -func WithSamplingRulesPollingInterval(polingInterval time.Duration) Option { - return optionFunc(func(cfg *config) *config { - cfg.samplingRulesPollingInterval = polingInterval - return cfg - }) -} - -// WithLogger sets custom logging for remote sampling implementation. -// If this option is not provided the default logger used will be go-logr/stdr (https://github.com/go-logr/stdr). -func WithLogger(l logr.Logger) Option { - return optionFunc(func(cfg *config) *config { - cfg.logger = l - return cfg - }) -} - -var defaultLogger = stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile), stdr.Options{LogCaller: stdr.Error}) - -func newConfig(opts ...Option) (*config, error) { - defaultProxyEndpoint, err := url.Parse("http://127.0.0.1:2000") - if err != nil { - return nil, err - } - - cfg := &config{ - endpoint: *defaultProxyEndpoint, - samplingRulesPollingInterval: defaultPollingInterval * time.Second, - logger: defaultLogger, - } - - for _, option := range opts { - option.apply(cfg) - } - - if math.Signbit(float64(cfg.samplingRulesPollingInterval)) { - return nil, fmt.Errorf("config validation error: samplingRulesPollingInterval should be positive number") - } - - return cfg, nil -} diff --git a/samplers/aws/xray/remote_sampler_config_test.go b/samplers/aws/xray/remote_sampler_config_test.go deleted file mode 100644 index c2906ca09a2..00000000000 --- a/samplers/aws/xray/remote_sampler_config_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray - -import ( - "net/url" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" -) - -// assert that user provided values are tied to config. -func TestNewConfig(t *testing.T) { - endpoint, err := url.Parse("https://127.0.0.1:5000") - require.NoError(t, err) - - cfg, err := newConfig(WithSamplingRulesPollingInterval(400*time.Second), WithEndpoint(*endpoint), WithLogger(logr.Logger{})) - require.NoError(t, err) - - assert.Equal(t, 400*time.Second, cfg.samplingRulesPollingInterval) - assert.Equal(t, cfg.endpoint, *endpoint) - assert.Equal(t, logr.Logger{}, cfg.logger) -} - -// assert that when user did not provide values are then config would be picked up from default values. -func TestDefaultConfig(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1:2000") - require.NoError(t, err) - - cfg, err := newConfig() - require.NoError(t, err) - - assert.Equal(t, 300*time.Second, cfg.samplingRulesPollingInterval) - assert.Equal(t, cfg.endpoint, *endpoint) - assert.Equal(t, cfg.logger, defaultLogger) -} - -// assert when some config is provided by user then other config will be picked up from default config. -func TestPartialUserProvidedConfig(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1:2000") - require.NoError(t, err) - - cfg, err := newConfig(WithSamplingRulesPollingInterval(500 * time.Second)) - require.NoError(t, err) - - assert.Equal(t, 500*time.Second, cfg.samplingRulesPollingInterval) - assert.Equal(t, cfg.endpoint, *endpoint) - assert.Equal(t, cfg.logger, defaultLogger) -} - -// assert that valid endpoint would not result in an error. -func TestValidEndpoint(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1:2000") - require.NoError(t, err) - - cfg, err := newConfig(WithEndpoint(*endpoint)) - require.NoError(t, err) - - assert.Equal(t, cfg.endpoint, *endpoint) -} - -// assert that host name with special character would not result in an error. -func TestValidateHostNameWithSpecialCharacterEndpoint(t *testing.T) { - endpoint, err := url.Parse("http://127.0.0.1@:2000") - require.NoError(t, err) - - cfg, err := newConfig(WithEndpoint(*endpoint)) - require.NoError(t, err) - - assert.Equal(t, cfg.endpoint, *endpoint) -} - -// assert that endpoint without host name would not result in an error. -func TestValidateInvalidEndpoint(t *testing.T) { - endpoint, err := url.Parse("https://") - require.NoError(t, err) - - cfg, err := newConfig(WithEndpoint(*endpoint)) - require.NoError(t, err) - - assert.Equal(t, cfg.endpoint, *endpoint) -} - -// assert negative sampling rules interval leads to an error. -func TestValidateConfigNegativeDuration(t *testing.T) { - _, err := newConfig(WithSamplingRulesPollingInterval(-300 * time.Second)) - assert.Error(t, err) -} - -// assert positive sampling rules interval. -func TestValidateConfigPositiveDuration(t *testing.T) { - _, err := newConfig(WithSamplingRulesPollingInterval(300 * time.Second)) - assert.NoError(t, err) -} diff --git a/samplers/aws/xray/remote_sampler_test.go b/samplers/aws/xray/remote_sampler_test.go deleted file mode 100644 index a71020abbe0..00000000000 --- a/samplers/aws/xray/remote_sampler_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestRemoteSamplerDescription assert remote sampling description. -func TestRemoteSamplerDescription(t *testing.T) { - rs := &remoteSampler{} - - s := rs.Description() - assert.Equal(t, "AWSXRayRemoteSampler{remote sampling with AWS X-Ray}", s) -} diff --git a/samplers/aws/xray/timer.go b/samplers/aws/xray/timer.go deleted file mode 100644 index 1f9ad7fef54..00000000000 --- a/samplers/aws/xray/timer.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -import ( - "time" -) - -// ticker is the same as time.Ticker except that it has jitters. -// A Ticker must be created with newTicker. -type ticker struct { - tick *time.Ticker - duration time.Duration - jitter time.Duration -} - -// newTicker creates a new Ticker that will send the current time on its channel with the passed jitter. -func newTicker(duration, jitter time.Duration) *ticker { - t := time.NewTicker(duration - time.Duration(newGlobalRand().Int63n(int64(jitter)))) - - jitteredTicker := ticker{ - tick: t, - duration: duration, - jitter: jitter, - } - - return &jitteredTicker -} - -// c returns a channel that receives when the ticker fires. -func (j *ticker) c() <-chan time.Time { - return j.tick.C -} diff --git a/samplers/aws/xray/version.go b/samplers/aws/xray/version.go deleted file mode 100644 index 63da9311097..00000000000 --- a/samplers/aws/xray/version.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package xray // import "go.opentelemetry.io/contrib/samplers/aws/xray" - -// Version is the current release version of the AWS XRay remote sampler. -func Version() string { - return "0.24.0" - // This string is updated by the pre_release.sh script during release -} - -// SemVersion is the semantic version to be supplied to tracer/meter creation. -// -// Deprecated: Use [Version] instead. -func SemVersion() string { - return Version() -} diff --git a/versions.yaml b/versions.yaml index 4295aaddd7f..f0f3ae0dff0 100644 --- a/versions.yaml +++ b/versions.yaml @@ -64,7 +64,6 @@ module-sets: experimental-samplers: version: v0.24.0 modules: - - go.opentelemetry.io/contrib/samplers/aws/xray - go.opentelemetry.io/contrib/samplers/jaegerremote - go.opentelemetry.io/contrib/samplers/jaegerremote/example - go.opentelemetry.io/contrib/samplers/probability/consistent