From 98a7bac4e0d8e8aa104669a92736aac24a67a01f Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Thu, 3 Oct 2024 16:02:08 -0700 Subject: [PATCH] Add PeriodicExportingMetricReader interval when is_lambda --- solarwinds_apm/configurator.py | 18 +- .../test_configurator_metrics_exporter.py | 193 ++++++++++++++++++ 2 files changed, 206 insertions(+), 5 deletions(-) diff --git a/solarwinds_apm/configurator.py b/solarwinds_apm/configurator.py index ef19a29d..2845aa1a 100644 --- a/solarwinds_apm/configurator.py +++ b/solarwinds_apm/configurator.py @@ -437,11 +437,19 @@ def _configure_metrics_exporter( "Creating PeriodicExportingMetricReader using %s", exporter_name, ) - # Inf interval to not invoke periodic collection - reader = PeriodicExportingMetricReader( - exporter, - export_interval_millis=math.inf, - ) + + reader = None + if apm_config.is_lambda: + # Inf interval to not invoke periodic collection + reader = PeriodicExportingMetricReader( + exporter, + export_interval_millis=math.inf, + ) + else: + # Use default interval 60s else OTEL_METRIC_EXPORT_INTERVAL + reader = PeriodicExportingMetricReader( + exporter, + ) metric_readers.append(reader) # Use configured Resource attributes then merge with diff --git a/tests/unit/test_configurator/test_configurator_metrics_exporter.py b/tests/unit/test_configurator/test_configurator_metrics_exporter.py index 71e9c869..cf49aeaf 100644 --- a/tests/unit/test_configurator/test_configurator_metrics_exporter.py +++ b/tests/unit/test_configurator/test_configurator_metrics_exporter.py @@ -217,6 +217,87 @@ def test_configure_metrics_exporter_valid( ] ) mock_pemreader.assert_called_once() + mock_pemreader.assert_has_calls( + [ + mocker.call( + mock_exporter, + ) + ] + ) + trace_mocks.get_tracer_provider.assert_called_once() + trace_mocks.get_tracer_provider().get_tracer.assert_called_once() + mock_meterprovider.assert_called_once() + + # Restore old EXPORTER + if old_metrics_exporter: + os.environ["OTEL_METRICS_EXPORTER"] = old_metrics_exporter + + def test_configure_metrics_exporter_valid_is_lambda( + self, + mocker, + mock_apmconfig_enabled_is_lambda, + mock_pemreader, + mock_meterprovider, + ): + # Save any EXPORTER env var for later + old_metrics_exporter = os.environ.get("OTEL_METRICS_EXPORTER", None) + if old_metrics_exporter: + del os.environ["OTEL_METRICS_EXPORTER"] + + # Mock entry points + mock_exporter = mocker.Mock() + mock_exporter_class = mocker.Mock() + mock_exporter_class.configure_mock(return_value=mock_exporter) + mock_load = mocker.Mock() + mock_load.configure_mock(return_value=mock_exporter_class) + mock_exporter_entry_point = mocker.Mock() + mock_exporter_entry_point.configure_mock( + **{ + "load": mock_load, + } + ) + mock_points = iter([mock_exporter_entry_point]) + mock_iter_entry_points = mocker.patch( + "solarwinds_apm.configurator.iter_entry_points" + ) + mock_iter_entry_points.configure_mock( + return_value=mock_points + ) + mocker.patch.dict( + os.environ, + { + "OTEL_METRICS_EXPORTER": "valid_exporter" + } + ) + + # Mock Otel + get_resource_mocks(mocker) + trace_mocks = get_trace_mocks(mocker) + mock_histogram = mocker.patch( + "solarwinds_apm.configurator.Histogram" + ) + mock_agg_temp = mocker.patch( + "solarwinds_apm.configurator.AggregationTemporality" + ) + mock_agg_temp.DELTA = "foo-delta" + + # Test! + test_configurator = configurator.SolarWindsConfigurator() + test_configurator._configure_metrics_exporter( + mock_apmconfig_enabled_is_lambda, + ) + mock_exporter_entry_point.load.assert_called_once() + mock_exporter_entry_point.load.assert_has_calls( + [ + mocker.call(), + mocker.call()( + preferred_temporality={ + mock_histogram: "foo-delta" + } + ) + ] + ) + mock_pemreader.assert_called_once() mock_pemreader.assert_has_calls( [ mocker.call( @@ -411,6 +492,118 @@ def test_configure_metrics_exporter_valid_invalid_mixed( ) # Called for the valid one mock_pemreader.assert_called_once() + mock_pemreader.assert_has_calls( + [ + mocker.call( + mock_exporter, + ) + ] + ) + # Rest not called at all + trace_mocks.get_tracer_provider.assert_not_called() + trace_mocks.get_tracer_provider().get_tracer.assert_not_called() + mock_meterprovider.assert_not_called() + + # Restore old EXPORTER + if old_metrics_exporter: + os.environ["OTEL_METRICS_EXPORTER"] = old_metrics_exporter + + def test_configure_metrics_exporter_valid_invalid_mixed_is_lambda( + self, + mocker, + mock_apmconfig_enabled_is_lambda, + mock_pemreader, + mock_meterprovider, + ): + # Save any EXPORTER env var for later + old_metrics_exporter = os.environ.get("OTEL_METRICS_EXPORTER", None) + if old_metrics_exporter: + del os.environ["OTEL_METRICS_EXPORTER"] + + # Mock entry points + mock_exporter = mocker.Mock() + mock_exporter_class = mocker.Mock() + mock_exporter_class.configure_mock(return_value=mock_exporter) + mock_load = mocker.Mock() + mock_load.configure_mock(return_value=mock_exporter_class) + mock_exporter_entry_point = mocker.Mock() + mock_exporter_entry_point.configure_mock( + **{ + "load": mock_load, + } + ) + mock_exporter_class_invalid = mocker.Mock() + mock_exporter_class_invalid.configure_mock( + side_effect=Exception("mock error invalid exporter") + ) + mock_exporter_entry_point_invalid = mocker.Mock() + mock_exporter_entry_point_invalid.configure_mock( + **{ + "load": mock_exporter_class_invalid + } + ) + + mock_points = iter( + [ + mock_exporter_entry_point, + mock_exporter_entry_point_invalid, + ] + ) + mock_iter_entry_points = mocker.patch( + "solarwinds_apm.configurator.iter_entry_points" + ) + mock_iter_entry_points.configure_mock( + return_value=mock_points + ) + mocker.patch.dict( + os.environ, + { + "OTEL_METRICS_EXPORTER": "valid_exporter,invalid_exporter" + } + ) + + # Mock Otel + get_resource_mocks(mocker) + trace_mocks = get_trace_mocks(mocker) + mock_histogram = mocker.patch( + "solarwinds_apm.configurator.Histogram" + ) + mock_agg_temp = mocker.patch( + "solarwinds_apm.configurator.AggregationTemporality" + ) + mock_agg_temp.DELTA = "foo-delta" + + # Test! + test_configurator = configurator.SolarWindsConfigurator() + with pytest.raises(Exception): + test_configurator._configure_metrics_exporter( + mock_apmconfig_enabled_is_lambda, + ) + mock_iter_entry_points.assert_has_calls( + [ + mocker.call("opentelemetry_metrics_exporter", "valid_exporter"), + mocker.call("opentelemetry_metrics_exporter", "invalid_exporter"), + ] + ) + mock_exporter_entry_point.load.assert_called_once() + mock_exporter_entry_point.load.assert_has_calls( + [ + mocker.call(), + mocker.call()( + preferred_temporality={ + mock_histogram: "foo-delta" + } + ) + ] + ) + mock_exporter_entry_point_invalid.load.assert_called_once() + mock_exporter_entry_point_invalid.load.assert_has_calls( + [ + mocker.call(), + ] + ) + # Called for the valid one + mock_pemreader.assert_called_once() mock_pemreader.assert_has_calls( [ mocker.call(