Skip to content

Commit 6be8beb

Browse files
authored
Fix sampleRate in AppInsightsSampler (#26771)
1 parent 1d43ea6 commit 6be8beb

File tree

12 files changed

+110
-11
lines changed

12 files changed

+110
-11
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
### Bugs Fixed
1313

14+
- Fixed sampleRate field in ApplicationInsightsSampler, changed attribute to `_MS.sampleRate`
15+
([#26771](https://github.com/Azure/azure-sdk-for-python/pull/26771))
16+
1417
### Other Changes
1518

1619
- Update `README.md`

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

+4
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,7 @@
9898
]
9999
# cSpell:enable
100100
_INSTRUMENTATIONS_BIT_MAP = {_INSTRUMENTATIONS_LIST[i]: _BASE**i for i in range(len(_INSTRUMENTATIONS_LIST))}
101+
102+
# sampleRate
103+
104+
_SAMPLE_RATE_KEY = "_MS.sampleRate"

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py

+3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ def __init__(self, **kwargs: Any) -> None:
5858
"""Azure Monitor base exporter for OpenTelemetry.
5959
6060
:keyword str api_version: The service API version used. Defaults to latest.
61+
:keyword str connection_string: The connection string used for your Application Insights resource.
62+
:keyword bool enable_local_storage: Determines whether to store failed telemetry records for retry. Defaults to `True`.
63+
:keyword str storage_path: Storage path in which to store retry files. Defaults to `<tempfile.gettempdir()>/opentelemetry-python-<your-instrumentation-key>`.
6164
:rtype: None
6265
"""
6366
parsed_connection_string = ConnectionStringParser(kwargs.get('connection_string'))

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
1212
from opentelemetry.trace import SpanKind
1313

14+
from azure.monitor.opentelemetry.exporter._constants import _SAMPLE_RATE_KEY
1415
from azure.monitor.opentelemetry.exporter import _utils
1516
from azure.monitor.opentelemetry.exporter._generated.models import (
1617
MessageData,
@@ -45,6 +46,10 @@
4546
"code.",
4647
]
4748

49+
_STANDARD_AZURE_MONITOR_ATTRIBUTES = [
50+
_SAMPLE_RATE_KEY,
51+
]
52+
4853

4954
class AzureMonitorTraceExporter(BaseExporter, SpanExporter):
5055
"""Azure Monitor Trace exporter for OpenTelemetry."""
@@ -420,9 +425,13 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
420425
if target:
421426
data.target = str(target)[:1024]
422427

428+
# sampleRate
429+
if _SAMPLE_RATE_KEY in span.attributes:
430+
envelope.sample_rate = span.attributes[_SAMPLE_RATE_KEY]
431+
423432
data.properties = _utils._filter_custom_properties(
424433
span.attributes,
425-
lambda key, val: not _is_opentelemetry_standard_attribute(key)
434+
lambda key, val: not _is_standard_attribute(key)
426435
)
427436
if span.links:
428437
# Max length for value is 8192
@@ -450,7 +459,7 @@ def _convert_span_events_to_envelopes(span: ReadableSpan) -> Sequence[TelemetryI
450459
)
451460
properties = _utils._filter_custom_properties(
452461
event.attributes,
453-
lambda key, val: not _is_opentelemetry_standard_attribute(key)
462+
lambda key, val: not _is_standard_attribute(key)
454463
)
455464
if event.name == "exception":
456465
envelope.name = 'Microsoft.ApplicationInsights.Exception'
@@ -545,11 +554,12 @@ def _check_instrumentation_span(span: ReadableSpan) -> None:
545554
_utils.add_instrumentation(name)
546555

547556

548-
def _is_opentelemetry_standard_attribute(key: str) -> bool:
557+
def _is_standard_attribute(key: str) -> bool:
549558
for prefix in _STANDARD_OPENTELEMETRY_ATTRIBUTE_PREFIXES:
550559
if key.startswith(prefix):
551560
return True
552-
return False
561+
return key in _STANDARD_AZURE_MONITOR_ATTRIBUTES
562+
553563

554564
def _get_azure_sdk_target_source(attributes: Attributes) -> Optional[str]:
555565
# Currently logic only works for ServiceBus and EventHub

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_sampling.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from opentelemetry.trace.span import TraceState
1616
from opentelemetry.util.types import Attributes
1717

18+
from azure.monitor.opentelemetry.exporter._constants import _SAMPLE_RATE_KEY
19+
1820

1921
_HASH = 5381
2022
_INTEGER_MAX = Int32.maxval
@@ -62,7 +64,7 @@ def should_sample(
6264
# Add sample rate as span attribute
6365
if attributes is None:
6466
attributes = {}
65-
attributes["sampleRate"] = self._sample_rate
67+
attributes[_SAMPLE_RATE_KEY] = self._sample_rate
6668
return SamplingResult(
6769
decision,
6870
attributes,

sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ products:
1010

1111
These code samples show common champion scenario operations with the AzureMonitorMetricExporter.
1212

13+
* Attributes: [sample_attributes.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py)
1314
* Instruments: [sample_instruments.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py)
15+
* Views: [sample_views.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py)
1416

1517
## Installation
1618

@@ -20,6 +22,17 @@ $ pip install azure-monitor-opentelemetry-exporter --pre
2022

2123
## Run the Applications
2224

25+
### Metrics with attributes
26+
27+
* Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable
28+
29+
* Run the sample
30+
31+
```sh
32+
$ # from this directory
33+
$ python sample_attributes.py
34+
```
35+
2336
### Instrument usage
2437

2538
* Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable
@@ -31,6 +44,17 @@ $ # from this directory
3144
$ python sample_instruments.py
3245
```
3346

47+
### Configuring metrics with views
48+
49+
* Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable
50+
51+
* Run the sample
52+
53+
```sh
54+
$ # from this directory
55+
$ python sample_views.py
56+
```
57+
3458
## Explore the data
3559

3660
After running the applications, data would be available in [Azure](

sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
An example to show an application using different attributes with instruments in the OpenTelemetry SDK.
55
Metrics created and recorded using the sdk are tracked and telemetry is exported to application insights
6-
with the AzureMonitorMetricsExporter.
6+
with the AzureMonitorMetricExporter.
77
"""
88
import os
99

@@ -16,7 +16,8 @@
1616
exporter = AzureMonitorMetricExporter.from_connection_string(
1717
os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"]
1818
)
19-
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000)
19+
# Metrics are reported every 1 minute
20+
reader = PeriodicExportingMetricReader(exporter)
2021
metrics.set_meter_provider(MeterProvider(metric_readers=[reader]))
2122

2223
attribute_set1 = {

sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
# cSpell:disable
34
"""
45
An example to show an application using all instruments in the OpenTelemetry SDK. Metrics created
56
and recorded using the sdk are tracked and telemetry is exported to application insights with the
6-
AzureMonitorMetricsExporter.
7+
AzureMonitorMetricExporter.
78
"""
89
import os
910
from typing import Iterable
@@ -18,7 +19,8 @@
1819
exporter = AzureMonitorMetricExporter.from_connection_string(
1920
os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"]
2021
)
21-
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000)
22+
# Metrics are reported every 1 minute
23+
reader = PeriodicExportingMetricReader(exporter)
2224
metrics.set_meter_provider(MeterProvider(metric_readers=[reader]))
2325

2426
# Create a namespaced meter
@@ -63,3 +65,5 @@ def observable_gauge_func(options: CallbackOptions) -> Iterable[Observation]:
6365

6466
# Async Gauge
6567
gauge = meter.create_observable_gauge("gauge", [observable_gauge_func])
68+
69+
# cSpell:disable

sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
This example shows how to customize the metrics that are output by the SDK using Views. Metrics created
55
and recorded using the sdk are tracked and telemetry is exported to application insights with the
6-
AzureMonitorMetricsExporter.
6+
AzureMonitorMetricExporter.
77
"""
88
import os
99

@@ -25,7 +25,8 @@
2525
name="my.counter.total",
2626
)
2727

28-
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000)
28+
# Metrics are reported every 1 minute
29+
reader = PeriodicExportingMetricReader(exporter)
2930
provider = MeterProvider(
3031
metric_readers=[
3132
reader,

sdk/monitor/azure-monitor-opentelemetry-exporter/samples/traces/sample_sampling.py

+2
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@
2929
# Approximately 25% of these spans should be sampled out
3030
with tracer.start_as_current_span("hello"):
3131
print("Hello, World!")
32+
33+
input()

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_sampling.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33
import unittest
4+
from unittest import mock
45

56
from azure.monitor.opentelemetry.exporter.export.trace._sampling import (
67
ApplicationInsightsSampler,
@@ -21,3 +22,19 @@ def test_invalid_ratio(self):
2122
self.assertRaises(
2223
ValueError, lambda: ApplicationInsightsSampler(-0.01)
2324
)
25+
26+
@mock.patch.object(ApplicationInsightsSampler, '_get_DJB2_sample_score')
27+
def test_should_sample(self, score_mock):
28+
sampler = ApplicationInsightsSampler(0.75)
29+
score_mock.return_value = 0.7
30+
result = sampler.should_sample(None, 0, "test")
31+
self.assertEqual(result.attributes["_MS.sampleRate"], 75)
32+
self.assertTrue(result.decision.is_sampled())
33+
34+
@mock.patch.object(ApplicationInsightsSampler, '_get_DJB2_sample_score')
35+
def test_should_sample_not_sampled(self, score_mock):
36+
sampler = ApplicationInsightsSampler(0.5)
37+
score_mock.return_value = 0.7
38+
result = sampler.should_sample(None, 0, "test")
39+
self.assertEqual(result.attributes["_MS.sampleRate"], 50)
40+
self.assertFalse(result.decision.is_sampled())

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py

+28
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,34 @@ def test_span_to_envelope_success_error(self):
976976
envelope = exporter._span_to_envelope(span)
977977
self.assertFalse(envelope.data.base_data.success)
978978

979+
def test_span_to_envelope_sample_rate(self):
980+
exporter = self._exporter
981+
start_time = 1575494316027613500
982+
end_time = start_time + 1001000000
983+
984+
span = trace._Span(
985+
name="test",
986+
context=SpanContext(
987+
trace_id=36873507687745823477771305566750195431,
988+
span_id=12030755672171557337,
989+
is_remote=False,
990+
),
991+
attributes={
992+
"test": "asd",
993+
"http.method": "GET",
994+
"http.url": "https://www.wikipedia.org/wiki/Rabbit",
995+
"http.status_code": 200,
996+
"_MS.sampleRate": 50,
997+
},
998+
kind=SpanKind.CLIENT,
999+
)
1000+
span._status = Status(status_code=StatusCode.OK)
1001+
span.start(start_time=start_time)
1002+
span.end(end_time=end_time)
1003+
envelope = exporter._span_to_envelope(span)
1004+
self.assertEqual(envelope.sample_rate, 50)
1005+
self.assertIsNone(envelope.data.base_data.properties.get("_MS.sampleRate"))
1006+
9791007
def test_span_to_envelope_properties(self):
9801008
exporter = self._exporter
9811009
start_time = 1575494316027613500

0 commit comments

Comments
 (0)