Skip to content

Commit f4f5eb9

Browse files
authored
Added changes for rate limited sampler (azure-distro changes) (#41976)
* RateLimitedSampler distro changes * Fixed formatting errors * Updated config and tests * Updated CHANGELOG with URL * Modified CHANGELOG * Retrigger build * Upgraded exporter version in setup.py * Fixed argument and linting errors * Addressed comments * Bumped up the unreleased version number * Updated CHANGELOG * Updated CHANGELOG and README * Updated url in README * Fix link issue * Fix link * bandit * capitalized rate limited sampler
1 parent dc7a11e commit f4f5eb9

File tree

8 files changed

+333
-25
lines changed

8 files changed

+333
-25
lines changed

sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## 1.7.1 (Unreleased)
44

55
### Features Added
6+
- Added configuration changes for RateLimited Sampler
7+
([#41976](https://github.com/Azure/azure-sdk-for-python/pull/41976))
68

79
### Breaking Changes
810

sdk/monitor/azure-monitor-opentelemetry/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to
6767
| `resource` | Specifies the OpenTelemetry [Resource][ot_spec_resource] associated with your application. Passed in [Resource Attributes][ot_spec_resource_attributes] take priority over default attributes and those from [Resource Detectors][ot_python_resource_detectors]. | [OTEL_SERVICE_NAME][ot_spec_service_name], [OTEL_RESOURCE_ATTRIBUTES][ot_spec_resource_attributes], [OTEL_EXPERIMENTAL_RESOURCE_DETECTORS][ot_python_resource_detectors] |
6868
| `span_processors` | A list of [span processors][ot_span_processor] that will perform processing on each of your spans before they are exported. Useful for filtering/modifying telemetry. | `N/A` |
6969
| `views` | A list of [views][ot_view] that will be used to customize metrics exported by the SDK. | `N/A` |
70+
| `traces_per_second` | Configures the Rate Limited sampler by specifying the maximum number of traces to sample per second. When set, this automatically enables the rate-limited sampler. Alternatively, you can configure sampling using the `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` environment variables as described in the table below. Please note that the sampling configuration via environment variables will have precedence over the sampling exporter/distro options. | `N/A`
7071

7172
You can configure further with [OpenTelemetry environment variables][ot_env_vars].
7273

@@ -78,7 +79,8 @@ You can configure further with [OpenTelemetry environment variables][ot_env_vars
7879
| `OTEL_TRACES_EXPORTER` | If set to `None`, disables collection and export of distributed tracing telemetry. |
7980
| `OTEL_BLRP_SCHEDULE_DELAY` | Specifies the logging export interval in milliseconds. Defaults to 5000. |
8081
| `OTEL_BSP_SCHEDULE_DELAY` | Specifies the distributed tracing export interval in milliseconds. Defaults to 5000. |
81-
| `OTEL_TRACES_SAMPLER_ARG` | Specifies the ratio of distributed tracing telemetry to be [sampled][application_insights_sampling]. Accepted values are in the range [0,1]. Defaults to 1.0, meaning no telemetry is sampled out. |
82+
| `OTEL_TRACES_SAMPLER` | Specifies the sampler to be used for traces. Supports both [application_insights_sampling] and [rate_limited_sampling]. Use `microsoft.fixed.percentage` for the Application Insights sampler or `microsoft.rate_limited` for the Rate Limited sampler. |
83+
| `OTEL_TRACES_SAMPLER_ARG` | Specifies the sampling parameter for the configured sampler. For the Application Insights sampler, this sets the ratio of distributed tracing telemetry to be [sampled][application_insights_sampling] with accepted values in the range [0,1]. Defaults to 1.0 (no sampling). For the Rate Limited sampler, this sets the maximum traces per second to be [sampled][rate_limited_sampler]. For example, 0.5 means one trace every two seconds, while 5.0 means five traces per second. |
8284
| `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS` | Specifies which of the supported instrumentations to disable. Disabled instrumentations will not be instrumented as part of `configure_azure_monitor`. However, they can still be manually instrumented with `instrument()` directly. Accepts a comma-separated list of lowercase [Library Names](#officially-supported-instrumentations). For example, set to `"psycopg2,fastapi"` to disable the Psycopg2 and FastAPI instrumentations. Defaults to an empty list, enabling all supported instrumentations. |
8385
| `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` | An experimental OpenTelemetry environment variable used to specify Resource Detectors to be used to generate Resource Attributes. This is an experimental feature and the name of this variable and its behavior can change in a non-backwards compatible way. Defaults to "azure_app_service,azure_vm" to enable the [Azure Resource Detectors][ot_resource_detector_azure] for Azure App Service and Azure VM. To add or remove specific resource detectors, set the environment variable accordingly. See the [OpenTelemetry Python Resource Detector Documentation][ot_python_resource_detectors] for more. |
8486

@@ -216,6 +218,7 @@ contact [[email protected]](mailto:[email protected]) with any additio
216218
[application_insights_live_metrics]: https://learn.microsoft.com/azure/azure-monitor/app/live-stream
217219
[application_insights_namespace]: https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview
218220
[application_insights_sampling]: https://learn.microsoft.com/azure/azure-monitor/app/sampling
221+
[rate_limited_sampling]: https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-configuration
219222
[connection_string_doc]: https://learn.microsoft.com/azure/azure-monitor/app/sdk-connection-string
220223
[distro_feature_request]: https://github.com/Azure/azure-sdk-for-python/issues/new
221224
[exporter_configuration_docs]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry-exporter#configuration

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
LOGGING_FORMATTER_ARG,
3636
RESOURCE_ARG,
3737
SAMPLING_RATIO_ARG,
38+
SAMPLING_TRACES_PER_SECOND_ARG,
3839
SPAN_PROCESSORS_ARG,
3940
VIEWS_ARG,
4041
)
@@ -50,6 +51,7 @@
5051
ApplicationInsightsSampler,
5152
AzureMonitorMetricExporter,
5253
AzureMonitorTraceExporter,
54+
RateLimitedSampler,
5355
)
5456
from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module
5557
_is_attach_enabled,
@@ -133,10 +135,21 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758
133135

134136
def _setup_tracing(configurations: Dict[str, ConfigurationValue]):
135137
resource: Resource = configurations[RESOURCE_ARG] # type: ignore
136-
sampling_ratio = configurations[SAMPLING_RATIO_ARG]
137-
tracer_provider = TracerProvider(
138-
sampler=ApplicationInsightsSampler(sampling_ratio=cast(float, sampling_ratio)), resource=resource
139-
)
138+
if SAMPLING_TRACES_PER_SECOND_ARG in configurations:
139+
traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG]
140+
tracer_provider = TracerProvider(
141+
sampler=RateLimitedSampler(
142+
target_spans_per_second_limit=cast(float, traces_per_second)
143+
),
144+
resource=resource
145+
)
146+
else:
147+
sampling_ratio = configurations[SAMPLING_RATIO_ARG]
148+
tracer_provider = TracerProvider(
149+
sampler=ApplicationInsightsSampler(sampling_ratio=cast(float, sampling_ratio)), resource=resource
150+
)
151+
152+
140153
for span_processor in configurations[SPAN_PROCESSORS_ARG]: # type: ignore
141154
tracer_provider.add_span_processor(span_processor) # type: ignore
142155
if configurations.get(ENABLE_LIVE_METRICS_ARG):

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
SAMPLING_RATIO_ARG = "sampling_ratio"
2525
SPAN_PROCESSORS_ARG = "span_processors"
2626
VIEWS_ARG = "views"
27-
27+
RATE_LIMITED_SAMPLER = "microsoft.rate_limited"
28+
FIXED_PERCENTAGE_SAMPLER = "microsoft.fixed.percentage"
29+
SAMPLING_TRACES_PER_SECOND_ARG = "traces_per_second"
2830

2931
# --------------------Autoinstrumentation Configuration------------------------------------------
3032

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opentelemetry.sdk.environment_variables import (
2020
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
2121
OTEL_TRACES_SAMPLER_ARG,
22+
OTEL_TRACES_SAMPLER
2223
)
2324
from opentelemetry.sdk.resources import Resource
2425

@@ -39,25 +40,25 @@
3940
LOGGING_FORMAT_ENV_ARG,
4041
RESOURCE_ARG,
4142
SAMPLING_RATIO_ARG,
43+
SAMPLING_TRACES_PER_SECOND_ARG,
4244
SPAN_PROCESSORS_ARG,
4345
VIEWS_ARG,
46+
RATE_LIMITED_SAMPLER,
47+
FIXED_PERCENTAGE_SAMPLER,
4448
)
4549
from azure.monitor.opentelemetry._types import ConfigurationValue
4650
from azure.monitor.opentelemetry._version import VERSION
4751

4852

4953
_INVALID_FLOAT_MESSAGE = "Value of %s must be a float. Defaulting to %s: %s"
54+
_INVALID_TRACES_PER_SECOND_MESSAGE = "Value of %s must be a positive number for traces per second. Defaulting to %s: %s"
5055
_SUPPORTED_RESOURCE_DETECTORS = (
5156
_AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME,
5257
_AZURE_VM_RESOURCE_DETECTOR_NAME,
5358
)
54-
# TODO: remove when sampler uses env var instead
55-
SAMPLING_RATIO_ENV_VAR = OTEL_TRACES_SAMPLER_ARG
56-
5759

5860
_logger = getLogger(__name__)
5961

60-
6162
def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]:
6263
configurations = {}
6364

