Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ffd9471
remote sampling - initial classes and rules poller
jj22ee Mar 14, 2025
2006f5d
run generate-workflows and ruff
jj22ee Mar 16, 2025
8b62fec
add component owner for aws sampler, run lint
jj22ee Mar 16, 2025
2493c1c
move sampler into aws sdk-extensions
jj22ee Mar 18, 2025
073935a
move sampler tests to trace dir, update otel api/sdk deps, update cha…
jj22ee Mar 18, 2025
ba89dda
move mock_clock into tests dir
jj22ee Apr 16, 2025
5279c38
update component owners for sdk-extension-aws
jj22ee Apr 16, 2025
f4cf224
Merge remote-tracking branch 'upstream/main' into xray-sampler-pr0
jj22ee Apr 16, 2025
392f80f
ruff and lint
jj22ee Apr 16, 2025
efdb8b3
Merge branch 'main' into xray-sampler-pr0
jj22ee Apr 22, 2025
6834c29
Merge branch 'main' into xray-sampler-pr0
jj22ee May 5, 2025
1917bbf
Merge branch 'main' into xray-sampler-pr0
jj22ee May 24, 2025
be1d26d
address comments
jj22ee May 28, 2025
95baa3c
Merge branch 'main' into xray-sampler-pr0
jj22ee Jun 17, 2025
22afe4b
make sampler implementation internal until completion, update tests t…
jj22ee Jun 17, 2025
95f42c2
Merge branch 'main' into xray-sampler-pr0
jj22ee Jul 1, 2025
ad0c105
remove use of Optional, restore README of the package
jj22ee Jul 1, 2025
d5f06d7
remove unused clock and client_id
jj22ee Jul 1, 2025
d214394
Update component_owners.yml
jj22ee Jul 3, 2025
111bcb2
Merge branch 'main' into xray-sampler-pr0
jj22ee Jul 3, 2025
1ec4c13
Merge branch 'main' into xray-sampler-pr0
jj22ee Aug 4, 2025
b62339c
Update CHANGELOG.md
jj22ee Aug 4, 2025
e5d79e2
Merge branch 'main' into xray-sampler-pr0
xrmx Aug 25, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ components:
- dorkolog

propagator/opentelemetry-propagator-aws-xray:
- NathanielRN
- jj22ee

sdk-extension/opentelemetry-sdk-extension-aws:
- NathanielRN
- Kausik-A
- srprash
- jj22ee

