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

Commit a4496dd

Browse files
authored
Attach rate metrics for VM (#935)
1 parent b7c7a67 commit a4496dd

File tree

7 files changed

+212
-55
lines changed

7 files changed

+212
-55
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
## 0.7.10
6+
Released 2020-06-29
7+
8+
- Updated `azure` module
9+
([#903](https://github.com/census-instrumentation/opencensus-python/pull/903),
10+
[#925](https://github.com/census-instrumentation/opencensus-python/pull/925))
11+
12+
- Updated `stackdriver` module
13+
([#919](https://github.com/census-instrumentation/opencensus-python/pull/919))
14+
515
## 0.7.9
616
Released 2020-06-17
717

context/opencensus-context/CHANGELOG.md

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

33
## Unreleased
44

5+
## opencensus-ext-context 0.1.2
6+
7+
## 0.1.2
8+
Released 2020-06-29
9+
10+
- Release source distribution
11+
512
## 0.1.1
613
Released 2019-05-31
714

contrib/opencensus-ext-azure/CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@
22

33
## Unreleased
44

5+
- Attach rate metrics via Heartbeat for Web and Function apps
6+
([#930](https://github.com/census-instrumentation/opencensus-python/pull/930))
7+
- Attach rate metrics for VM
8+
([#935](https://github.com/census-instrumentation/opencensus-python/pull/935))
9+
10+
## 1.0.4
11+
Released 2020-06-29
12+
513
- Remove dependency rate from standard metrics
614
([#903](https://github.com/census-instrumentation/opencensus-python/pull/903))
15+
- Implement customEvents using AzureEventHandler
16+
([#925](https://github.com/census-instrumentation/opencensus-python/pull/925))
717

818
## 1.0.3
919
Released 2020-06-17
@@ -13,7 +23,6 @@ Released 2020-06-17
1323
- Add support to initialize azure exporters with proxies
1424
([#902](https://github.com/census-instrumentation/opencensus-python/pull/902))
1525

16-
1726
## 1.0.2
1827
Released 2020-02-04
1928

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
HeartbeatMetric,
2020
)
2121
from opencensus.metrics import transport
22-
from opencensus.metrics.export.gauge import Registry
2322
from opencensus.metrics.export.metric_producer import MetricProducer
2423

2524
_HEARTBEAT_METRICS = None
@@ -43,20 +42,13 @@ def enable_heartbeat_metrics(connection_string, ikey):
4342
exporter.options.export_interval)
4443

4544

46-
def register_metrics():
47-
registry = Registry()
48-
metric = HeartbeatMetric()
49-
registry.add_gauge(metric())
50-
return registry
51-
52-
5345
class AzureHeartbeatMetricsProducer(MetricProducer):
5446
"""Implementation of the producer of heartbeat metrics.
5547
5648
Includes Azure attach rate metrics, implemented using gauges.
5749
"""
5850
def __init__(self):
59-
self.registry = register_metrics()
51+
self._heartbeat = HeartbeatMetric()
6052

6153
def get_metrics(self):
62-
return self.registry.get_metrics()
54+
return self._heartbeat.get_metrics()

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

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,60 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import datetime
16+
import json
1517
import os
1618
import platform
1719
from collections import OrderedDict
1820

21+
import requests
22+
1923
from opencensus.common.version import __version__ as opencensus_version
2024
from opencensus.ext.azure.common.version import __version__ as ext_version
2125
from opencensus.metrics.export.gauge import LongGauge
2226
from opencensus.metrics.label_key import LabelKey
2327
from opencensus.metrics.label_value import LabelValue
2428

29+
_AIMS_URI = "http://169.254.169.254/metadata/instance/compute"
30+
_AIMS_API_VERSION = "api-version=2017-12-01"
31+
_AIMS_FORMAT = "format=json"
32+
2533

2634
class HeartbeatMetric:
2735
NAME = "Heartbeat"
2836

2937
def __init__(self):
38+
self.vm_data = {}
39+
self.is_vm = False
3040
self.properties = OrderedDict()
41+
self.update_properties()
42+
self.heartbeat = LongGauge(
43+
HeartbeatMetric.NAME,
44+
'Heartbeat metric with custom dimensions',
45+
'count',
46+
list(self.properties.keys()),
47+
)
48+
self.heartbeat.get_or_create_time_series(
49+
list(self.properties.values())
50+
)
51+
52+
def get_metrics(self):
53+
if self.is_vm:
54+
# Only need to update if in vm (properties could change)
55+
self.properties.clear()
56+
self.update_properties()
57+
self.heartbeat = LongGauge(
58+
HeartbeatMetric.NAME,
59+
'Heartbeat metric with custom dimensions',
60+
'count',
61+
list(self.properties.keys()),
62+
)
63+
self.heartbeat.get_or_create_time_series(
64+
list(self.properties.values())
65+
)
66+
return [self.heartbeat.get_metric(datetime.datetime.utcnow())]
67+
68+
def update_properties(self):
3169
self.properties[LabelKey("sdk", '')] = LabelValue(
3270
'py{}:oc{}:ext{}'.format(
3371
platform.python_version(),
@@ -48,17 +86,34 @@ def __init__(self):
4886
# Function apps
4987
self.properties[LabelKey("azfunction_appId", '')] = \
5088
LabelValue(os.environ.get("WEBSITE_HOSTNAME"))
89+
elif self.get_azure_compute_metadata():
90+
# VM
91+
if self.vm_data:
92+
self.properties[LabelKey("azInst_vmId", '')] = \
93+
LabelValue(self.vm_data.get("vmId", ''))
94+
self.properties[LabelKey("azInst_subscriptionId", '')] = \
95+
LabelValue(self.vm_data.get("subscriptionId", ''))
96+
self.properties[LabelKey("azInst_osType", '')] = \
97+
LabelValue(self.vm_data.get("osType", ''))
5198

52-
def __call__(self):
53-
""" Returns a derived gauge for the heartbeat metric.
99+
def get_azure_compute_metadata(self):
100+
try:
101+
request_url = "{0}?{1}&{2}".format(
102+
_AIMS_URI, _AIMS_API_VERSION, _AIMS_FORMAT)
103+
response = requests.get(request_url, headers={"MetaData": "True"})
104+
except requests.exceptions.ConnectionError:
105+
# Not in VM
106+
self.is_vm = False
107+
return False
108+
except requests.exceptions.RequestException:
109+
pass # retry
54110

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
111+
self.is_vm = True
112+
try:
113+
text = response.text
114+
self.vm_data = json.loads(text)
115+
except Exception: # pylint: disable=broad-except
116+
# Error in reading response body, retry
117+
pass
118+
119+
return True

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

Lines changed: 113 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,53 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import json
1516
import os
1617
import platform
1718
import unittest
1819

1920
import mock
21+
import requests
2022

2123
from opencensus.common.version import __version__ as opencensus_version
2224
from opencensus.ext.azure.common.version import __version__ as ext_version
2325
from opencensus.ext.azure.metrics_exporter import heartbeat_metrics
2426

2527

28+
class MockResponse(object):
29+
def __init__(self, status_code, text):
30+
self.status_code = status_code
31+
self.text = text
32+
33+
34+
def throw(exc_type, *args, **kwargs):
35+
def func(*_args, **_kwargs):
36+
raise exc_type(*args, **kwargs)
37+
return func
38+
39+
2640
class TestHeartbeatMetrics(unittest.TestCase):
2741
def setUp(self):
2842
# pylint: disable=protected-access
2943
heartbeat_metrics._HEARTBEAT_METRICS = None
3044

31-
@mock.patch('opencensus.ext.azure.metrics_exporter'
32-
'.heartbeat_metrics.register_metrics')
33-
def test_producer_ctor(self, avail_mock):
34-
heartbeat_metrics.AzureHeartbeatMetricsProducer()
35-
36-
self.assertEqual(len(avail_mock.call_args_list), 1)
45+
def test_producer_ctor(self):
46+
producer = heartbeat_metrics.AzureHeartbeatMetricsProducer()
47+
# pylint: disable=protected-access
48+
metric = producer._heartbeat
49+
self.assertTrue(
50+
isinstance(
51+
metric,
52+
heartbeat_metrics.heartbeat.HeartbeatMetric
53+
)
54+
)
3755

3856
def test_producer_get_metrics(self):
3957
producer = heartbeat_metrics.AzureHeartbeatMetricsProducer()
4058
metrics = producer.get_metrics()
4159

4260
self.assertEqual(len(metrics), 1)
4361

44-
def test_register_metrics(self):
45-
registry = heartbeat_metrics.register_metrics()
46-
47-
self.assertEqual(len(registry.get_metrics()), 1)
48-
4962
@mock.patch('opencensus.metrics.transport.get_exporter_thread')
5063
def test_enable_heartbeat_metrics(self, transport_mock):
5164
ikey = '12345678-1234-5678-abcd-12345678abcd'
@@ -61,7 +74,7 @@ def test_enable_heartbeat_metrics(self, transport_mock):
6174
transport_mock.assert_called()
6275

6376
@mock.patch('opencensus.metrics.transport.get_exporter_thread')
64-
def test_enable_heartbeat_metrics_exits(self, transport_mock):
77+
def test_enable_heartbeat_metrics_exists(self, transport_mock):
6578
# pylint: disable=protected-access
6679
producer = heartbeat_metrics.AzureHeartbeatMetricsProducer()
6780
heartbeat_metrics._HEARTBEAT_METRICS = producer
@@ -85,6 +98,23 @@ def test_heartbeat_metric_init(self):
8598
ext_version,
8699
))
87100
self.assertEqual(values[1].value, platform.system())
101+
gauge = metric.heartbeat
102+
103+
self.assertEqual(gauge.descriptor.name, 'Heartbeat')
104+
self.assertEqual(
105+
gauge.descriptor.description,
106+
'Heartbeat metric with custom dimensions'
107+
)
108+
self.assertEqual(gauge.descriptor.unit, 'count')
109+
self.assertEqual(gauge.descriptor._type, 1)
110+
self.assertEqual(
111+
gauge.descriptor.label_keys,
112+
list(metric.properties.keys())
113+
)
114+
self.assertEqual(
115+
gauge._len_label_keys,
116+
len(metric.properties.keys())
117+
)
88118

89119
@mock.patch.dict(
90120
os.environ,
@@ -143,23 +173,74 @@ def test_heartbeat_metric_init_functionapp(self):
143173
self.assertEqual(keys[2].key, "azfunction_appId")
144174
self.assertEqual(values[2].value, "host_name")
145175

146-
def test_heartbeat_metric(self):
147-
# pylint: disable=protected-access
148-
metric = heartbeat_metrics.HeartbeatMetric()
149-
gauge = metric()
150-
151-
self.assertEqual(gauge.descriptor.name, 'Heartbeat')
152-
self.assertEqual(
153-
gauge.descriptor.description,
154-
'Heartbeat metric with custom dimensions'
155-
)
156-
self.assertEqual(gauge.descriptor.unit, 'count')
157-
self.assertEqual(gauge.descriptor._type, 1)
158-
self.assertEqual(
159-
gauge.descriptor.label_keys,
160-
list(metric.properties.keys())
161-
)
162-
self.assertEqual(
163-
gauge._len_label_keys,
164-
len(metric.properties.keys())
165-
)
176+
def test_heartbeat_metric_init_vm(self):
177+
with mock.patch('requests.get') as get:
178+
get.return_value = MockResponse(
179+
200,
180+
json.dumps(
181+
{
182+
'vmId': 5,
183+
'subscriptionId': 3,
184+
'osType': 'Linux'
185+
}
186+
)
187+
)
188+
metric = heartbeat_metrics.HeartbeatMetric()
189+
self.assertTrue(metric.is_vm)
190+
self.assertEqual(metric.NAME, 'Heartbeat')
191+
keys = list(metric.properties.keys())
192+
values = list(metric.properties.values())
193+
self.assertEqual(len(keys), 5)
194+
self.assertEqual(len(keys), len(values))
195+
self.assertEqual(keys[0].key, "sdk")
196+
self.assertEqual(keys[1].key, "osType")
197+
self.assertEqual(values[0].value, 'py{}:oc{}:ext{}'.format(
198+
platform.python_version(),
199+
opencensus_version,
200+
ext_version,
201+
))
202+
self.assertEqual(values[1].value, platform.system())
203+
self.assertEqual(keys[2].key, "azInst_vmId")
204+
self.assertEqual(values[2].value, 5)
205+
self.assertEqual(keys[3].key, "azInst_subscriptionId")
206+
self.assertEqual(values[3].value, 3)
207+
self.assertEqual(keys[4].key, "azInst_osType")
208+
self.assertEqual(values[4].value, "Linux")
209+
210+
def test_heartbeat_metric_not_vm(self):
211+
with mock.patch(
212+
'requests.get',
213+
throw(requests.exceptions.ConnectionError)
214+
):
215+
metric = heartbeat_metrics.HeartbeatMetric()
216+
self.assertFalse(metric.is_vm)
217+
self.assertEqual(metric.NAME, 'Heartbeat')
218+
keys = list(metric.properties.keys())
219+
self.assertEqual(len(keys), 2)
220+
221+
def test_heartbeat_metric_vm_error_response(self):
222+
with mock.patch('requests.get') as get:
223+
get.return_value = MockResponse(
224+
200,
225+
json.dumps(
226+
{
227+
'vmId': 5,
228+
'subscriptionId': 3,
229+
'osType': 'Linux'
230+
}
231+
)
232+
)
233+
metric = heartbeat_metrics.HeartbeatMetric()
234+
self.assertTrue(metric.is_vm)
235+
keys = list(metric.properties.keys())
236+
self.assertEqual(len(keys), 5)
237+
with mock.patch(
238+
'requests.get',
239+
throw(Exception)
240+
):
241+
metric.vm_data.clear()
242+
self.assertTrue(metric.is_vm)
243+
self.assertEqual(len(metric.vm_data), 0)
244+
self.assertTrue(metric.is_vm)
245+
keys = list(metric.properties.keys())
246+
self.assertEqual(len(keys), 5)

contrib/opencensus-ext-stackdriver/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+
## 0.7.3
6+
Released 2020-06-29
7+
58
- Add mean property for distribution values
69
([#919](https://github.com/census-instrumentation/opencensus-python/pull/919))
710

0 commit comments

Comments
 (0)