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

Commit b7c7a67

Browse files
authored
Attach rate metrics via Heartbeat for Azure exporter (#930)
1 parent 329b214 commit b7c7a67

File tree

12 files changed

+352
-9
lines changed

12 files changed

+352
-9
lines changed

contrib/opencensus-ext-azure/examples/traces/simple.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323

2424
with tracer.span(name='foo'):
2525
print('Hello, World!')
26+
input(...)

contrib/opencensus-ext-azure/opencensus/ext/azure/common/exporter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def __init__(self, src, dst):
6161
self.src = src
6262
self.dst = dst
6363
self._stopping = False
64-
super(Worker, self).__init__()
64+
super(Worker, self).__init__(
65+
name="AzureExporter Worker"
66+
)
6567

6668
def run(self): # pragma: NO COVER
6769
# Indicate that this thread is an exporter thread.

contrib/opencensus-ext-azure/opencensus/ext/azure/common/storage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(
8181
maintenance_period=60, # 1 minute
8282
retention_period=7*24*60*60, # 7 days
8383
write_timeout=60, # 1 minute
84+
source=None,
8485
):
8586
self.path = os.path.abspath(path)
8687
self.max_size = max_size
@@ -92,6 +93,7 @@ def __init__(
9293
self._maintenance_task = PeriodicTask(
9394
interval=self.maintenance_period,
9495
function=self._maintenance_routine,
96+
name='{} Storage Worker'.format(source)
9597
)
9698
self._maintenance_task.daemon = True
9799
self._maintenance_task.start()

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from opencensus.ext.azure.common.storage import LocalFileStorage
3232
from opencensus.ext.azure.common.transport import TransportMixin
33+
from opencensus.ext.azure.metrics_exporter import heartbeat_metrics
3334
from opencensus.trace import execution_context
3435

3536
logger = logging.getLogger(__name__)
@@ -52,12 +53,15 @@ def __init__(self, **options):
5253
max_size=self.options.storage_max_size,
5354
maintenance_period=self.options.storage_maintenance_period,
5455
retention_period=self.options.storage_retention_period,
56+
source=self.__class__.__name__,
5557
)
5658
self._telemetry_processors = []
5759
self.addFilter(SamplingFilter(self.options.logging_sampling_rate))
5860
self._queue = Queue(capacity=8192) # TODO: make this configurable
5961
self._worker = Worker(self._queue, self)
6062
self._worker.start()
63+
heartbeat_metrics.enable_heartbeat_metrics(
64+
self.options.connection_string, self.options.instrumentation_key)
6165

6266
def _export(self, batch, event=None): # pragma: NO COVER
6367
try:

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ def __init__(self, **options):
5252
max_size=self.options.storage_max_size,
5353
maintenance_period=self.options.storage_maintenance_period,
5454
retention_period=self.options.storage_retention_period,
55+
source=self.__class__.__name__,
5556
)
57+
self._atexit_handler = atexit.register(self.shutdown)
5658
super(MetricsExporter, self).__init__()
5759

5860
def export_metrics(self, metrics):
@@ -133,6 +135,13 @@ def _create_envelope(self, data_point, timestamp, properties):
133135
envelope.data = Data(baseData=data, baseType="MetricData")
134136
return envelope
135137

138+
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
144+
136145