instrumentation/opentelemetry-instrumentation-tortoiseorm:
- tonybaloney
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3685](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3685))
- `opentelemetry-instrumentation-system-metrics`: Add `cpython.gc.collected_objects` and `cpython.gc.uncollectable_objects` metrics
([#3666](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3666))

- `opentelemetry-sdk-extension-aws` Add AWS X-Ray Remote Sampler with initial Rules Poller implementation
([#3366](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3366))

## Version 1.36.0/0.57b0 (2025-07-29)

Expand Down
10 changes: 9 additions & 1 deletion sdk-extension/opentelemetry-sdk-extension-aws/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-sdk ~= 1.12",
"opentelemetry-api ~= 1.23",
"opentelemetry-sdk ~= 1.23",
"opentelemetry-instrumentation ~= 0.44b0",
"opentelemetry-semantic-conventions ~= 0.44b0",
"requests ~= 2.28",
]

[project.entry-points.opentelemetry_id_generator]
Expand All @@ -38,6 +42,10 @@ aws_eks = "opentelemetry.sdk.extension.aws.resource.eks:AwsEksResourceDetector"
aws_elastic_beanstalk = "opentelemetry.sdk.extension.aws.resource.beanstalk:AwsBeanstalkResourceDetector"
aws_lambda = "opentelemetry.sdk.extension.aws.resource._lambda:AwsLambdaResourceDetector"

# TODO: Uncomment this when Sampler implementation is complete
# [project.entry-points.opentelemetry_sampler]
# aws_xray_remote_sampler = "opentelemetry.sdk.extension.aws.trace.sampler:AwsXRayRemoteSampler"

[project.urls]
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/sdk-extension/opentelemetry-sdk-extension-aws"
Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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.

# pylint: disable=no-name-in-module
from opentelemetry.sdk.extension.aws.trace.sampler.aws_xray_remote_sampler import (
_AwsXRayRemoteSampler,
)

__all__ = ["_AwsXRayRemoteSampler"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# 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.

# Includes work from:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import json
from logging import getLogger
from typing import List

import requests

# pylint: disable=no-name-in-module
from opentelemetry.instrumentation.utils import suppress_instrumentation
from opentelemetry.sdk.extension.aws.trace.sampler._sampling_rule import (
_SamplingRule,
)
from opentelemetry.sdk.extension.aws.trace.sampler._sampling_target import (
_SamplingTargetResponse,
)

_logger = getLogger(__name__)
DEFAULT_SAMPLING_PROXY_ENDPOINT = "http://127.0.0.1:2000"


class _AwsXRaySamplingClient:
def __init__(
self,
endpoint: str = DEFAULT_SAMPLING_PROXY_ENDPOINT,
log_level: str | None = None,
):
# Override default log level
if log_level is not None:
_logger.setLevel(log_level)

self.__get_sampling_rules_endpoint = endpoint + "/GetSamplingRules"
self.__get_sampling_targets_endpoint = endpoint + "/SamplingTargets"

self.__session = requests.Session()

def get_sampling_rules(self) -> List[_SamplingRule]:
sampling_rules: List["_SamplingRule"] = []
headers = {"content-type": "application/json"}

with suppress_instrumentation():
try:
xray_response = self.__session.post(
url=self.__get_sampling_rules_endpoint,
headers=headers,
timeout=20,
)
sampling_rules_response = xray_response.json()
if (
sampling_rules_response is None
or "SamplingRuleRecords" not in sampling_rules_response
):
_logger.error(
"SamplingRuleRecords is missing in getSamplingRules response: %s",
sampling_rules_response,
)
return []
sampling_rules_records = sampling_rules_response[
"SamplingRuleRecords"
]
for record in sampling_rules_records:
if "SamplingRule" not in record:
_logger.error(
"SamplingRule is missing in SamplingRuleRecord"
)
else:
sampling_rules.append(
_SamplingRule(**record["SamplingRule"])
)

except requests.exceptions.RequestException as req_err:
_logger.error("Request error occurred: %s", req_err)
except json.JSONDecodeError as json_err:
_logger.error("Error in decoding JSON response: %s", json_err)
# pylint: disable=broad-exception-caught
except Exception as err:
_logger.error(
"Error occurred when attempting to fetch rules: %s", err
)

return sampling_rules

def get_sampling_targets(
self, statistics: List["dict[str, str | float | int]"]
) -> _SamplingTargetResponse:
sampling_targets_response = _SamplingTargetResponse(
LastRuleModification=None,
SamplingTargetDocuments=None,
UnprocessedStatistics=None,
)
headers = {"content-type": "application/json"}

with suppress_instrumentation():
try:
xray_response = self.__session.post(
url=self.__get_sampling_targets_endpoint,
headers=headers,
timeout=20,
json={"SamplingStatisticsDocuments": statistics},
)
xray_response_json = xray_response.json()
if (
xray_response_json is None
or "SamplingTargetDocuments" not in xray_response_json
or "LastRuleModification" not in xray_response_json
):
_logger.debug(
"getSamplingTargets response is invalid. Unable to update targets."
)
return sampling_targets_response

sampling_targets_response = _SamplingTargetResponse(
**xray_response_json
)
except requests.exceptions.RequestException as req_err:
_logger.debug("Request error occurred: %s", req_err)
except json.JSONDecodeError as json_err:
_logger.debug("Error in decoding JSON response: %s", json_err)
# pylint: disable=broad-exception-caught
except Exception as err:
_logger.debug(
"Error occurred when attempting to fetch targets: %s", err
)

return sampling_targets_response
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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.

# Includes work from:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import datetime


class _Clock:
def __init__(self):
self.__datetime = datetime.datetime

def now(self) -> datetime.datetime:
return self.__datetime.now()

# pylint: disable=no-self-use
def from_timestamp(self, timestamp: float) -> datetime.datetime:
return datetime.datetime.fromtimestamp(timestamp)

def time_delta(self, seconds: float) -> datetime.timedelta:
return datetime.timedelta(seconds=seconds)

def max(self) -> datetime.datetime:
return datetime.datetime.max
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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.

# Includes work from:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations


# Disable snake_case naming style so this class can match the sampling rules response from X-Ray
# pylint: disable=invalid-name
class _SamplingRule:
def __init__(
self,
Attributes: dict[str, str] | None = None,
FixedRate: float | None = None,
HTTPMethod: str | None = None,
Host: str | None = None,
Priority: int | None = None,
ReservoirSize: int | None = None,
ResourceARN: str | None = None,
RuleARN: str | None = None,
RuleName: str | None = None,
ServiceName: str | None = None,
ServiceType: str | None = None,
URLPath: str | None = None,
Version: int | None = None,
):
self.Attributes = Attributes if Attributes is not None else {}
self.FixedRate = FixedRate if FixedRate is not None else 0.0
self.HTTPMethod = HTTPMethod if HTTPMethod is not None else ""
self.Host = Host if Host is not None else ""
# Default to value with lower priority than default rule
self.Priority = Priority if Priority is not None else 10001
self.ReservoirSize = ReservoirSize if ReservoirSize is not None else 0
self.ResourceARN = ResourceARN if ResourceARN is not None else ""
self.RuleARN = RuleARN if RuleARN is not None else ""
self.RuleName = RuleName if RuleName is not None else ""
self.ServiceName = ServiceName if ServiceName is not None else ""
self.ServiceType = ServiceType if ServiceType is not None else ""
self.URLPath = URLPath if URLPath is not None else ""
self.Version = Version if Version is not None else 0

def __lt__(self, other: "_SamplingRule") -> bool:
if self.Priority == other.Priority:
# String order priority example:
# "A","Abc","a","ab","abc","abcdef"
return self.RuleName < other.RuleName
return self.Priority < other.Priority

def __eq__(self, other: object) -> bool:
if not isinstance(other, _SamplingRule):
return False
return (
self.FixedRate == other.FixedRate
and self.HTTPMethod == other.HTTPMethod
and self.Host == other.Host
and self.Priority == other.Priority
and self.ReservoirSize == other.ReservoirSize
and self.ResourceARN == other.ResourceARN
and self.RuleARN == other.RuleARN
and self.RuleName == other.RuleName
and self.ServiceName == other.ServiceName
and self.ServiceType == other.ServiceType
and self.URLPath == other.URLPath
and self.Version == other.Version
and self.Attributes == other.Attributes
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.

# Includes work from:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

# pylint: disable=no-name-in-module
from opentelemetry.sdk.extension.aws.trace.sampler._clock import _Clock
from opentelemetry.sdk.extension.aws.trace.sampler._sampling_rule import (
_SamplingRule,
)
from opentelemetry.sdk.extension.aws.trace.sampler._sampling_statistics_document import (
_SamplingStatisticsDocument,
)
from opentelemetry.sdk.extension.aws.trace.sampler._sampling_target import (
_SamplingTarget,
)


class _SamplingRuleApplier:
def __init__(
self,
sampling_rule: _SamplingRule,
client_id: str,
clock: _Clock,
statistics: _SamplingStatisticsDocument | None = None,
target: _SamplingTarget | None = None,
):
self.__client_id = client_id # pylint: disable=W0238
self._clock = clock
self.sampling_rule = sampling_rule

# (TODO) Just store Sampling Rules for now, rest of implementation for later
Loading