@@ -137,21 +138,61 @@ def _default_resource(configurations):
137138
configurations[RESOURCE_ARG] = Resource.create(configurations[RESOURCE_ARG].attributes)
138139

139140

140-
# TODO: remove when sampler uses env var instead
141141
def _default_sampling_ratio(configurations):
142-
default = 1.0
143-
if SAMPLING_RATIO_ENV_VAR in environ:
142+
default_value = 1.0
143+
sampler_type = environ.get(OTEL_TRACES_SAMPLER)
144+
sampler_arg = environ.get(OTEL_TRACES_SAMPLER_ARG)
145+
146+
# Handle rate-limited sampler
147+
if sampler_type == RATE_LIMITED_SAMPLER:
148+
try:
149+
sampler_value = float(sampler_arg)
150+
if sampler_value < 0:
151+
_logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.")
152+
sampler_value = default_value
153+
else:
154+
_logger.info("Using rate limited sampler: %s traces per second", sampler_value)
155+
configurations[SAMPLING_TRACES_PER_SECOND_ARG] = sampler_value
156+
except ValueError as e:
157+
_logger.error( # pylint: disable=C
158+
_INVALID_TRACES_PER_SECOND_MESSAGE,
159+
OTEL_TRACES_SAMPLER_ARG,
160+
default_value,
161+
e,
162+
)
163+
configurations[SAMPLING_TRACES_PER_SECOND_ARG] = default_value
164+
165+
# Handle fixed percentage sampler
166+
elif sampler_type == FIXED_PERCENTAGE_SAMPLER:
144167
try:
145-
default = float(environ[SAMPLING_RATIO_ENV_VAR])
168+
sampler_value = float(sampler_arg)
169+
if sampler_value < 0:
170+
_logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.")
171+
sampler_value = default_value
172+
else:
173+
_logger.info("Using sampling ratio: %s", sampler_value)
174+
configurations[SAMPLING_RATIO_ARG] = sampler_value
146175
except ValueError as e:
147176
_logger.error( # pylint: disable=C
148177
_INVALID_FLOAT_MESSAGE,
149-
SAMPLING_RATIO_ENV_VAR,
150-
default,
178+
OTEL_TRACES_SAMPLER_ARG,
179+
default_value,
151180
e,
152181
)
153-
configurations[SAMPLING_RATIO_ARG] = default
182+
configurations[SAMPLING_RATIO_ARG] = default_value
154183

