Skip to content

Commit 1f39e15

Browse files
committed
Config changes for rate-limited-sampler
1 parent a08ee78 commit 1f39e15

File tree

6 files changed

+232
-23
lines changed

6 files changed

+232
-23
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- Added customer-facing statsbeat preview.
1010
([#41669](https://github.com/Azure/azure-sdk-for-python/pull/41669))
1111
- Added RateLimited Sampler
12-
([#41925](https://github.com/Azure/azure-sdk-for-python/pull/41925))
12+
([#41954](https://github.com/Azure/azure-sdk-for-python/pull/41954))
1313

1414
### Breaking Changes
1515

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.6.11 (Unreleased)
44

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

79
### Breaking Changes
810

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

Lines changed: 14 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,18 @@ 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+
sampling_traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG]
140+
tracer_provider = TracerProvider(
141+
sampler=RateLimitedSampler(sampling_ratio=cast(float, sampling_traces_per_second), resource=resource)
142+
)
143+
else:
144+
sampling_ratio = configurations[SAMPLING_RATIO_ARG]
145+
tracer_provider = TracerProvider(
146+
sampler=ApplicationInsightsSampler(sampling_ratio=cast(float, sampling_ratio)), resource=resource
147+
)
148+
149+
140150
for span_processor in configurations[SPAN_PROCESSORS_ARG]: # type: ignore
141151
tracer_provider.add_span_processor(span_processor) # type: ignore
142152
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 = "sampling_traces_per_second"
2830

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

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

Lines changed: 44 additions & 10 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

@@ -37,25 +38,25 @@
3738
LOGGING_FORMATTER_ARG,
3839
RESOURCE_ARG,
3940
SAMPLING_RATIO_ARG,
41+
SAMPLING_TRACES_PER_SECOND_ARG,
4042
SPAN_PROCESSORS_ARG,
4143
VIEWS_ARG,
44+
RATE_LIMITED_SAMPLER,
45+
FIXED_PERCENTAGE_SAMPLER,
4246
)
4347
from azure.monitor.opentelemetry._types import ConfigurationValue
4448
from azure.monitor.opentelemetry._version import VERSION
4549

4650

4751
_INVALID_FLOAT_MESSAGE = "Value of %s must be a float. Defaulting to %s: %s"
52+
_INVALID_TRACES_PER_SECOND_MESSAGE = "Value of %s must be a positive number for traces per second. Defaulting to %s: %s"
4853
_SUPPORTED_RESOURCE_DETECTORS = (
4954
_AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME,
5055
_AZURE_VM_RESOURCE_DETECTOR_NAME,
5156
)
52-
# TODO: remove when sampler uses env var instead
53-
SAMPLING_RATIO_ENV_VAR = OTEL_TRACES_SAMPLER_ARG
54-
5557

5658
_logger = getLogger(__name__)
5759

58-
5960
def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]:
6061
configurations = {}
6162

@@ -120,21 +121,54 @@ def _default_resource(configurations):
120121
configurations[RESOURCE_ARG] = Resource.create(configurations[RESOURCE_ARG].attributes)
121122

122123

123-
# TODO: remove when sampler uses env var instead
124124
def _default_sampling_ratio(configurations):
125125
default = 1.0
126-
if SAMPLING_RATIO_ENV_VAR in environ:
126+
127+
if environ.get(OTEL_TRACES_SAMPLER_ARG) is not None:
128+
try:
129+
if float(environ[OTEL_TRACES_SAMPLER_ARG]) < 0:
130+
_logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.")
131+
except ValueError:
132+
pass
133+
else:
134+
_logger.error("OTEL_TRACES_SAMPLER_ARG is not set.")
135+
136+
# Check if rate-limited sampler is configured
137+
if environ.get(OTEL_TRACES_SAMPLER) == RATE_LIMITED_SAMPLER:
138+
try:
139+
default = float(environ[OTEL_TRACES_SAMPLER_ARG])
140+
print(f"Using rate limited sampler: {default} traces per second")
141+
except ValueError as e:
142+
_logger.error( # pylint: disable=C
143+
_INVALID_TRACES_PER_SECOND_MESSAGE,
144+
OTEL_TRACES_SAMPLER_ARG,
145+
default,
146+
e,
147+
)
148+
configurations[SAMPLING_TRACES_PER_SECOND_ARG] = default
149+
elif environ.get(OTEL_TRACES_SAMPLER) == FIXED_PERCENTAGE_SAMPLER:
127150
try:
128-
default = float(environ[SAMPLING_RATIO_ENV_VAR])
151+
default = float(environ[OTEL_TRACES_SAMPLER_ARG])
152+
print(f"Using sampling ratio: {default}")
129153
except ValueError as e:
130154
_logger.error( # pylint: disable=C
131155
_INVALID_FLOAT_MESSAGE,
132-
SAMPLING_RATIO_ENV_VAR,
156+
OTEL_TRACES_SAMPLER_ARG,
133157
default,
134158
e,
135159
)
136-
configurations[SAMPLING_RATIO_ARG] = default
137-
160+
configurations[SAMPLING_RATIO_ARG] = default
161+
else:
162+
# Default behavior - always set sampling_ratio
163+
configurations[SAMPLING_RATIO_ARG] = default
164+
_logger.error( # pylint: disable=C
165+
"Invalid argument for the sampler to be used for tracing. "
166+
"Supported values are %s and %s. Defaulting to %s: %s",
167+
RATE_LIMITED_SAMPLER,
168+
FIXED_PERCENTAGE_SAMPLER,
169+
OTEL_TRACES_SAMPLER,
170+
OTEL_TRACES_SAMPLER_ARG,
171+
)
138172

139173
def _default_instrumentation_options(configurations):
140174
otel_disabled_instrumentations = _get_otel_disabled_instrumentations()

sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@
1616
from unittest import TestCase
1717
from unittest.mock import patch
1818

19-
from opentelemetry.instrumentation.environment_variables import (
20-
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
19+
from opentelemetry.sdk.environment_variables import (
20+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
21+
OTEL_TRACES_SAMPLER_ARG,
22+
OTEL_TRACES_SAMPLER
2123
)
24+
from opentelemetry.instrumentation.environment_variables import (
25+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,)
2226
from azure.monitor.opentelemetry._utils.configurations import (
23-
SAMPLING_RATIO_ENV_VAR,
2427
_get_configurations,
2528
)
29+
from azure.monitor.opentelemetry._constants import (
30+
RATE_LIMITED_SAMPLER,
31+
FIXED_PERCENTAGE_SAMPLER,
32+
)
2633
from opentelemetry.environment_variables import (
2734
OTEL_LOGS_EXPORTER,
2835
OTEL_METRICS_EXPORTER,
@@ -134,7 +141,7 @@ def test_get_configurations_defaults(self, resource_create_mock):
134141
"os.environ",
135142
{
136143
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk",
137-
SAMPLING_RATIO_ENV_VAR: "0.5",
144+
OTEL_TRACES_SAMPLER_ARG: "0.5",
138145
OTEL_TRACES_EXPORTER: "None",
139146
OTEL_LOGS_EXPORTER: "none",
140147
OTEL_METRICS_EXPORTER: "NONE",
@@ -166,12 +173,13 @@ def test_get_configurations_env_vars(self, resource_create_mock):
166173
self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes)
167174
self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector")
168175
resource_create_mock.assert_called_once_with()
169-
self.assertEqual(configurations["sampling_ratio"], 0.5)
176+
self.assertEqual(configurations["sampling_ratio"], 1.0)
170177

171178
@patch.dict(
172179
"os.environ",
173180
{
174-
SAMPLING_RATIO_ENV_VAR: "Half",
181+
OTEL_TRACES_SAMPLER: FIXED_PERCENTAGE_SAMPLER,
182+
OTEL_TRACES_SAMPLER_ARG: "Half",
175183
OTEL_TRACES_EXPORTER: "False",
176184
OTEL_LOGS_EXPORTER: "no",
177185
OTEL_METRICS_EXPORTER: "True",
@@ -181,7 +189,7 @@ def test_get_configurations_env_vars(self, resource_create_mock):
181189
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
182190
def test_get_configurations_env_vars_validation(self, resource_create_mock):
183191
configurations = _get_configurations()
184-
192+
print(configurations)
185193
self.assertTrue("connection_string" not in configurations)
186194
self.assertEqual(configurations["disable_logging"], False)
187195
self.assertEqual(configurations["disable_metrics"], False)
@@ -260,3 +268,156 @@ def test_merge_instrumentation_options_extra_args(self, resource_create_mock):
260268
"urllib3": {"enabled": True},
261269
},
262270
)
271+
@patch.dict(
272+
"os.environ",
273+
{
274+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk",
275+
OTEL_TRACES_SAMPLER: RATE_LIMITED_SAMPLER,
276+
OTEL_TRACES_SAMPLER_ARG: "0.5",
277+
OTEL_TRACES_EXPORTER: "None",
278+
OTEL_LOGS_EXPORTER: "none",
279+
OTEL_METRICS_EXPORTER: "NONE",
280+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector",
281+
},
282+
clear=True,
283+
)
284+
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
285+
def test_get_configurations_env_vars_rate_limited(self, resource_create_mock):
286+
configurations = _get_configurations()
287+
288+
self.assertTrue("connection_string" not in configurations)
289+
self.assertEqual(configurations["disable_logging"], True)
290+
self.assertEqual(configurations["disable_metrics"], True)
291+
self.assertEqual(configurations["disable_tracing"], True)
292+
self.assertEqual(
293+
configurations["instrumentation_options"],
294+
{
295+
"azure_sdk": {"enabled": False},
296+
"django": {"enabled": True},
297+
"fastapi": {"enabled": False},
298+
"flask": {"enabled": False},
299+
"psycopg2": {"enabled": True},
300+
"requests": {"enabled": False},
301+
"urllib": {"enabled": True},
302+
"urllib3": {"enabled": True},
303+
},
304+
)
305+
self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes)
306+
self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector")
307+
resource_create_mock.assert_called_once_with()
308+
self.assertEqual(configurations["sampling_traces_per_second"], 0.5)
309+
310+
@patch.dict(
311+
"os.environ",
312+
{
313+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk",
314+
OTEL_TRACES_SAMPLER_ARG: "34",
315+
OTEL_TRACES_EXPORTER: "None",
316+
OTEL_LOGS_EXPORTER: "none",
317+
OTEL_METRICS_EXPORTER: "NONE",
318+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector",
319+
},
320+
clear=True,
321+
)
322+
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
323+
def test_get_configurations_env_vars_no_preference(self, resource_create_mock):
324+
configurations = _get_configurations()
325+
326+
self.assertTrue("connection_string" not in configurations)
327+
self.assertEqual(configurations["disable_logging"], True)
328+
self.assertEqual(configurations["disable_metrics"], True)
329+
self.assertEqual(configurations["disable_tracing"], True)
330+
self.assertEqual(
331+
configurations["instrumentation_options"],
332+
{
333+
"azure_sdk": {"enabled": False},
334+
"django": {"enabled": True},
335+
"fastapi": {"enabled": False},
336+
"flask": {"enabled": False},
337+
"psycopg2": {"enabled": True},
338+
"requests": {"enabled": False},
339+
"urllib": {"enabled": True},
340+
"urllib3": {"enabled": True},
341+
},
342+
)
343+
self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes)
344+
self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector")
345+
resource_create_mock.assert_called_once_with()
346+
self.assertEqual(configurations["sampling_ratio"], 1.0)
347+
348+
@patch.dict(
349+
"os.environ",
350+
{
351+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk",
352+
OTEL_TRACES_SAMPLER_ARG: "2 traces per second",
353+
OTEL_TRACES_EXPORTER: "None",
354+
OTEL_LOGS_EXPORTER: "none",
355+
OTEL_METRICS_EXPORTER: "NONE",
356+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector",
357+
},
358+
clear=True,
359+
)
360+
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
361+
def test_get_configurations_env_vars_check_default(self, resource_create_mock):
362+
configurations = _get_configurations()
363+
364+
self.assertTrue("connection_string" not in configurations)
365+
self.assertEqual(configurations["disable_logging"], True)
366+
self.assertEqual(configurations["disable_metrics"], True)
367+
self.assertEqual(configurations["disable_tracing"], True)
368+
self.assertEqual(
369+
configurations["instrumentation_options"],
370+
{
371+
"azure_sdk": {"enabled": False},
372+
"django": {"enabled": True},
373+
"fastapi": {"enabled": False},
374+
"flask": {"enabled": False},
375+
"psycopg2": {"enabled": True},
376+
"requests": {"enabled": False},
377+
"urllib": {"enabled": True},
378+
"urllib3": {"enabled": True},
379+
},
380+
)
381+
self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes)
382+
self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector")
383+
resource_create_mock.assert_called_once_with()
384+
self.assertEqual(configurations["sampling_ratio"], 1.0)
385+
386+
@patch.dict(
387+
"os.environ",
388+
{
389+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk",
390+
OTEL_TRACES_SAMPLER: FIXED_PERCENTAGE_SAMPLER,
391+
OTEL_TRACES_SAMPLER_ARG: "0.9",
392+
OTEL_TRACES_EXPORTER: "None",
393+
OTEL_LOGS_EXPORTER: "none",
394+
OTEL_METRICS_EXPORTER: "NONE",
395+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector",
396+
},
397+
clear=True,
398+
)
399+
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
400+
def test_get_configurations_env_vars_fixed_percentage(self, resource_create_mock):
401+
configurations = _get_configurations()
402+
403+
self.assertTrue("connection_string" not in configurations)
404+
self.assertEqual(configurations["disable_logging"], True)
405+
self.assertEqual(configurations["disable_metrics"], True)
406+
self.assertEqual(configurations["disable_tracing"], True)
407+
self.assertEqual(
408+
configurations["instrumentation_options"],
409+
{
410+
"azure_sdk": {"enabled": False},
411+
"django": {"enabled": True},
412+
"fastapi": {"enabled": False},
413+
"flask": {"enabled": False},
414+
"psycopg2": {"enabled": True},
415+
"requests": {"enabled": False},
416+
"urllib": {"enabled": True},
417+
"urllib3": {"enabled": True},
418+
},
419+
)
420+
self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes)
421+
self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector")
422+
resource_create_mock.assert_called_once_with()
423+
self.assertEqual(configurations["sampling_ratio"], 0.9)

0 commit comments

Comments
 (0)