Skip to content

Commit 17e5f2d

Browse files
rads-1996Copilot
andauthored
Added export interval env var for customer sdkstats (#42551)
* Added the export interval env var for customer sdkstats * Added CHANGELOG * Update sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_customer_statsbeat.py Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent eecee5b commit 17e5f2d

File tree

5 files changed

+90
-11
lines changed

5 files changed

+90
-11
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
([#42502](https://github.com/Azure/azure-sdk-for-python/pull/42502))
1010
- Customer Facing Statsbeat: Refactored logic for tracking dropped items from storage
1111
([#42542](https://github.com/Azure/azure-sdk-for-python/pull/42542))
12+
- Customer Facing SDKStats: Added the export interval env var for customer sdkstats
13+
([#42551](https://github.com/Azure/azure-sdk-for-python/pull/42551))
1214

1315
### Breaking Changes
1416

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148

149149
# Customer Facing Statsbeat
150150
_APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW = "APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW"
151-
151+
_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL = "APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL"
152152
_CUSTOMER_STATSBEAT_LANGUAGE = "python"
153153

154154
class DropCode(str, Enum, metaclass=CaseInsensitiveEnumMeta):

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_customer_statsbeat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
from azure.monitor.opentelemetry.exporter._constants import (
1818
_APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW,
19-
_DEFAULT_STATS_SHORT_EXPORT_INTERVAL,
2019
CustomerStatsbeatProperties,
2120
DropCode,
2221
DropCodeType,
@@ -34,6 +33,7 @@
3433

3534
from azure.monitor.opentelemetry.exporter.statsbeat._utils import (
3635
categorize_status_code,
36+
_get_customer_sdkstats_export_interval,
3737
)
3838
from azure.monitor.opentelemetry.exporter import VERSION
3939

@@ -67,7 +67,7 @@ def __init__(self, connection_string):
6767
self._customer_statsbeat_exporter._is_customer_statsbeat = True
6868
metric_reader_options = {
6969
"exporter": self._customer_statsbeat_exporter,
70-
"export_interval_millis": _DEFAULT_STATS_SHORT_EXPORT_INTERVAL
70+
"export_interval_millis": _get_customer_sdkstats_export_interval()
7171
}
7272
self._customer_statsbeat_metric_reader = PeriodicExportingMetricReader(**metric_reader_options)
7373
self._customer_statsbeat_meter_provider = MeterProvider(

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
_EU_ENDPOINTS,
2828
_REQ_DURATION_NAME,
2929
_REQ_SUCCESS_NAME,
30+
_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL,
3031
)
3132
from azure.monitor.opentelemetry.exporter.statsbeat._state import (
3233
_REQUESTS_MAP_LOCK,
@@ -202,3 +203,12 @@ def _track_dropped_items_from_storage(customer_statsbeat_metrics, result_from_st
202203
else:
203204
# LocalFileBlob.put returns StorageExportResult.LOCAL_FILE_BLOB_SUCCESS here. Don't need to track anything in this case. # pylint: disable=line-too-long
204205
pass
206+
207+
def _get_customer_sdkstats_export_interval() -> int:
208+
customer_sdkstats_ei_env = os.environ.get(_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL)
209+
if customer_sdkstats_ei_env:
210+
try:
211+
return int(customer_sdkstats_ei_env)
212+
except ValueError:
213+
return _DEFAULT_STATS_SHORT_EXPORT_INTERVAL
214+
return _DEFAULT_STATS_SHORT_EXPORT_INTERVAL

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_customer_statsbeat.py

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
_DEPENDENCY,
1919
_REQ_RETRY_NAME,
2020
_CUSTOMER_STATSBEAT_LANGUAGE,
21+
_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL,
2122
_DEFAULT_STATS_SHORT_EXPORT_INTERVAL,
2223
_UNKNOWN,
2324
_TYPE_MAP,
@@ -40,6 +41,7 @@
4041
from azure.monitor.opentelemetry.exporter.statsbeat._state import _REQUESTS_MAP
4142
from azure.monitor.opentelemetry.exporter.statsbeat._utils import (
4243
categorize_status_code,
44+
_get_customer_sdkstats_export_interval
4345
)
4446

4547
def convert_envelope_names_to_base_type(envelope_name):
@@ -64,12 +66,13 @@ def setUp(self):
6466
CustomerStatsbeatMetrics._instance = None
6567

6668
self.env_patcher = mock.patch.dict(os.environ, {
67-
"APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW": "true"
69+
"APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW": "true",
70+
"APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL": ""
6871
})
6972
self.env_patcher.start()
7073
self.mock_options = mock.Mock()
7174
self.mock_options.instrumentation_key = "363331ca-f431-4119-bdcd-31a75920f958"
72-
self.mock_options.network_collection_interval = _DEFAULT_STATS_SHORT_EXPORT_INTERVAL
75+
self.mock_options.network_collection_interval = _get_customer_sdkstats_export_interval()
7376
self.mock_options.connection_string = "InstrumentationKey=363331ca-f431-4119-bdcd-31a75920f958;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/"
7477
self.mock_options.language = _CUSTOMER_STATSBEAT_LANGUAGE
7578
self.original_trace_provider = trace._TRACER_PROVIDER
@@ -80,16 +83,25 @@ def setUp(self):
8083
def tearDown(self):
8184
self.env_patcher.stop()
8285
# Restore trace provider
86+
8387
trace._TRACER_PROVIDER = self.original_trace_provider
84-
# Clean up singleton instances to prevent cross-test contamination
88+
8589
if hasattr(self.mock_options, 'metrics') and self.mock_options.metrics:
8690
metrics = self.mock_options.metrics
91+
8792
if hasattr(metrics, '_customer_statsbeat_metric_reader'):
88-
try:
89-
# Shutdown to prevent additional periodic exports
90-
metrics._customer_statsbeat_metric_reader.shutdown()
91-
except Exception:
92-
pass # Ignore shutdown errors
93+
reader = metrics._customer_statsbeat_metric_reader
94+
if not getattr(reader, '_shutdown', False):
95+
setattr(reader, '_shutdown', True)
96+
97+
metrics._customer_statsbeat_metric_reader = None
98+
99+
if hasattr(metrics, '_customer_statsbeat_exporter'):
100+
metrics._customer_statsbeat_exporter = None
101+
102+
if hasattr(metrics, '_customer_statsbeat_meter_provider'):
103+
metrics._customer_statsbeat_meter_provider = None
104+
93105
CustomerStatsbeatMetrics._instance = None
94106

95107
def test_customer_statsbeat_not_initialized_when_disabled(self):
@@ -108,6 +120,61 @@ def test_customer_statsbeat_not_initialized_when_disabled(self):
108120
# Verify callbacks return empty lists when disabled
109121
self.assertEqual(metrics._item_success_callback(mock.Mock()), [])
110122
self.assertEqual(metrics._item_drop_callback(mock.Mock()), [])
123+
124+
def test_custom_export_interval_from_env_var(self):
125+
"""Test that a custom export interval is picked up from the environment variable."""
126+
# Use a non-default value to test
127+
custom_interval = 300
128+
129+
# Mock the environment variable with our custom interval
130+
with mock.patch.dict(os.environ, {
131+
_APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW: "true",
132+
_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL: str(custom_interval)
133+
}):
134+
# Get the export interval
135+
actual_interval = _get_customer_sdkstats_export_interval()
136+
137+
# Verify it matches our custom value
138+
self.assertEqual(
139+
actual_interval,
140+
custom_interval,
141+
f"Expected export interval to be {custom_interval}, got {actual_interval}"
142+
)
143+
144+
# Verify the CustomerStatsbeatMetrics instance picks up the custom interval
145+
CustomerStatsbeatMetrics._instance = None
146+
metrics = CustomerStatsbeatMetrics(self.mock_options.connection_string)
147+
self.assertEqual(
148+
metrics._customer_statsbeat_metric_reader._export_interval_millis,
149+
custom_interval,
150+
f"CustomerStatsbeatMetrics should use export interval {custom_interval}, got {metrics._customer_statsbeat_metric_reader._export_interval_millis}"
151+
)
152+
153+
def test_default_export_interval_when_env_var_empty(self):
154+
"""Test that the default export interval is used when the environment variable is empty."""
155+
# Mock the environment variable as empty
156+
with mock.patch.dict(os.environ, {
157+
_APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW: "true",
158+
_APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL: ""
159+
}):
160+
# Get the export interval
161+
actual_interval = _get_customer_sdkstats_export_interval()
162+
163+
# Verify it matches the default value
164+
self.assertEqual(
165+
actual_interval,
166+
_DEFAULT_STATS_SHORT_EXPORT_INTERVAL,
167+
f"Expected export interval to be default {_DEFAULT_STATS_SHORT_EXPORT_INTERVAL}, got {actual_interval}"
168+
)
169+
170+
# Verify the CustomerStatsbeatMetrics instance picks up the default interval
171+
CustomerStatsbeatMetrics._instance = None
172+
metrics = CustomerStatsbeatMetrics(self.mock_options.connection_string)
173+
self.assertEqual(
174+
metrics._customer_statsbeat_metric_reader._export_interval_millis,
175+
_DEFAULT_STATS_SHORT_EXPORT_INTERVAL,
176+
f"CustomerStatsbeatMetrics should use default export interval {_DEFAULT_STATS_SHORT_EXPORT_INTERVAL}, got {metrics._customer_statsbeat_metric_reader._export_interval_millis}"
177+
)
111178

112179
def test_successful_items_count(self):
113180
successful_dependencies = 0

0 commit comments

Comments
 (0)