184+
# Handle all other cases (no sampler type specified or unsupported sampler type)
185+
else:
186+
configurations[SAMPLING_RATIO_ARG] = default_value
187+
if sampler_type is not None:
188+
_logger.error( # pylint: disable=C
189+
"Invalid argument for the sampler to be used for tracing. "
190+
"Supported values are %s and %s. Defaulting to %s: %s",
191+
RATE_LIMITED_SAMPLER,
192+
FIXED_PERCENTAGE_SAMPLER,
193+
OTEL_TRACES_SAMPLER,
194+
OTEL_TRACES_SAMPLER_ARG,
195+
)
155196

156197
def _default_instrumentation_options(configurations):
157198
otel_disabled_instrumentations = _get_otel_disabled_instrumentations()

sdk/monitor/azure-monitor-opentelemetry/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
install_requires=[
8383
"azure-core<2.0.0,>=1.28.0",
8484
"azure-core-tracing-opentelemetry~=1.0.0b11",
85-
"azure-monitor-opentelemetry-exporter~=1.0.0b40",
85+
"azure-monitor-opentelemetry-exporter~=1.0.0b41",
8686
"opentelemetry-instrumentation-django~=0.57b0",
8787
"opentelemetry-instrumentation-fastapi~=0.57b0",
8888
"opentelemetry-instrumentation-flask~=0.57b0",

sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,66 @@ def test_setup_tracing(
309309
tp_init_mock.add_span_processor.assert_has_calls([call(custom_sp), call(bsp_init_mock)])
310310
self.assertEqual(settings_mock.tracing_implementation, opentelemetry_span_mock)
311311

312+
@patch(
313+
"azure.monitor.opentelemetry._configure.BatchSpanProcessor",
314+
)
315+
@patch(
316+
"azure.monitor.opentelemetry._configure.AzureMonitorTraceExporter",
317+
)
318+
@patch(
319+
"azure.monitor.opentelemetry._configure.set_tracer_provider",
320+
)
321+
@patch(
322+
"azure.monitor.opentelemetry._configure.TracerProvider",
323+
autospec=True,
324+
)
325+
@patch(
326+
"azure.monitor.opentelemetry._configure.RateLimitedSampler",
327+
)
328+
def test_setup_tracing_rate_limited_sampler(
329+
self,
330+
sampler_mock,
331+
tp_mock,
332+
set_tracer_provider_mock,
333+
trace_exporter_mock,
334+
bsp_mock,
335+
):
336+
sampler_init_mock = Mock()
337+
sampler_mock.return_value = sampler_init_mock
338+
tp_init_mock = Mock()
339+
tp_mock.return_value = tp_init_mock
340+
trace_exp_init_mock = Mock()
341+
trace_exporter_mock.return_value = trace_exp_init_mock
342+
bsp_init_mock = Mock()
343+
bsp_mock.return_value = bsp_init_mock
344+
custom_sp = Mock()
345+
346+
settings_mock = Mock()
347+
opentelemetry_span_mock = Mock()
348+
349+
configurations = {
350+
"connection_string": "test_cs",
351+
"instrumentation_options": {"azure_sdk": {"enabled": True}},
352+
"traces_per_second": 2.0,
353+
"span_processors": [custom_sp],
354+
"resource": TEST_RESOURCE,
355+
}
356+
with patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") as instr_mock:
357+
instr_mock.return_value = True
358+
with patch.dict('sys.modules', {
359+
'azure.core.settings': Mock(settings=settings_mock),
360+
'azure.core.tracing.ext.opentelemetry_span': Mock(OpenTelemetrySpan=opentelemetry_span_mock)
361+
}):
362+
_setup_tracing(configurations)
363+
sampler_mock.assert_called_once_with(target_spans_per_second_limit=2.0)
364+
tp_mock.assert_called_once_with(sampler=sampler_init_mock, resource=TEST_RESOURCE)
365+
set_tracer_provider_mock.assert_called_once_with(tp_init_mock)
366+
trace_exporter_mock.assert_called_once_with(**configurations)
367+
bsp_mock.assert_called_once_with(trace_exp_init_mock)
368+
self.assertEqual(tp_init_mock.add_span_processor.call_count, 2)
369+
tp_init_mock.add_span_processor.assert_has_calls([call(custom_sp), call(bsp_init_mock)])
370+
self.assertEqual(settings_mock.tracing_implementation, opentelemetry_span_mock)
371+
312372

313373
@patch("azure.monitor.opentelemetry._configure.getLogger")
314374
def test_setup_logging(self, get_logger_mock):

0 commit comments

Comments
 (0)