diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c6e56fb..016b82da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 0.7.10 +Released 2020-06-29 + +- Updated `azure` module +([#903](https://github.com/census-instrumentation/opencensus-python/pull/903), + [#925](https://github.com/census-instrumentation/opencensus-python/pull/925)) + +- Updated `stackdriver` module +([#919](https://github.com/census-instrumentation/opencensus-python/pull/919)) + ## 0.7.9 Released 2020-06-17 diff --git a/context/opencensus-context/CHANGELOG.md b/context/opencensus-context/CHANGELOG.md index a470e2320..a49592ac1 100644 --- a/context/opencensus-context/CHANGELOG.md +++ b/context/opencensus-context/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## opencensus-ext-context 0.1.2 + +## 0.1.2 +Released 2020-06-29 + +- Release source distribution + ## 0.1.1 Released 2019-05-31 diff --git a/contrib/opencensus-ext-azure/CHANGELOG.md b/contrib/opencensus-ext-azure/CHANGELOG.md index ce9f4a741..f7a69a9b1 100644 --- a/contrib/opencensus-ext-azure/CHANGELOG.md +++ b/contrib/opencensus-ext-azure/CHANGELOG.md @@ -2,8 +2,18 @@ ## Unreleased +- Attach rate metrics via Heartbeat for Web and Function apps + ([#930](https://github.com/census-instrumentation/opencensus-python/pull/930)) +- Attach rate metrics for VM + ([#935](https://github.com/census-instrumentation/opencensus-python/pull/935)) + +## 1.0.4 +Released 2020-06-29 + - Remove dependency rate from standard metrics ([#903](https://github.com/census-instrumentation/opencensus-python/pull/903)) +- Implement customEvents using AzureEventHandler + ([#925](https://github.com/census-instrumentation/opencensus-python/pull/925)) ## 1.0.3 Released 2020-06-17 @@ -13,7 +23,6 @@ Released 2020-06-17 - Add support to initialize azure exporters with proxies ([#902](https://github.com/census-instrumentation/opencensus-python/pull/902)) - ## 1.0.2 Released 2020-02-04 diff --git a/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/__init__.py b/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/__init__.py index 51c59a7cf..b6cc12ede 100644 --- a/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/__init__.py +++ b/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/__init__.py @@ -19,7 +19,6 @@ HeartbeatMetric, ) from opencensus.metrics import transport -from opencensus.metrics.export.gauge import Registry from opencensus.metrics.export.metric_producer import MetricProducer _HEARTBEAT_METRICS = None @@ -43,20 +42,13 @@ def enable_heartbeat_metrics(connection_string, ikey): exporter.options.export_interval) -def register_metrics(): - registry = Registry() - metric = HeartbeatMetric() - registry.add_gauge(metric()) - return registry - - class AzureHeartbeatMetricsProducer(MetricProducer): """Implementation of the producer of heartbeat metrics. Includes Azure attach rate metrics, implemented using gauges. """ def __init__(self): - self.registry = register_metrics() + self._heartbeat = HeartbeatMetric() def get_metrics(self): - return self.registry.get_metrics() + return self._heartbeat.get_metrics() diff --git a/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/heartbeat.py b/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/heartbeat.py index d23417585..12bbe8124 100644 --- a/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/heartbeat.py +++ b/contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/heartbeat_metrics/heartbeat.py @@ -12,22 +12,60 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime +import json import os import platform from collections import OrderedDict +import requests + from opencensus.common.version import __version__ as opencensus_version from opencensus.ext.azure.common.version import __version__ as ext_version from opencensus.metrics.export.gauge import LongGauge from opencensus.metrics.label_key import LabelKey from opencensus.metrics.label_value import LabelValue +_AIMS_URI = "http://169.254.169.254/metadata/instance/compute" +_AIMS_API_VERSION = "api-version=2017-12-01" +_AIMS_FORMAT = "format=json" + class HeartbeatMetric: NAME = "Heartbeat" def __init__(self): + self.vm_data = {} + self.is_vm = False self.properties = OrderedDict() + self.update_properties() + self.heartbeat = LongGauge( + HeartbeatMetric.NAME, + 'Heartbeat metric with custom dimensions', + 'count', + list(self.properties.keys()), + ) + self.heartbeat.get_or_create_time_series( + list(self.properties.values()) + ) + + def get_metrics(self): + if self.is_vm: + # Only need to update if in vm (properties could change) + self.properties.clear() + self.update_properties() + self.heartbeat = LongGauge( + HeartbeatMetric.NAME, + 'Heartbeat metric with custom dimensions', + 'count', + list(self.properties.keys()), + ) + self.heartbeat.get_or_create_time_series( + list(self.properties.values()) + ) + return [self.heartbeat.get_metric(datetime.datetime.utcnow())] + + def update_properties(self): self.properties[LabelKey("sdk", '')] = LabelValue( 'py{}:oc{}:ext{}'.format( platform.python_version(), @@ -48,17 +86,34 @@ def __init__(self): # Function apps self.properties[LabelKey("azfunction_appId", '')] = \ LabelValue(os.environ.get("WEBSITE_HOSTNAME")) + elif self.get_azure_compute_metadata(): + # VM + if self.vm_data: + self.properties[LabelKey("azInst_vmId", '')] = \ + LabelValue(self.vm_data.get("vmId", '')) + self.properties[LabelKey("azInst_subscriptionId", '')] = \ + LabelValue(self.vm_data.get("subscriptionId", '')) + self.properties[LabelKey("azInst_osType", '')] = \ + LabelValue(self.vm_data.get("osType", '')) - def __call__(self): - """ Returns a derived gauge for the heartbeat metric. + def get_azure_compute_metadata(self): + try: + request_url = "{0}?{1}&{2}".format( + _AIMS_URI, _AIMS_API_VERSION, _AIMS_FORMAT) + response = requests.get(request_url, headers={"MetaData": "True"}) + except requests.exceptions.ConnectionError: + # Not in VM + self.is_vm = False + return False + except requests.exceptions.RequestException: + pass # retry - :rtype: :class:`opencensus.metrics.export.gauge.LongGauge` - :return: The gauge representing the heartbeat metric - """ - gauge = LongGauge( - HeartbeatMetric.NAME, - 'Heartbeat metric with custom dimensions', - 'count', - list(self.properties.keys())) - gauge.get_or_create_time_series(list(self.properties.values())) - return gauge + self.is_vm = True + try: + text = response.text + self.vm_data = json.loads(text) + except Exception: # pylint: disable=broad-except + # Error in reading response body, retry + pass + + return True diff --git a/contrib/opencensus-ext-azure/tests/test_azure_heartbeat_metrics.py b/contrib/opencensus-ext-azure/tests/test_azure_heartbeat_metrics.py index f3f3b11f4..c86f48446 100644 --- a/contrib/opencensus-ext-azure/tests/test_azure_heartbeat_metrics.py +++ b/contrib/opencensus-ext-azure/tests/test_azure_heartbeat_metrics.py @@ -12,28 +12,46 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import platform import unittest import mock +import requests from opencensus.common.version import __version__ as opencensus_version from opencensus.ext.azure.common.version import __version__ as ext_version from opencensus.ext.azure.metrics_exporter import heartbeat_metrics +class MockResponse(object): + def __init__(self, status_code, text): + self.status_code = status_code + self.text = text + + +def throw(exc_type, *args, **kwargs): + def func(*_args, **_kwargs): + raise exc_type(*args, **kwargs) + return func + + class TestHeartbeatMetrics(unittest.TestCase): def setUp(self): # pylint: disable=protected-access heartbeat_metrics._HEARTBEAT_METRICS = None - @mock.patch('opencensus.ext.azure.metrics_exporter' - '.heartbeat_metrics.register_metrics') - def test_producer_ctor(self, avail_mock): - heartbeat_metrics.AzureHeartbeatMetricsProducer() - - self.assertEqual(len(avail_mock.call_args_list), 1) + def test_producer_ctor(self): + producer = heartbeat_metrics.AzureHeartbeatMetricsProducer() + # pylint: disable=protected-access + metric = producer._heartbeat + self.assertTrue( + isinstance( + metric, + heartbeat_metrics.heartbeat.HeartbeatMetric + ) + ) def test_producer_get_metrics(self): producer = heartbeat_metrics.AzureHeartbeatMetricsProducer() @@ -41,11 +59,6 @@ def test_producer_get_metrics(self): self.assertEqual(len(metrics), 1) - def test_register_metrics(self): - registry = heartbeat_metrics.register_metrics() - - self.assertEqual(len(registry.get_metrics()), 1) - @mock.patch('opencensus.metrics.transport.get_exporter_thread') def test_enable_heartbeat_metrics(self, transport_mock): ikey = '12345678-1234-5678-abcd-12345678abcd' @@ -61,7 +74,7 @@ def test_enable_heartbeat_metrics(self, transport_mock): transport_mock.assert_called() @mock.patch('opencensus.metrics.transport.get_exporter_thread') - def test_enable_heartbeat_metrics_exits(self, transport_mock): + def test_enable_heartbeat_metrics_exists(self, transport_mock): # pylint: disable=protected-access producer = heartbeat_metrics.AzureHeartbeatMetricsProducer() heartbeat_metrics._HEARTBEAT_METRICS = producer @@ -85,6 +98,23 @@ def test_heartbeat_metric_init(self): ext_version, )) self.assertEqual(values[1].value, platform.system()) + gauge = metric.heartbeat + + self.assertEqual(gauge.descriptor.name, 'Heartbeat') + self.assertEqual( + gauge.descriptor.description, + 'Heartbeat metric with custom dimensions' + ) + self.assertEqual(gauge.descriptor.unit, 'count') + self.assertEqual(gauge.descriptor._type, 1) + self.assertEqual( + gauge.descriptor.label_keys, + list(metric.properties.keys()) + ) + self.assertEqual( + gauge._len_label_keys, + len(metric.properties.keys()) + ) @mock.patch.dict( os.environ, @@ -143,23 +173,74 @@ def test_heartbeat_metric_init_functionapp(self): self.assertEqual(keys[2].key, "azfunction_appId") self.assertEqual(values[2].value, "host_name") - def test_heartbeat_metric(self): - # pylint: disable=protected-access - metric = heartbeat_metrics.HeartbeatMetric() - gauge = metric() - - self.assertEqual(gauge.descriptor.name, 'Heartbeat') - self.assertEqual( - gauge.descriptor.description, - 'Heartbeat metric with custom dimensions' - ) - self.assertEqual(gauge.descriptor.unit, 'count') - self.assertEqual(gauge.descriptor._type, 1) - self.assertEqual( - gauge.descriptor.label_keys, - list(metric.properties.keys()) - ) - self.assertEqual( - gauge._len_label_keys, - len(metric.properties.keys()) - ) + def test_heartbeat_metric_init_vm(self): + with mock.patch('requests.get') as get: + get.return_value = MockResponse( + 200, + json.dumps( + { + 'vmId': 5, + 'subscriptionId': 3, + 'osType': 'Linux' + } + ) + ) + metric = heartbeat_metrics.HeartbeatMetric() + self.assertTrue(metric.is_vm) + self.assertEqual(metric.NAME, 'Heartbeat') + keys = list(metric.properties.keys()) + values = list(metric.properties.values()) + self.assertEqual(len(keys), 5) + self.assertEqual(len(keys), len(values)) + self.assertEqual(keys[0].key, "sdk") + self.assertEqual(keys[1].key, "osType") + self.assertEqual(values[0].value, 'py{}:oc{}:ext{}'.format( + platform.python_version(), + opencensus_version, + ext_version, + )) + self.assertEqual(values[1].value, platform.system()) + self.assertEqual(keys[2].key, "azInst_vmId") + self.assertEqual(values[2].value, 5) + self.assertEqual(keys[3].key, "azInst_subscriptionId") + self.assertEqual(values[3].value, 3) + self.assertEqual(keys[4].key, "azInst_osType") + self.assertEqual(values[4].value, "Linux") + + def test_heartbeat_metric_not_vm(self): + with mock.patch( + 'requests.get', + throw(requests.exceptions.ConnectionError) + ): + metric = heartbeat_metrics.HeartbeatMetric() + self.assertFalse(metric.is_vm) + self.assertEqual(metric.NAME, 'Heartbeat') + keys = list(metric.properties.keys()) + self.assertEqual(len(keys), 2) + + def test_heartbeat_metric_vm_error_response(self): + with mock.patch('requests.get') as get: + get.return_value = MockResponse( + 200, + json.dumps( + { + 'vmId': 5, + 'subscriptionId': 3, + 'osType': 'Linux' + } + ) + ) + metric = heartbeat_metrics.HeartbeatMetric() + self.assertTrue(metric.is_vm) + keys = list(metric.properties.keys()) + self.assertEqual(len(keys), 5) + with mock.patch( + 'requests.get', + throw(Exception) + ): + metric.vm_data.clear() + self.assertTrue(metric.is_vm) + self.assertEqual(len(metric.vm_data), 0) + self.assertTrue(metric.is_vm) + keys = list(metric.properties.keys()) + self.assertEqual(len(keys), 5) diff --git a/contrib/opencensus-ext-stackdriver/CHANGELOG.md b/contrib/opencensus-ext-stackdriver/CHANGELOG.md index 98e4c6e6b..8e79c9f15 100644 --- a/contrib/opencensus-ext-stackdriver/CHANGELOG.md +++ b/contrib/opencensus-ext-stackdriver/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## 0.7.3 +Released 2020-06-29 + - Add mean property for distribution values ([#919](https://github.com/census-instrumentation/opencensus-python/pull/919))