diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index 205e66e7b860..2633e9085b06 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -15,6 +15,8 @@ - Add attachType character to sdkVersion prefix ([#34226](https://github.com/Azure/azure-sdk-for-python/pull/34226)) +- Add AKS scenarios to statsbeat metric and sdkVersion prefix + ([#34427](https://github.com/Azure/azure-sdk-for-python/pull/34427)) ## 1.0.0b22 (2024-02-01) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py index a6d26f1ca607..8fffcb3e424f 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py @@ -2,11 +2,18 @@ # Licensed under the MIT License. # cSpell:disable +from opentelemetry.semconv.metrics import MetricInstruments + # Environment variables _APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL = "APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL" _APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED = \ "APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED" +_WEBSITE_SITE_NAME = "WEBSITE_SITE_NAME" +_WEBSITE_HOME_STAMPNAME = "WEBSITE_HOME_STAMPNAME" +_WEBSITE_HOSTNAME = "WEBSITE_HOSTNAME" +_FUNCTIONS_WORKER_RUNTIME = "FUNCTIONS_WORKER_RUNTIME" +_AKS_ARM_NAMESPACE_ID = "AKS_ARM_NAMESPACE_ID" # Network @@ -132,15 +139,14 @@ # Standard metrics # List of metric instrument names that are autocollected from instrumentations -# TODO: switch to semconv constants _AUTOCOLLECTED_INSTRUMENT_NAMES = ( - "http.server.duration", - "http.server.request.size", - "http.server.response.size", - "http.server.active_requests", - "http.client.duration", - "http.client.request.size", - "http.client.response.size", + MetricInstruments.HTTP_SERVER_DURATION, + MetricInstruments.HTTP_SERVER_REQUEST_SIZE, + MetricInstruments.HTTP_SERVER_RESPONSE_SIZE, + MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + MetricInstruments.HTTP_CLIENT_DURATION, + MetricInstruments.HTTP_CLIENT_REQUEST_SIZE, + MetricInstruments.HTTP_CLIENT_RESPONSE_SIZE, ) # Temporary solution for checking which instrumentations support metric collection diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py index 8aecae9bba05..69b2e85a3937 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py @@ -17,7 +17,12 @@ from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys, TelemetryItem from azure.monitor.opentelemetry.exporter._version import VERSION as ext_version -from azure.monitor.opentelemetry.exporter._constants import _INSTRUMENTATIONS_BIT_MAP +from azure.monitor.opentelemetry.exporter._constants import ( + _INSTRUMENTATIONS_BIT_MAP, + _WEBSITE_SITE_NAME, + _FUNCTIONS_WORKER_RUNTIME, + _AKS_ARM_NAMESPACE_ID, +) opentelemetry_version = "" @@ -35,16 +40,24 @@ ).version +# Azure App Service + def _is_on_app_service(): - return environ.get("WEBSITE_SITE_NAME") is not None + return environ.get(_WEBSITE_SITE_NAME) is not None def _is_on_functions(): - return environ.get("FUNCTIONS_WORKER_RUNTIME") is not None + return environ.get(_FUNCTIONS_WORKER_RUNTIME) is not None def _is_attach_enabled(): return isdir("/agents/python/") +# AKS + +def _is_on_aks(): + return _AKS_ARM_NAMESPACE_ID in environ + + def _get_sdk_version_prefix(): sdk_version_prefix = '' rp = 'u' @@ -55,9 +68,8 @@ def _get_sdk_version_prefix(): # TODO: Add VM scenario outside statsbeat # elif _is_on_vm(): # rp = 'v' - # TODO: Add AKS scenario - # elif _is_on_aks(): - # rp = 'k' + elif _is_on_aks(): + rp = 'k' os = 'u' system = platform.system() diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py index 1001b3e1a264..a3b4d9859526 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from enum import Enum import json import os import platform @@ -23,6 +24,10 @@ _REQ_RETRY_NAME, _REQ_SUCCESS_NAME, _REQ_THROTTLE_NAME, + _WEBSITE_HOME_STAMPNAME, + _WEBSITE_HOSTNAME, + _WEBSITE_SITE_NAME, + _AKS_ARM_NAMESPACE_ID, ) from azure.monitor.opentelemetry.exporter.statsbeat._state import ( _REQUESTS_MAP_LOCK, @@ -38,7 +43,12 @@ _AIMS_FORMAT = "format=json" _ENDPOINT_TYPES = ["breeze"] -_RP_NAMES = ["appsvc", "functions", "vm", "unknown"] +class _RP_Names(Enum): + APP_SERVICE = "appsvc" + FUNCTIONS = "functions" + AKS = "aks" + VM = "vm" + UNKNOWN = "unknown" _HOST_PATTERN = re.compile('^https?://(?:www\\.)?([^/.]+)') @@ -66,7 +76,7 @@ class _AttachTypes: class _StatsbeatMetrics: _COMMON_ATTRIBUTES: Dict[str, Any] = { - "rp": _RP_NAMES[3], + "rp": _RP_Names.UNKNOWN.value, "attach": _AttachTypes.MANUAL, "cikey": None, "runtimeVersion": platform.python_version(), @@ -161,27 +171,30 @@ def _get_attach_metric(self, options: CallbackOptions) -> Iterable[Observation]: # rp, rpId if _utils._is_on_app_service(): # Web apps - rp = _RP_NAMES[0] + rp = _RP_Names.APP_SERVICE.value rpId = '{}/{}'.format( - os.environ.get("WEBSITE_SITE_NAME"), - os.environ.get("WEBSITE_HOME_STAMPNAME", '') + os.environ.get(_WEBSITE_SITE_NAME), + os.environ.get(_WEBSITE_HOME_STAMPNAME, '') ) elif _utils._is_on_functions(): # Function apps - rp = _RP_NAMES[1] - rpId = os.environ.get("WEBSITE_HOSTNAME", '') + rp = _RP_Names.FUNCTIONS.value + rpId = os.environ.get(_WEBSITE_HOSTNAME, '') + elif _utils._is_on_aks(): + # AKS + rp = _RP_Names.AKS.value + rpId = os.environ.get(_AKS_ARM_NAMESPACE_ID, '') elif self._vm_retry and self._get_azure_compute_metadata(): # VM - rp = _RP_NAMES[2] + rp = _RP_Names.VM.value rpId = '{}/{}'.format( self._vm_data.get("vmId", ''), self._vm_data.get("subscriptionId", '')) os_type = self._vm_data.get("osType", '') - # TODO: add AKS scenario else: # Not in any rp or VM metadata failed - rp = _RP_NAMES[3] - rpId = _RP_NAMES[3] + rp = _RP_Names.UNKNOWN.value + rpId = _RP_Names.UNKNOWN.value _StatsbeatMetrics._COMMON_ATTRIBUTES["rp"] = rp _StatsbeatMetrics._COMMON_ATTRIBUTES["os"] = os_type or platform.system() diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py index 0f053020ce7b..b660b8701072 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py @@ -38,7 +38,7 @@ _StatsbeatFeature, _StatsbeatMetrics, _AttachTypes, - _RP_NAMES, + _RP_Names, ) class MockResponse(object): @@ -334,7 +334,7 @@ def test_statsbeat_metric_init(self): self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["cikey"], ikey) self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["attach"], _AttachTypes.MANUAL) self.assertEqual(_StatsbeatMetrics._NETWORK_ATTRIBUTES["host"], "westus-1") - self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_NAMES[3]) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.UNKNOWN.value) self.assertEqual(_StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"], 1) self.assertEqual(_StatsbeatMetrics._FEATURE_ATTRIBUTES["type"], _FEATURE_TYPES.FEATURE) self.assertEqual(metric._meter_provider, mp) @@ -368,7 +368,7 @@ def test_statsbeat_metric_init_attach_enabled(self, attach_mock): self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["cikey"], ikey) self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["attach"], _AttachTypes.INTEGRATED) self.assertEqual(_StatsbeatMetrics._NETWORK_ATTRIBUTES["host"], "westus-1") - self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_NAMES[3]) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.UNKNOWN.value) self.assertEqual(_StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"], 1) self.assertEqual(_StatsbeatMetrics._FEATURE_ATTRIBUTES["type"], _FEATURE_TYPES.FEATURE) self.assertEqual(metric._meter_provider, mp) @@ -430,16 +430,17 @@ def test_get_attach_metric_does_not_meet_threshold(self): "WEBSITE_HOME_STAMPNAME": "stamp_name", } ) - def test_get_attach_metric_appsvc(self): + @mock.patch("azure.monitor.opentelemetry.exporter.statsbeat._statsbeat_metrics._StatsbeatMetrics._get_azure_compute_metadata", return_value=False) + def test_get_attach_metric_appsvc(self, metadata_mock): attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) - self.assertEqual(attributes["rp"], _RP_NAMES[3]) - attributes["rp"] = _RP_NAMES[0] + self.assertEqual(attributes["rp"], _RP_Names.UNKNOWN.value) + attributes["rp"] = _RP_Names.APP_SERVICE.value attributes["rpId"] = "site_name/stamp_name" observations = self._metric._get_attach_metric(options=None) for obs in observations: self.assertEqual(obs.value, 1) self.assertEqual(obs.attributes, attributes) - self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_NAMES[0]) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.APP_SERVICE.value) # pylint: disable=protected-access @mock.patch.dict( @@ -449,18 +450,38 @@ def test_get_attach_metric_appsvc(self): "WEBSITE_HOSTNAME": "host_name", } ) - def test_get_attach_metric_functions(self): + @mock.patch("azure.monitor.opentelemetry.exporter.statsbeat._statsbeat_metrics._StatsbeatMetrics._get_azure_compute_metadata", return_value=False) + def test_get_attach_metric_functions(self, metadata_mock): attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) - self.assertEqual(attributes["rp"], _RP_NAMES[3]) - attributes["rp"] = _RP_NAMES[1] + self.assertEqual(attributes["rp"], _RP_Names.UNKNOWN.value) + attributes["rp"] = _RP_Names.FUNCTIONS.value attributes["rpId"] = "host_name" observations = self._metric._get_attach_metric(options=None) for obs in observations: self.assertEqual(obs.value, 1) self.assertEqual(obs.attributes, attributes) - self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_NAMES[1]) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.FUNCTIONS.value) - def test_get_attach_metric_vm(self): + # pylint: disable=protected-access + @mock.patch.dict( + os.environ, + { + "AKS_ARM_NAMESPACE_ID": "namespace_id", + } + ) + def test_get_attach_metric_aks(self): + attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) + self.assertEqual(attributes["rp"], _RP_Names.UNKNOWN.value) + attributes["rp"] = _RP_Names.AKS.value + attributes["rpId"] = "namespace_id" + observations = self._metric._get_attach_metric(options=None) + for obs in observations: + self.assertEqual(obs.value, 1) + self.assertEqual(obs.attributes, attributes) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.AKS.value) + + @mock.patch("azure.monitor.opentelemetry.exporter.statsbeat._statsbeat_metrics._StatsbeatMetrics._get_azure_compute_metadata") + def test_get_attach_metric_vm(self, metadata_mock): mp = MeterProvider() ikey = "1aa11111-bbbb-1ccc-8ddd-eeeeffff3334" endpoint = "https://westus-1.in.applicationinsights.azure.com/" @@ -478,23 +499,22 @@ def test_get_attach_metric_vm(self): _vm_data["osType"] = "test_os" metric._vm_data = _vm_data metric._vm_retry = True - metadata_mock = mock.Mock() metadata_mock.return_value = True - metric._get_azure_compute_metadata = metadata_mock attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) - self.assertEqual(attributes["rp"], _RP_NAMES[3]) + self.assertEqual(attributes["rp"], _RP_Names.UNKNOWN.value) self.assertEqual(attributes["os"], platform.system()) - attributes["rp"] = _RP_NAMES[2] + attributes["rp"] = _RP_Names.VM.value attributes["rpId"] = "123/sub123" attributes["os"] = "test_os" observations = metric._get_attach_metric(options=None) for obs in observations: self.assertEqual(obs.value, 1) self.assertEqual(obs.attributes, attributes) - self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_NAMES[2]) + self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["rp"], _RP_Names.VM.value) self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["os"], "test_os") - def test_get_attach_metric_vm_no_os(self): + @mock.patch("azure.monitor.opentelemetry.exporter.statsbeat._statsbeat_metrics._StatsbeatMetrics._get_azure_compute_metadata", return_value=False) + def test_get_attach_metric_vm_no_os(self, metadata_mock): mp = MeterProvider() ikey = "1aa11111-bbbb-1ccc-8ddd-eeeeffff3334" endpoint = "https://westus-1.in.applicationinsights.azure.com/" @@ -512,10 +532,8 @@ def test_get_attach_metric_vm_no_os(self): _vm_data["osType"] = None metric._vm_data = _vm_data metric._vm_retry = True - metadata_mock = mock.Mock() metadata_mock.return_value = True self.assertEqual(_StatsbeatMetrics._COMMON_ATTRIBUTES["os"], platform.system()) - metric._get_azure_compute_metadata = metadata_mock observations = metric._get_attach_metric(options=None) for obs in observations: self.assertEqual(obs.value, 1) @@ -536,11 +554,11 @@ def test_get_attach_metric_unknown(self): ) metric._vm_retry = False attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) - self.assertEqual(attributes["rp"], _RP_NAMES[3]) + self.assertEqual(attributes["rp"], _RP_Names.UNKNOWN.value) observations = metric._get_attach_metric(options=None) for obs in observations: self.assertEqual(obs.value, 1) - self.assertEqual(obs.attributes["rp"], _RP_NAMES[3]) + self.assertEqual(obs.attributes["rp"], _RP_Names.UNKNOWN.value) def test_get_azure_compute_metadata(self): mp = MeterProvider()