Repository: open-telemetry/opentelemetry-go-contrib
Pull request #1859 (2022-02-25 18:08:21)

Remote sampling support - AWS X-Ray
This PR introduces remote sampling support using AWS X-Ray. This implementation uses 2 X-Ray APIs as mentioned below.

1. getSamplingRules (retrieve customer set rules on AWS X-Ray console)
2. getSamplingTargets (retrieve targets assigned to each client for each rule but API request should also send a snapshot on how many requests are sample, borrowed and matched using each rule)

Reference docs for some of the data structures used in implementation
https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html#xray-console-config
https://docs.aws.amazon.com/xray/latest/api/API_GetSamplingRules.html
https://docs.aws.amazon.com/xray/latest/api/API_GetSamplingTargets.html

codecov[bot]: # [Codecov](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
> Merging [#1859](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (3565ccf) into [main](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/commit/c59b4421d98c35a7fa9735647507304da112ff51?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (c59b442) will **decrease** coverage by `0.0%`.
> The diff coverage is `68.8%`.

[![Impacted file tree graph](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/graphs/tree.svg?width=650&height=150&src=pr&token=P6F3W9WA7Q&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

```diff
@@           Coverage Diff           @@
##            main   #1859     +/-   ##
=======================================
- Coverage   69.6%   69.5%   -0.1%     
=======================================
  Files        127     135      +8     
  Lines       5391    5989    +598     
=======================================
+ Hits        3755    4167    +412     
- Misses      1498    1666    +168     
- Partials     138     156     +18     
```


| [Impacted Files](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [samplers/aws/xray/remote\_sampler.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvcmVtb3RlX3NhbXBsZXIuZ28=) | `11.9% <11.9%> (ø)` | |
| [samplers/aws/xray/internal/client.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvaW50ZXJuYWwvY2xpZW50Lmdv) | `59.3% <59.3%> (ø)` | |
| [samplers/aws/xray/internal/manifest.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvaW50ZXJuYWwvbWFuaWZlc3QuZ28=) | `65.2% <65.2%> (ø)` | |
| [samplers/aws/xray/remote\_sampler\_config.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvcmVtb3RlX3NhbXBsZXJfY29uZmlnLmdv) | `93.4% <93.4%> (ø)` | |
| [samplers/aws/xray/fallback\_sampler.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvZmFsbGJhY2tfc2FtcGxlci5nbw==) | `95.6% <95.6%> (ø)` | |
| [samplers/aws/xray/internal/rule.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvaW50ZXJuYWwvcnVsZS5nbw==) | `96.7% <96.7%> (ø)` | |
| [samplers/aws/xray/internal/match.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvaW50ZXJuYWwvbWF0Y2guZ28=) | `100.0% <100.0%> (ø)` | |
| [samplers/aws/xray/internal/reservoir.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/1859/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2FtcGxlcnMvYXdzL3hyYXkvaW50ZXJuYWwvcmVzZXJ2b2lyLmdv) | `100.0% <100.0%> (ø)` | |

bhautikpip: @anuraaga @MrAlias @jmacd @Aneurysm9 Can you review this PR? I have closed the older one and opened a fresh PR with complete remote sampling support and tried to address most of the concerns/comments we have discussed. Feel free to take a look again. Thanks! 
bhautikpip: @hanyuancheung @MrAlias @jmacd Can you also take a look? planning to merge this ASAP to include this in next release if possible. 
bhautikpip: @Aneurysm9 addressed your comments. Feel free to take a look at it again and suggest if I missed anything :) 
Aneurysm9: @hanyuancheung if all of your comments have been addressed can you please approve and release the hold on this PR?  
bhautikpip: @MadVikingGod Can you take another look if your comments have been successfully addressed or any more suggestions on this PR? I'm planning to include this change in the next release so would really appreciate a quick review :) Thanks :)
MadVikingGod: I had trouble decerning if the manifest type you have is thread-safe.  Two things you could do is:
1. Put all of your lock logic at the same level, all in the Exported functions, or use your function names to differentiate what must be used within a lock.
2. Build some race tests around your Exported functions.
bhautikpip: > I had trouble decerning if the manifest type you have is thread-safe. Two things you could do is:
> 
> 1. Put all of your lock logic at the same level, all in the Exported functions, or use your function names to differentiate what must be used within a lock.
> 2. Build some race tests around your Exported functions.

Sure I think I can modify the function names to have suffix like `Locked` to better understand the code. I can also write some race tests around exported functions.
bhautikpip: @MrAlias @MadVikingGod I'm planning to add race test but other than this I have addressed most your comments. Let me know if you have any suggestions/examples for race tests. 
bhautikpip: While running the `make precommit` I have started receiving this error. There is some checksum mis match. Are we planning to fix this issue as part of the other PR ? 

```
bhautip@38f9d362ae67 opentelemetry-go-contrib % make precommit     
cd ./tools && \
        go build -o /Users/bhautip/Documents/lambda/opentelemetry-go-contrib/.tools/dbotconf go.opentelemetry.io/build-tools/dbotconf
verifying github.com/blizzy78/varnamelen@v0.6.1/go.mod: checksum mismatch
        downloaded: h1:mGBHm+Uo4e8JnZEKHRoZgVEOQdSBdQfY/x+k4NAXBWA=
        go.sum:     h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8=

```
Aneurysm9: That version [should be retracted](https://github.com/blizzy78/varnamelen/blob/master/go.mod#L12).  Not sure why it is being used.  You may be able to work around it temporarily by putting an `exclude` directive for it in `tools/go.mod`.
MrAlias: One final unresolved issue as far as I see: https://github.com/open-telemetry/opentelemetry-go-contrib/pull/1859#discussion_r836842309

Otherwise this is looking good :+1: 
bhautikpip: Also, not sure why linter check is failing. I'm able to run it on my local. Maybe re-running it would be helpful since it's some timeout issue.
MrAlias: @MadVikingGod PTAL
bhautikpip: @MadVikingGod Can you take a look again to see if you have further comments on this or this is good to merge? 
Aneurysm9: I spoke with @MadVikingGod and he's good with this proceeding.  He will ask for follow-up PRs if he has any concerns that need to be addressed.

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 34294b9be..c90364682 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -586,6 +586,15 @@ updates:
     schedule:
       interval: weekly
       day: sunday
+  - package-ecosystem: gomod
+    directory: /samplers/aws/xray
+    labels:
+      - dependencies
+      - go
+      - Skip Changelog
+    schedule:
+      interval: weekly
+      day: sunday
   - package-ecosystem: gomod
     directory: /samplers/jaegerremote
     labels:
diff --git a/samplers/aws/xray/fallback_sampler.go b/samplers/aws/xray/fallback_sampler.go
new file mode 100644
index 000000000..1a7a5ea1b
--- /dev/null
+++ b/samplers/aws/xray/fallback_sampler.go
@@ -0,0 +1,102 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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 {
+	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/go.mod b/samplers/aws/xray/go.mod
new file mode 100644
index 000000000..4a07f421d
--- /dev/null
+++ b/samplers/aws/xray/go.mod
@@ -0,0 +1,13 @@
+module go.opentelemetry.io/contrib/samplers/aws/xray
+
+go 1.16
+
+require (
+	github.com/go-logr/logr v1.2.3
+	github.com/go-logr/stdr v1.2.2
+	github.com/stretchr/testify v1.7.1
+	go.opentelemetry.io/otel v1.6.0
+	go.opentelemetry.io/otel/sdk v1.6.0
+	go.opentelemetry.io/otel/trace v1.6.0
+	golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
+)
diff --git a/samplers/aws/xray/go.sum b/samplers/aws/xray/go.sum
new file mode 100644
index 000000000..8c5508c96
--- /dev/null
+++ b/samplers/aws/xray/go.sum
@@ -0,0 +1,28 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/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.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+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.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.opentelemetry.io/otel v1.6.0 h1:YV6GkGe/Ag2PKsm4rjlqdSNs0w0A5ZzxeGkxhx1T+t4=
+go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ=
+go.opentelemetry.io/otel/sdk v1.6.0 h1:JoriAoiNENuxxIQApR1O0k2h1Md5QegZhbentcRJpWk=
+go.opentelemetry.io/otel/sdk v1.6.0/go.mod h1:PjLRUfDsoPy0zl7yrDGSUqjj43tL7rEtFdCEiGlxXRM=
+go.opentelemetry.io/otel/trace v1.6.0 h1:NDzPermp9ISkhxIaJXjBTi2O60xOSHDHP/EezjOL2wo=
+go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+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.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/samplers/aws/xray/internal/client.go b/samplers/aws/xray/internal/client.go
new file mode 100644
index 000000000..e98901f6f
--- /dev/null
+++ b/samplers/aws/xray/internal/client.go
@@ -0,0 +1,190 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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) {
+	// 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("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 settings: %w", err)
+	}
+	defer output.Body.Close()
+
+	var samplingRulesOutput *getSamplingRulesOutput
+	if err := json.NewDecoder(output.Body).Decode(&samplingRulesOutput); err != nil {
+		return nil, fmt.Errorf("xray client: 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 settings: %w", err)
+	}
+	defer output.Body.Close()
+
+	var samplingTargetsOutput *getSamplingTargetsOutput
+	if err := json.NewDecoder(output.Body).Decode(&samplingTargetsOutput); err != nil {
+		return nil, fmt.Errorf("xray client: unable to unmarshal the response body: %w", err)
+	}
+
+	return samplingTargetsOutput, nil
+}
diff --git a/samplers/aws/xray/internal/clock.go b/samplers/aws/xray/internal/clock.go
new file mode 100644
index 000000000..4482cb0f9
--- /dev/null
+++ b/samplers/aws/xray/internal/clock.go
@@ -0,0 +1,43 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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
new file mode 100644
index 000000000..ed6fb063d
--- /dev/null
+++ b/samplers/aws/xray/internal/manifest.go
@@ -0,0 +1,387 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package internal // import "go.opentelemetry.io/contrib/samplers/aws/xray/internal"
+
+import (
+	"context"
+	crypto "crypto/rand"
+	"fmt"
+	"math"
+	"net/url"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/go-logr/logr"
+
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+)
+
+const manifestTTL = 3600
+const 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()
+
+	m.mu.Lock()
+	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) {
+	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() {
+	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/match.go b/samplers/aws/xray/internal/match.go
new file mode 100644
index 000000000..111f1f3ef
--- /dev/null
+++ b/samplers/aws/xray/internal/match.go
@@ -0,0 +1,71 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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/reservoir.go b/samplers/aws/xray/internal/reservoir.go
new file mode 100644
index 000000000..3c6ddd57a
--- /dev/null
+++ b/samplers/aws/xray/internal/reservoir.go
@@ -0,0 +1,112 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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 {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	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) {
+	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 {
+		totalQuotaBalanceCapacity := elapsedTime.Seconds() * r.capacity
+		r.quotaBalance += elapsedTime.Seconds() * r.quota
+
+		if r.quotaBalance > totalQuotaBalanceCapacity {
+			r.quotaBalance = totalQuotaBalanceCapacity
+		}
+	}
+}
diff --git a/samplers/aws/xray/internal/rule.go b/samplers/aws/xray/internal/rule.go
new file mode 100644
index 000000000..358a26b14
--- /dev/null
+++ b/samplers/aws/xray/internal/rule.go
@@ -0,0 +1,249 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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/rand.go b/samplers/aws/xray/rand.go
new file mode 100644
index 000000000..59681bf3c
--- /dev/null
+++ b/samplers/aws/xray/rand.go
@@ -0,0 +1,41 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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)
+	}
+	return rand.New(src)
+}
diff --git a/samplers/aws/xray/remote_sampler.go b/samplers/aws/xray/remote_sampler.go
new file mode 100644
index 000000000..8b9898f83
--- /dev/null
+++ b/samplers/aws/xray/remote_sampler.go
@@ -0,0 +1,183 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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
new file mode 100644
index 000000000..b12a96fb6
--- /dev/null
+++ b/samplers/aws/xray/remote_sampler_config.go
@@ -0,0 +1,98 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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
+	})
+}
+
+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:                       stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile), stdr.Options{LogCaller: stdr.Error}),
+	}
+
+	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/timer.go b/samplers/aws/xray/timer.go
new file mode 100644
index 000000000..592908719
--- /dev/null
+++ b/samplers/aws/xray/timer.go
@@ -0,0 +1,45 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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/tools/go.mod b/tools/go.mod
index 1837062b4..7894a74f1 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -2,7 +2,10 @@ module go.opentelemetry.io/contrib/tools
 
 go 1.13
 
