diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index fbcf65d95e09..d791aab6e7e9 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -16,6 +16,8 @@ ### Bugs Fixed ### Other Changes +- Track live metrics disabling in feature SDKstats + ([#47297](https://github.com/Azure/azure-sdk-for-python/pull/47297)) ## 1.0.0b52 (2026-05-12) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py index b9d7b76e4e64..267eeaa4b8e8 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py @@ -45,10 +45,7 @@ def enable_live_metrics(**kwargs: Any) -> None: # pylint: disable=C4758 if config_manager: config_manager.register_callback(get_quickpulse_configuration_callback) - # We can detect feature usage for statsbeat since we are in an opt-in model currently - # Once we move to live metrics on-by-default, we will have to check for both explicit usage - # and whether or not user is actually using live metrics (being on live metrics blade in UX) - set_statsbeat_live_metrics_feature_set() + # Live metrics disable tracking is handled via local config flow. def get_quickpulse_configuration_callback(settings: Dict[str, str]) -> None: @@ -74,8 +71,14 @@ def get_quickpulse_configuration_callback(settings: Dict[str, str]) -> None: resource=manager._resource, # pylint:disable=protected-access ) elif live_metrics_enabled is False and manager.is_initialized(): + # Track explicit live metrics disable for statsbeat feature reporting. + # (Tracking the disable live metrics feature starting 06/03/2026) + set_statsbeat_live_metrics_feature_set() # Disable live metrics if it's currently enabled manager.shutdown() + elif live_metrics_enabled is False: + # Track explicit live metrics disable even when quickpulse is already off. + set_statsbeat_live_metrics_feature_set() def shutdown_live_metrics() -> bool: diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_live_metrics.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_live_metrics.py index 3559538dd005..e38be1acea13 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_live_metrics.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_live_metrics.py @@ -8,7 +8,10 @@ from opentelemetry.sdk.resources import Resource -from azure.monitor.opentelemetry.exporter._quickpulse._live_metrics import enable_live_metrics +from azure.monitor.opentelemetry.exporter._quickpulse._live_metrics import ( + enable_live_metrics, + get_quickpulse_configuration_callback, +) class TestLiveMetrics(unittest.TestCase): @@ -35,8 +38,8 @@ def test_enable_live_metrics_basic(self, manager_mock, statsbeat_mock): credential="test-credential", ) - # Verify statsbeat feature was set - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -54,8 +57,8 @@ def test_enable_live_metrics_initialization_fails(self, manager_mock, statsbeat_ # Verify manager was initialized with connection string mock_manager_instance.initialize.assert_called_once_with(connection_string="InstrumentationKey=test-key") - # Verify statsbeat feature was still set (regardless of initialization success) - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -73,8 +76,8 @@ def test_enable_live_metrics_with_minimal_args(self, manager_mock, statsbeat_moc # Verify initialization was attempted with no kwargs mock_manager_instance.initialize.assert_called_once_with() - # Verify statsbeat feature was set - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -142,8 +145,8 @@ def test_enable_live_metrics_empty_string_connection(self, manager_mock, statsbe # Verify initialization was called with empty string mock_manager_instance.initialize.assert_called_once_with(connection_string="") - # Verify statsbeat feature was set - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -178,8 +181,8 @@ def test_enable_live_metrics_complex_resource(self, manager_mock, statsbeat_mock connection_string="InstrumentationKey=test-key", resource=mock_resource ) - # Verify statsbeat feature was set - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -205,8 +208,8 @@ def test_enable_live_metrics_multiple_calls(self, manager_mock, statsbeat_mock): ] mock_manager_instance.initialize.assert_has_calls(expected_calls) - # Verify statsbeat feature was set twice - self.assertEqual(statsbeat_mock.call_count, 2) + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") @@ -233,8 +236,55 @@ def test_enable_live_metrics_kwargs_preservation(self, manager_mock, statsbeat_m # Verify all kwargs were passed through to initialize mock_manager_instance.initialize.assert_called_once_with(**custom_kwargs) - # Verify statsbeat feature was set - statsbeat_mock.assert_called_once() + # Enable path should not explicitly track statsbeat feature. + statsbeat_mock.assert_not_called() + + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.evaluate_feature") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") + def test_get_quickpulse_configuration_callback_disable_tracks_feature( + self, manager_mock, evaluate_mock, statsbeat_mock + ): + mock_manager_instance = mock.Mock() + mock_manager_instance.is_initialized.return_value = True + manager_mock.return_value = mock_manager_instance + evaluate_mock.return_value = False + + get_quickpulse_configuration_callback({}) + + statsbeat_mock.assert_called_once_with() + mock_manager_instance.shutdown.assert_called_once() + + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.evaluate_feature") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") + def test_get_quickpulse_configuration_callback_enable_does_not_track_feature( + self, manager_mock, evaluate_mock, statsbeat_mock + ): + mock_manager_instance = mock.Mock() + mock_manager_instance.is_initialized.return_value = True + manager_mock.return_value = mock_manager_instance + evaluate_mock.return_value = True + + get_quickpulse_configuration_callback({}) + + statsbeat_mock.assert_not_called() + + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.set_statsbeat_live_metrics_feature_set") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.evaluate_feature") + @mock.patch("azure.monitor.opentelemetry.exporter._quickpulse._live_metrics.get_quickpulse_manager") + def test_get_quickpulse_configuration_callback_disable_tracks_feature_when_manager_off( + self, manager_mock, evaluate_mock, statsbeat_mock + ): + mock_manager_instance = mock.Mock() + mock_manager_instance.is_initialized.return_value = False + manager_mock.return_value = mock_manager_instance + evaluate_mock.return_value = False + + get_quickpulse_configuration_callback({}) + + statsbeat_mock.assert_called_once_with() + mock_manager_instance.shutdown.assert_not_called() # cSpell:enable