-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AWS X-Ray Remote Sampler Part 3 - rate limiter logic and get sampling…
… targets (#55) *Issue #, if available:* This is the 3rd and final part of the X-Ray Remote Sampler implementation for Python [See Part 2](#47) *Description of changes:* - Added logic to fetch sampling targets for each sampling rule applier. - The sampling targets are periodically fetched every 10 seconds by making the [GetSamplingTargets API call to X-Ray](https://docs.aws.amazon.com/xray/latest/api/API_GetSamplingTargets.html). - The targets determine the reservoir quota and the rate at which a sampling rule applier will sample the requests. - Each rule applier keeps and updates a sampling statistics document which is required in `GetSamplingTargets` call to determine the next target - Added the rate limiting and fixed rate samplers to be used in each rule applier. - Together these sampler determine how many requests to sample every second and what percentage of additional requests to sample in that second. - The FallbackSampler is a combination of above samplers to sample 1 req/sec and 5% of additional requests in that second. *Testing:* Unit Tests and Remote Sampling Testbed ## **Testbed:** 1. Have XRay Daemon running or OTel collector with XRay Proxy Client setup running. Ensure AWS credentials used has only default rule with 5% sampling and 1 req/s 2. Checkout this PR Branch, and install `pip3 install aws-opentelemetry-distro/` 3. Download this Python Sample App: https://github.com/jj22ee/aws-otel-community/tree/python-sample/centralized-sampling-tests/sample-apps/python-flask Install python3 requirements.txt Replace the following: ``` ### ### Set sampler HERE ### ``` with: ``` from amazon.opentelemetry.distro.sampler.aws_xray_remote_sampler import AwsXRayRemoteSampler xray_sampler = AwsXRayRemoteSampler(resource, polling_interval=10) trace.set_tracer_provider(TracerProvider(sampler=xray_sampler)) ``` Run Python Sample app with `python3 app.py` 4. Download this repository: https://github.com/aws-observability/aws-otel-community/ Within directory `centralized-sampling-tests/` run: `./gradlew :integration-tests:run` Check that the tests have passed. TODO: wire in the remote sampler to the ADOT Python customizer in this PR or a new PR By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
- Loading branch information
Showing
25 changed files
with
1,428 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
aws-opentelemetry-distro/src/amazon/opentelemetry/distro/sampler/_clock.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
aws-opentelemetry-distro/src/amazon/opentelemetry/distro/sampler/_rate_limiter.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
from decimal import Decimal | ||
from threading import Lock | ||
|
||
from amazon.opentelemetry.distro.sampler._clock import _Clock | ||
|
||
|
||
class _RateLimiter: | ||
def __init__(self, max_balance_in_seconds: int, quota: int, clock: _Clock): | ||
# max_balance_in_seconds is usually 1 | ||
# pylint: disable=invalid-name | ||
self.MAX_BALANCE_MILLIS = Decimal(max_balance_in_seconds * 1000.0) | ||
self._clock = clock | ||
|
||
self._quota = Decimal(quota) | ||
self.__wallet_floor_millis = Decimal(self._clock.now().timestamp() * 1000.0) | ||
# current "wallet_balance" would be ceiling - floor | ||
|
||
self.__lock = Lock() | ||
|
||
def try_spend(self, cost: float) -> bool: | ||
if self._quota == 0: | ||
return False | ||
|
||
quota_per_millis = self._quota / Decimal(1000.0) | ||
|
||
# assume divide by zero not possible | ||
cost_in_millis = Decimal(cost) / quota_per_millis | ||
|
||
with self.__lock: | ||
wallet_ceiling_millis = Decimal(self._clock.now().timestamp() * 1000.0) | ||
current_balance_millis = wallet_ceiling_millis - self.__wallet_floor_millis | ||
if current_balance_millis > self.MAX_BALANCE_MILLIS: | ||
current_balance_millis = self.MAX_BALANCE_MILLIS | ||
|
||
pending_remaining_balance_millis = current_balance_millis - cost_in_millis | ||
if pending_remaining_balance_millis >= 0: | ||
self.__wallet_floor_millis = wallet_ceiling_millis - pending_remaining_balance_millis | ||
return True | ||
# No changes to the wallet state | ||
return False |
41 changes: 41 additions & 0 deletions
41
aws-opentelemetry-distro/src/amazon/opentelemetry/distro/sampler/_rate_limiting_sampler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
from typing import Optional, Sequence | ||
|
||
from amazon.opentelemetry.distro.sampler._clock import _Clock | ||
from amazon.opentelemetry.distro.sampler._rate_limiter import _RateLimiter | ||
from opentelemetry.context import Context | ||
from opentelemetry.sdk.trace.sampling import Decision, Sampler, SamplingResult | ||
from opentelemetry.trace import Link, SpanKind | ||
from opentelemetry.trace.span import TraceState | ||
from opentelemetry.util.types import Attributes | ||
|
||
|
||
class _RateLimitingSampler(Sampler): | ||
def __init__(self, quota: int, clock: _Clock): | ||
self.__quota = quota | ||
self.__reservoir = _RateLimiter(1, quota, clock) | ||
|
||
# pylint: disable=no-self-use | ||
def should_sample( | ||
self, | ||
parent_context: Optional[Context], | ||
trace_id: int, | ||
name: str, | ||
kind: SpanKind = None, | ||
attributes: Attributes = None, | ||
links: Sequence[Link] = None, | ||
trace_state: TraceState = None, | ||
) -> SamplingResult: | ||
if self.__reservoir.try_spend(1): | ||
return SamplingResult(decision=Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=trace_state) | ||
return SamplingResult(decision=Decision.DROP, attributes=attributes, trace_state=trace_state) | ||
|
||
# pylint: disable=no-self-use | ||
def get_description(self) -> str: | ||
description = ( | ||
"RateLimitingSampler{rate limiting sampling with sampling config of " | ||
+ self.__quota | ||
+ " req/sec and 0% of additional requests}" | ||
) | ||
return description |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.