Skip to content

Commit cc5f253

Browse files
authored
Add AMW Deduping flag, SentToAMW, for AKS Attach (#41410)
* deduping flag * Simplify * Changelog. Needs tests * Tests pass * simplify tests * lint * lint * statsbeat check
1 parent aa65a86 commit cc5f253

File tree

3 files changed

+130
-37
lines changed

3 files changed

+130
-37
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Features Added
66

7+
- Add AMW deduplicating flag, SentToAMW, for AKS Attach
8+
([#41410](https://github.com/Azure/azure-sdk-for-python/pull/41410))
9+
710
### Breaking Changes
811

912
### Bugs Fixed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
from typing import Dict, Optional, Union, Any
77

8+
from opentelemetry.environment_variables import OTEL_METRICS_EXPORTER
89
from opentelemetry.util.types import Attributes
10+
from opentelemetry.sdk.environment_variables import OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
911
from opentelemetry.sdk.metrics import (
1012
Counter,
1113
Histogram,
@@ -142,6 +144,7 @@ def shutdown(
142144
if self.storage:
143145
self.storage.close()
144146

147+
# pylint: disable=protected-access
145148
def _point_to_envelope(
146149
self,
147150
point: DataPointT,
@@ -154,6 +157,16 @@ def _point_to_envelope(
154157
envelope = _handle_std_metric_envelope(envelope, name, point.attributes) # type: ignore
155158
if envelope is not None:
156159
envelope.instrumentation_key = self._instrumentation_key
160+
# Only set SentToAMW on AKS Attach
161+
if _utils._is_on_aks() and _utils._is_attach_enabled() and not self._is_stats_exporter():
162+
if (
163+
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT in os.environ
164+
and "otlp" in os.environ.get(OTEL_METRICS_EXPORTER, "")
165+
):
166+
envelope.data.base_data.properties["_MS.SentToAMW"] = "True" # type: ignore
167+
else:
168+
envelope.data.base_data.properties["_MS.SentToAMW"] = "False" # type: ignore
169+
157170
return envelope
158171

159172
# pylint: disable=docstring-keyword-should-match-keyword-only

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/metrics/test_metrics.py

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
AzureMonitorMetricExporter,
2727
_get_metric_export_result,
2828
)
29+
from azure.monitor.opentelemetry.exporter.statsbeat._exporter import _StatsBeatExporter
2930
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
3031
from azure.monitor.opentelemetry.exporter._utils import (
3132
azure_monitor_context,
@@ -85,6 +86,27 @@ def setUpClass(cls):
8586
)
8687
]
8788
)
89+
cls._histogram_data_point = HistogramDataPoint(
90+
attributes={
91+
"test": "attribute",
92+
},
93+
bucket_counts=[0, 3, 4],
94+
count=7,
95+
explicit_bounds=[0, 5, 10, 0],
96+
max=18,
97+
min=1,
98+
start_time_unix_nano=1646865018558419456,
99+
time_unix_nano=1646865018558419457,
100+
sum=31,
101+
)
102+
cls._number_data_point = NumberDataPoint(
103+
attributes={
104+
"test": "attribute",
105+
},
106+
start_time_unix_nano=1646865018558419456,
107+
time_unix_nano=1646865018558419457,
108+
value=10,
109+
)
88110

89111
@classmethod
90112
def tearDownClass(cls):
@@ -202,14 +224,7 @@ def test_point_to_envelope_partA(self):
202224
def test_point_to_envelope_partA_default(self):
203225
exporter = self._exporter
204226
resource = Resource({"service.name": "testServiceName"})
205-
point = NumberDataPoint(
206-
attributes={
207-
"test": "attribute",
208-
},
209-
start_time_unix_nano=1646865018558419456,
210-
time_unix_nano=1646865018558419457,
211-
value=10,
212-
)
227+
point = self._number_data_point
213228
envelope = exporter._point_to_envelope(point, "test name", resource)
214229
self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), "testServiceName")
215230
self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), platform.node())
@@ -222,14 +237,7 @@ def test_point_to_envelope_number(self):
222237
exporter = self._exporter
223238
resource = Resource.create(attributes={"asd": "test_resource"})
224239
scope = InstrumentationScope("test_scope")
225-
point = NumberDataPoint(
226-
attributes={
227-
"test": "attribute",
228-
},
229-
start_time_unix_nano=1646865018558419456,
230-
time_unix_nano=1646865018558419457,
231-
value=10,
232-
)
240+
point = self._number_data_point
233241
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
234242
self.assertEqual(envelope.instrumentation_key, exporter._instrumentation_key)
235243
self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Metric")
@@ -246,19 +254,7 @@ def test_point_to_envelope_number(self):
246254
def test_point_to_envelope_histogram(self):
247255
exporter = self._exporter
248256
resource = Resource.create(attributes={"asd": "test_resource"})
249-
point = HistogramDataPoint(
250-
attributes={
251-
"test": "attribute",
252-
},
253-
bucket_counts=[0, 3, 4],
254-
count=7,
255-
explicit_bounds=[0, 5, 10, 0],
256-
max=18,
257-
min=1,
258-
start_time_unix_nano=1646865018558419456,
259-
time_unix_nano=1646865018558419457,
260-
sum=31,
261-
)
257+
point = self._histogram_data_point
262258
envelope = exporter._point_to_envelope(point, "test name", resource)
263259
self.assertEqual(envelope.instrumentation_key, exporter._instrumentation_key)
264260
self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Metric")
@@ -271,6 +267,90 @@ def test_point_to_envelope_histogram(self):
271267
self.assertEqual(envelope.data.base_data.metrics[0].value, 31)
272268
self.assertEqual(envelope.data.base_data.metrics[0].count, 7)
273269

