Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 67502e5

Browse files
authored
Azure Monitor metrics exporter atexit fixes (#943)
1 parent 9d6a607 commit 67502e5

File tree

6 files changed

+90
-33
lines changed

6 files changed

+90
-33
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- PeriodicMetricTask flush on exit
6+
([#943](https://github.com/census-instrumentation/opencensus-python/pull/943))
7+
58
## 0.7.10
69
Released 2020-06-29
710

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/__init__.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def __init__(self, **options):
5555
source=self.__class__.__name__,
5656
)
5757
self._atexit_handler = atexit.register(self.shutdown)
58+
self.exporter_thread = None
5859
super(MetricsExporter, self).__init__()
5960

6061
def export_metrics(self, metrics):
@@ -136,21 +137,22 @@ def _create_envelope(self, data_point, timestamp, properties):
136137
return envelope
137138

138139
def shutdown(self):
139-
# flush metrics on exit
140-
self.export_metrics(stats_module.stats.get_metrics())
141-
if self._atexit_handler is not None:
142-
atexit.unregister(self._atexit_handler)
143-
self._atexit_handler = None
140+
# Flush the exporter thread
141+
if self.exporter_thread:
142+
self.exporter_thread.close()
143+
# Shutsdown storage worker
144+
self.storage.close()
144145

145146

146147
def new_metrics_exporter(**options):
147148
exporter = MetricsExporter(**options)
148149
producers = [stats_module.stats]
149150
if exporter.options.enable_standard_metrics:
150151
producers.append(standard_metrics.producer)
151-
transport.get_exporter_thread(producers,
152-
exporter,
153-
interval=exporter.options.export_interval)
152+
exporter.exporter_thread = transport.get_exporter_thread(
153+
producers,
154+
exporter,
155+
interval=exporter.options.export_interval)
154156
from opencensus.ext.azure.metrics_exporter import heartbeat_metrics
155157
heartbeat_metrics.enable_heartbeat_metrics(
156158
exporter.options.connection_string,

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ def enable_heartbeat_metrics(connection_string, ikey):
3737
)
3838
producer = AzureHeartbeatMetricsProducer()
3939
_HEARTBEAT_METRICS = producer
40-
transport.get_exporter_thread([_HEARTBEAT_METRICS],
41-
exporter,
42-
exporter.options.export_interval)
40+
exporter.exporter_thread = \
41+
transport.get_exporter_thread([_HEARTBEAT_METRICS],
42+
exporter,
43+
exporter.options.export_interval)
4344

4445

4546
class AzureHeartbeatMetricsProducer(MetricProducer):

contrib/opencensus-ext-azure/tests/test_azure_metrics_exporter.py

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -186,32 +186,65 @@ def test_create_envelope(self):
186186
self.assertTrue('properties' in envelope.data.baseData)
187187
self.assertEqual(envelope.data.baseData.properties, properties)
188188

189+
def test_shutdown(self):
190+
mock_thread = mock.Mock()
191+
mock_storage = mock.Mock()
192+
exporter = metrics_exporter.MetricsExporter(
193+
instrumentation_key='12345678-1234-5678-abcd-12345678abcd'
194+
)
195+
exporter.exporter_thread = mock_thread
196+
exporter.storage = mock_storage
197+
exporter.shutdown()
198+
mock_thread.close.assert_called_once()
199+
mock_storage.close.assert_called_once()
200+
189201
@mock.patch('opencensus.ext.azure.metrics_exporter'
190202
'.transport.get_exporter_thread')
191203
def test_new_metrics_exporter(self, exporter_mock):
192-
iKey = '12345678-1234-5678-abcd-12345678abcd'
193-
exporter = metrics_exporter.new_metrics_exporter(
194-
instrumentation_key=iKey)
195-
196-
self.assertEqual(exporter.options.instrumentation_key, iKey)
197-
self.assertEqual(len(exporter_mock.call_args_list), 1)
198-
self.assertEqual(len(exporter_mock.call_args[0][0]), 2)
199-
producer_class = standard_metrics.AzureStandardMetricsProducer
200-
self.assertFalse(isinstance(exporter_mock.call_args[0][0][0],
201-
producer_class))
202-
self.assertTrue(isinstance(exporter_mock.call_args[0][0][1],
203-
producer_class))
204+
with mock.patch('opencensus.ext.azure.metrics_exporter'
205+
'.heartbeat_metrics.enable_heartbeat_metrics') as hb:
206+
hb.return_value = None
207+
iKey = '12345678-1234-5678-abcd-12345678abcd'
208+
exporter = metrics_exporter.new_metrics_exporter(
209+
instrumentation_key=iKey)
210+
211+
self.assertEqual(exporter.options.instrumentation_key, iKey)
212+
self.assertEqual(len(exporter_mock.call_args_list), 1)
213+
self.assertEqual(len(exporter_mock.call_args[0][0]), 2)
214+
producer_class = standard_metrics.AzureStandardMetricsProducer
215+
self.assertFalse(isinstance(exporter_mock.call_args[0][0][0],
216+
producer_class))
217+
self.assertTrue(isinstance(exporter_mock.call_args[0][0][1],
218+
producer_class))
204219

205220
@mock.patch('opencensus.ext.azure.metrics_exporter'
206221
'.transport.get_exporter_thread')
207222
def test_new_metrics_exporter_no_standard_metrics(self, exporter_mock):
208-
iKey = '12345678-1234-5678-abcd-12345678abcd'
209-
exporter = metrics_exporter.new_metrics_exporter(
210-
instrumentation_key=iKey, enable_standard_metrics=False)
211-
212-
self.assertEqual(exporter.options.instrumentation_key, iKey)
213-
self.assertEqual(len(exporter_mock.call_args_list), 1)
214-
self.assertEqual(len(exporter_mock.call_args[0][0]), 1)
215-
producer_class = standard_metrics.AzureStandardMetricsProducer
216-
self.assertFalse(isinstance(exporter_mock.call_args[0][0][0],
217-
producer_class))
223+
with mock.patch('opencensus.ext.azure.metrics_exporter'
224+
'.heartbeat_metrics.enable_heartbeat_metrics') as hb:
225+
hb.return_value = None
226+
iKey = '12345678-1234-5678-abcd-12345678abcd'
227+
exporter = metrics_exporter.new_metrics_exporter(
228+
instrumentation_key=iKey, enable_standard_metrics=False)
229+
230+
self.assertEqual(exporter.options.instrumentation_key, iKey)
231+
self.assertEqual(len(exporter_mock.call_args_list), 1)
232+
self.assertEqual(len(exporter_mock.call_args[0][0]), 1)
233+
producer_class = standard_metrics.AzureStandardMetricsProducer
234+
self.assertFalse(isinstance(exporter_mock.call_args[0][0][0],
235+
producer_class))
236+
237+
@mock.patch('opencensus.ext.azure.metrics_exporter'
238+
'.transport.get_exporter_thread')
239+
def test_new_metrics_exporter_heartbeat(self, exporter_mock):
240+
with mock.patch('opencensus.ext.azure.metrics_exporter'
241+
'.heartbeat_metrics.enable_heartbeat_metrics') as hb:
242+
iKey = '12345678-1234-5678-abcd-12345678abcd'
243+
exporter = metrics_exporter.new_metrics_exporter(
244+
instrumentation_key=iKey)
245+
246+
self.assertEqual(exporter.options.instrumentation_key, iKey)
247+
self.assertEqual(len(hb.call_args_list), 1)
248+
self.assertEqual(len(hb.call_args[0]), 2)
249+
self.assertEqual(hb.call_args[0][0], None)
250+
self.assertEqual(hb.call_args[0][1], iKey)

opencensus/metrics/transport.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def __init__(
6262
interval = DEFAULT_INTERVAL
6363

6464
self.func = function
65+
self.args = args
66+
self.kwargs = kwargs
6567

6668
def func(*aa, **kw):
6769
try:
@@ -82,6 +84,13 @@ def run(self):
8284
execution_context.set_is_exporter(True)
8385
super(PeriodicMetricTask, self).run()
8486

87+
def close(self):
88+
try:
89+
return self.func(*self.args, **self.kwargs)
90+
except Exception as ex:
91+
logger.exception("Error handling metric flush: {}".format(ex))
92+
self.cancel()
93+
8594

8695
def get_exporter_thread(metric_producers, exporter, interval=None):
8796
"""Get a running task that periodically exports metrics.

tests/unit/metrics/test_transport.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def test_periodic_task(self):
5858
self.assertEqual(mock_func.call_count, 2)
5959
time.sleep(INTERVAL)
6060
self.assertEqual(mock_func.call_count, 3)
61+
task.cancel()
6162

6263
def test_periodic_task_cancel(self):
6364
mock_func = mock.Mock()
@@ -69,6 +70,14 @@ def test_periodic_task_cancel(self):
6970
time.sleep(INTERVAL)
7071
self.assertEqual(mock_func.call_count, 1)
7172

73+
def test_periodic_task_close(self):
74+
mock_func = mock.Mock()
75+
task = transport.PeriodicMetricTask(100, mock_func)
76+
task.start()
77+
mock_func.assert_not_called()
78+
task.close()
79+
self.assertEqual(mock_func.call_count, 1)
80+
7281

7382
@mock.patch('opencensus.metrics.transport.DEFAULT_INTERVAL', INTERVAL)
7483
@mock.patch('opencensus.metrics.transport.logger')

0 commit comments

Comments
 (0)