From 06aa807fa7c26870715f8b5910c572b4d0c17217 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 Jan 2026 14:23:14 -0800 Subject: [PATCH 1/9] Initialize performance counters before adding processors --- .../azure/monitor/opentelemetry/_configure.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 2d84f9565214..c3664936c12b 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -133,6 +133,12 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 disable_metrics = configurations[DISABLE_METRICS_ARG] enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] + # Setup metrics pipeline + # Setup up metrics with Performance Counters before _PerformanceCountersSpanProcessor and _PerformanceCountersLogRecordProcessor + # This avoids a circular dependency in the case that Performance Counter setup produces a log. + if not disable_metrics: + _setup_metrics(configurations) + # Setup live metrics if enable_live_metrics_config: _setup_live_metrics(configurations) @@ -145,10 +151,6 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 if not disable_logging: _setup_logging(configurations) - # Setup metrics pipeline - if not disable_metrics: - _setup_metrics(configurations) - # Setup instrumentations # Instrumentations need to be setup last so to use the global providers # instanstiated in the other setup steps From 962278feed7ccbe0246f7be07c4cbe14e7a54977 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 Jan 2026 14:30:16 -0800 Subject: [PATCH 2/9] changelog --- sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index c88457fdd1aa..ab9ac16851c2 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -9,6 +9,8 @@ ### Bugs Fixed - Fix the format of the fixed percentage sampler constant and ensure backward compatability ([#44656](https://github.com/Azure/azure-sdk-for-python/pull/44656)) +- Only add PerformanceCounter Processors after PerformanceCounter setup to avoid circular dependency. + ([#xxxxx](https://github.com/Azure/azure-sdk-for-python/pull/xxxxx)) ### Other Changes From 9975d919ae3b927fe8564bf13cd995416f893d87 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 Jan 2026 15:09:28 -0800 Subject: [PATCH 3/9] rm warning log for OSs that don't support process I/O --- .../opentelemetry/exporter/_performance_counters/_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_performance_counters/_manager.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_performance_counters/_manager.py index a09ad4ab0579..beb9591e1fe1 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_performance_counters/_manager.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_performance_counters/_manager.py @@ -597,7 +597,6 @@ def __init__(self, meter_provider=None): try: # Note: ProcessIORate may not be available on all platforms if metric_class == ProcessIORate and not _IO_AVAILABLE: - _logger.warning("Process I/O Rate performance counter is not available on this platform.") continue performance_counter = metric_class(self._meter) self._performance_counters.append(performance_counter) From b3f5e2e7d5369847c0ba900ab60c68423ef07da8 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 Jan 2026 15:25:01 -0800 Subject: [PATCH 4/9] Add tests for execution order --- .../tests/test_configure.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py index 06e959130980..11ac33c2fb34 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py @@ -114,12 +114,19 @@ def test_configure_azure_monitor_disable_tracing( "resource": TEST_RESOURCE, } config_mock.return_value = configurations + # Track call order using side_effect + call_order = [] + metrics_mock.side_effect = lambda *args, **kwargs: call_order.append("metrics") + logging_mock.side_effect = lambda *args, **kwargs: call_order.append("logging") + tracing_mock.side_effect = lambda *args, **kwargs: call_order.append("tracing") configure_azure_monitor() tracing_mock.assert_not_called() logging_mock.assert_called_once_with(configurations) metrics_mock.assert_called_once_with(configurations) live_metrics_mock.assert_not_called() instrumentation_mock.assert_called_once_with(configurations) + # Assert setup_metrics is called before setup_logging + self.assertLess(call_order.index("metrics"), call_order.index("logging")) @patch( "azure.monitor.opentelemetry._configure._setup_instrumentations", @@ -158,12 +165,19 @@ def test_configure_azure_monitor_disable_logging( "resource": TEST_RESOURCE, } config_mock.return_value = configurations + # Track call order using side_effect + call_order = [] + metrics_mock.side_effect = lambda *args, **kwargs: call_order.append("metrics") + logging_mock.side_effect = lambda *args, **kwargs: call_order.append("logging") + tracing_mock.side_effect = lambda *args, **kwargs: call_order.append("tracing") configure_azure_monitor() tracing_mock.assert_called_once_with(configurations) logging_mock.assert_not_called() metrics_mock.assert_called_once_with(configurations) live_metrics_mock.assert_not_called() instrumentation_mock.assert_called_once_with(configurations) + # Assert setup_metrics is called before setup_tracing + self.assertLess(call_order.index("metrics"), call_order.index("tracing")) @patch( "azure.monitor.opentelemetry._configure._setup_instrumentations", @@ -202,12 +216,18 @@ def test_configure_azure_monitor_disable_metrics( "resource": TEST_RESOURCE, } config_mock.return_value = configurations + # Track call order using side_effect + call_order = [] + metrics_mock.side_effect = lambda *args, **kwargs: call_order.append("metrics") + logging_mock.side_effect = lambda *args, **kwargs: call_order.append("logging") + tracing_mock.side_effect = lambda *args, **kwargs: call_order.append("tracing") configure_azure_monitor() tracing_mock.assert_called_once_with(configurations) logging_mock.assert_called_once_with(configurations) metrics_mock.assert_not_called() live_metrics_mock.assert_not_called() instrumentation_mock.assert_called_once_with(configurations) + # Assert setup_metrics is called before setup_logging and setup_tracing (metrics not called in this test) @patch( "azure.monitor.opentelemetry._configure._setup_instrumentations", @@ -246,12 +266,20 @@ def test_configure_azure_monitor_enable_live_metrics( "resource": TEST_RESOURCE, } config_mock.return_value = configurations + # Track call order using side_effect + call_order = [] + metrics_mock.side_effect = lambda *args, **kwargs: call_order.append("metrics") + logging_mock.side_effect = lambda *args, **kwargs: call_order.append("logging") + tracing_mock.side_effect = lambda *args, **kwargs: call_order.append("tracing") configure_azure_monitor() tracing_mock.assert_called_once_with(configurations) logging_mock.assert_called_once_with(configurations) metrics_mock.assert_called_once_with(configurations) live_metrics_mock.assert_called_once_with(configurations) instrumentation_mock.assert_called_once_with(configurations) + # Assert setup_metrics is called before setup_logging and setup_tracing + self.assertLess(call_order.index("metrics"), call_order.index("logging")) + self.assertLess(call_order.index("metrics"), call_order.index("tracing")) @patch( "azure.monitor.opentelemetry._configure._setup_instrumentations", From 5cd174b459020b419afe117870a7f8c82d49010f Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Thu, 15 Jan 2026 09:08:20 -0800 Subject: [PATCH 5/9] pr number --- sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index ab9ac16851c2..2728ef1dd242 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -10,7 +10,7 @@ - Fix the format of the fixed percentage sampler constant and ensure backward compatability ([#44656](https://github.com/Azure/azure-sdk-for-python/pull/44656)) - Only add PerformanceCounter Processors after PerformanceCounter setup to avoid circular dependency. - ([#xxxxx](https://github.com/Azure/azure-sdk-for-python/pull/xxxxx)) + ([#44661](https://github.com/Azure/azure-sdk-for-python/pull/44661)) ### Other Changes From 3fa85a06526c02ffb8075fc1791c9b6af6fa8a4e Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Thu, 15 Jan 2026 09:15:29 -0800 Subject: [PATCH 6/9] Update sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../azure/monitor/opentelemetry/_configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index c3664936c12b..f800bcd9e389 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -134,7 +134,7 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] # Setup metrics pipeline - # Setup up metrics with Performance Counters before _PerformanceCountersSpanProcessor and _PerformanceCountersLogRecordProcessor + # Set up metrics with Performance Counters before _PerformanceCountersSpanProcessor and _PerformanceCountersLogRecordProcessor # This avoids a circular dependency in the case that Performance Counter setup produces a log. if not disable_metrics: _setup_metrics(configurations) From 0fce6441c64b4af398b0bfee94648646587ce36c Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Thu, 15 Jan 2026 09:18:41 -0800 Subject: [PATCH 7/9] lint --- .../azure/monitor/opentelemetry/_configure.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index f800bcd9e389..41e352830d70 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -133,27 +133,27 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 disable_metrics = configurations[DISABLE_METRICS_ARG] enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] - # Setup metrics pipeline + # Set up metrics pipeline # Set up metrics with Performance Counters before _PerformanceCountersSpanProcessor and _PerformanceCountersLogRecordProcessor # This avoids a circular dependency in the case that Performance Counter setup produces a log. if not disable_metrics: _setup_metrics(configurations) - # Setup live metrics + # Set up live metrics if enable_live_metrics_config: _setup_live_metrics(configurations) - # Setup tracing pipeline + # Set up tracing pipeline if not disable_tracing: _setup_tracing(configurations) - # Setup logging pipeline + # Set up logging pipeline if not disable_logging: _setup_logging(configurations) - # Setup instrumentations - # Instrumentations need to be setup last so to use the global providers - # instanstiated in the other setup steps + # Set up instrumentations + # Instrumentations need to be set up last so to use the global providers + # instantiated in the other setup steps _setup_instrumentations(configurations) From deff368cbd5f521382b4e33bc308c98c235c24c8 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Thu, 15 Jan 2026 09:21:41 -0800 Subject: [PATCH 8/9] Remove unnecessary test --- .../azure-monitor-opentelemetry/tests/test_configure.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py index 11ac33c2fb34..04cb3b402d06 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py @@ -216,18 +216,12 @@ def test_configure_azure_monitor_disable_metrics( "resource": TEST_RESOURCE, } config_mock.return_value = configurations - # Track call order using side_effect - call_order = [] - metrics_mock.side_effect = lambda *args, **kwargs: call_order.append("metrics") - logging_mock.side_effect = lambda *args, **kwargs: call_order.append("logging") - tracing_mock.side_effect = lambda *args, **kwargs: call_order.append("tracing") configure_azure_monitor() tracing_mock.assert_called_once_with(configurations) logging_mock.assert_called_once_with(configurations) metrics_mock.assert_not_called() live_metrics_mock.assert_not_called() instrumentation_mock.assert_called_once_with(configurations) - # Assert setup_metrics is called before setup_logging and setup_tracing (metrics not called in this test) @patch( "azure.monitor.opentelemetry._configure._setup_instrumentations", From 209233d69c21d5ae5aac1f94627915ec360f6552 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Thu, 15 Jan 2026 09:56:50 -0800 Subject: [PATCH 9/9] lint --- .../azure/monitor/opentelemetry/_configure.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 41e352830d70..e0013ae3bd11 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -134,8 +134,9 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] # Set up metrics pipeline - # Set up metrics with Performance Counters before _PerformanceCountersSpanProcessor and _PerformanceCountersLogRecordProcessor - # This avoids a circular dependency in the case that Performance Counter setup produces a log. + # Set up metrics with Performance Counters before _PerformanceCountersSpanProcessor and + # _PerformanceCountersLogRecordProcessor. This avoids a circular dependency in the case that Performance Counter + # setup produces a log. if not disable_metrics: _setup_metrics(configurations)