270+
@mock.patch.dict("os.environ", {
271+
"OTEL_METRICS_EXPORTER": " foo, otlp, bar",
272+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "TEST_ENDPOINT",
273+
})
274+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=True)
275+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=True)
276+
def test_point_to_envelope_aks_amw(self, attach_mock, aks_mock):
277+
exporter = self._exporter
278+
resource = Resource.create(attributes={"asd": "test_resource"})
279+
scope = InstrumentationScope("test_scope")
280+
point = self._number_data_point
281+
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
282+
self.assertEqual(len(envelope.data.base_data.properties), 2)
283+
self.assertEqual(envelope.data.base_data.properties["_MS.SentToAMW"], "True")
284+
285+
@mock.patch.dict("os.environ", {
286+
"OTEL_METRICS_EXPORTER": " foo, otlp, bar",
287+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "TEST_ENDPOINT",
288+
})
289+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=True)
290+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=True)
291+
def test_point_to_envelope_statsbeat(self, attach_mock, aks_mock):
292+
exporter = _StatsBeatExporter()
293+
point = self._number_data_point
294+
envelope = exporter._point_to_envelope(point, "attach")
295+
self.assertEqual(len(envelope.data.base_data.properties), 1)
296+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
297+
298+
@mock.patch.dict("os.environ", {
299+
"OTEL_METRICS_EXPORTER": " foo ,otlp ,bar",
300+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "TEST_ENDPOINT",
301+
})
302+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=False)
303+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=True)
304+
def test_point_to_envelope_otlp_no_aks(self, attach_mock, aks_mock):
305+
exporter = self._exporter
306+
resource = Resource.create(attributes={"asd": "test_resource"})
307+
point = self._histogram_data_point
308+
envelope = exporter._point_to_envelope(point, "test name", resource)
309+
self.assertEqual(len(envelope.data.base_data.properties), 1)
310+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
311+
312+
@mock.patch.dict("os.environ", {
313+
"OTEL_METRICS_EXPORTER": " foo ,otlp ,bar",
314+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "TEST_ENDPOINT",
315+
})
316+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=True)
317+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=False)
318+
def test_point_to_envelope_otlp_aks_no_attach(self, attach_mock, aks_mock):
319+
exporter = self._exporter
320+
resource = Resource.create(attributes={"asd": "test_resource"})
321+
point = self._histogram_data_point
322+
envelope = exporter._point_to_envelope(point, "test name", resource)
323+
self.assertEqual(len(envelope.data.base_data.properties), 1)
324+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
325+
326+
@mock.patch.dict("os.environ", {
327+
"OTEL_METRICS_EXPORTER": " foo, bar",
328+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "TEST_ENDPOINT",
329+
})
330+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=True)
331+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=True)
332+
def test_point_to_envelope_aks_attach_no_otlp(self, attach_mock, aks_mock):
333+
exporter = self._exporter
334+
resource = Resource.create(attributes={"asd": "test_resource"})
335+
scope = InstrumentationScope("test_scope")
336+
point = self._number_data_point
337+
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
338+
self.assertEqual(len(envelope.data.base_data.properties), 2)
339+
self.assertEqual(envelope.data.base_data.properties["_MS.SentToAMW"], "False")
340+
341+
@mock.patch.dict("os.environ", {
342+
"OTEL_METRICS_EXPORTER": " foo ,otlp ,bar",
343+
})
344+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_on_aks", return_value=True)
345+
@mock.patch("azure.monitor.opentelemetry.exporter.export.metrics._exporter._utils._is_attach_enabled", return_value=True)
346+
def test_point_to_envelope_aks_attach_no_endpoint(self, attach_mock, aks_mock):
347+
exporter = self._exporter
348+
resource = Resource.create(attributes={"asd": "test_resource"})
349+
point = self._histogram_data_point
350+
envelope = exporter._point_to_envelope(point, "test name", resource)
351+
self.assertEqual(len(envelope.data.base_data.properties), 2)
352+
self.assertEqual(envelope.data.base_data.properties["_MS.SentToAMW"], "False")
353+
274354
@mock.patch.dict(
275355
"os.environ",
276356
{
@@ -281,14 +361,7 @@ def test_point_to_envelope_metric_namespace(self):
281361
exporter = self._exporter
282362
resource = Resource.create(attributes={"asd": "test_resource"})
283363
scope = InstrumentationScope("test_scope")
284-
point = NumberDataPoint(
285-
attributes={
286-
"test": "attribute",
287-
},
288-
start_time_unix_nano=1646865018558419456,
289-
time_unix_nano=1646865018558419457,
290-
value=10,
291-
)
364+
point = self._number_data_point
292365
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
293366
self.assertEqual(envelope.instrumentation_key, exporter._instrumentation_key)
294367
self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Metric")
@@ -328,6 +401,7 @@ def test_point_to_envelope_std_metric_client_duration(self, target_mock):
328401
self.assertEqual(envelope.data.base_type, "MetricData")
329402
self.assertEqual(envelope.data.base_data.properties["_MS.MetricId"], "dependencies/duration")
330403
self.assertEqual(envelope.data.base_data.properties["_MS.IsAutocollected"], "True")
404+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
331405
self.assertEqual(envelope.data.base_data.properties["Dependency.Type"], "http")
332406
self.assertEqual(envelope.data.base_data.properties["Dependency.Success"], "True")
333407
self.assertEqual(envelope.data.base_data.properties["dependency/target"], "test_service")
@@ -366,6 +440,7 @@ def test_point_to_envelope_std_metric_client_duration(self, target_mock):
366440
self.assertEqual(envelope.data.base_type, "MetricData")
367441
self.assertEqual(envelope.data.base_data.properties["_MS.MetricId"], "dependencies/duration")
368442
self.assertEqual(envelope.data.base_data.properties["_MS.IsAutocollected"], "True")
443+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
369444
self.assertEqual(envelope.data.base_data.properties["Dependency.Type"], "http")
370445
self.assertEqual(envelope.data.base_data.properties["Dependency.Success"], "True")
371446
self.assertEqual(envelope.data.base_data.properties["dependency/target"], "test_service")
@@ -401,6 +476,7 @@ def test_point_to_envelope_std_metric_server_duration(self):
401476
self.assertEqual(envelope.data.base_type, "MetricData")
402477
self.assertEqual(envelope.data.base_data.properties["_MS.MetricId"], "requests/duration")
403478
self.assertEqual(envelope.data.base_data.properties["_MS.IsAutocollected"], "True")
479+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
404480
self.assertEqual(envelope.data.base_data.properties["Request.Success"], "True")
405481
self.assertEqual(envelope.data.base_data.properties["request/resultCode"], "200")
406482
self.assertEqual(envelope.data.base_data.properties["cloud/roleInstance"], "testServiceInstanceId")
@@ -442,6 +518,7 @@ def test_point_to_envelope_std_metric_server_duration(self):
442518
self.assertEqual(envelope.data.base_type, "MetricData")
443519
self.assertEqual(envelope.data.base_data.properties["_MS.MetricId"], "requests/duration")
444520
self.assertEqual(envelope.data.base_data.properties["_MS.IsAutocollected"], "True")
521+
self.assertNotIn("_MS.SentToAMW", envelope.data.base_data.properties)
445522
self.assertEqual(envelope.data.base_data.properties["Request.Success"], "True")
446523
self.assertEqual(envelope.data.base_data.properties["request/resultCode"], "200")
447524
self.assertEqual(envelope.data.base_data.properties["cloud/roleInstance"], "testServiceInstanceId")

0 commit comments

Comments
 (0)