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

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