+exclude github.com/blizzy78/varnamelen v0.6.1
+
 require (
+	github.com/blizzy78/varnamelen v0.7.0 // indirect
 	github.com/client9/misspell v0.3.4
 	github.com/golangci/golangci-lint v1.45.2
 	github.com/jcchavezs/porto v0.4.0
diff --git a/tools/go.sum b/tools/go.sum
index 38a5e0946..8569097b7 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -118,8 +118,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A=
 github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI=
-github.com/blizzy78/varnamelen v0.6.1 h1:kttPCLzXFa+0nt++Cw9fb7GrSSM4KkyIAoX/vXsbuqA=
-github.com/blizzy78/varnamelen v0.6.1/go.mod h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8=
+github.com/blizzy78/varnamelen v0.7.0 h1:Y7LzPAysnU3YAabhqXkP9/G9/r7Ks9fUEvOjXoXStlY=
+github.com/blizzy78/varnamelen v0.7.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
 github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM=
 github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
 github.com/breml/bidichk v0.2.2 h1:w7QXnpH0eCBJm55zGCTJveZEkQBt6Fs5zThIdA6qQ9Y=


Test patch:
diff --git a/samplers/aws/xray/fallback_sampler_test.go b/samplers/aws/xray/fallback_sampler_test.go
new file mode 100644
index 000000000..cb68f5518
--- /dev/null
+++ b/samplers/aws/xray/fallback_sampler_test.go
@@ -0,0 +1,74 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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, fs.quotaBalance, 1.0)
+
+	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, fs.quotaBalance, 0.0)
+}
+
+// assert fallback sampling description.
+func TestFallbackSamplerDescription(t *testing.T) {
+	fs := NewFallbackSampler()
+	s := fs.Description()
+	assert.Equal(t, s, "FallbackSampler{fallback sampling with sampling config of 1 req/sec and 5% of additional requests}")
+}
diff --git a/samplers/aws/xray/internal/client_test.go b/samplers/aws/xray/internal/client_test.go
new file mode 100644
index 000000000..40e881cf9
--- /dev/null
+++ b/samplers/aws/xray/internal/client_test.go
@@ -0,0 +1,274 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package internal
+
+import (
+	"context"
+	"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 {
+	testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
+		_, err := res.Write(body)
+		require.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, samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName, "Default")
+	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, samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize, 60.0)
+	assert.Equal(t, samplingRules.SamplingRuleRecords[0].SamplingRule.FixedRate, 0.5)
+
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.RuleName, "test-rule")
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.ServiceType, "local")
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.Host, "*")
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.URLPath, "/aws-sdk-call")
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.ReservoirSize, 3.0)
+	assert.Equal(t, samplingRules.SamplingRuleRecords[1].SamplingRule.FixedRate, 0.09)
+
+	assert.Equal(t, samplingRules.SamplingRuleRecords[2].SamplingRule.RuleName, "test-rule-1")
+	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, samplingRules.SamplingRuleRecords[2].SamplingRule.ReservoirSize, 100.0)
+	assert.Equal(t, samplingRules.SamplingRuleRecords[2].SamplingRule.FixedRate, 0.09)
+}
+
+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, samplingRules.SamplingRuleRecords[0].SamplingRule.Priority, int64(0))
+	assert.Equal(t, samplingRules.SamplingRuleRecords[0].SamplingRule.ReservoirSize, 0.0)
+
+	// other values are stored as expected
+	assert.Equal(t, samplingRules.SamplingRuleRecords[0].SamplingRule.RuleName, "Default")
+}
+
+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, *samplingTragets.LastRuleModification, float64(123456))
+	assert.Equal(t, *samplingTragets.SamplingTargetDocuments[0].FixedRate, float64(5))
+	assert.Equal(t, *samplingTragets.SamplingTargetDocuments[0].Interval, int64(5))
+	assert.Equal(t, *samplingTragets.SamplingTargetDocuments[0].ReservoirQuota, 3.0)
+	assert.Equal(t, *samplingTragets.SamplingTargetDocuments[0].ReservoirQuotaTTL, float64(456789))
+	assert.Equal(t, *samplingTragets.SamplingTargetDocuments[0].RuleName, "r1")
+	assert.Equal(t, *samplingTragets.UnprocessedStatistics[0].RuleName, "r1")
+	assert.Equal(t, *samplingTragets.UnprocessedStatistics[0].ErrorCode, "200")
+	assert.Equal(t, *samplingTragets.UnprocessedStatistics[0].Message, "ok")
+}
+
+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)
+
+	samplingTragets, err := client.getSamplingTargets(ctx, nil)
+	require.NoError(t, err)
+
+	assert.Nil(t, samplingTragets.SamplingTargetDocuments[0].Interval)
+	assert.Nil(t, samplingTragets.SamplingTargetDocuments[0].ReservoirQuota)
+}
+
+func TestNilContext(t *testing.T) {
+	client := createTestClient(t, []byte(``))
+	samplingRulesOutput, err := client.getSamplingRules(context.TODO())
+	require.Error(t, err)
+	require.Nil(t, samplingRulesOutput)
+
+	samplingTargetsOutput, err := client.getSamplingTargets(context.TODO(), nil)
+	require.Error(t, err)
+	require.Nil(t, samplingTargetsOutput)
+}
+
+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)
+
+	_, err = client.getSamplingRules(context.Background())
+	assert.Error(t, err)
+}
diff --git a/samplers/aws/xray/internal/manifest_test.go b/samplers/aws/xray/internal/manifest_test.go
new file mode 100644
index 000000000..4ff24c850
--- /dev/null
+++ b/samplers/aws/xray/internal/manifest_test.go
@@ -0,0 +1,1705 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package internal
+
+import (
+	"context"
+	"net/url"
+	"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 {
+	return &samplingTargetDocument{
+		FixedRate:         &rate,
+		Interval:          &interval,
+		ReservoirQuota:    &quota,
+		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.Nil(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.Len(t, m.Rules, 0)
+}
+
+// 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.Len(t, m.Rules, 0)
+}
+
+// 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, m.Rules[0].ruleProperties.FixedRate, 0.06)
+	assert.Equal(t, m.Rules[0].reservoir.quota, 23.0)
+	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 TestRaceUpdatingRulesWhileMatching(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
+	go func() {
+		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)
+	}
+}
+
+// validate no data race is happening when updating rule properties and rule targets in manifest while matching
+func TestRaceUpdatingRulesAndTargetsWhileMatching(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,
+	}
+
+	// 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(createSamplingTargetDocument("r1", 0, 0.05, 10, 13000000))
+			require.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)
+	}
+}
+
+// validate no data race is when capturing sampling statistics in manifest while sampling
+func TestRaceUpdatingSamplingStatisticsWhenSampling(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
+	go func() {
+		for i := 0; i < 100; i++ {
+			manifest := m.deepCopy()
+
+			_, err := manifest.snapshots()
+			require.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)
+	}
+}
diff --git a/samplers/aws/xray/internal/match_test.go b/samplers/aws/xray/internal/match_test.go
new file mode 100644
index 000000000..5dff0e0a8
--- /dev/null
+++ b/samplers/aws/xray/internal/match_test.go
@@ -0,0 +1,114 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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_test.go b/samplers/aws/xray/internal/reservoir_test.go
new file mode 100644
index 000000000..723b766ec
--- /dev/null
+++ b/samplers/aws/xray/internal/reservoir_test.go
@@ -0,0 +1,245 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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, r.quotaBalance, 0.0)
+	assert.True(t, r.take(clock.now(), false, 1.0))
+	assert.Equal(t, r.quotaBalance, 1.0)
+	assert.True(t, r.take(clock.now(), false, 1.0))
+	assert.Equal(t, r.quotaBalance, 0.0)
+	// 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, r.quotaBalance, 0.0)
+	assert.True(t, r.take(clock.now(), false, 1.0))
+	assert.Equal(t, r.quotaBalance, 1.0)
+	assert.True(t, r.take(clock.now(), false, 1.0))
+	assert.Equal(t, r.quotaBalance, 0.0)
+	// 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 5
+	clock.nowTime = 1500000005
+
+	// elapsedTime is 4 seconds so quota balance should be elapsedTime * quota = 8 and below take would consume 1 so
+	// ultimately 7
+	assert.True(t, r.take(clock.now(), false, 1.0))
+	assert.Equal(t, r.quotaBalance, 7.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 ununsed quota is available
+	assert.True(t, r.take(clock.now(), false, 1.0))
+}
+
+// assert that when quotaBalance exceeds totalQuotaBalanceCapacity then totalQuotaBalanceCapacity
+// gets assigned to quotaBalance
+func TestQuotaBalanceNonBorrowExceedsCapacity(t *testing.T) {
+	clock := &mockClock{
+		nowTime: 1500000001,
+	}
+
+	r := &reservoir{
+		quota:    6,
+		capacity: 5,
+		lastTick: time.Unix(1500000000, 0),
+	}
+
+	r.refreshQuotaBalanceLocked(clock.now(), false)
+
+	// assert that if quotaBalance exceeds capacity then total capacity would be new quotaBalance
+	assert.Equal(t, r.quotaBalance, r.capacity)
+}
+
+// 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_test.go b/samplers/aws/xray/internal/rule_test.go
new file mode 100644
index 000000000..fd34aef5f
--- /dev/null
+++ b/samplers/aws/xray/internal/rule_test.go
@@ -0,0 +1,661 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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{
+			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, sd.Decision, trace.Drop)
+}
+
+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 TestRaceUpdatingRulesAndTargetsWhileSampling(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:    &quota,
+		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)
+			require.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/remote_sampler_config_test.go b/samplers/aws/xray/remote_sampler_config_test.go
new file mode 100644
index 000000000..1c6368065
--- /dev/null
+++ b/samplers/aws/xray/remote_sampler_config_test.go
@@ -0,0 +1,113 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package xray
+
+import (
+	"log"
+	"net/url"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/go-logr/logr"
+	"github.com/go-logr/stdr"
+	"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, cfg.samplingRulesPollingInterval, 400*time.Second)
+	assert.Equal(t, cfg.endpoint, *endpoint)
+	assert.Equal(t, cfg.logger, logr.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, cfg.samplingRulesPollingInterval, 300*time.Second)
+	assert.Equal(t, cfg.endpoint, *endpoint)
+	assert.Equal(t, cfg.logger, stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile), stdr.Options{LogCaller: stdr.Error}))
+}
+
+// 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, cfg.samplingRulesPollingInterval, 500*time.Second)
+	assert.Equal(t, cfg.endpoint, *endpoint)
+	assert.Equal(t, cfg.logger, stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile), stdr.Options{LogCaller: stdr.Error}))
+}
+
+// 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
new file mode 100644
index 000000000..f7da63d1e
--- /dev/null
+++ b/samplers/aws/xray/remote_sampler_test.go
@@ -0,0 +1,29 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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, s, "AWSXRayRemoteSampler{remote sampling with AWS X-Ray}")
+}