137146
def new_metrics_exporter(**options):
138147
exporter = MetricsExporter(**options)
@@ -142,5 +151,9 @@ def new_metrics_exporter(**options):
142151
transport.get_exporter_thread(producers,
143152
exporter,
144153
interval=exporter.options.export_interval)
145-
atexit.register(exporter.export_metrics, stats_module.stats.get_metrics())
154+
from opencensus.ext.azure.metrics_exporter import heartbeat_metrics
155+
heartbeat_metrics.enable_heartbeat_metrics(
156+
exporter.options.connection_string,
157+
exporter.options.instrumentation_key
158+
)
146159
return exporter
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2020, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import threading
16+
17+
from opencensus.ext.azure.metrics_exporter import MetricsExporter
18+
from opencensus.ext.azure.metrics_exporter.heartbeat_metrics.heartbeat import (
19+
HeartbeatMetric,
20+
)
21+
from opencensus.metrics import transport
22+
from opencensus.metrics.export.gauge import Registry
23+
from opencensus.metrics.export.metric_producer import MetricProducer
24+
25+
_HEARTBEAT_METRICS = None
26+
_HEARTBEAT_LOCK = threading.Lock()
27+
28+
29+
def enable_heartbeat_metrics(connection_string, ikey):
30+
with _HEARTBEAT_LOCK:
31+
# Only start heartbeat if did not exist before
32+
global _HEARTBEAT_METRICS # pylint: disable=global-statement
33+
if _HEARTBEAT_METRICS is None:
34+
exporter = MetricsExporter(
35+
connection_string=connection_string,
36+
instrumentation_key=ikey,
37+
export_interval=900.0, # Send every 15 minutes
38+
)
39+
producer = AzureHeartbeatMetricsProducer()
40+
_HEARTBEAT_METRICS = producer
41+
transport.get_exporter_thread([_HEARTBEAT_METRICS],
42+
exporter,
43+
exporter.options.export_interval)
44+
45+
46+
def register_metrics():
47+
registry = Registry()
48+
metric = HeartbeatMetric()
49+
registry.add_gauge(metric())
50+
return registry
51+
52+
53+
class AzureHeartbeatMetricsProducer(MetricProducer):
54+
"""Implementation of the producer of heartbeat metrics.
55+
56+
Includes Azure attach rate metrics, implemented using gauges.
57+
"""
58+
def __init__(self):
59+
self.registry = register_metrics()
60+
61+
def get_metrics(self):
62+
return self.registry.get_metrics()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import platform
17+
from collections import OrderedDict
18+
19+
from opencensus.common.version import __version__ as opencensus_version
20+
from opencensus.ext.azure.common.version import __version__ as ext_version
21+
from opencensus.metrics.export.gauge import LongGauge
22+
from opencensus.metrics.label_key import LabelKey
23+
from opencensus.metrics.label_value import LabelValue
24+
25+
26+
class HeartbeatMetric:
27+
NAME = "Heartbeat"
28+
29+
def __init__(self):
30+
self.properties = OrderedDict()
31+
self.properties[LabelKey("sdk", '')] = LabelValue(
32+
'py{}:oc{}:ext{}'.format(
33+
platform.python_version(),
34+
opencensus_version,
35+
ext_version,
36+
)
37+
)
38+
self.properties[LabelKey("osType", '')] = LabelValue(platform.system())
39+
if os.environ.get("WEBSITE_SITE_NAME") is not None:
40+
# Web apps
41+
self.properties[LabelKey("appSrv_SiteName", '')] = \
42+
LabelValue(os.environ.get("WEBSITE_SITE_NAME"))
43+
self.properties[LabelKey("appSrv_wsStamp", '')] = \
44+
LabelValue(os.environ.get("WEBSITE_HOME_STAMPNAME", ''))
45+
self.properties[LabelKey("appSrv_wsHost", '')] = \
46+
LabelValue(os.environ.get("WEBSITE_HOSTNAME", ''))
47+
elif os.environ.get("FUNCTIONS_WORKER_RUNTIME") is not None:
48+
# Function apps
49+
self.properties[LabelKey("azfunction_appId", '')] = \
50+
LabelValue(os.environ.get("WEBSITE_HOSTNAME"))
51+
52+
def __call__(self):
53+
""" Returns a derived gauge for the heartbeat metric.
54+
55+
:rtype: :class:`opencensus.metrics.export.gauge.LongGauge`
56+
:return: The gauge representing the heartbeat metric
57+
"""
58+
gauge = LongGauge(
59+
HeartbeatMetric.NAME,
60+
'Heartbeat metric with custom dimensions',
61+
'count',
62+
list(self.properties.keys()))
63+
gauge.get_or_create_time_series(list(self.properties.values()))
64+
return gauge

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from opencensus.ext.azure.common.storage import LocalFileStorage
2828
from opencensus.ext.azure.common.transport import TransportMixin
29+
from opencensus.ext.azure.metrics_exporter import heartbeat_metrics
2930
from opencensus.trace.span import SpanKind
3031

3132
try:
@@ -52,9 +53,12 @@ def __init__(self, **options):
5253
max_size=self.options.storage_max_size,
5354
maintenance_period=self.options.storage_maintenance_period,
5455
retention_period=self.options.storage_retention_period,
56+
source=self.__class__.__name__,
5557
)
5658
self._telemetry_processors = []
5759
super(AzureExporter, self).__init__(**options)
60+
heartbeat_metrics.enable_heartbeat_metrics(
61+
self.options.connection_string, self.options.instrumentation_key)
5862

5963
def span_data_to_envelope(self, sd):
6064
envelope = Envelope(

0 commit comments

Comments
 (0)