From 826a874abf6c892227296c1446012c3f2af52d60 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 19 May 2020 18:39:59 -0700 Subject: [PATCH 01/39] bump version --- azure_monitor/CHANGELOG.md | 18 ++++++++++++++++-- azure_monitor/src/azure_monitor/version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index ca2f99e..96f2543 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,12 +2,26 @@ ## Unreleased -## 0.2.0 +## 0.3b.0 +Released 2020-05-19 + +- Implement max size logic for local storage + ([#74](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/74)) +- Remove label sets + add is_remote to spancontext + ([#75](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/75)) +- Adding live metrics manager + ([#78](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/78)) +- Handle status 439 - Too Many Requests over extended time + ([#80](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/80)) +- Fix breaking changes from OT release 0.7b.0 + ([#86](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/86)) + +## 0.2b.0 Released 2020-03-31 - Initial beta release -## 0.1.0 +## 0.1a.0 Released 2019-11-06 - Initial alpha release diff --git a/azure_monitor/src/azure_monitor/version.py b/azure_monitor/src/azure_monitor/version.py index 835d02b..7942442 100644 --- a/azure_monitor/src/azure_monitor/version.py +++ b/azure_monitor/src/azure_monitor/version.py @@ -1,3 +1,3 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -__version__ = "0.3.dev0" +__version__ = "0.4.dev0" From f2f86ecbe3bc3e9bd65fb247f067c27546207515 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 21 May 2020 13:12:50 -0700 Subject: [PATCH 02/39] fix serializable bug --- azure_monitor/src/azure_monitor/export/metrics/__init__.py | 7 ++++++- azure_monitor/tests/metrics/test_metrics.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/metrics/__init__.py b/azure_monitor/src/azure_monitor/export/metrics/__init__.py index d50dfc0..c0ac9c3 100644 --- a/azure_monitor/src/azure_monitor/export/metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/export/metrics/__init__.py @@ -35,7 +35,12 @@ def export( self, metric_records: Sequence[MetricRecord] ) -> MetricsExportResult: envelopes = list(map(self._metric_to_envelope, metric_records)) - envelopes = self._apply_telemetry_processors(envelopes) + envelopes = list( + map( + lambda x: x.to_dict(), + self._apply_telemetry_processors(envelopes), + ) + ) try: result = self._transmit(envelopes) if result == ExportResult.FAILED_RETRYABLE: diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index 63e3bc2..aa7910d 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -107,7 +107,7 @@ def test_export(self, mte, transmit): CounterAggregator(), self._test_labels, self._test_metric ) exporter = self._exporter - mte.return_value = [] + mte.return_value = Envelope() transmit.return_value = ExportResult.SUCCESS result = exporter.export([record]) self.assertEqual(result, MetricsExportResult.SUCCESS) @@ -124,7 +124,7 @@ def test_export_failed_retryable(self, mte, transmit): ) exporter = self._exporter transmit.return_value = ExportResult.FAILED_RETRYABLE - mte.return_value = [] + mte.return_value = Envelope() storage_mock = mock.Mock() exporter.storage.put = storage_mock result = exporter.export([record]) @@ -143,7 +143,7 @@ def test_export_exception(self, mte, transmit, logger_mock): CounterAggregator(), self._test_labels, self._test_metric ) exporter = self._exporter - mte.return_value = [] + mte.return_value = Envelope() transmit.side_effect = throw(Exception) result = exporter.export([record]) self.assertEqual(result, MetricsExportResult.FAILURE) From 40cce3e4976fb5e5f3b3b887710ceb2597254a3c Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 21 May 2020 15:09:22 -0700 Subject: [PATCH 03/39] changelog --- azure_monitor/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 96f2543..7534ca4 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 0.3b.1 +Released 2020-05-21 + +- Fix metrics exporter serialization bug + ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) + ## 0.3b.0 Released 2020-05-19 From 02c3bd1b4e852285a0ad98a46e44754ae252649d Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Tue, 2 Jun 2020 17:01:17 -0700 Subject: [PATCH 04/39] Adding live metrics --- .../src/azure_monitor/export/__init__.py | 107 +++++++++++ .../azure_monitor/export/trace/__init__.py | 103 +---------- .../sdk/auto_collection/__init__.py | 18 +- .../sdk/auto_collection/dependency_metrics.py | 33 ++-- .../auto_collection/live_metrics/__init__.py | 53 ++++++ .../live_metrics/dependency_metrics.py | 109 +++++++++++ .../live_metrics/performance_metrics.py | 41 ++++ .../live_metrics/request_metrics.py | 76 ++++++++ .../auto_collection/metrics_span_processor.py | 63 +++++++ .../sdk/auto_collection/request_metrics.py | 56 +++--- .../live_metrics/test_dependency_metrics.py | 175 ++++++++++++++++++ .../live_metrics/test_performance_metrics.py | 73 ++++++++ .../live_metrics/test_request_metrics.py | 125 +++++++++++++ .../auto_collection/test_auto_collection.py | 13 +- .../test_dependency_metrics.py | 51 ++--- .../test_metrics_span_processor.py | 129 +++++++++++++ .../auto_collection/test_request_metrics.py | 106 +++-------- 17 files changed, 1061 insertions(+), 270 deletions(-) create mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py create mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py create mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py create mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py create mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py create mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py create mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py create mode 100644 azure_monitor/tests/auto_collection/test_metrics_span_processor.py diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 814d15e..9b3c91b 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -4,11 +4,16 @@ import logging import typing from enum import Enum +from urllib.parse import urlparse import requests +from opentelemetry.trace import Span, SpanKind +from opentelemetry.sdk.util import ns_to_iso_str from opentelemetry.sdk.metrics.export import MetricsExportResult from opentelemetry.sdk.trace.export import SpanExportResult +from opentelemetry.trace.status import StatusCanonicalCode +from azure_monitor import protocol, utils from azure_monitor.options import ExporterOptions from azure_monitor.protocol import Envelope from azure_monitor.storage import LocalFileStorage @@ -204,3 +209,105 @@ def get_metrics_export_result(result: ExportResult) -> MetricsExportResult: ): return MetricsExportResult.FAILURE return None + + +# pylint: disable=too-many-statements +# pylint: disable=too-many-branches +def convert_span_to_envelope(span: Span) -> protocol.Envelope: + if not span: + return None + envelope = protocol.Envelope( + ikey="", + tags=dict(utils.azure_monitor_context), + time=ns_to_iso_str(span.start_time), + ) + envelope.tags["ai.operation.id"] = "{:032x}".format(span.context.trace_id) + parent = span.parent + if isinstance(parent, Span): + parent = parent.context + if parent: + envelope.tags["ai.operation.parentId"] = "{:016x}".format( + parent.span_id + ) + if span.kind in (SpanKind.CONSUMER, SpanKind.SERVER): + envelope.name = "Microsoft.ApplicationInsights.Request" + data = protocol.Request( + id="{:016x}".format(span.context.span_id), + duration=utils.ns_to_duration(span.end_time - span.start_time), + response_code=str(span.status.canonical_code.value), + success=span.status.canonical_code + == StatusCanonicalCode.OK, # Modify based off attributes or Status + properties={}, + ) + envelope.data = protocol.Data(base_data=data, base_type="RequestData") + if "http.method" in span.attributes: + data.name = span.attributes["http.method"] + if "http.route" in span.attributes: + data.name = data.name + " " + span.attributes["http.route"] + envelope.tags["ai.operation.name"] = data.name + data.properties["request.name"] = data.name + elif "http.path" in span.attributes: + data.properties["request.name"] = ( + data.name + " " + span.attributes["http.path"] + ) + if "http.url" in span.attributes: + data.url = span.attributes["http.url"] + data.properties["request.url"] = span.attributes["http.url"] + if "http.status_code" in span.attributes: + status_code = span.attributes["http.status_code"] + data.response_code = str(status_code) + data.success = 200 <= status_code < 400 + else: + envelope.name = "Microsoft.ApplicationInsights.RemoteDependency" + data = protocol.RemoteDependency( + name=span.name, + id="{:016x}".format(span.context.span_id), + result_code=str(span.status.canonical_code.value), + duration=utils.ns_to_duration(span.end_time - span.start_time), + success=span.status.canonical_code + == StatusCanonicalCode.OK, # Modify based off attributes or Status + properties={}, + ) + envelope.data = protocol.Data( + base_data=data, base_type="RemoteDependencyData" + ) + if span.kind in (SpanKind.CLIENT, SpanKind.PRODUCER): + if ( + "component" in span.attributes + and span.attributes["component"] == "http" + ): + data.type = "HTTP" + if "http.url" in span.attributes: + url = span.attributes["http.url"] + # data is the url + data.data = url + parse_url = urlparse(url) + # TODO: error handling, probably put scheme as well + # target matches authority (host:port) + data.target = parse_url.netloc + if "http.method" in span.attributes: + # name is METHOD/path + data.name = ( + span.attributes["http.method"] + "/" + parse_url.path + ) + if "http.status_code" in span.attributes: + status_code = span.attributes["http.status_code"] + data.result_code = str(status_code) + data.success = 200 <= status_code < 400 + else: # SpanKind.INTERNAL + data.type = "InProc" + data.success = True + for key in span.attributes: + # This removes redundant data from ApplicationInsights + if key.startswith("http."): + continue + data.properties[key] = span.attributes[key] + if span.links: + links = [] + for link in span.links: + operation_id = "{:032x}".format(link.context.trace_id) + span_id = "{:016x}".format(link.context.span_id) + links.append({"operation_Id": operation_id, "id": span_id}) + data.properties["_MS.links"] = json.dumps(links) + # TODO: tracestate, tags + return envelope diff --git a/azure_monitor/src/azure_monitor/export/trace/__init__.py b/azure_monitor/src/azure_monitor/export/trace/__init__.py index 805d271..05f5a5e 100644 --- a/azure_monitor/src/azure_monitor/export/trace/__init__.py +++ b/azure_monitor/src/azure_monitor/export/trace/__init__.py @@ -13,6 +13,7 @@ from azure_monitor import protocol, utils from azure_monitor.export import ( BaseExporter, + convert_span_to_envelope, ExportResult, get_trace_export_result, ) @@ -52,104 +53,6 @@ def export(self, spans: Sequence[Span]) -> SpanExportResult: def _span_to_envelope(self, span: Span) -> protocol.Envelope: if not span: return None - envelope = protocol.Envelope( - ikey=self.options.instrumentation_key, - tags=dict(utils.azure_monitor_context), - time=ns_to_iso_str(span.start_time), - ) - envelope.tags["ai.operation.id"] = "{:032x}".format( - span.context.trace_id - ) - parent = span.parent - if isinstance(parent, Span): - parent = parent.context - if parent: - envelope.tags["ai.operation.parentId"] = "{:016x}".format( - parent.span_id - ) - if span.kind in (SpanKind.CONSUMER, SpanKind.SERVER): - envelope.name = "Microsoft.ApplicationInsights.Request" - data = protocol.Request( - id="{:016x}".format(span.context.span_id), - duration=utils.ns_to_duration(span.end_time - span.start_time), - response_code=str(span.status.canonical_code.value), - success=span.status.canonical_code - == StatusCanonicalCode.OK, # Modify based off attributes or Status - properties={}, - ) - envelope.data = protocol.Data( - base_data=data, base_type="RequestData" - ) - if "http.method" in span.attributes: - data.name = span.attributes["http.method"] - if "http.route" in span.attributes: - data.name = data.name + " " + span.attributes["http.route"] - envelope.tags["ai.operation.name"] = data.name - data.properties["request.name"] = data.name - elif "http.path" in span.attributes: - data.properties["request.name"] = ( - data.name + " " + span.attributes["http.path"] - ) - if "http.url" in span.attributes: - data.url = span.attributes["http.url"] - data.properties["request.url"] = span.attributes["http.url"] - if "http.status_code" in span.attributes: - status_code = span.attributes["http.status_code"] - data.response_code = str(status_code) - data.success = 200 <= status_code < 400 - else: - envelope.name = "Microsoft.ApplicationInsights.RemoteDependency" - data = protocol.RemoteDependency( - name=span.name, - id="{:016x}".format(span.context.span_id), - result_code=str(span.status.canonical_code.value), - duration=utils.ns_to_duration(span.end_time - span.start_time), - success=span.status.canonical_code - == StatusCanonicalCode.OK, # Modify based off attributes or Status - properties={}, - ) - envelope.data = protocol.Data( - base_data=data, base_type="RemoteDependencyData" - ) - if span.kind in (SpanKind.CLIENT, SpanKind.PRODUCER): - if ( - "component" in span.attributes - and span.attributes["component"] == "http" - ): - data.type = "HTTP" - if "http.url" in span.attributes: - url = span.attributes["http.url"] - # data is the url - data.data = url - parse_url = urlparse(url) - # TODO: error handling, probably put scheme as well - # target matches authority (host:port) - data.target = parse_url.netloc - if "http.method" in span.attributes: - # name is METHOD/path - data.name = ( - span.attributes["http.method"] - + "/" - + parse_url.path - ) - if "http.status_code" in span.attributes: - status_code = span.attributes["http.status_code"] - data.result_code = str(status_code) - data.success = 200 <= status_code < 400 - else: # SpanKind.INTERNAL - data.type = "InProc" - data.success = True - for key in span.attributes: - # This removes redundant data from ApplicationInsights - if key.startswith("http."): - continue - data.properties[key] = span.attributes[key] - if span.links: - links = [] - for link in span.links: - operation_id = "{:032x}".format(link.context.trace_id) - span_id = "{:016x}".format(link.context.span_id) - links.append({"operation_Id": operation_id, "id": span_id}) - data.properties["_MS.links"] = json.dumps(links) - # TODO: tracestate, tags + envelope = convert_span_to_envelope(span) + envelope.ikey = self.options.instrumentation_key return envelope diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 949c015..ab6ca65 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -8,13 +8,18 @@ from azure_monitor.sdk.auto_collection.dependency_metrics import ( DependencyMetrics, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) from azure_monitor.sdk.auto_collection.performance_metrics import ( PerformanceMetrics, ) from azure_monitor.sdk.auto_collection.request_metrics import RequestMetrics + __all__ = [ "AutoCollection", + "AzureMetricsSpanProcessor", "DependencyMetrics", "RequestMetrics", "PerformanceMetrics", @@ -30,7 +35,14 @@ class AutoCollection: labels: Dictionary of labels """ - def __init__(self, meter: Meter, labels: Dict[str, str]): + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): self._performance_metrics = PerformanceMetrics(meter, labels) - self._dependency_metrics = DependencyMetrics(meter, labels) - self._request_metrics = RequestMetrics(meter, labels) + self._dependency_metrics = DependencyMetrics( + meter, labels, span_processor + ) + self._request_metrics = RequestMetrics(meter, labels, span_processor) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py index 5600612..a9ab637 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py @@ -1,27 +1,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import threading import time from typing import Dict -import requests -from opentelemetry import context from opentelemetry.metrics import Meter -dependency_map = dict() -_dependency_lock = threading.Lock() -ORIGINAL_REQUEST = requests.Session.request - +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) -def dependency_patch(*args, **kwargs) -> None: - result = ORIGINAL_REQUEST(*args, **kwargs) - # Only collect request metric if sent from non-exporter thread - if context.get_value("suppress_instrumentation") is None: - # We don't want multiple threads updating this at once - with _dependency_lock: - count = dependency_map.get("count", 0) - dependency_map["count"] = count + 1 - return result +dependency_map = dict() class DependencyMetrics: @@ -33,11 +21,16 @@ class DependencyMetrics: labels: Dictionary of labels """ - def __init__(self, meter: Meter, labels: Dict[str, str]): + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): self._meter = meter self._labels = labels - # Patch requests - requests.Session.request = dependency_patch + self._span_processor = span_processor + meter.register_observer( callback=self._track_dependency_rate, name="\\ApplicationInsights\\Dependency Calls/Sec", @@ -53,7 +46,7 @@ def _track_dependency_rate(self, observer) -> None: using the requests library within an elapsed time and dividing that value over the elapsed time. """ - current_count = dependency_map.get("count", 0) + current_count = self._span_processor.dependency_count current_time = time.time() last_count = dependency_map.get("last_count", 0) last_time = dependency_map.get("last_time") diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index 5b7f7a9..43d4e69 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -1,2 +1,55 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# +from typing import Dict + +from opentelemetry.metrics import Meter + +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) +from azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics import ( + DependencyLiveMetrics, +) +from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( + LiveMetricsExporter, +) +from azure_monitor.sdk.auto_collection.live_metrics.performance_metrics import ( + PerformanceLiveMetrics, +) +from azure_monitor.sdk.auto_collection.live_metrics.request_metrics import ( + RequestLiveMetrics, +) + + +__all__ = [ + "LiveMetricsAutoCollection", + "LiveMetricsExporter", + "DependencyLiveMetrics", + "PerformanceLiveMetrics", + "RequestLiveMetrics", +] + + +class LiveMetricsAutoCollection: + """Starts auto collection of live metrics, including performance, + dependency and request metrics. + + Args: + meter: OpenTelemetry Meter + labels: Dictionary of labels + """ + + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): + self._performance_metrics = PerformanceLiveMetrics(meter, labels) + self._dependency_metrics = DependencyLiveMetrics( + meter, labels, span_processor + ) + self._request_metrics = RequestLiveMetrics( + meter, labels, span_processor + ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py new file mode 100644 index 0000000..430e643 --- /dev/null +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py @@ -0,0 +1,109 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import time +from typing import Dict + +from opentelemetry.metrics import Meter + +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) + +dependency_map = dict() + + +class DependencyLiveMetrics: + """Starts auto collection of dependency live metrics. + + Args: + meter: OpenTelemetry Meter + labels: Dictionary of labels + """ + + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): + self._meter = meter + self._labels = labels + self._span_processor = span_processor + + meter.register_observer( + callback=self._track_dependency_duration, + name="\\ApplicationInsights\\Dependency Call Duration", + description="Average Outgoing Requests duration", + unit="milliseconds", + value_type=float, + ) + meter.register_observer( + callback=self._track_failure_rate, + name="\\ApplicationInsights\\Dependency Calls Failed\/Sec", + description="Failed Outgoing Requests per second", + unit="rps", + value_type=int, + ) + + def _track_dependency_duration(self, observer) -> None: + """ Track Dependency average duration + + Calculated by getting the time it takes to make an outgoing request + and dividing over the amount of outgoing requests over an elapsed time. + """ + last_average_duration = dependency_map.get("last_average_duration", 0) + interval_duration = ( + self._span_processor.dependency_duration + - dependency_map.get("last_duration", 0) + ) + interval_count = ( + self._span_processor.dependency_count + - dependency_map.get("last_count", 0) + ) + try: + result = interval_duration / interval_count + dependency_map[ + "last_count" + ] = self._span_processor.dependency_count + dependency_map["last_average_duration"] = result + dependency_map[ + "last_duration" + ] = self._span_processor.dependency_duration + # Convert to milliseconds + observer.observe(int(result * 1000.0), self._labels) + except ZeroDivisionError: + # If interval_count is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_average_duration * 1000.0), self._labels) + + def _track_failure_rate(self, observer) -> None: + """ Track Failed Dependency rate + + Calculated by obtaining the number of failed outgoing requests made + using the requests library within an elapsed time and dividing + that value over the elapsed time. + """ + current_failed_count = self._span_processor.failed_dependency_count + current_time = time.time() + last_failed_count = dependency_map.get("last_failed_count", 0) + last_time = dependency_map.get("last_time") + last_result = dependency_map.get("last_result", 0) + + try: + # last_time is None the very first time this function is called + if last_time is not None: + elapsed_seconds = current_time - last_time + interval_failed_count = ( + current_failed_count - last_failed_count + ) + result = interval_failed_count / elapsed_seconds + else: + result = 0 + dependency_map["last_time"] = current_time + dependency_map["last_failed_count"] = current_failed_count + dependency_map["last_result"] = result + observer.observe(int(result), self._labels) + except ZeroDivisionError: + # If elapsed_seconds is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_result), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py new file mode 100644 index 0000000..6a33270 --- /dev/null +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import logging +from typing import Dict + +import psutil +from opentelemetry.metrics import Meter + +logger = logging.getLogger(__name__) +PROCESS = psutil.Process() + + +class PerformanceLiveMetrics: + """Starts auto collection of performance live metrics. + + Args: + meter: OpenTelemetry Meter + labels: Dictionary of labels + """ + + def __init__(self, meter: Meter, labels: Dict[str, str]): + self._meter = meter + self._labels = labels + # Create performance metrics + meter.register_observer( + callback=self._track_commited_memory, + name="\Memory\Committed Bytes", + description="Amount of commited memory in bytes", + unit="byte", + value_type=int, + ) + + def _track_commited_memory(self, observer) -> None: + """ Track Commited Memory + + Available commited memory is defined as total memory minus available memory. + """ + observer.observe( + psutil.virtual_memory().total - psutil.virtual_memory().available, + self._labels, + ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py new file mode 100644 index 0000000..eb0d715 --- /dev/null +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py @@ -0,0 +1,76 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import logging +import time +from typing import Dict + +from opentelemetry.metrics import Meter + +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) + +logger = logging.getLogger(__name__) + +requests_map = dict() + + +class RequestLiveMetrics: + """Starts auto collection of request live metrics, including + "Failed Incoming Requests Rate" metric. + + Args: + meter: OpenTelemetry Meter + labels: Dictionary of labels + """ + + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): + self._meter = meter + self._labels = labels + self._span_processor = span_processor + + meter.register_observer( + callback=self._track_request_failed_rate, + name="\ApplicationInsights\Requests Failed/Sec", + description="Incoming Requests Failed Rate", + unit="rps", + value_type=int, + ) + + def _track_request_failed_rate(self, observer) -> None: + """ Track Request failed execution rate + + Calculated by obtaining by getting the number of failed incoming requests + made to an HTTPServer within an elapsed time and dividing that value + over the elapsed time. + """ + current_time = time.time() + last_rate = requests_map.get("last_rate", 0) + last_time = requests_map.get("last_time") + + try: + # last_rate_time is None the first time this function is called + if last_time is not None: + interval_time = current_time - requests_map.get("last_time", 0) + interval_count = ( + self._span_processor.failed_request_count + - requests_map.get("last_failed_count", 0) + ) + result = interval_count / interval_time + else: + result = 0 + requests_map["last_time"] = current_time + requests_map[ + "last_failed_count" + ] = self._span_processor.failed_request_count + requests_map["last_rate"] = result + observer.observe(int(result), self._labels) + except ZeroDivisionError: + # If elapsed_seconds is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_rate), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py new file mode 100644 index 0000000..64ab547 --- /dev/null +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py @@ -0,0 +1,63 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import logging + +from opentelemetry.trace import SpanKind +from opentelemetry.sdk.trace import Span, SpanProcessor + +from azure_monitor.export import convert_span_to_envelope + +logger = logging.getLogger(__name__) + + +class AzureMetricsSpanProcessor(SpanProcessor): + """AzureMetricsSpanProcessor is an implementation of `SpanProcessor` used + to generate Azure specific metrics, including dependencies/requests rate, average duration + and failed dependencies/requests. + """ + + def __init__(self, exporter=None): + self._exporter = exporter + self.request_count = 0 + self.dependency_count = 0 + self.failed_request_count = 0 + self.failed_dependency_count = 0 + self.request_duration = 0 + self.dependency_duration = 0 + + def on_start(self, span: Span) -> None: + pass + + def on_end(self, span: Span) -> None: + try: + if span.kind == SpanKind.SERVER: + self.request_count = self.request_count + 1 + self.request_duration = self.request_duration + ( + span.end_time - span.start_time + ) + if not span.status.is_ok: + self.failed_request_count = self.failed_request_count + 1 + if self._exporter is not None: + self._exporter.add_document( + convert_span_to_envelope(span) + ) + elif span.kind == SpanKind.CLIENT: + self.dependency_count = self.dependency_count + 1 + self.dependency_duration = self.dependency_duration + ( + span.end_time - span.start_time + ) + if not span.status.is_ok: + self.failed_dependency_count = ( + self.failed_dependency_count + 1 + ) + if self._exporter is not None: + self._exporter.add_document( + convert_span_to_envelope(span) + ) + + # pylint: disable=broad-except + except Exception: + logger.exception("Exception while processing Span.") + + def shutdown(self) -> None: + pass diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index 8647ec6..69cf9b5 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -8,6 +8,10 @@ from opentelemetry.metrics import Meter +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) + logger = logging.getLogger(__name__) _requests_lock = threading.Lock() @@ -32,27 +36,6 @@ def wrapper(self=None): return wrapper -def server_patch(*args, **kwargs): - if len(args) >= 3: - handler = args[2] - if handler: - # Patch the handler methods if they exist - if "do_DELETE" in dir(handler): - handler.do_DELETE = request_patch(handler.do_DELETE) - if "do_GET" in dir(handler): - handler.do_GET = request_patch(handler.do_GET) - if "do_HEAD" in dir(handler): - handler.do_HEAD = request_patch(handler.do_HEAD) - if "do_OPTIONS" in dir(handler): - handler.do_OPTIONS = request_patch(handler.do_OPTIONS) - if "do_POST" in dir(handler): - handler.do_POST = request_patch(handler.do_POST) - if "do_PUT" in dir(handler): - handler.do_PUT = request_patch(handler.do_PUT) - result = ORIGINAL_CONSTRUCTOR(*args, **kwargs) - return result - - class RequestMetrics: """Starts auto collection of request metrics, including "Incoming Requests Average Execution Time" and @@ -63,11 +46,15 @@ class RequestMetrics: labels: Dictionary of labels """ - def __init__(self, meter: Meter, labels: Dict[str, str]): + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + span_processor: AzureMetricsSpanProcessor, + ): self._meter = meter self._labels = labels - # Patch the HTTPServer handler to track request information - HTTPServer.__init__ = server_patch + self._span_processor = span_processor meter.register_observer( callback=self._track_request_duration, @@ -92,16 +79,20 @@ def _track_request_duration(self, observer) -> None: and dividing over the amount of incoming requests over an elapsed time. """ last_average_duration = requests_map.get("last_average_duration", 0) - interval_duration = requests_map.get("duration", 0) - requests_map.get( - "last_duration", 0 + interval_duration = ( + self._span_processor.request_duration + - requests_map.get("last_duration", 0) ) - interval_count = requests_map.get("count", 0) - requests_map.get( + interval_count = self._span_processor.request_count - requests_map.get( "last_count", 0 ) try: result = interval_duration / interval_count + requests_map["last_count"] = self._span_processor.request_count requests_map["last_average_duration"] = result - requests_map["last_duration"] = requests_map.get("duration", 0) + requests_map[ + "last_duration" + ] = self._span_processor.request_duration # Convert to milliseconds observer.observe(int(result * 1000.0), self._labels) except ZeroDivisionError: @@ -124,14 +115,15 @@ def _track_request_rate(self, observer) -> None: # last_rate_time is None the first time this function is called if last_time is not None: interval_time = current_time - requests_map.get("last_time", 0) - interval_count = requests_map.get( - "count", 0 - ) - requests_map.get("last_count", 0) + interval_count = ( + self._span_processor.request_count + - requests_map.get("last_count", 0) + ) result = interval_count / interval_time else: result = 0 requests_map["last_time"] = current_time - requests_map["last_count"] = requests_map.get("count", 0) + requests_map["last_count"] = self._span_processor.request_count requests_map["last_rate"] = result observer.observe(int(result), self._labels) except ZeroDivisionError: diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py new file mode 100644 index 0000000..b51148b --- /dev/null +++ b/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py @@ -0,0 +1,175 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest +from unittest import mock + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider, Observer + +from azure_monitor.sdk.auto_collection.live_metrics import dependency_metrics +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) + + +# pylint: disable=protected-access +class TestDependencyLiveMetrics(unittest.TestCase): + @classmethod + def setUpClass(cls): + metrics.set_meter_provider(MeterProvider()) + cls._meter = metrics.get_meter(__name__) + cls._test_labels = {"environment": "staging"} + cls._span_processor = AzureMetricsSpanProcessor() + + @classmethod + def tearDown(cls): + metrics._METER_PROVIDER = None + + def setUp(self): + dependency_metrics.dependency_map.clear() + + def test_constructor(self): + mock_meter = mock.Mock() + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + self.assertEqual(metrics_collector._meter, mock_meter) + self.assertEqual(metrics_collector._labels, self._test_labels) + self.assertEqual(mock_meter.register_observer.call_count, 2) + create_metric_calls = mock_meter.register_observer.call_args_list + + create_metric_calls[0].assert_called_with( + callback=metrics_collector._track_dependency_duration, + name="\\ApplicationInsights\\Dependency Call Duration", + description="Average Outgoing Requests duration", + unit="milliseconds", + value_type=float, + ) + create_metric_calls[1].assert_called_with( + callback=metrics_collector._track_failure_rate, + name="\\ApplicationInsights\\Dependency Calls Failed\/Sec", + description="Failed Outgoing Requests per second", + unit="rps", + value_type=int, + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" + ) + def test_track_failed_dependency_rate(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + dependency_metrics.dependency_map["last_time"] = 98 + self._span_processor.failed_dependency_count = 4 + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 2 + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" + ) + def test_track_dependency_rate_time_none(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + dependency_metrics.dependency_map["last_time"] = None + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" + ) + def test_track_dependency_rate_error(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + dependency_metrics.dependency_map["last_time"] = 100 + dependency_metrics.dependency_map["last_result"] = 5 + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 5 + ) + + def test_track_dependency_duration(self): + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_count = 10 + dependency_metrics.dependency_map["last_count"] = 5 + obs = Observer( + callback=metrics_collector._track_dependency_duration, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_dependency_duration(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 20 + ) + + def test_track_dependency_duration_error(self): + metrics_collector = dependency_metrics.DependencyLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_count = 10 + dependency_metrics.dependency_map["last_count"] = 10 + obs = Observer( + callback=metrics_collector._track_dependency_duration, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_dependency_duration(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py new file mode 100644 index 0000000..dbd3457 --- /dev/null +++ b/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py @@ -0,0 +1,73 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import collections +import unittest +from unittest import mock + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider, Observer + +from azure_monitor.sdk.auto_collection.live_metrics import ( + PerformanceLiveMetrics, +) + + +def throw(exc_type, *args, **kwargs): + def func(*_args, **_kwargs): + raise exc_type(*args, **kwargs) + + return func + + +# pylint: disable=protected-access +class TestPerformanceLiveMetrics(unittest.TestCase): + @classmethod + def setUpClass(cls): + metrics.set_meter_provider(MeterProvider()) + cls._meter = metrics.get_meter(__name__) + cls._test_labels = {"environment": "staging"} + + @classmethod + def tearDownClass(cls): + metrics._METER_PROVIDER = None + + def test_constructor(self): + mock_meter = mock.Mock() + performance_metrics_collector = PerformanceLiveMetrics( + meter=mock_meter, labels=self._test_labels + ) + self.assertEqual(performance_metrics_collector._meter, mock_meter) + self.assertEqual( + performance_metrics_collector._labels, self._test_labels + ) + self.assertEqual(mock_meter.register_observer.call_count, 1) + reg_obs_calls = mock_meter.register_observer.call_args_list + reg_obs_calls[0].assert_called_with( + callback=performance_metrics_collector._track_commited_memory, + name="\Memory\Committed Bytes", + description="Amount of commited memory in bytes", + unit="byte", + value_type=int, + ) + + @mock.patch("psutil.virtual_memory") + def test_track_memory(self, psutil_mock): + performance_metrics_collector = PerformanceLiveMetrics( + meter=self._meter, labels=self._test_labels + ) + memory = collections.namedtuple("memory", ["available", "total"]) + vmem = memory(available=100, total=150) + psutil_mock.return_value = vmem + obs = Observer( + callback=performance_metrics_collector._track_commited_memory, + name="\\Memory\\Available Bytes", + description="Amount of available memory in bytes", + unit="byte", + value_type=int, + meter=self._meter, + ) + performance_metrics_collector._track_commited_memory(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 50 + ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py new file mode 100644 index 0000000..849479a --- /dev/null +++ b/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py @@ -0,0 +1,125 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest +from unittest import mock + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider, Observer + +from azure_monitor.sdk.auto_collection.live_metrics import request_metrics +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) + + +# pylint: disable=protected-access +class TestRequestMetrics(unittest.TestCase): + @classmethod + def setUpClass(cls): + metrics.set_meter_provider(MeterProvider()) + cls._meter = metrics.get_meter(__name__) + cls._test_labels = {"environment": "staging"} + cls._span_processor = AzureMetricsSpanProcessor() + + @classmethod + def tearDown(cls): + metrics._METER_PROVIDER = None + + def setUp(self): + request_metrics.requests_map.clear() + + def test_constructor(self): + mock_meter = mock.Mock() + request_metrics_collector = request_metrics.RequestLiveMetrics( + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + self.assertEqual(request_metrics_collector._meter, mock_meter) + self.assertEqual(request_metrics_collector._labels, self._test_labels) + self.assertEqual(mock_meter.register_observer.call_count, 1) + + create_metric_calls = mock_meter.register_observer.call_args_list + + create_metric_calls[0].assert_called_with( + callback=request_metrics_collector._track_request_failed_rate, + name="\ApplicationInsights\Requests Failed/Sec", + description="Incoming Requests Failed Rate", + unit="rps", + value_type=int, + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" + ) + def test_track_request_rate(self, time_mock): + request_metrics_collector = request_metrics.RequestLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + time_mock.time.return_value = 100 + request_metrics.requests_map["last_time"] = 98 + self._span_processor.failed_request_count = 4 + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 2 + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" + ) + def test_track_request_rate_time_none(self, time_mock): + time_mock.time.return_value = 100 + request_metrics_collector = request_metrics.RequestLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + request_metrics.requests_map["last_time"] = None + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) + + @mock.patch( + "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" + ) + def test_track_request_rate_error(self, time_mock): + request_metrics_collector = request_metrics.RequestLiveMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) + time_mock.time.return_value = 100 + request_metrics.requests_map["last_rate"] = 5 + request_metrics.requests_map["last_time"] = 100 + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 5 + ) diff --git a/azure_monitor/tests/auto_collection/test_auto_collection.py b/azure_monitor/tests/auto_collection/test_auto_collection.py index c965d4b..25c1c81 100644 --- a/azure_monitor/tests/auto_collection/test_auto_collection.py +++ b/azure_monitor/tests/auto_collection/test_auto_collection.py @@ -7,7 +7,10 @@ from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider -from azure_monitor.sdk.auto_collection import AutoCollection +from azure_monitor.sdk.auto_collection import ( + AutoCollection, + AzureMetricsSpanProcessor, +) # pylint: disable=protected-access @@ -17,6 +20,7 @@ def setUpClass(cls): metrics.set_meter_provider(MeterProvider()) cls._meter = metrics.get_meter(__name__) cls._test_labels = tuple({"environment": "staging"}.items()) + cls._span_processor = AzureMetricsSpanProcessor() @classmethod def tearDownClass(cls): @@ -35,7 +39,12 @@ def test_constructor( self, mock_performance, mock_dependencies, mock_requests ): """Test the constructor.""" - AutoCollection(meter=self._meter, labels=self._test_labels) + + AutoCollection( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + ) self.assertEqual(mock_performance.called, True) self.assertEqual(mock_dependencies.called, True) self.assertEqual(mock_requests.called, True) diff --git a/azure_monitor/tests/auto_collection/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/test_dependency_metrics.py index df0a8a1..9bbb645 100644 --- a/azure_monitor/tests/auto_collection/test_dependency_metrics.py +++ b/azure_monitor/tests/auto_collection/test_dependency_metrics.py @@ -2,17 +2,15 @@ # Licensed under the MIT License. import unittest -from http.server import HTTPServer from unittest import mock -import requests from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider, Observer from azure_monitor.sdk.auto_collection import dependency_metrics - -ORIGINAL_FUNCTION = requests.Session.request -ORIGINAL_CONS = HTTPServer.__init__ +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) # pylint: disable=protected-access @@ -22,22 +20,21 @@ def setUpClass(cls): metrics.set_meter_provider(MeterProvider()) cls._meter = metrics.get_meter(__name__) cls._test_labels = {"environment": "staging"} + cls._span_processor = AzureMetricsSpanProcessor() @classmethod def tearDown(cls): metrics._METER_PROVIDER = None - requests.Session.request = ORIGINAL_FUNCTION - dependency_metrics.ORIGINAL_CONSTRUCTOR = ORIGINAL_CONS def setUp(self): dependency_metrics.dependency_map.clear() - requests.Session.request = ORIGINAL_FUNCTION - dependency_metrics.ORIGINAL_CONSTRUCTOR = ORIGINAL_CONS def test_constructor(self): mock_meter = mock.Mock() metrics_collector = dependency_metrics.DependencyMetrics( - meter=mock_meter, labels=self._test_labels + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, ) self.assertEqual(metrics_collector._meter, mock_meter) self.assertEqual(metrics_collector._labels, self._test_labels) @@ -54,7 +51,9 @@ def test_constructor(self): def test_track_dependency_rate(self, time_mock): time_mock.time.return_value = 100 metrics_collector = dependency_metrics.DependencyMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) obs = Observer( callback=metrics_collector._track_dependency_rate, @@ -65,7 +64,7 @@ def test_track_dependency_rate(self, time_mock): meter=self._meter, ) dependency_metrics.dependency_map["last_time"] = 98 - dependency_metrics.dependency_map["count"] = 4 + self._span_processor.dependency_count = 4 metrics_collector._track_dependency_rate(obs) self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 2 @@ -75,7 +74,9 @@ def test_track_dependency_rate(self, time_mock): def test_track_dependency_rate_time_none(self, time_mock): time_mock.time.return_value = 100 metrics_collector = dependency_metrics.DependencyMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) dependency_metrics.dependency_map["last_time"] = None obs = Observer( @@ -95,7 +96,9 @@ def test_track_dependency_rate_time_none(self, time_mock): def test_track_dependency_rate_error(self, time_mock): time_mock.time.return_value = 100 metrics_collector = dependency_metrics.DependencyMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) dependency_metrics.dependency_map["last_time"] = 100 dependency_metrics.dependency_map["last_result"] = 5 @@ -111,23 +114,3 @@ def test_track_dependency_rate_error(self, time_mock): self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 5 ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.dependency_metrics.ORIGINAL_REQUEST" - ) - def test_dependency_patch(self, request_mock): - session = requests.Session() - dependency_metrics.dependency_patch(session) - self.assertEqual(dependency_metrics.dependency_map["count"], 1) - request_mock.assert_called_with(session) - - @mock.patch( - "azure_monitor.sdk.auto_collection.dependency_metrics.ORIGINAL_REQUEST" - ) - @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.context") - def test_dependency_patch_suppress(self, context_mock, request_mock): - context_mock.get_value.return_value = {} - session = requests.Session() - dependency_metrics.dependency_patch(session) - self.assertEqual(dependency_metrics.dependency_map.get("count"), None) - request_mock.assert_called_with(session) diff --git a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py new file mode 100644 index 0000000..a01584c --- /dev/null +++ b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py @@ -0,0 +1,129 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest + +from opentelemetry.sdk.trace import Span +from opentelemetry.trace import SpanContext, SpanKind +from opentelemetry.trace.status import Status, StatusCanonicalCode + +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) +from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( + LiveMetricsExporter, +) + + +# pylint: disable=protected-access +class TestAutoCollection(unittest.TestCase): + def test_constructor(self): + """Test the constructor.""" + instrumentation_key = "TEST_IKEY" + live_metrics_exporter = LiveMetricsExporter(instrumentation_key) + span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + self.assertEqual(span_processor.dependency_count, 0) + self.assertEqual(span_processor.dependency_duration, 0) + self.assertEqual(span_processor.failed_dependency_count, 0) + self.assertEqual(span_processor.request_count, 0) + self.assertEqual(span_processor.request_duration, 0) + self.assertEqual(span_processor.failed_request_count, 0) + self.assertEqual(span_processor._exporter, live_metrics_exporter) + + def test_ok_dependency(self): + """Test the fucntionality when Client Span is ended.""" + instrumentation_key = "TEST_IKEY" + live_metrics_exporter = LiveMetricsExporter(instrumentation_key) + span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + test_span = Span( + name="test", + kind=SpanKind.CLIENT, + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557338, + is_remote=False, + ), + ) + test_span._start_time = 5 + test_span._end_time = 15 + span_processor.on_end(test_span) + self.assertEqual(span_processor.request_count, 0) + self.assertEqual(span_processor.request_duration, 0) + self.assertEqual(span_processor.failed_request_count, 0) + self.assertEqual(span_processor.dependency_count, 1) + self.assertEqual(span_processor.dependency_duration, 10) + self.assertEqual(span_processor.failed_dependency_count, 0) + + def test_failed_dependency(self): + """Test the functionality when Client Span is ended.""" + instrumentation_key = "TEST_IKEY" + live_metrics_exporter = LiveMetricsExporter(instrumentation_key) + span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + test_span = Span( + name="test", + kind=SpanKind.CLIENT, + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557338, + is_remote=False, + ), + ) + test_span.set_status(Status(StatusCanonicalCode.INTERNAL, "test")) + test_span._start_time = 5 + test_span._end_time = 15 + span_processor.on_end(test_span) + self.assertEqual(span_processor.request_count, 0) + self.assertEqual(span_processor.request_duration, 0) + self.assertEqual(span_processor.failed_request_count, 0) + self.assertEqual(span_processor.dependency_count, 1) + self.assertEqual(span_processor.dependency_duration, 10) + self.assertEqual(span_processor.failed_dependency_count, 1) + + def test_ok_request(self): + """Test the fucntionality when Server Span is ended.""" + instrumentation_key = "TEST_IKEY" + live_metrics_exporter = LiveMetricsExporter(instrumentation_key) + span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + test_span = Span( + name="test", + kind=SpanKind.SERVER, + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557338, + is_remote=False, + ), + ) + test_span._start_time = 5 + test_span._end_time = 15 + span_processor.on_end(test_span) + self.assertEqual(span_processor.dependency_count, 0) + self.assertEqual(span_processor.dependency_duration, 0) + self.assertEqual(span_processor.failed_dependency_count, 0) + self.assertEqual(span_processor.request_count, 1) + self.assertEqual(span_processor.request_duration, 10) + self.assertEqual(span_processor.failed_request_count, 0) + + def test_failed_request(self): + """Test the functionality when Server Span is ended.""" + instrumentation_key = "TEST_IKEY" + live_metrics_exporter = LiveMetricsExporter(instrumentation_key) + span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + test_span = Span( + name="test", + kind=SpanKind.SERVER, + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557338, + is_remote=False, + ), + ) + test_span.set_status(Status(StatusCanonicalCode.INTERNAL, "test")) + test_span._start_time = 5 + test_span._end_time = 15 + span_processor.on_end(test_span) + self.assertEqual(span_processor.dependency_count, 0) + self.assertEqual(span_processor.dependency_duration, 0) + self.assertEqual(span_processor.failed_dependency_count, 0) + self.assertEqual(span_processor.request_count, 1) + self.assertEqual(span_processor.request_duration, 10) + self.assertEqual(span_processor.failed_request_count, 1) diff --git a/azure_monitor/tests/auto_collection/test_request_metrics.py b/azure_monitor/tests/auto_collection/test_request_metrics.py index 62b5a88..1ccde36 100644 --- a/azure_monitor/tests/auto_collection/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/test_request_metrics.py @@ -2,17 +2,15 @@ # Licensed under the MIT License. import unittest -from http.server import HTTPServer from unittest import mock -import requests from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider, Observer from azure_monitor.sdk.auto_collection import request_metrics - -ORIGINAL_FUNCTION = requests.Session.request -ORIGINAL_CONS = HTTPServer.__init__ +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) # pylint: disable=protected-access @@ -22,6 +20,7 @@ def setUpClass(cls): metrics.set_meter_provider(MeterProvider()) cls._meter = metrics.get_meter(__name__) cls._test_labels = {"environment": "staging"} + cls._span_processor = AzureMetricsSpanProcessor() @classmethod def tearDown(cls): @@ -29,13 +28,13 @@ def tearDown(cls): def setUp(self): request_metrics.requests_map.clear() - requests.Session.request = ORIGINAL_FUNCTION - request_metrics.ORIGINAL_CONSTRUCTOR = ORIGINAL_CONS def test_constructor(self): mock_meter = mock.Mock() request_metrics_collector = request_metrics.RequestMetrics( - meter=mock_meter, labels=self._test_labels + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, ) self.assertEqual(request_metrics_collector._meter, mock_meter) self.assertEqual(request_metrics_collector._labels, self._test_labels) @@ -62,10 +61,12 @@ def test_constructor(self): def test_track_request_duration(self): request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) - request_metrics.requests_map["duration"] = 0.1 - request_metrics.requests_map["count"] = 10 + self._span_processor.request_duration = 0.1 + self._span_processor.request_count = 10 request_metrics.requests_map["last_count"] = 5 obs = Observer( callback=request_metrics_collector._track_request_duration, @@ -82,10 +83,12 @@ def test_track_request_duration(self): def test_track_request_duration_error(self): request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) - request_metrics.requests_map["duration"] = 0.1 - request_metrics.requests_map["count"] = 10 + self._span_processor.request_duration = 0.1 + self._span_processor.request_count = 10 request_metrics.requests_map["last_count"] = 10 obs = Observer( callback=request_metrics_collector._track_request_duration, @@ -103,11 +106,13 @@ def test_track_request_duration_error(self): @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") def test_track_request_rate(self, time_mock): request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_time"] = 98 - request_metrics.requests_map["count"] = 4 + self._span_processor.request_count = 4 obs = Observer( callback=request_metrics_collector._track_request_rate, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", @@ -125,7 +130,9 @@ def test_track_request_rate(self, time_mock): def test_track_request_rate_time_none(self, time_mock): time_mock.time.return_value = 100 request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) request_metrics.requests_map["last_time"] = None obs = Observer( @@ -144,7 +151,9 @@ def test_track_request_rate_time_none(self, time_mock): @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") def test_track_request_rate_error(self, time_mock): request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_rate"] = 5 @@ -161,64 +170,3 @@ def test_track_request_rate_error(self, time_mock): self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 5 ) - - def test_request_patch(self): - map = request_metrics.requests_map # pylint: disable=redefined-builtin - func = mock.Mock() - new_func = request_metrics.request_patch(func) - new_func() - - self.assertEqual(map["count"], 1) - self.assertIsNotNone(map["duration"]) - self.assertEqual(len(func.call_args_list), 1) - - def test_server_patch(self): - request_metrics.ORIGINAL_CONSTRUCTOR = lambda x, y, z: None - with mock.patch( - "azure_monitor.sdk.auto_collection.request_metrics.request_patch" - ) as request_mock: - handler = mock.Mock() - handler.do_DELETE.return_value = None - handler.do_GET.return_value = None - handler.do_HEAD.return_value = None - handler.do_OPTIONS.return_value = None - handler.do_POST.return_value = None - handler.do_PUT.return_value = None - result = request_metrics.server_patch(None, None, handler) - handler.do_DELETE() - handler.do_GET() - handler.do_HEAD() - handler.do_OPTIONS() - handler.do_POST() - handler.do_PUT() - - self.assertEqual(result, None) - self.assertEqual(len(request_mock.call_args_list), 6) - - def test_server_patch_no_methods(self): - request_metrics.ORIGINAL_CONSTRUCTOR = lambda x, y, z: None - with mock.patch( - "azure_monitor.sdk.auto_collection.request_metrics.request_patch" - ) as request_mock: - handler = mock.Mock() - result = request_metrics.server_patch(None, None, handler) - handler.do_DELETE() - handler.do_GET() - handler.do_HEAD() - handler.do_OPTIONS() - handler.do_POST() - handler.do_PUT() - - self.assertEqual(result, None) - self.assertEqual(len(request_mock.call_args_list), 0) - - def test_server_patch_no_args(self): - request_metrics.ORIGINAL_CONSTRUCTOR = lambda x, y: None - req = request_metrics.server_patch(None, None) - - self.assertEqual(req, None) - - def test_server_patch_no_handler(self): - request_metrics.ORIGINAL_CONSTRUCTOR = lambda x, y, z: None - req = request_metrics.server_patch(None, None, None) - self.assertEqual(req, None) From 5173e559cf646a9dde8e411735b6b5d2336537e9 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Wed, 3 Jun 2020 15:42:29 -0700 Subject: [PATCH 05/39] Fixing lint issues --- azure_monitor/src/azure_monitor/export/__init__.py | 4 ++-- .../src/azure_monitor/export/trace/__init__.py | 2 +- .../src/azure_monitor/sdk/auto_collection/__init__.py | 1 - .../sdk/auto_collection/live_metrics/__init__.py | 7 +++---- .../auto_collection/live_metrics/dependency_metrics.py | 2 +- .../live_metrics/performance_metrics.py | 2 +- .../auto_collection/live_metrics/request_metrics.py | 2 +- .../sdk/auto_collection/metrics_span_processor.py | 2 +- .../live_metrics/test_dependency_metrics.py | 2 +- .../tests/auto_collection/live_metrics/test_manager.py | 2 +- .../live_metrics/test_performance_metrics.py | 2 +- .../live_metrics/test_request_metrics.py | 2 +- .../auto_collection/test_metrics_span_processor.py | 10 +++++----- 13 files changed, 19 insertions(+), 21 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 9b3c91b..8460a5d 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -7,10 +7,10 @@ from urllib.parse import urlparse import requests -from opentelemetry.trace import Span, SpanKind -from opentelemetry.sdk.util import ns_to_iso_str from opentelemetry.sdk.metrics.export import MetricsExportResult from opentelemetry.sdk.trace.export import SpanExportResult +from opentelemetry.sdk.util import ns_to_iso_str +from opentelemetry.trace import Span, SpanKind from opentelemetry.trace.status import StatusCanonicalCode from azure_monitor import protocol, utils diff --git a/azure_monitor/src/azure_monitor/export/trace/__init__.py b/azure_monitor/src/azure_monitor/export/trace/__init__.py index 05f5a5e..1a0e32a 100644 --- a/azure_monitor/src/azure_monitor/export/trace/__init__.py +++ b/azure_monitor/src/azure_monitor/export/trace/__init__.py @@ -13,8 +13,8 @@ from azure_monitor import protocol, utils from azure_monitor.export import ( BaseExporter, - convert_span_to_envelope, ExportResult, + convert_span_to_envelope, get_trace_export_result, ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index ab6ca65..47cfa23 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -16,7 +16,6 @@ ) from azure_monitor.sdk.auto_collection.request_metrics import RequestMetrics - __all__ = [ "AutoCollection", "AzureMetricsSpanProcessor", diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index 43d4e69..54f3519 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -5,9 +5,6 @@ from opentelemetry.metrics import Meter -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) from azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics import ( DependencyLiveMetrics, ) @@ -20,7 +17,9 @@ from azure_monitor.sdk.auto_collection.live_metrics.request_metrics import ( RequestLiveMetrics, ) - +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) __all__ = [ "LiveMetricsAutoCollection", diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py index 430e643..a222758 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py @@ -39,7 +39,7 @@ def __init__( ) meter.register_observer( callback=self._track_failure_rate, - name="\\ApplicationInsights\\Dependency Calls Failed\/Sec", + name="\\ApplicationInsights\\Dependency Calls Failed/Sec", description="Failed Outgoing Requests per second", unit="rps", value_type=int, diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py index 6a33270..7962b51 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py @@ -24,7 +24,7 @@ def __init__(self, meter: Meter, labels: Dict[str, str]): # Create performance metrics meter.register_observer( callback=self._track_commited_memory, - name="\Memory\Committed Bytes", + name="\\Memory\\Committed Bytes", description="Amount of commited memory in bytes", unit="byte", value_type=int, diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py index eb0d715..e8e5b77 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py @@ -36,7 +36,7 @@ def __init__( meter.register_observer( callback=self._track_request_failed_rate, - name="\ApplicationInsights\Requests Failed/Sec", + name="\\ApplicationInsights\\Requests Failed/Sec", description="Incoming Requests Failed Rate", unit="rps", value_type=int, diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py index 64ab547..28324e2 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py @@ -2,8 +2,8 @@ # Licensed under the MIT License. import logging -from opentelemetry.trace import SpanKind from opentelemetry.sdk.trace import Span, SpanProcessor +from opentelemetry.trace import SpanKind from azure_monitor.export import convert_span_to_envelope diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py index b51148b..1bbeb98 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py @@ -50,7 +50,7 @@ def test_constructor(self): ) create_metric_calls[1].assert_called_with( callback=metrics_collector._track_failure_rate, - name="\\ApplicationInsights\\Dependency Calls Failed\/Sec", + name="\\ApplicationInsights\\Dependency Calls Failed/Sec", description="Failed Outgoing Requests per second", unit="rps", value_type=int, diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py index 4c4cab7..59c5d4a 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py @@ -115,7 +115,7 @@ def test_ping_error(self): self._ping = LiveMetricsPing( instrumentation_key=self._instrumentation_key ) - self._ping.last_request_success_time = time.time() - 21 + self._ping.last_request_success_time = time.time() - 60 self._ping.ping() self.assertFalse(self._ping.last_send_succeeded) self.assertEqual(self._ping.interval, 60) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py index dbd3457..bfb6492 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py @@ -45,7 +45,7 @@ def test_constructor(self): reg_obs_calls = mock_meter.register_observer.call_args_list reg_obs_calls[0].assert_called_with( callback=performance_metrics_collector._track_commited_memory, - name="\Memory\Committed Bytes", + name="\\Memory\\Committed Bytes", description="Amount of commited memory in bytes", unit="byte", value_type=int, diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py index 849479a..5d25d28 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py @@ -44,7 +44,7 @@ def test_constructor(self): create_metric_calls[0].assert_called_with( callback=request_metrics_collector._track_request_failed_rate, - name="\ApplicationInsights\Requests Failed/Sec", + name="\\ApplicationInsights\\Requests Failed/Sec", description="Incoming Requests Failed Rate", unit="rps", value_type=int, diff --git a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py index a01584c..9918e92 100644 --- a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py +++ b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py @@ -7,12 +7,12 @@ from opentelemetry.trace import SpanContext, SpanKind from opentelemetry.trace.status import Status, StatusCanonicalCode -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) # pylint: disable=protected-access @@ -31,7 +31,7 @@ def test_constructor(self): self.assertEqual(span_processor._exporter, live_metrics_exporter) def test_ok_dependency(self): - """Test the fucntionality when Client Span is ended.""" + """Test the functionality when Client Span is ended.""" instrumentation_key = "TEST_IKEY" live_metrics_exporter = LiveMetricsExporter(instrumentation_key) span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) @@ -80,7 +80,7 @@ def test_failed_dependency(self): self.assertEqual(span_processor.failed_dependency_count, 1) def test_ok_request(self): - """Test the fucntionality when Server Span is ended.""" + """Test the functionality when Server Span is ended.""" instrumentation_key = "TEST_IKEY" live_metrics_exporter = LiveMetricsExporter(instrumentation_key) span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) From db4914c19815184027235c434aa4b2ed2dc53f6b Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Thu, 4 Jun 2020 15:54:07 -0700 Subject: [PATCH 06/39] Using single classes for live/standard metrics to avoid duplication SpanProcessor used in LiveMetricExporter instead of other way around Updating example --- .../examples/metrics/auto_collector.py | 17 +- .../sdk/auto_collection/__init__.py | 11 +- .../sdk/auto_collection/dependency_metrics.py | 96 +++++++++- .../auto_collection/live_metrics/__init__.py | 39 ++-- .../live_metrics/dependency_metrics.py | 109 ----------- .../auto_collection/live_metrics/exporter.py | 19 +- .../auto_collection/live_metrics/manager.py | 16 +- .../live_metrics/performance_metrics.py | 41 ---- .../live_metrics/request_metrics.py | 76 -------- .../auto_collection/metrics_span_processor.py | 19 +- .../auto_collection/performance_metrics.py | 97 ++++++---- .../sdk/auto_collection/request_metrics.py | 100 ++++++---- .../sdk/auto_collection/utils.py | 11 ++ .../live_metrics/test_dependency_metrics.py | 175 ------------------ .../live_metrics/test_exporter.py | 33 ++-- .../live_metrics/test_manager.py | 21 ++- .../live_metrics/test_performance_metrics.py | 73 -------- .../live_metrics/test_request_metrics.py | 125 ------------- .../test_dependency_metrics.py | 171 ++++++++++++++++- .../test_metrics_span_processor.py | 24 +-- .../test_performance_metrics.py | 75 +++++++- .../auto_collection/test_request_metrics.py | 116 +++++++++++- 22 files changed, 688 insertions(+), 776 deletions(-) delete mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py delete mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py delete mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py create mode 100644 azure_monitor/src/azure_monitor/sdk/auto_collection/utils.py delete mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py delete mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py delete mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py diff --git a/azure_monitor/examples/metrics/auto_collector.py b/azure_monitor/examples/metrics/auto_collector.py index 75b1230..b15c41b 100644 --- a/azure_monitor/examples/metrics/auto_collector.py +++ b/azure_monitor/examples/metrics/auto_collector.py @@ -1,11 +1,22 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. from opentelemetry import metrics +from opentelemetry import trace from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export.controller import PushController +from opentelemetry.sdk.trace import TracerProvider from azure_monitor import AzureMonitorMetricsExporter -from azure_monitor.sdk.auto_collection import AutoCollection +from azure_monitor.sdk.auto_collection import ( + AutoCollection, + AzureMetricsSpanProcessor, +) + +# Add Span Processor to get metrics about traces +span_processor = AzureMetricsSpanProcessor() +tracer_provider = TracerProvider() +tracer_provider.add_span_processor(span_processor) +trace.set_tracer_provider(tracer_provider) metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) @@ -17,7 +28,9 @@ testing_label_set = {"environment": "testing"} # Automatically collect standard metrics -auto_collection = AutoCollection(meter=meter, labels=testing_label_set) +auto_collection = AutoCollection( + meter=meter, labels=testing_label_set, span_processor=span_processor +) # To configure a separate export interval specific for standard metrics # meter_standard = metrics.get_meter(__name__ + "_standard") diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 47cfa23..7449a58 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -15,9 +15,11 @@ PerformanceMetrics, ) from azure_monitor.sdk.auto_collection.request_metrics import RequestMetrics +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType __all__ = [ "AutoCollection", + "AutoCollectionType", "AzureMetricsSpanProcessor", "DependencyMetrics", "RequestMetrics", @@ -40,8 +42,11 @@ def __init__( labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, ): - self._performance_metrics = PerformanceMetrics(meter, labels) + col_type = AutoCollectionType.STANDARD_METRICS + self._performance_metrics = PerformanceMetrics(meter, labels, col_type) self._dependency_metrics = DependencyMetrics( - meter, labels, span_processor + meter, labels, span_processor, col_type + ) + self._request_metrics = RequestMetrics( + meter, labels, span_processor, col_type ) - self._request_metrics = RequestMetrics(meter, labels, span_processor) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py index a9ab637..150bd7e 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py @@ -3,22 +3,25 @@ import time from typing import Dict -from opentelemetry.metrics import Meter +from opentelemetry.metrics import Meter, Observer from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType dependency_map = dict() class DependencyMetrics: - """Starts auto collection of dependency metrics, including - "Outgoing Requests per second" metric. + """Auto collection of dependency metrics, including + "Outgoing Requests per second", "Outgoing Request rate" and "Failed Outgoing Request rate" metrics. Args: meter: OpenTelemetry Meter labels: Dictionary of labels + span_processor: Azure Metrics Span Processor + collection_type: Standard or Live Metrics """ def __init__( @@ -26,20 +29,36 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, + collection_type: AutoCollectionType, ): self._meter = meter self._labels = labels self._span_processor = span_processor meter.register_observer( - callback=self._track_dependency_rate, - name="\\ApplicationInsights\\Dependency Calls/Sec", - description="Outgoing Requests per second", + callback=self._track_dependency_duration, + name="\\ApplicationInsights\\Dependency Call Duration", + description="Average Outgoing Requests duration", + unit="milliseconds", + value_type=float, + ) + meter.register_observer( + callback=self._track_failure_rate, + name="\\ApplicationInsights\\Dependency Calls Failed/Sec", + description="Failed Outgoing Requests per second", unit="rps", value_type=int, ) + if collection_type == AutoCollectionType.STANDARD_METRICS: + meter.register_observer( + callback=self._track_dependency_rate, + name="\\ApplicationInsights\\Dependency Calls/Sec", + description="Outgoing Requests per second", + unit="rps", + value_type=int, + ) - def _track_dependency_rate(self, observer) -> None: + def _track_dependency_rate(self, observer: Observer) -> None: """ Track Dependency rate Calculated by obtaining the number of outgoing requests made @@ -68,3 +87,66 @@ def _track_dependency_rate(self, observer) -> None: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case observer.observe(int(last_result), self._labels) + + def _track_dependency_duration(self, observer: Observer) -> None: + """ Track Dependency average duration + + Calculated by getting the time it takes to make an outgoing request + and dividing over the amount of outgoing requests over an elapsed time. + """ + last_average_duration = dependency_map.get("last_average_duration", 0) + interval_duration = ( + self._span_processor.dependency_duration + - dependency_map.get("last_duration", 0) + ) + interval_count = ( + self._span_processor.dependency_count + - dependency_map.get("last_count", 0) + ) + try: + result = interval_duration / interval_count + dependency_map[ + "last_count" + ] = self._span_processor.dependency_count + dependency_map["last_average_duration"] = result + dependency_map[ + "last_duration" + ] = self._span_processor.dependency_duration + # Convert to milliseconds + observer.observe(int(result * 1000.0), self._labels) + except ZeroDivisionError: + # If interval_count is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_average_duration * 1000.0), self._labels) + + def _track_failure_rate(self, observer: Observer) -> None: + """ Track Failed Dependency rate + + Calculated by obtaining the number of failed outgoing requests made + using the requests library within an elapsed time and dividing + that value over the elapsed time. + """ + current_failed_count = self._span_processor.failed_dependency_count + current_time = time.time() + last_failed_count = dependency_map.get("last_failed_count", 0) + last_time = dependency_map.get("last_time") + last_result = dependency_map.get("last_result", 0) + + try: + # last_time is None the very first time this function is called + if last_time is not None: + elapsed_seconds = current_time - last_time + interval_failed_count = ( + current_failed_count - last_failed_count + ) + result = interval_failed_count / elapsed_seconds + else: + result = 0 + dependency_map["last_time"] = current_time + dependency_map["last_failed_count"] = current_failed_count + dependency_map["last_result"] = result + observer.observe(int(result), self._labels) + except ZeroDivisionError: + # If elapsed_seconds is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_result), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index 54f3519..c907898 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -5,29 +5,25 @@ from opentelemetry.metrics import Meter -from azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics import ( - DependencyLiveMetrics, +from azure_monitor.sdk.auto_collection import AutoCollectionType +from azure_monitor.sdk.auto_collection.dependency_metrics import ( + DependencyMetrics, ) from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) -from azure_monitor.sdk.auto_collection.live_metrics.performance_metrics import ( - PerformanceLiveMetrics, -) -from azure_monitor.sdk.auto_collection.live_metrics.request_metrics import ( - RequestLiveMetrics, +from azure_monitor.sdk.auto_collection.live_metrics.manager import ( + LiveMetricsManager, ) from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.performance_metrics import ( + PerformanceMetrics, +) +from azure_monitor.sdk.auto_collection.request_metrics import RequestMetrics -__all__ = [ - "LiveMetricsAutoCollection", - "LiveMetricsExporter", - "DependencyLiveMetrics", - "PerformanceLiveMetrics", - "RequestLiveMetrics", -] +__all__ = ["LiveMetricsAutoCollection"] class LiveMetricsAutoCollection: @@ -44,11 +40,16 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, + instrumentation_key: str, ): - self._performance_metrics = PerformanceLiveMetrics(meter, labels) - self._dependency_metrics = DependencyLiveMetrics( - meter, labels, span_processor + col_type = AutoCollectionType.LIVE_METRICS + self._performance_metrics = PerformanceMetrics(meter, labels, col_type) + self._dependency_metrics = DependencyMetrics( + meter, labels, span_processor, col_type + ) + self._request_metrics = RequestMetrics( + meter, labels, span_processor, col_type ) - self._request_metrics = RequestLiveMetrics( - meter, labels, span_processor + self._manager = LiveMetricsManager( + meter, instrumentation_key, span_processor ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py deleted file mode 100644 index a222758..0000000 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/dependency_metrics.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import time -from typing import Dict - -from opentelemetry.metrics import Meter - -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) - -dependency_map = dict() - - -class DependencyLiveMetrics: - """Starts auto collection of dependency live metrics. - - Args: - meter: OpenTelemetry Meter - labels: Dictionary of labels - """ - - def __init__( - self, - meter: Meter, - labels: Dict[str, str], - span_processor: AzureMetricsSpanProcessor, - ): - self._meter = meter - self._labels = labels - self._span_processor = span_processor - - meter.register_observer( - callback=self._track_dependency_duration, - name="\\ApplicationInsights\\Dependency Call Duration", - description="Average Outgoing Requests duration", - unit="milliseconds", - value_type=float, - ) - meter.register_observer( - callback=self._track_failure_rate, - name="\\ApplicationInsights\\Dependency Calls Failed/Sec", - description="Failed Outgoing Requests per second", - unit="rps", - value_type=int, - ) - - def _track_dependency_duration(self, observer) -> None: - """ Track Dependency average duration - - Calculated by getting the time it takes to make an outgoing request - and dividing over the amount of outgoing requests over an elapsed time. - """ - last_average_duration = dependency_map.get("last_average_duration", 0) - interval_duration = ( - self._span_processor.dependency_duration - - dependency_map.get("last_duration", 0) - ) - interval_count = ( - self._span_processor.dependency_count - - dependency_map.get("last_count", 0) - ) - try: - result = interval_duration / interval_count - dependency_map[ - "last_count" - ] = self._span_processor.dependency_count - dependency_map["last_average_duration"] = result - dependency_map[ - "last_duration" - ] = self._span_processor.dependency_duration - # Convert to milliseconds - observer.observe(int(result * 1000.0), self._labels) - except ZeroDivisionError: - # If interval_count is 0, exporter call made too close to previous - # Return the previous result if this is the case - observer.observe(int(last_average_duration * 1000.0), self._labels) - - def _track_failure_rate(self, observer) -> None: - """ Track Failed Dependency rate - - Calculated by obtaining the number of failed outgoing requests made - using the requests library within an elapsed time and dividing - that value over the elapsed time. - """ - current_failed_count = self._span_processor.failed_dependency_count - current_time = time.time() - last_failed_count = dependency_map.get("last_failed_count", 0) - last_time = dependency_map.get("last_time") - last_result = dependency_map.get("last_result", 0) - - try: - # last_time is None the very first time this function is called - if last_time is not None: - elapsed_seconds = current_time - last_time - interval_failed_count = ( - current_failed_count - last_failed_count - ) - result = interval_failed_count / elapsed_seconds - else: - result = 0 - dependency_map["last_time"] = current_time - dependency_map["last_failed_count"] = current_failed_count - dependency_map["last_result"] = result - observer.observe(int(result), self._labels) - except ZeroDivisionError: - # If elapsed_seconds is 0, exporter call made too close to previous - # Return the previous result if this is the case - observer.observe(int(last_result), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py index a8cc64d..c53a6bf 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py @@ -1,7 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # -import collections import logging import typing @@ -23,6 +22,9 @@ from azure_monitor.sdk.auto_collection.live_metrics.sender import ( LiveMetricsSender, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) logger = logging.getLogger(__name__) @@ -36,14 +38,15 @@ class LiveMetricsExporter(MetricsExporter): Export data to Azure Live Metrics service and determine if user is subscribed. """ - def __init__(self, instrumentation_key): + def __init__( + self, + instrumentation_key: str, + span_processor: AzureMetricsSpanProcessor, + ): self._instrumentation_key = instrumentation_key + self._span_processor = span_processor self._sender = LiveMetricsSender(self._instrumentation_key) self.subscribed = True - self._document_envelopes = collections.deque() - - def add_document(self, envelope: Envelope): - self._document_envelopes.append(envelope) def export( self, metric_records: typing.Sequence[MetricRecord] @@ -91,8 +94,8 @@ def _get_live_metric_documents( self, ) -> typing.Sequence[LiveMetricDocument]: live_metric_documents = [] - while self._document_envelopes: - for envelope in self._document_envelopes.popleft(): + while self._span_processor.documents: + for envelope in self._span_processor.documents.popleft(): base_type = envelope.data.baseType if base_type: document = LiveMetricDocument( diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py index 0e9c407..7616dac 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py @@ -5,6 +5,7 @@ import time from opentelemetry.context import attach, detach, set_value +from opentelemetry.sdk.metrics import Meter from opentelemetry.sdk.metrics.export import MetricsExportResult from azure_monitor.sdk.auto_collection.live_metrics import utils @@ -14,6 +15,9 @@ from azure_monitor.sdk.auto_collection.live_metrics.sender import ( LiveMetricsSender, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) # Interval for failures threshold reached in seconds FALLBACK_INTERVAL = 60.0 @@ -34,14 +38,22 @@ class LiveMetricsManager(threading.Thread): daemon = True - def __init__(self, meter, instrumentation_key): + def __init__( + self, + meter: Meter, + instrumentation_key: str, + span_processor: AzureMetricsSpanProcessor, + ): super().__init__() self.thread_event = threading.Event() self.interval = MAIN_INTERVAL self._instrumentation_key = instrumentation_key self._is_user_subscribed = False self._meter = meter - self._exporter = LiveMetricsExporter(self._instrumentation_key) + self._span_processor = span_processor + self._exporter = LiveMetricsExporter( + self._instrumentation_key, self._span_processor + ) self._post = None self._ping = LiveMetricsPing(self._instrumentation_key) self.start() diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py deleted file mode 100644 index 7962b51..0000000 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/performance_metrics.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import logging -from typing import Dict - -import psutil -from opentelemetry.metrics import Meter - -logger = logging.getLogger(__name__) -PROCESS = psutil.Process() - - -class PerformanceLiveMetrics: - """Starts auto collection of performance live metrics. - - Args: - meter: OpenTelemetry Meter - labels: Dictionary of labels - """ - - def __init__(self, meter: Meter, labels: Dict[str, str]): - self._meter = meter - self._labels = labels - # Create performance metrics - meter.register_observer( - callback=self._track_commited_memory, - name="\\Memory\\Committed Bytes", - description="Amount of commited memory in bytes", - unit="byte", - value_type=int, - ) - - def _track_commited_memory(self, observer) -> None: - """ Track Commited Memory - - Available commited memory is defined as total memory minus available memory. - """ - observer.observe( - psutil.virtual_memory().total - psutil.virtual_memory().available, - self._labels, - ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py deleted file mode 100644 index e8e5b77..0000000 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/request_metrics.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import logging -import time -from typing import Dict - -from opentelemetry.metrics import Meter - -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) - -logger = logging.getLogger(__name__) - -requests_map = dict() - - -class RequestLiveMetrics: - """Starts auto collection of request live metrics, including - "Failed Incoming Requests Rate" metric. - - Args: - meter: OpenTelemetry Meter - labels: Dictionary of labels - """ - - def __init__( - self, - meter: Meter, - labels: Dict[str, str], - span_processor: AzureMetricsSpanProcessor, - ): - self._meter = meter - self._labels = labels - self._span_processor = span_processor - - meter.register_observer( - callback=self._track_request_failed_rate, - name="\\ApplicationInsights\\Requests Failed/Sec", - description="Incoming Requests Failed Rate", - unit="rps", - value_type=int, - ) - - def _track_request_failed_rate(self, observer) -> None: - """ Track Request failed execution rate - - Calculated by obtaining by getting the number of failed incoming requests - made to an HTTPServer within an elapsed time and dividing that value - over the elapsed time. - """ - current_time = time.time() - last_rate = requests_map.get("last_rate", 0) - last_time = requests_map.get("last_time") - - try: - # last_rate_time is None the first time this function is called - if last_time is not None: - interval_time = current_time - requests_map.get("last_time", 0) - interval_count = ( - self._span_processor.failed_request_count - - requests_map.get("last_failed_count", 0) - ) - result = interval_count / interval_time - else: - result = 0 - requests_map["last_time"] = current_time - requests_map[ - "last_failed_count" - ] = self._span_processor.failed_request_count - requests_map["last_rate"] = result - observer.observe(int(result), self._labels) - except ZeroDivisionError: - # If elapsed_seconds is 0, exporter call made too close to previous - # Return the previous result if this is the case - observer.observe(int(last_rate), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py index 28324e2..eeb57bf 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import collections import logging from opentelemetry.sdk.trace import Span, SpanProcessor @@ -16,8 +17,9 @@ class AzureMetricsSpanProcessor(SpanProcessor): and failed dependencies/requests. """ - def __init__(self, exporter=None): - self._exporter = exporter + def __init__(self, collect_documents: bool = False): + self._collect_documents = collect_documents + self.documents = collections.deque() self.request_count = 0 self.dependency_count = 0 self.failed_request_count = 0 @@ -37,10 +39,9 @@ def on_end(self, span: Span) -> None: ) if not span.status.is_ok: self.failed_request_count = self.failed_request_count + 1 - if self._exporter is not None: - self._exporter.add_document( - convert_span_to_envelope(span) - ) + if self._collect_documents: + self.documents.append(convert_span_to_envelope(span)) + elif span.kind == SpanKind.CLIENT: self.dependency_count = self.dependency_count + 1 self.dependency_duration = self.dependency_duration + ( @@ -50,10 +51,8 @@ def on_end(self, span: Span) -> None: self.failed_dependency_count = ( self.failed_dependency_count + 1 ) - if self._exporter is not None: - self._exporter.add_document( - convert_span_to_envelope(span) - ) + if self._collect_documents: + self.documents.append(convert_span_to_envelope(span)) # pylint: disable=broad-except except Exception: diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py index 9e1927a..34e022e 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py @@ -4,7 +4,9 @@ from typing import Dict import psutil -from opentelemetry.metrics import Meter +from opentelemetry.metrics import Meter, Observer + +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType logger = logging.getLogger(__name__) PROCESS = psutil.Process() @@ -19,42 +21,57 @@ class PerformanceMetrics: Args: meter: OpenTelemetry Meter labels: Dictionary of labels + collection_type: Standard or Live Metrics """ - def __init__(self, meter: Meter, labels: Dict[str, str]): + def __init__( + self, + meter: Meter, + labels: Dict[str, str], + collection_type: AutoCollectionType, + ): self._meter = meter self._labels = labels - # Create performance metrics - meter.register_observer( - callback=self._track_cpu, - name="\\Processor(_Total)\\% Processor Time", - description="Processor time as a percentage", - unit="percentage", - value_type=float, - ) - meter.register_observer( - callback=self._track_memory, - name="\\Memory\\Available Bytes", - description="Amount of available memory in bytes", - unit="byte", - value_type=int, - ) - meter.register_observer( - callback=self._track_process_cpu, - name="\\Process(??APP_WIN32_PROC??)\\% Processor Time", - description="Process CPU usage as a percentage", - unit="percentage", - value_type=float, - ) - meter.register_observer( - callback=self._track_process_memory, - name="\\Process(??APP_WIN32_PROC??)\\Private Bytes", - description="Amount of memory process has used in bytes", - unit="byte", - value_type=int, - ) - def _track_cpu(self, observer) -> None: + if collection_type == AutoCollectionType.STANDARD_METRICS: + self._meter.register_observer( + callback=self._track_cpu, + name="\\Processor(_Total)\\% Processor Time", + description="Processor time as a percentage", + unit="percentage", + value_type=float, + ) + self._meter.register_observer( + callback=self._track_memory, + name="\\Memory\\Available Bytes", + description="Amount of available memory in bytes", + unit="byte", + value_type=int, + ) + self._meter.register_observer( + callback=self._track_process_cpu, + name="\\Process(??APP_WIN32_PROC??)\\% Processor Time", + description="Process CPU usage as a percentage", + unit="percentage", + value_type=float, + ) + self._meter.register_observer( + callback=self._track_process_memory, + name="\\Process(??APP_WIN32_PROC??)\\Private Bytes", + description="Amount of memory process has used in bytes", + unit="byte", + value_type=int, + ) + if collection_type == AutoCollectionType.LIVE_METRICS: + self._meter.register_observer( + callback=self._track_commited_memory, + name="\\Memory\\Committed Bytes", + description="Amount of commited memory in bytes", + unit="byte", + value_type=int, + ) + + def _track_cpu(self, observer: Observer) -> None: """ Track CPU time Processor time is defined as a float representing the current system @@ -65,7 +82,7 @@ def _track_cpu(self, observer) -> None: cpu_times_percent = psutil.cpu_times_percent() observer.observe(100.0 - cpu_times_percent.idle, self._labels) - def _track_memory(self, observer) -> None: + def _track_memory(self, observer: Observer) -> None: """ Track Memory Available memory is defined as memory that can be given instantly to @@ -73,7 +90,7 @@ def _track_memory(self, observer) -> None: """ observer.observe(psutil.virtual_memory().available, self._labels) - def _track_process_cpu(self, observer) -> None: + def _track_process_cpu(self, observer: Observer) -> None: """ Track Process CPU time Returns a derived gauge for the CPU usage for the current process. @@ -88,7 +105,7 @@ def _track_process_cpu(self, observer) -> None: except Exception: # pylint: disable=broad-except logger.exception("Error handling get process cpu usage.") - def _track_process_memory(self, observer) -> None: + def _track_process_memory(self, observer: Observer) -> None: """ Track Memory Available memory is defined as memory that can be given instantly to @@ -98,3 +115,13 @@ def _track_process_memory(self, observer) -> None: observer.observe(PROCESS.memory_info().rss, self._labels) except Exception: # pylint: disable=broad-except logger.exception("Error handling get process private bytes.") + + def _track_commited_memory(self, observer: Observer) -> None: + """ Track Commited Memory + + Available commited memory is defined as total memory minus available memory. + """ + observer.observe( + psutil.virtual_memory().total - psutil.virtual_memory().available, + self._labels, + ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index 69cf9b5..b8fe18b 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -1,49 +1,30 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import logging -import threading import time -from http.server import HTTPServer from typing import Dict -from opentelemetry.metrics import Meter +from opentelemetry.metrics import Meter, Observer from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType logger = logging.getLogger(__name__) - -_requests_lock = threading.Lock() requests_map = dict() -ORIGINAL_CONSTRUCTOR = HTTPServer.__init__ - - -def request_patch(func): - def wrapper(self=None): - start_time = time.time() - func(self) - end_time = time.time() - - with _requests_lock: - # Update Count - count = requests_map.get("count", 0) - requests_map["count"] = count + 1 - # Update duration - duration = requests_map.get("duration", 0) - requests_map["duration"] = duration + (end_time - start_time) - - return wrapper class RequestMetrics: """Starts auto collection of request metrics, including - "Incoming Requests Average Execution Time" and - "Incoming Requests Average Execution Rate" metrics. + "Incoming Requests Average Execution Time", + "Incoming Requests Rate" and "Failed Incoming Requests Rate" metrics. Args: meter: OpenTelemetry Meter labels: Dictionary of labels + span_processor: Azure Metrics Span Processor + collection_type: Standard or Live Metrics """ def __init__( @@ -51,28 +32,36 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, + collection_type: AutoCollectionType, ): self._meter = meter self._labels = labels self._span_processor = span_processor meter.register_observer( - callback=self._track_request_duration, - name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", - description="Incoming Requests Average Execution Time", - unit="milliseconds", - value_type=int, - ) - - meter.register_observer( - callback=self._track_request_rate, - name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", - description="Incoming Requests Average Execution Rate", + callback=self._track_request_failed_rate, + name="\\ApplicationInsights\\Requests Failed/Sec", + description="Incoming Requests Failed Rate", unit="rps", value_type=int, ) - - def _track_request_duration(self, observer) -> None: + if collection_type == AutoCollectionType.STANDARD_METRICS: + meter.register_observer( + callback=self._track_request_duration, + name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", + description="Incoming Requests Average Execution Time", + unit="milliseconds", + value_type=int, + ) + meter.register_observer( + callback=self._track_request_rate, + name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", + description="Incoming Requests Rate", + unit="rps", + value_type=int, + ) + + def _track_request_duration(self, observer: Observer) -> None: """ Track Request execution time Calculated by getting the time it takes to make an incoming request @@ -100,7 +89,7 @@ def _track_request_duration(self, observer) -> None: # Return the previous result if this is the case observer.observe(int(last_average_duration * 1000.0), self._labels) - def _track_request_rate(self, observer) -> None: + def _track_request_rate(self, observer: Observer) -> None: """ Track Request execution rate Calculated by obtaining by getting the number of incoming requests @@ -130,3 +119,36 @@ def _track_request_rate(self, observer) -> None: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case observer.observe(int(last_rate), self._labels) + + def _track_request_failed_rate(self, observer: Observer) -> None: + """ Track Request failed execution rate + + Calculated by obtaining by getting the number of failed incoming requests + made to an HTTPServer within an elapsed time and dividing that value + over the elapsed time. + """ + current_time = time.time() + last_rate = requests_map.get("last_rate", 0) + last_time = requests_map.get("last_time") + + try: + # last_rate_time is None the first time this function is called + if last_time is not None: + interval_time = current_time - requests_map.get("last_time", 0) + interval_count = ( + self._span_processor.failed_request_count + - requests_map.get("last_failed_count", 0) + ) + result = interval_count / interval_time + else: + result = 0 + requests_map["last_time"] = current_time + requests_map[ + "last_failed_count" + ] = self._span_processor.failed_request_count + requests_map["last_rate"] = result + observer.observe(int(result), self._labels) + except ZeroDivisionError: + # If elapsed_seconds is 0, exporter call made too close to previous + # Return the previous result if this is the case + observer.observe(int(last_rate), self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/utils.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/utils.py new file mode 100644 index 0000000..acbc811 --- /dev/null +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/utils.py @@ -0,0 +1,11 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from enum import Enum + + +class AutoCollectionType(Enum): + """Automatic collection of metrics type + """ + + STANDARD_METRICS = 0 + LIVE_METRICS = 1 diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py deleted file mode 100644 index 1bbeb98..0000000 --- a/azure_monitor/tests/auto_collection/live_metrics/test_dependency_metrics.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import unittest -from unittest import mock - -from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider, Observer - -from azure_monitor.sdk.auto_collection.live_metrics import dependency_metrics -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) - - -# pylint: disable=protected-access -class TestDependencyLiveMetrics(unittest.TestCase): - @classmethod - def setUpClass(cls): - metrics.set_meter_provider(MeterProvider()) - cls._meter = metrics.get_meter(__name__) - cls._test_labels = {"environment": "staging"} - cls._span_processor = AzureMetricsSpanProcessor() - - @classmethod - def tearDown(cls): - metrics._METER_PROVIDER = None - - def setUp(self): - dependency_metrics.dependency_map.clear() - - def test_constructor(self): - mock_meter = mock.Mock() - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=mock_meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - self.assertEqual(metrics_collector._meter, mock_meter) - self.assertEqual(metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 2) - create_metric_calls = mock_meter.register_observer.call_args_list - - create_metric_calls[0].assert_called_with( - callback=metrics_collector._track_dependency_duration, - name="\\ApplicationInsights\\Dependency Call Duration", - description="Average Outgoing Requests duration", - unit="milliseconds", - value_type=float, - ) - create_metric_calls[1].assert_called_with( - callback=metrics_collector._track_failure_rate, - name="\\ApplicationInsights\\Dependency Calls Failed/Sec", - description="Failed Outgoing Requests per second", - unit="rps", - value_type=int, - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" - ) - def test_track_failed_dependency_rate(self, time_mock): - time_mock.time.return_value = 100 - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - obs = Observer( - callback=metrics_collector._track_failure_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - dependency_metrics.dependency_map["last_time"] = 98 - self._span_processor.failed_dependency_count = 4 - metrics_collector._track_failure_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2 - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" - ) - def test_track_dependency_rate_time_none(self, time_mock): - time_mock.time.return_value = 100 - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - dependency_metrics.dependency_map["last_time"] = None - obs = Observer( - callback=metrics_collector._track_failure_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - metrics_collector._track_failure_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.dependency_metrics.time" - ) - def test_track_dependency_rate_error(self, time_mock): - time_mock.time.return_value = 100 - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - dependency_metrics.dependency_map["last_time"] = 100 - dependency_metrics.dependency_map["last_result"] = 5 - obs = Observer( - callback=metrics_collector._track_failure_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - metrics_collector._track_failure_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 - ) - - def test_track_dependency_duration(self): - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - self._span_processor.dependency_duration = 0.1 - self._span_processor.dependency_count = 10 - dependency_metrics.dependency_map["last_count"] = 5 - obs = Observer( - callback=metrics_collector._track_dependency_duration, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - metrics_collector._track_dependency_duration(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 20 - ) - - def test_track_dependency_duration_error(self): - metrics_collector = dependency_metrics.DependencyLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - self._span_processor.dependency_duration = 0.1 - self._span_processor.dependency_count = 10 - dependency_metrics.dependency_map["last_count"] = 10 - obs = Observer( - callback=metrics_collector._track_dependency_duration, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - metrics_collector._track_dependency_duration(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 - ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index f37e8ec..4dd011b 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -14,6 +14,9 @@ from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) def throw(exc_type, *args, **kwargs): @@ -34,25 +37,28 @@ def setUpClass(cls): "testname", "testdesc", "unit", int, Counter, ["environment"] ) cls._test_labels = tuple({"environment": "staging"}.items()) + cls._span_processor = AzureMetricsSpanProcessor() def test_constructor(self): """Test the constructor.""" exporter = LiveMetricsExporter( - instrumentation_key=self._instrumentation_key + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) self.assertEqual(exporter.subscribed, True) self.assertEqual( exporter._instrumentation_key, self._instrumentation_key ) - def test_add_document(self): - """Test adding a document.""" - exporter = LiveMetricsExporter( - instrumentation_key=self._instrumentation_key - ) - envelope = Envelope() - exporter.add_document(envelope) - self.assertEqual(exporter._document_envelopes.pop(), envelope) + # def test_add_document(self): + # """Test adding a document.""" + # exporter = LiveMetricsExporter( + # instrumentation_key=self._instrumentation_key, + # span_processor=self._span_processor, + # ) + # envelope = Envelope() + # exporter.add_document(envelope) + # self.assertEqual(exporter._document_envelopes.pop(), envelope) def test_export(self): """Test export.""" @@ -60,7 +66,8 @@ def test_export(self): CounterAggregator(), self._test_labels, self._test_metric ) exporter = LiveMetricsExporter( - instrumentation_key=self._instrumentation_key + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) with mock.patch( "azure_monitor.sdk.auto_collection.live_metrics.sender.LiveMetricsSender.post" @@ -76,7 +83,8 @@ def test_export_failed(self): CounterAggregator(), self._test_labels, self._test_metric ) exporter = LiveMetricsExporter( - instrumentation_key=self._instrumentation_key + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) with mock.patch( "azure_monitor.sdk.auto_collection.live_metrics.sender.LiveMetricsSender.post" @@ -92,7 +100,8 @@ def test_export_exception(self): CounterAggregator(), self._test_labels, self._test_metric ) exporter = LiveMetricsExporter( - instrumentation_key=self._instrumentation_key + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) with mock.patch( "azure_monitor.sdk.auto_collection.live_metrics.sender.LiveMetricsSender.post", diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py index 59c5d4a..695929f 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py @@ -16,6 +16,9 @@ LiveMetricsPing, LiveMetricsPost, ) +from azure_monitor.sdk.auto_collection.metrics_span_processor import ( + AzureMetricsSpanProcessor, +) # pylint: disable=protected-access @@ -33,6 +36,7 @@ def setUpClass(cls): cls._manager = None cls._ping = None cls._post = None + cls._span_processor = AzureMetricsSpanProcessor() @classmethod def tearDownClass(cls): @@ -55,6 +59,7 @@ def test_constructor(self): self._manager = LiveMetricsManager( meter=self._meter, instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) self.assertFalse(self._manager._is_user_subscribed) self.assertEqual( @@ -72,6 +77,7 @@ def test_switch(self): self._manager = LiveMetricsManager( meter=self._meter, instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, ) self._manager.interval = 60 time.sleep(1) @@ -127,7 +133,10 @@ def test_post_ok(self): 200, None, {"x-ms-qps-subscribed": "false"} ) self._post = LiveMetricsPost( - exporter=LiveMetricsExporter(self._instrumentation_key), + exporter=LiveMetricsExporter( + self._instrumentation_key, + span_processor=self._span_processor, + ), meter=self._meter, instrumentation_key=self._instrumentation_key, ) @@ -144,7 +153,10 @@ def test_post_subscribed(self): 200, None, {"x-ms-qps-subscribed": "true"} ) self._post = LiveMetricsPost( - exporter=LiveMetricsExporter(self._instrumentation_key), + exporter=LiveMetricsExporter( + self._instrumentation_key, + span_processor=self._span_processor, + ), meter=self._meter, instrumentation_key=self._instrumentation_key, ) @@ -156,7 +168,10 @@ def test_post_error(self): with mock.patch("requests.post") as request: request.return_value = MockResponse(400, None, {}) self._post = LiveMetricsPost( - exporter=LiveMetricsExporter(self._instrumentation_key), + exporter=LiveMetricsExporter( + self._instrumentation_key, + span_processor=self._span_processor, + ), meter=self._meter, instrumentation_key=self._instrumentation_key, ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py deleted file mode 100644 index bfb6492..0000000 --- a/azure_monitor/tests/auto_collection/live_metrics/test_performance_metrics.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import collections -import unittest -from unittest import mock - -from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider, Observer - -from azure_monitor.sdk.auto_collection.live_metrics import ( - PerformanceLiveMetrics, -) - - -def throw(exc_type, *args, **kwargs): - def func(*_args, **_kwargs): - raise exc_type(*args, **kwargs) - - return func - - -# pylint: disable=protected-access -class TestPerformanceLiveMetrics(unittest.TestCase): - @classmethod - def setUpClass(cls): - metrics.set_meter_provider(MeterProvider()) - cls._meter = metrics.get_meter(__name__) - cls._test_labels = {"environment": "staging"} - - @classmethod - def tearDownClass(cls): - metrics._METER_PROVIDER = None - - def test_constructor(self): - mock_meter = mock.Mock() - performance_metrics_collector = PerformanceLiveMetrics( - meter=mock_meter, labels=self._test_labels - ) - self.assertEqual(performance_metrics_collector._meter, mock_meter) - self.assertEqual( - performance_metrics_collector._labels, self._test_labels - ) - self.assertEqual(mock_meter.register_observer.call_count, 1) - reg_obs_calls = mock_meter.register_observer.call_args_list - reg_obs_calls[0].assert_called_with( - callback=performance_metrics_collector._track_commited_memory, - name="\\Memory\\Committed Bytes", - description="Amount of commited memory in bytes", - unit="byte", - value_type=int, - ) - - @mock.patch("psutil.virtual_memory") - def test_track_memory(self, psutil_mock): - performance_metrics_collector = PerformanceLiveMetrics( - meter=self._meter, labels=self._test_labels - ) - memory = collections.namedtuple("memory", ["available", "total"]) - vmem = memory(available=100, total=150) - psutil_mock.return_value = vmem - obs = Observer( - callback=performance_metrics_collector._track_commited_memory, - name="\\Memory\\Available Bytes", - description="Amount of available memory in bytes", - unit="byte", - value_type=int, - meter=self._meter, - ) - performance_metrics_collector._track_commited_memory(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 50 - ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py b/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py deleted file mode 100644 index 5d25d28..0000000 --- a/azure_monitor/tests/auto_collection/live_metrics/test_request_metrics.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import unittest -from unittest import mock - -from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider, Observer - -from azure_monitor.sdk.auto_collection.live_metrics import request_metrics -from azure_monitor.sdk.auto_collection.metrics_span_processor import ( - AzureMetricsSpanProcessor, -) - - -# pylint: disable=protected-access -class TestRequestMetrics(unittest.TestCase): - @classmethod - def setUpClass(cls): - metrics.set_meter_provider(MeterProvider()) - cls._meter = metrics.get_meter(__name__) - cls._test_labels = {"environment": "staging"} - cls._span_processor = AzureMetricsSpanProcessor() - - @classmethod - def tearDown(cls): - metrics._METER_PROVIDER = None - - def setUp(self): - request_metrics.requests_map.clear() - - def test_constructor(self): - mock_meter = mock.Mock() - request_metrics_collector = request_metrics.RequestLiveMetrics( - meter=mock_meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - self.assertEqual(request_metrics_collector._meter, mock_meter) - self.assertEqual(request_metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 1) - - create_metric_calls = mock_meter.register_observer.call_args_list - - create_metric_calls[0].assert_called_with( - callback=request_metrics_collector._track_request_failed_rate, - name="\\ApplicationInsights\\Requests Failed/Sec", - description="Incoming Requests Failed Rate", - unit="rps", - value_type=int, - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" - ) - def test_track_request_rate(self, time_mock): - request_metrics_collector = request_metrics.RequestLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - time_mock.time.return_value = 100 - request_metrics.requests_map["last_time"] = 98 - self._span_processor.failed_request_count = 4 - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2 - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" - ) - def test_track_request_rate_time_none(self, time_mock): - time_mock.time.return_value = 100 - request_metrics_collector = request_metrics.RequestLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - request_metrics.requests_map["last_time"] = None - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 - ) - - @mock.patch( - "azure_monitor.sdk.auto_collection.live_metrics.request_metrics.time" - ) - def test_track_request_rate_error(self, time_mock): - request_metrics_collector = request_metrics.RequestLiveMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - time_mock.time.return_value = 100 - request_metrics.requests_map["last_rate"] = 5 - request_metrics.requests_map["last_time"] = 100 - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=int, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 - ) diff --git a/azure_monitor/tests/auto_collection/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/test_dependency_metrics.py index 9bbb645..d8aa72d 100644 --- a/azure_monitor/tests/auto_collection/test_dependency_metrics.py +++ b/azure_monitor/tests/auto_collection/test_dependency_metrics.py @@ -11,6 +11,7 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType # pylint: disable=protected-access @@ -29,17 +30,34 @@ def tearDown(cls): def setUp(self): dependency_metrics.dependency_map.clear() - def test_constructor(self): + def test_constructor_standard_metrics(self): mock_meter = mock.Mock() metrics_collector = dependency_metrics.DependencyMetrics( meter=mock_meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(metrics_collector._meter, mock_meter) self.assertEqual(metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 1) - mock_meter.register_observer.assert_called_with( + self.assertEqual(mock_meter.register_observer.call_count, 3) + + create_metric_calls = mock_meter.register_observer.call_args_list + create_metric_calls[0].assert_called_with( + callback=metrics_collector._track_dependency_duration, + name="\\ApplicationInsights\\Dependency Call Duration", + description="Average Outgoing Requests duration", + unit="milliseconds", + value_type=float, + ) + create_metric_calls[1].assert_called_with( + callback=metrics_collector._track_failure_rate, + name="\\ApplicationInsights\\Dependency Calls Failed/Sec", + description="Failed Outgoing Requests per second", + unit="rps", + value_type=int, + ) + create_metric_calls[2].assert_called_with( callback=metrics_collector._track_dependency_rate, name="\\ApplicationInsights\\Dependency Calls/Sec", description="Outgoing Requests per second", @@ -47,6 +65,33 @@ def test_constructor(self): value_type=int, ) + def test_constructor_live_metrics(self): + mock_meter = mock.Mock() + metrics_collector = dependency_metrics.DependencyMetrics( + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + self.assertEqual(metrics_collector._meter, mock_meter) + self.assertEqual(metrics_collector._labels, self._test_labels) + self.assertEqual(mock_meter.register_observer.call_count, 2) + create_metric_calls = mock_meter.register_observer.call_args_list + create_metric_calls[0].assert_called_with( + callback=metrics_collector._track_dependency_duration, + name="\\ApplicationInsights\\Dependency Call Duration", + description="Average Outgoing Requests duration", + unit="milliseconds", + value_type=float, + ) + create_metric_calls[1].assert_called_with( + callback=metrics_collector._track_failure_rate, + name="\\ApplicationInsights\\Dependency Calls Failed/Sec", + description="Failed Outgoing Requests per second", + unit="rps", + value_type=int, + ) + @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") def test_track_dependency_rate(self, time_mock): time_mock.time.return_value = 100 @@ -54,6 +99,7 @@ def test_track_dependency_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) obs = Observer( callback=metrics_collector._track_dependency_rate, @@ -77,6 +123,7 @@ def test_track_dependency_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = None obs = Observer( @@ -99,6 +146,7 @@ def test_track_dependency_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = 100 dependency_metrics.dependency_map["last_result"] = 5 @@ -114,3 +162,120 @@ def test_track_dependency_rate_error(self, time_mock): self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 5 ) + + @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") + def test_track_failed_dependency_rate(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, + ) + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + dependency_metrics.dependency_map["last_time"] = 98 + self._span_processor.failed_dependency_count = 4 + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 2 + ) + + @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") + def test_track_failed_dependency_rate_time_none(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, + ) + dependency_metrics.dependency_map["last_time"] = None + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) + + @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") + def test_track_failed_dependency_rate_error(self, time_mock): + time_mock.time.return_value = 100 + metrics_collector = dependency_metrics.DependencyMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, + ) + dependency_metrics.dependency_map["last_time"] = 100 + dependency_metrics.dependency_map["last_result"] = 5 + obs = Observer( + callback=metrics_collector._track_failure_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_failure_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 5 + ) + + def test_track_dependency_duration(self): + metrics_collector = dependency_metrics.DependencyMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, + ) + self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_count = 10 + dependency_metrics.dependency_map["last_count"] = 5 + obs = Observer( + callback=metrics_collector._track_dependency_duration, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_dependency_duration(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 20 + ) + + def test_track_dependency_duration_error(self): + metrics_collector = dependency_metrics.DependencyMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, + ) + self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_count = 10 + dependency_metrics.dependency_map["last_count"] = 10 + obs = Observer( + callback=metrics_collector._track_dependency_duration, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + metrics_collector._track_dependency_duration(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) diff --git a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py index 9918e92..3496999 100644 --- a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py +++ b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py @@ -7,9 +7,6 @@ from opentelemetry.trace import SpanContext, SpanKind from opentelemetry.trace.status import Status, StatusCanonicalCode -from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( - LiveMetricsExporter, -) from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) @@ -19,22 +16,17 @@ class TestAutoCollection(unittest.TestCase): def test_constructor(self): """Test the constructor.""" - instrumentation_key = "TEST_IKEY" - live_metrics_exporter = LiveMetricsExporter(instrumentation_key) - span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + span_processor = AzureMetricsSpanProcessor() self.assertEqual(span_processor.dependency_count, 0) self.assertEqual(span_processor.dependency_duration, 0) self.assertEqual(span_processor.failed_dependency_count, 0) self.assertEqual(span_processor.request_count, 0) self.assertEqual(span_processor.request_duration, 0) self.assertEqual(span_processor.failed_request_count, 0) - self.assertEqual(span_processor._exporter, live_metrics_exporter) def test_ok_dependency(self): """Test the functionality when Client Span is ended.""" - instrumentation_key = "TEST_IKEY" - live_metrics_exporter = LiveMetricsExporter(instrumentation_key) - span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + span_processor = AzureMetricsSpanProcessor() test_span = Span( name="test", kind=SpanKind.CLIENT, @@ -56,9 +48,7 @@ def test_ok_dependency(self): def test_failed_dependency(self): """Test the functionality when Client Span is ended.""" - instrumentation_key = "TEST_IKEY" - live_metrics_exporter = LiveMetricsExporter(instrumentation_key) - span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + span_processor = AzureMetricsSpanProcessor() test_span = Span( name="test", kind=SpanKind.CLIENT, @@ -81,9 +71,7 @@ def test_failed_dependency(self): def test_ok_request(self): """Test the functionality when Server Span is ended.""" - instrumentation_key = "TEST_IKEY" - live_metrics_exporter = LiveMetricsExporter(instrumentation_key) - span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + span_processor = AzureMetricsSpanProcessor() test_span = Span( name="test", kind=SpanKind.SERVER, @@ -105,9 +93,7 @@ def test_ok_request(self): def test_failed_request(self): """Test the functionality when Server Span is ended.""" - instrumentation_key = "TEST_IKEY" - live_metrics_exporter = LiveMetricsExporter(instrumentation_key) - span_processor = AzureMetricsSpanProcessor(live_metrics_exporter) + span_processor = AzureMetricsSpanProcessor() test_span = Span( name="test", kind=SpanKind.SERVER, diff --git a/azure_monitor/tests/auto_collection/test_performance_metrics.py b/azure_monitor/tests/auto_collection/test_performance_metrics.py index ec81d2a..0431e02 100644 --- a/azure_monitor/tests/auto_collection/test_performance_metrics.py +++ b/azure_monitor/tests/auto_collection/test_performance_metrics.py @@ -9,6 +9,7 @@ from opentelemetry.sdk.metrics import MeterProvider, Observer from azure_monitor.sdk.auto_collection import PerformanceMetrics +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType def throw(exc_type, *args, **kwargs): @@ -30,10 +31,12 @@ def setUpClass(cls): def tearDownClass(cls): metrics._METER_PROVIDER = None - def test_constructor(self): + def test_constructor_standard_metrics(self): mock_meter = mock.Mock() performance_metrics_collector = PerformanceMetrics( - meter=mock_meter, labels=self._test_labels + meter=mock_meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(performance_metrics_collector._meter, mock_meter) self.assertEqual( @@ -70,9 +73,32 @@ def test_constructor(self): value_type=int, ) + def test_constructor_live_metrics(self): + mock_meter = mock.Mock() + performance_metrics_collector = PerformanceMetrics( + meter=mock_meter, + labels=self._test_labels, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + self.assertEqual(performance_metrics_collector._meter, mock_meter) + self.assertEqual( + performance_metrics_collector._labels, self._test_labels + ) + self.assertEqual(mock_meter.register_observer.call_count, 1) + reg_obs_calls = mock_meter.register_observer.call_args_list + reg_obs_calls[0].assert_called_with( + callback=performance_metrics_collector._track_commited_memory, + name="\\Memory\\Committed Bytes", + description="Amount of commited memory in bytes", + unit="byte", + value_type=int, + ) + def test_track_cpu(self): performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) with mock.patch("psutil.cpu_times_percent") as processor_mock: cpu = collections.namedtuple("cpu", "idle") @@ -94,7 +120,9 @@ def test_track_cpu(self): @mock.patch("psutil.virtual_memory") def test_track_memory(self, psutil_mock): performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) memory = collections.namedtuple("memory", "available") vmem = memory(available=100) @@ -112,13 +140,38 @@ def test_track_memory(self, psutil_mock): obs.aggregators[tuple(self._test_labels.items())].current, 100 ) + @mock.patch("psutil.virtual_memory") + def test_track_commited_memory(self, psutil_mock): + performance_metrics_collector = PerformanceMetrics( + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + memory = collections.namedtuple("memory", ["available", "total"]) + vmem = memory(available=100, total=150) + psutil_mock.return_value = vmem + obs = Observer( + callback=performance_metrics_collector._track_commited_memory, + name="\\Memory\\Available Bytes", + description="Amount of available memory in bytes", + unit="byte", + value_type=int, + meter=self._meter, + ) + performance_metrics_collector._track_commited_memory(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 50 + ) + @mock.patch("azure_monitor.sdk.auto_collection.performance_metrics.psutil") def test_track_process_cpu(self, psutil_mock): with mock.patch( "azure_monitor.sdk.auto_collection.performance_metrics.PROCESS" ) as process_mock: performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) process_mock.cpu_percent.return_value = 44.4 psutil_mock.cpu_count.return_value = 2 @@ -141,7 +194,9 @@ def test_track_process_cpu_exception(self, logger_mock): "azure_monitor.sdk.auto_collection.performance_metrics.psutil" ) as psutil_mock: performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) psutil_mock.cpu_count.return_value = None obs = Observer( @@ -160,7 +215,9 @@ def test_track_process_memory(self): "azure_monitor.sdk.auto_collection.performance_metrics.PROCESS" ) as process_mock: performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) memory = collections.namedtuple("memory", "rss") pmem = memory(rss=100) @@ -185,7 +242,9 @@ def test_track_process_memory_exception(self, logger_mock): throw(Exception), ): performance_metrics_collector = PerformanceMetrics( - meter=self._meter, labels=self._test_labels + meter=self._meter, + labels=self._test_labels, + collection_type=AutoCollectionType.STANDARD_METRICS, ) obs = Observer( callback=performance_metrics_collector._track_process_memory, diff --git a/azure_monitor/tests/auto_collection/test_request_metrics.py b/azure_monitor/tests/auto_collection/test_request_metrics.py index 1ccde36..d426ec9 100644 --- a/azure_monitor/tests/auto_collection/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/test_request_metrics.py @@ -11,6 +11,7 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType # pylint: disable=protected-access @@ -29,21 +30,26 @@ def tearDown(cls): def setUp(self): request_metrics.requests_map.clear() - def test_constructor(self): + def test_constructor_standard_metrics(self): mock_meter = mock.Mock() request_metrics_collector = request_metrics.RequestMetrics( meter=mock_meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(request_metrics_collector._meter, mock_meter) self.assertEqual(request_metrics_collector._labels, self._test_labels) - - self.assertEqual(mock_meter.register_observer.call_count, 2) - + self.assertEqual(mock_meter.register_observer.call_count, 3) create_metric_calls = mock_meter.register_observer.call_args_list - create_metric_calls[0].assert_called_with( + callback=request_metrics_collector._track_request_failed_rate, + name="\\ApplicationInsights\\Requests Failed/Sec", + description="Incoming Requests Failed Rate", + unit="rps", + value_type=int, + ) + create_metric_calls[1].assert_called_with( callback=request_metrics_collector._track_request_duration, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", description="Incoming Requests Average Execution Time", @@ -51,10 +57,30 @@ def test_constructor(self): value_type=int, ) - create_metric_calls[1].assert_called_with( + create_metric_calls[2].assert_called_with( callback=request_metrics_collector._track_request_rate, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", - description="Incoming Requests Average Execution Rate", + description="Incoming Requests Rate", + unit="rps", + value_type=int, + ) + + def test_constructor_live_metrics(self): + mock_meter = mock.Mock() + request_metrics_collector = request_metrics.RequestMetrics( + meter=mock_meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + self.assertEqual(request_metrics_collector._meter, mock_meter) + self.assertEqual(request_metrics_collector._labels, self._test_labels) + self.assertEqual(mock_meter.register_observer.call_count, 1) + create_metric_calls = mock_meter.register_observer.call_args_list + create_metric_calls[0].assert_called_with( + callback=request_metrics_collector._track_request_failed_rate, + name="\\ApplicationInsights\\Requests Failed/Sec", + description="Incoming Requests Failed Rate", unit="rps", value_type=int, ) @@ -64,6 +90,7 @@ def test_track_request_duration(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self._span_processor.request_duration = 0.1 self._span_processor.request_count = 10 @@ -86,6 +113,7 @@ def test_track_request_duration_error(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self._span_processor.request_duration = 0.1 self._span_processor.request_count = 10 @@ -109,6 +137,7 @@ def test_track_request_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_time"] = 98 @@ -133,6 +162,7 @@ def test_track_request_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) request_metrics.requests_map["last_time"] = None obs = Observer( @@ -154,6 +184,7 @@ def test_track_request_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_rate"] = 5 @@ -170,3 +201,74 @@ def test_track_request_rate_error(self, time_mock): self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 5 ) + + @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") + def test_track_request_failed_rate(self, time_mock): + request_metrics_collector = request_metrics.RequestMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + time_mock.time.return_value = 100 + request_metrics.requests_map["last_time"] = 98 + self._span_processor.failed_request_count = 4 + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 2 + ) + + @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") + def test_track_request_failed_rate_time_none(self, time_mock): + time_mock.time.return_value = 100 + request_metrics_collector = request_metrics.RequestMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + request_metrics.requests_map["last_time"] = None + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 0 + ) + + @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") + def test_track_request_failed_rate_error(self, time_mock): + request_metrics_collector = request_metrics.RequestMetrics( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + collection_type=AutoCollectionType.LIVE_METRICS, + ) + time_mock.time.return_value = 100 + request_metrics.requests_map["last_rate"] = 5 + request_metrics.requests_map["last_time"] = 100 + obs = Observer( + callback=request_metrics_collector._track_request_failed_rate, + name="test", + description="test", + unit="test", + value_type=int, + meter=self._meter, + ) + request_metrics_collector._track_request_failed_rate(obs) + self.assertEqual( + obs.aggregators[tuple(self._test_labels.items())].current, 5 + ) From c18bf17e63cb14ac5c194a419e4e04aa55751cc1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Fri, 5 Jun 2020 16:32:24 -0700 Subject: [PATCH 07/39] Adding tests --- .../examples/metrics/auto_collector.py | 3 +- .../auto_collection/live_metrics/__init__.py | 3 + .../live_metrics/test_exporter.py | 56 ++++++++++++++++++- .../test_live_metrics_auto_collection.py | 47 ++++++++++++++++ 4 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py diff --git a/azure_monitor/examples/metrics/auto_collector.py b/azure_monitor/examples/metrics/auto_collector.py index b15c41b..d473c7e 100644 --- a/azure_monitor/examples/metrics/auto_collector.py +++ b/azure_monitor/examples/metrics/auto_collector.py @@ -1,7 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from opentelemetry import metrics -from opentelemetry import trace +from opentelemetry import metrics, trace from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export.controller import PushController from opentelemetry.sdk.trace import TracerProvider diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index c907898..585f3f9 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -53,3 +53,6 @@ def __init__( self._manager = LiveMetricsManager( meter, instrumentation_key, span_processor ) + + def shutdown(self): + self._manager.shutdown() diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index 4dd011b..d1b086b 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -6,11 +6,14 @@ import requests from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics import Counter, MeterProvider, Observer from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult -from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator +from opentelemetry.sdk.metrics.export.aggregate import ( + CounterAggregator, + ObserverAggregator, +) -from azure_monitor.protocol import Envelope +from azure_monitor.protocol import Envelope, LiveMetricEnvelope from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) @@ -36,6 +39,15 @@ def setUpClass(cls): cls._test_metric = cls._meter.create_metric( "testname", "testdesc", "unit", int, Counter, ["environment"] ) + cls._test_obs = cls._meter.register_observer( + lambda x: x, + "testname", + "testdesc", + "unit", + int, + Counter, + ["environment"], + ) cls._test_labels = tuple({"environment": "staging"}.items()) cls._span_processor = AzureMetricsSpanProcessor() @@ -109,3 +121,41 @@ def test_export_exception(self): ): result = exporter.export([record]) self.assertEqual(result, MetricsExportResult.FAILURE) + + def test_live_metric_envelope_counter(self): + aggregator = ObserverAggregator() + aggregator.update(123) + aggregator.take_checkpoint() + record = MetricRecord(aggregator, self._test_labels, self._test_obs) + exporter = LiveMetricsExporter( + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, + ) + + envelope = exporter._metric_to_live_metrics_envelope([record]) + self.assertIsInstance(envelope, LiveMetricEnvelope) + self.assertEqual( + envelope.instrumentation_key, + "99c42f65-1656-4c41-afde-bd86b709a4a7", + ) + self.assertEqual(envelope.documents, []) + self.assertEqual(envelope.metrics[0].name, "testname") + self.assertEqual(envelope.metrics[0].value, 123) + self.assertEqual(envelope.metrics[0].weight, 1) + + def test_live_metric_envelope_observer(self): + aggregator = CounterAggregator() + aggregator.update(123) + aggregator.take_checkpoint() + record = MetricRecord(aggregator, self._test_labels, self._test_metric) + exporter = LiveMetricsExporter( + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, + ) + + envelope = exporter._metric_to_live_metrics_envelope([record]) + self.assertIsInstance(envelope, LiveMetricEnvelope) + self.assertEqual(envelope.documents, []) + self.assertEqual(envelope.metrics[0].name, "testname") + self.assertEqual(envelope.metrics[0].value, 123) + self.assertEqual(envelope.metrics[0].weight, 1) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py new file mode 100644 index 0000000..591f5f1 --- /dev/null +++ b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest +from unittest import mock + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider + +from azure_monitor.sdk.auto_collection import AzureMetricsSpanProcessor +from azure_monitor.sdk.auto_collection.live_metrics import ( + LiveMetricsAutoCollection, +) + + +# pylint: disable=protected-access +class TestLiveMetricsAutoCollection(unittest.TestCase): + @classmethod + def setUpClass(cls): + metrics.set_meter_provider(MeterProvider()) + cls._meter = metrics.get_meter(__name__) + cls._test_labels = tuple({"environment": "staging"}.items()) + cls._span_processor = AzureMetricsSpanProcessor() + + @classmethod + def tearDownClass(cls): + metrics._METER_PROVIDER = None + + def setUp(self): + self._auto_collection = LiveMetricsAutoCollection( + meter=self._meter, + labels=self._test_labels, + span_processor=self._span_processor, + instrumentation_key="TEST", + ) + + def tearDown(self): + self._auto_collection.shutdown() + + def test_constructor(self): + """Test the constructor.""" + self.assertIsNotNone(self._auto_collection._performance_metrics) + self.assertIsNotNone(self._auto_collection._dependency_metrics) + self.assertIsNotNone(self._auto_collection._request_metrics) + self.assertIsNotNone(self._auto_collection._manager) + # Check observers + self.assertEqual(len(self._meter.observers), 4) From fc4a6424dfbe410d9ad89548ebef3adb39b5f9fd Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Fri, 5 Jun 2020 16:56:26 -0700 Subject: [PATCH 08/39] Lint --- .../auto_collection/live_metrics/test_exporter.py | 14 ++------------ .../test_live_metrics_auto_collection.py | 1 - 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index d1b086b..6271139 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -6,14 +6,14 @@ import requests from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, MeterProvider, Observer +from opentelemetry.sdk.metrics import Counter, MeterProvider from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( CounterAggregator, ObserverAggregator, ) -from azure_monitor.protocol import Envelope, LiveMetricEnvelope +from azure_monitor.protocol import LiveMetricEnvelope from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) @@ -62,16 +62,6 @@ def test_constructor(self): exporter._instrumentation_key, self._instrumentation_key ) - # def test_add_document(self): - # """Test adding a document.""" - # exporter = LiveMetricsExporter( - # instrumentation_key=self._instrumentation_key, - # span_processor=self._span_processor, - # ) - # envelope = Envelope() - # exporter.add_document(envelope) - # self.assertEqual(exporter._document_envelopes.pop(), envelope) - def test_export(self): """Test export.""" record = MetricRecord( diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py index 591f5f1..6f2e3d2 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. import unittest -from unittest import mock from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider From 0c23ccccc7227272ca00b2f16ccecb5888bc2e89 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Tue, 9 Jun 2020 16:25:24 -0700 Subject: [PATCH 09/39] Fixing issue with rates as int No need to differentiate between live and standard metrics in reuqests and dependencies Add logic to stop collecting documents when live metrics are not subscribed Fix issues with LiveMetric document creation --- azure_monitor/src/azure_monitor/protocol.py | 8 +- .../sdk/auto_collection/__init__.py | 6 +- .../sdk/auto_collection/dependency_metrics.py | 38 +++++----- .../auto_collection/live_metrics/__init__.py | 9 +-- .../auto_collection/live_metrics/exporter.py | 36 ++++----- .../auto_collection/live_metrics/manager.py | 2 + .../auto_collection/live_metrics/sender.py | 3 +- .../sdk/auto_collection/live_metrics/utils.py | 10 +-- .../auto_collection/metrics_span_processor.py | 18 +++-- .../auto_collection/performance_metrics.py | 15 ++-- .../sdk/auto_collection/request_metrics.py | 48 ++++++------ .../live_metrics/test_exporter.py | 75 ++++++++++++++++++- .../test_live_metrics_auto_collection.py | 2 +- .../live_metrics/test_manager.py | 6 ++ .../test_dependency_metrics.py | 75 +++++-------------- .../test_metrics_span_processor.py | 39 ++++++++-- .../test_performance_metrics.py | 9 ++- .../auto_collection/test_request_metrics.py | 72 ++++++------------ 18 files changed, 249 insertions(+), 222 deletions(-) diff --git a/azure_monitor/src/azure_monitor/protocol.py b/azure_monitor/src/azure_monitor/protocol.py index e10c8d4..053ccf3 100644 --- a/azure_monitor/src/azure_monitor/protocol.py +++ b/azure_monitor/src/azure_monitor/protocol.py @@ -575,7 +575,7 @@ def __init__(self, key: str, value: str) -> None: class LiveMetricDocument(BaseObject): __slots__ = ( - "__type", + "quickpulse_type", "document_type", "version", "operation_id", @@ -584,13 +584,13 @@ class LiveMetricDocument(BaseObject): def __init__( self, - __type: str = "", + quickpulse_type: str = "", document_type: str = "", version: str = "", operation_id: str = "", properties: typing.List[LiveMetricDocumentProperty] = None, ) -> None: - self.__type = __type + self.quickpulse_type = quickpulse_type self.document_type = document_type self.version = version self.operation_id = operation_id @@ -598,7 +598,7 @@ def __init__( def to_dict(self): return { - "__type": self.__type, + "__type": self.quickpulse_type, "DocumentType": self.document_type, "Version": self.version, "OperationId": self.operation_id, diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 7449a58..6c4d05e 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -45,8 +45,6 @@ def __init__( col_type = AutoCollectionType.STANDARD_METRICS self._performance_metrics = PerformanceMetrics(meter, labels, col_type) self._dependency_metrics = DependencyMetrics( - meter, labels, span_processor, col_type - ) - self._request_metrics = RequestMetrics( - meter, labels, span_processor, col_type + meter, labels, span_processor ) + self._request_metrics = RequestMetrics(meter, labels, span_processor) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py index 150bd7e..47850c3 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py @@ -8,7 +8,6 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) -from azure_monitor.sdk.auto_collection.utils import AutoCollectionType dependency_map = dict() @@ -29,7 +28,6 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, - collection_type: AutoCollectionType, ): self._meter = meter self._labels = labels @@ -40,23 +38,22 @@ def __init__( name="\\ApplicationInsights\\Dependency Call Duration", description="Average Outgoing Requests duration", unit="milliseconds", - value_type=float, + value_type=int, ) meter.register_observer( callback=self._track_failure_rate, name="\\ApplicationInsights\\Dependency Calls Failed/Sec", description="Failed Outgoing Requests per second", unit="rps", - value_type=int, + value_type=float, + ) + meter.register_observer( + callback=self._track_dependency_rate, + name="\\ApplicationInsights\\Dependency Calls/Sec", + description="Outgoing Requests per second", + unit="rps", + value_type=float, ) - if collection_type == AutoCollectionType.STANDARD_METRICS: - meter.register_observer( - callback=self._track_dependency_rate, - name="\\ApplicationInsights\\Dependency Calls/Sec", - description="Outgoing Requests per second", - unit="rps", - value_type=int, - ) def _track_dependency_rate(self, observer: Observer) -> None: """ Track Dependency rate @@ -78,15 +75,15 @@ def _track_dependency_rate(self, observer: Observer) -> None: interval_count = current_count - last_count result = interval_count / elapsed_seconds else: - result = 0 + result = 0.0 dependency_map["last_time"] = current_time dependency_map["last_count"] = current_count dependency_map["last_result"] = result - observer.observe(int(result), self._labels) + observer.observe(result, self._labels) except ZeroDivisionError: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_result), self._labels) + observer.observe(last_result, self._labels) def _track_dependency_duration(self, observer: Observer) -> None: """ Track Dependency average duration @@ -112,12 +109,11 @@ def _track_dependency_duration(self, observer: Observer) -> None: dependency_map[ "last_duration" ] = self._span_processor.dependency_duration - # Convert to milliseconds - observer.observe(int(result * 1000.0), self._labels) + observer.observe(int(result), self._labels) except ZeroDivisionError: # If interval_count is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_average_duration * 1000.0), self._labels) + observer.observe(int(last_average_duration), self._labels) def _track_failure_rate(self, observer: Observer) -> None: """ Track Failed Dependency rate @@ -141,12 +137,12 @@ def _track_failure_rate(self, observer: Observer) -> None: ) result = interval_failed_count / elapsed_seconds else: - result = 0 + result = 0.0 dependency_map["last_time"] = current_time dependency_map["last_failed_count"] = current_failed_count dependency_map["last_result"] = result - observer.observe(int(result), self._labels) + observer.observe(result, self._labels) except ZeroDivisionError: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_result), self._labels) + observer.observe(last_result, self._labels) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index 585f3f9..8c2cb46 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -9,9 +9,6 @@ from azure_monitor.sdk.auto_collection.dependency_metrics import ( DependencyMetrics, ) -from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( - LiveMetricsExporter, -) from azure_monitor.sdk.auto_collection.live_metrics.manager import ( LiveMetricsManager, ) @@ -45,11 +42,9 @@ def __init__( col_type = AutoCollectionType.LIVE_METRICS self._performance_metrics = PerformanceMetrics(meter, labels, col_type) self._dependency_metrics = DependencyMetrics( - meter, labels, span_processor, col_type - ) - self._request_metrics = RequestMetrics( - meter, labels, span_processor, col_type + meter, labels, span_processor ) + self._request_metrics = RequestMetrics(meter, labels, span_processor) self._manager = LiveMetricsManager( meter, instrumentation_key, span_processor ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py index c53a6bf..0372ea1 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py @@ -95,22 +95,22 @@ def _get_live_metric_documents( ) -> typing.Sequence[LiveMetricDocument]: live_metric_documents = [] while self._span_processor.documents: - for envelope in self._span_processor.documents.popleft(): - base_type = envelope.data.baseType - if base_type: - document = LiveMetricDocument( - __type=self._get_live_metric_type(base_type), - document_type=self._get_live_metric_document_type( - base_type - ), - properties=self._get_aggregated_properties(envelope), - version="1.0", - ) - live_metric_documents.append(document) - else: - logger.warning( - "Document type invalid; not sending live metric document" - ) + envelope = self._span_processor.documents.popleft() + base_type = envelope.data.base_type + if base_type: + document = LiveMetricDocument( + quickpulse_type=self._get_live_metric_type(base_type), + document_type=self._get_live_metric_document_type( + base_type + ), + properties=self._get_aggregated_properties(envelope), + version="1.0", + ) + live_metric_documents.append(document) + else: + logger.warning( + "Document type invalid; not sending live metric document" + ) return live_metric_documents @@ -155,7 +155,7 @@ def _get_aggregated_properties( aggregated_properties = [] measurements = ( envelope.data.base_data.measurements - if envelope.data.base_data.measurements + if envelope.data.base_data and envelope.data.base_data.measurements else [] ) for key in measurements: @@ -163,7 +163,7 @@ def _get_aggregated_properties( aggregated_properties.append(prop) properties = ( envelope.data.base_data.properties - if envelope.data.base_data.properties + if envelope.data.base_data and envelope.data.base_data.properties else [] ) for key in properties: diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py index 7616dac..c93de77 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/manager.py @@ -69,12 +69,14 @@ def check_if_user_is_subscribed(self): # Switch to Post self._ping.shutdown() self._ping = None + self._span_processor.is_collecting_documents = True self._post = LiveMetricsPost( self._meter, self._exporter, self._instrumentation_key ) if self._post: if not self._post.is_user_subscribed: # Switch to Ping + self._span_processor.is_collecting_documents = False self._post.shutdown() self._post = None self._ping = LiveMetricsPing(self._instrumentation_key) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/sender.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/sender.py index 248db5f..27f39e3 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/sender.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/sender.py @@ -3,6 +3,7 @@ # import json import logging +import time import requests @@ -41,7 +42,7 @@ def _send_request(self, data: str, request_type: str) -> requests.Response: "Expect": "100-continue", "Content-Type": "application/json; charset=utf-8", utils.LIVE_METRICS_TRANSMISSION_TIME_HEADER: str( - utils.calculate_ticks_since_epoch() + round(time.time()) * 1000 ), }, ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/utils.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/utils.py index 724467d..cfba984 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/utils.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/utils.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # +import time import uuid -from datetime import datetime from azure_monitor.protocol import LiveMetricEnvelope from azure_monitor.utils import azure_monitor_context @@ -22,13 +22,7 @@ def create_metric_envelope(instrumentation_key: str): machine_name=azure_monitor_context.get("ai.device.id"), metrics=None, stream_id=STREAM_ID, - timestamp="/Date({0})/".format(str(calculate_ticks_since_epoch())), + timestamp="/Date({0})/".format(str(int(time.time()) * 1000)), version=azure_monitor_context.get("ai.internal.sdkVersion"), ) return envelope - - -def calculate_ticks_since_epoch(): - return round( - (datetime.utcnow() - datetime(1, 1, 1)).total_seconds() * 10000000 - ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py index eeb57bf..1d007dd 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py @@ -17,8 +17,8 @@ class AzureMetricsSpanProcessor(SpanProcessor): and failed dependencies/requests. """ - def __init__(self, collect_documents: bool = False): - self._collect_documents = collect_documents + def __init__(self): + self.is_collecting_documents = False self.documents = collections.deque() self.request_count = 0 self.dependency_count = 0 @@ -34,24 +34,26 @@ def on_end(self, span: Span) -> None: try: if span.kind == SpanKind.SERVER: self.request_count = self.request_count + 1 - self.request_duration = self.request_duration + ( + duration = ( span.end_time - span.start_time - ) + ) / 1000000 # Convert to milliseconds + self.request_duration = self.request_duration + duration if not span.status.is_ok: self.failed_request_count = self.failed_request_count + 1 - if self._collect_documents: + if self.is_collecting_documents: self.documents.append(convert_span_to_envelope(span)) elif span.kind == SpanKind.CLIENT: self.dependency_count = self.dependency_count + 1 - self.dependency_duration = self.dependency_duration + ( + duration = ( span.end_time - span.start_time - ) + ) / 1000000 # Convert to milliseconds + self.dependency_duration = self.dependency_duration + duration if not span.status.is_ok: self.failed_dependency_count = ( self.failed_dependency_count + 1 ) - if self._collect_documents: + if self.is_collecting_documents: self.documents.append(convert_span_to_envelope(span)) # pylint: disable=broad-except diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py index 34e022e..59c335b 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py @@ -33,14 +33,15 @@ def __init__( self._meter = meter self._labels = labels + self._meter.register_observer( + callback=self._track_cpu, + name="\\Processor(_Total)\\% Processor Time", + description="Processor time as a percentage", + unit="percentage", + value_type=float, + ) + if collection_type == AutoCollectionType.STANDARD_METRICS: - self._meter.register_observer( - callback=self._track_cpu, - name="\\Processor(_Total)\\% Processor Time", - description="Processor time as a percentage", - unit="percentage", - value_type=float, - ) self._meter.register_observer( callback=self._track_memory, name="\\Memory\\Available Bytes", diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index b8fe18b..e1a3809 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -9,7 +9,6 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) -from azure_monitor.sdk.auto_collection.utils import AutoCollectionType logger = logging.getLogger(__name__) requests_map = dict() @@ -32,7 +31,6 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, - collection_type: AutoCollectionType, ): self._meter = meter self._labels = labels @@ -43,23 +41,22 @@ def __init__( name="\\ApplicationInsights\\Requests Failed/Sec", description="Incoming Requests Failed Rate", unit="rps", + value_type=float, + ) + meter.register_observer( + callback=self._track_request_duration, + name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", + description="Incoming Requests Average Execution Time", + unit="milliseconds", value_type=int, ) - if collection_type == AutoCollectionType.STANDARD_METRICS: - meter.register_observer( - callback=self._track_request_duration, - name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", - description="Incoming Requests Average Execution Time", - unit="milliseconds", - value_type=int, - ) - meter.register_observer( - callback=self._track_request_rate, - name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", - description="Incoming Requests Rate", - unit="rps", - value_type=int, - ) + meter.register_observer( + callback=self._track_request_rate, + name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", + description="Incoming Requests Rate", + unit="rps", + value_type=float, + ) def _track_request_duration(self, observer: Observer) -> None: """ Track Request execution time @@ -82,12 +79,11 @@ def _track_request_duration(self, observer: Observer) -> None: requests_map[ "last_duration" ] = self._span_processor.request_duration - # Convert to milliseconds - observer.observe(int(result * 1000.0), self._labels) + observer.observe(int(result), self._labels) except ZeroDivisionError: # If interval_count is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_average_duration * 1000.0), self._labels) + observer.observe(int(last_average_duration), self._labels) def _track_request_rate(self, observer: Observer) -> None: """ Track Request execution rate @@ -110,15 +106,15 @@ def _track_request_rate(self, observer: Observer) -> None: ) result = interval_count / interval_time else: - result = 0 + result = 0.0 requests_map["last_time"] = current_time requests_map["last_count"] = self._span_processor.request_count requests_map["last_rate"] = result - observer.observe(int(result), self._labels) + observer.observe(result, self._labels) except ZeroDivisionError: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_rate), self._labels) + observer.observe(last_rate, self._labels) def _track_request_failed_rate(self, observer: Observer) -> None: """ Track Request failed execution rate @@ -141,14 +137,14 @@ def _track_request_failed_rate(self, observer: Observer) -> None: ) result = interval_count / interval_time else: - result = 0 + result = 0.0 requests_map["last_time"] = current_time requests_map[ "last_failed_count" ] = self._span_processor.failed_request_count requests_map["last_rate"] = result - observer.observe(int(result), self._labels) + observer.observe(result, self._labels) except ZeroDivisionError: # If elapsed_seconds is 0, exporter call made too close to previous # Return the previous result if this is the case - observer.observe(int(last_rate), self._labels) + observer.observe(last_rate, self._labels) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index 6271139..b82f1d7 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -13,7 +13,12 @@ ObserverAggregator, ) -from azure_monitor.protocol import LiveMetricEnvelope +from azure_monitor.protocol import ( + Data, + Envelope, + LiveMetricEnvelope, + RemoteDependency, +) from azure_monitor.sdk.auto_collection.live_metrics.exporter import ( LiveMetricsExporter, ) @@ -149,3 +154,71 @@ def test_live_metric_envelope_observer(self): self.assertEqual(envelope.metrics[0].name, "testname") self.assertEqual(envelope.metrics[0].value, 123) self.assertEqual(envelope.metrics[0].weight, 1) + + def test_live_metric_envelope_documents(self): + aggregator = CounterAggregator() + aggregator.update(123) + aggregator.take_checkpoint() + record = MetricRecord(aggregator, self._test_labels, self._test_metric) + exporter = LiveMetricsExporter( + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, + ) + request_data = RemoteDependency( + name="testName", + id="", + result_code="testResultCode", + duration="testDuration", + success=True, + properties={}, + measurements={}, + ) + request_data.properties["test_property1"] = "test_property1Value" + request_data.properties["test_property2"] = "test_property2Value" + request_data.measurements[ + "test_measurement1" + ] = "test_measurement1Value" + request_data.measurements[ + "test_measurement2" + ] = "test_measurement2Value" + test_envelope = Envelope( + data=Data(base_type="RemoteDependencyData", base_data=request_data) + ) + self._span_processor.documents.append(test_envelope) + envelope = exporter._metric_to_live_metrics_envelope([record]) + self.assertIsInstance(envelope, LiveMetricEnvelope) + self.assertEqual(len(envelope.documents), 1) + self.assertEqual( + envelope.documents[0].quickpulse_type, + "DependencyTelemetryDocument", + ) + self.assertEqual( + envelope.documents[0].document_type, "RemoteDependency" + ) + self.assertEqual(envelope.documents[0].version, "1.0") + self.assertEqual(envelope.documents[0].operation_id, "") + self.assertEqual(len(envelope.documents[0].properties), 4) + self.assertEqual( + envelope.documents[0].properties[0].key, "test_measurement1" + ) + self.assertEqual( + envelope.documents[0].properties[1].key, "test_measurement2" + ) + self.assertEqual( + envelope.documents[0].properties[2].key, "test_property1" + ) + self.assertEqual( + envelope.documents[0].properties[3].key, "test_property2" + ) + self.assertEqual( + envelope.documents[0].properties[0].value, "test_measurement1Value" + ) + self.assertEqual( + envelope.documents[0].properties[1].value, "test_measurement2Value" + ) + self.assertEqual( + envelope.documents[0].properties[2].value, "test_property1Value" + ) + self.assertEqual( + envelope.documents[0].properties[3].value, "test_property2Value" + ) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py index 6f2e3d2..e94bcee 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_live_metrics_auto_collection.py @@ -43,4 +43,4 @@ def test_constructor(self): self.assertIsNotNone(self._auto_collection._request_metrics) self.assertIsNotNone(self._auto_collection._manager) # Check observers - self.assertEqual(len(self._meter.observers), 4) + self.assertEqual(len(self._meter.observers), 8) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py index 695929f..7b9dc1d 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_manager.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_manager.py @@ -84,10 +84,16 @@ def test_switch(self): self._manager.check_if_user_is_subscribed() self.assertIsNone(self._manager._ping) self.assertIsNotNone(self._manager._post) + self.assertEqual( + self._manager._span_processor.is_collecting_documents, True + ) self._manager._post.is_user_subscribed = False self._manager.check_if_user_is_subscribed() self.assertIsNone(self._manager._post) self.assertIsNotNone(self._manager._ping) + self.assertEqual( + self._manager._span_processor.is_collecting_documents, False + ) def test_ping_ok(self): """Test ping send requests to Live Metrics service.""" diff --git a/azure_monitor/tests/auto_collection/test_dependency_metrics.py b/azure_monitor/tests/auto_collection/test_dependency_metrics.py index d8aa72d..7ea3413 100644 --- a/azure_monitor/tests/auto_collection/test_dependency_metrics.py +++ b/azure_monitor/tests/auto_collection/test_dependency_metrics.py @@ -11,7 +11,6 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) -from azure_monitor.sdk.auto_collection.utils import AutoCollectionType # pylint: disable=protected-access @@ -30,13 +29,12 @@ def tearDown(cls): def setUp(self): dependency_metrics.dependency_map.clear() - def test_constructor_standard_metrics(self): + def test_constructor(self): mock_meter = mock.Mock() metrics_collector = dependency_metrics.DependencyMetrics( meter=mock_meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(metrics_collector._meter, mock_meter) self.assertEqual(metrics_collector._labels, self._test_labels) @@ -48,49 +46,22 @@ def test_constructor_standard_metrics(self): name="\\ApplicationInsights\\Dependency Call Duration", description="Average Outgoing Requests duration", unit="milliseconds", - value_type=float, + value_type=int, ) create_metric_calls[1].assert_called_with( callback=metrics_collector._track_failure_rate, name="\\ApplicationInsights\\Dependency Calls Failed/Sec", description="Failed Outgoing Requests per second", unit="rps", - value_type=int, + value_type=float, ) create_metric_calls[2].assert_called_with( callback=metrics_collector._track_dependency_rate, name="\\ApplicationInsights\\Dependency Calls/Sec", description="Outgoing Requests per second", unit="rps", - value_type=int, - ) - - def test_constructor_live_metrics(self): - mock_meter = mock.Mock() - metrics_collector = dependency_metrics.DependencyMetrics( - meter=mock_meter, - labels=self._test_labels, - span_processor=self._span_processor, - collection_type=AutoCollectionType.LIVE_METRICS, - ) - self.assertEqual(metrics_collector._meter, mock_meter) - self.assertEqual(metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 2) - create_metric_calls = mock_meter.register_observer.call_args_list - create_metric_calls[0].assert_called_with( - callback=metrics_collector._track_dependency_duration, - name="\\ApplicationInsights\\Dependency Call Duration", - description="Average Outgoing Requests duration", - unit="milliseconds", value_type=float, ) - create_metric_calls[1].assert_called_with( - callback=metrics_collector._track_failure_rate, - name="\\ApplicationInsights\\Dependency Calls Failed/Sec", - description="Failed Outgoing Requests per second", - unit="rps", - value_type=int, - ) @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") def test_track_dependency_rate(self, time_mock): @@ -99,17 +70,16 @@ def test_track_dependency_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) obs = Observer( callback=metrics_collector._track_dependency_rate, name="\\ApplicationInsights\\Dependency Calls/Sec", description="Outgoing Requests per second", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) - dependency_metrics.dependency_map["last_time"] = 98 + dependency_metrics.dependency_map["last_time"] = 98.0 self._span_processor.dependency_count = 4 metrics_collector._track_dependency_rate(obs) self.assertEqual( @@ -123,7 +93,6 @@ def test_track_dependency_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = None obs = Observer( @@ -131,12 +100,12 @@ def test_track_dependency_rate_time_none(self, time_mock): name="\\ApplicationInsights\\Dependency Calls/Sec", description="Outgoing Requests per second", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) metrics_collector._track_dependency_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 + obs.aggregators[tuple(self._test_labels.items())].current, 0.0 ) @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") @@ -146,21 +115,20 @@ def test_track_dependency_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = 100 - dependency_metrics.dependency_map["last_result"] = 5 + dependency_metrics.dependency_map["last_result"] = 5.0 obs = Observer( callback=metrics_collector._track_dependency_rate, name="\\ApplicationInsights\\Dependency Calls/Sec", description="Outgoing Requests per second", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) metrics_collector._track_dependency_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 + obs.aggregators[tuple(self._test_labels.items())].current, 5.0 ) @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") @@ -170,21 +138,20 @@ def test_track_failed_dependency_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) obs = Observer( callback=metrics_collector._track_failure_rate, name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) dependency_metrics.dependency_map["last_time"] = 98 self._span_processor.failed_dependency_count = 4 metrics_collector._track_failure_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2 + obs.aggregators[tuple(self._test_labels.items())].current, 2.0 ) @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") @@ -194,7 +161,6 @@ def test_track_failed_dependency_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = None obs = Observer( @@ -202,12 +168,12 @@ def test_track_failed_dependency_rate_time_none(self, time_mock): name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) metrics_collector._track_failure_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 + obs.aggregators[tuple(self._test_labels.items())].current, 0.0 ) @mock.patch("azure_monitor.sdk.auto_collection.dependency_metrics.time") @@ -217,21 +183,20 @@ def test_track_failed_dependency_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) dependency_metrics.dependency_map["last_time"] = 100 - dependency_metrics.dependency_map["last_result"] = 5 + dependency_metrics.dependency_map["last_result"] = 5.0 obs = Observer( callback=metrics_collector._track_failure_rate, name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) metrics_collector._track_failure_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 + obs.aggregators[tuple(self._test_labels.items())].current, 5.0 ) def test_track_dependency_duration(self): @@ -239,9 +204,8 @@ def test_track_dependency_duration(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) - self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_duration = 100 self._span_processor.dependency_count = 10 dependency_metrics.dependency_map["last_count"] = 5 obs = Observer( @@ -262,9 +226,8 @@ def test_track_dependency_duration_error(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) - self._span_processor.dependency_duration = 0.1 + self._span_processor.dependency_duration = 100 self._span_processor.dependency_count = 10 dependency_metrics.dependency_map["last_count"] = 10 obs = Observer( diff --git a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py index 3496999..216ca34 100644 --- a/azure_monitor/tests/auto_collection/test_metrics_span_processor.py +++ b/azure_monitor/tests/auto_collection/test_metrics_span_processor.py @@ -36,8 +36,8 @@ def test_ok_dependency(self): is_remote=False, ), ) - test_span._start_time = 5 - test_span._end_time = 15 + test_span._start_time = 5000000 + test_span._end_time = 15000000 span_processor.on_end(test_span) self.assertEqual(span_processor.request_count, 0) self.assertEqual(span_processor.request_duration, 0) @@ -59,8 +59,8 @@ def test_failed_dependency(self): ), ) test_span.set_status(Status(StatusCanonicalCode.INTERNAL, "test")) - test_span._start_time = 5 - test_span._end_time = 15 + test_span._start_time = 5000000 + test_span._end_time = 15000000 span_processor.on_end(test_span) self.assertEqual(span_processor.request_count, 0) self.assertEqual(span_processor.request_duration, 0) @@ -81,8 +81,8 @@ def test_ok_request(self): is_remote=False, ), ) - test_span._start_time = 5 - test_span._end_time = 15 + test_span._start_time = 5000000 + test_span._end_time = 15000000 span_processor.on_end(test_span) self.assertEqual(span_processor.dependency_count, 0) self.assertEqual(span_processor.dependency_duration, 0) @@ -104,8 +104,8 @@ def test_failed_request(self): ), ) test_span.set_status(Status(StatusCanonicalCode.INTERNAL, "test")) - test_span._start_time = 5 - test_span._end_time = 15 + test_span._start_time = 5000000 + test_span._end_time = 15000000 span_processor.on_end(test_span) self.assertEqual(span_processor.dependency_count, 0) self.assertEqual(span_processor.dependency_duration, 0) @@ -113,3 +113,26 @@ def test_failed_request(self): self.assertEqual(span_processor.request_count, 1) self.assertEqual(span_processor.request_duration, 10) self.assertEqual(span_processor.failed_request_count, 1) + + def test_document_collection(self): + """Test the document collection.""" + span_processor = AzureMetricsSpanProcessor() + span_processor.is_collecting_documents = True + test_span = Span( + name="test", + kind=SpanKind.SERVER, + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557338, + is_remote=False, + ), + ) + test_span.set_status(Status(StatusCanonicalCode.INTERNAL, "test")) + test_span._start_time = 5000000 + test_span._end_time = 15000000 + span_processor.on_end(test_span) + document = span_processor.documents.pop() + self.assertIsNotNone(document) + self.assertEqual( + document.name, "Microsoft.ApplicationInsights.Request" + ) diff --git a/azure_monitor/tests/auto_collection/test_performance_metrics.py b/azure_monitor/tests/auto_collection/test_performance_metrics.py index 0431e02..0474a22 100644 --- a/azure_monitor/tests/auto_collection/test_performance_metrics.py +++ b/azure_monitor/tests/auto_collection/test_performance_metrics.py @@ -84,9 +84,16 @@ def test_constructor_live_metrics(self): self.assertEqual( performance_metrics_collector._labels, self._test_labels ) - self.assertEqual(mock_meter.register_observer.call_count, 1) + self.assertEqual(mock_meter.register_observer.call_count, 2) reg_obs_calls = mock_meter.register_observer.call_args_list reg_obs_calls[0].assert_called_with( + callback=performance_metrics_collector._track_cpu, + name="\\Processor(_Total)\\% Processor Time", + description="Processor time as a percentage", + unit="percentage", + value_type=float, + ) + reg_obs_calls[1].assert_called_with( callback=performance_metrics_collector._track_commited_memory, name="\\Memory\\Committed Bytes", description="Amount of commited memory in bytes", diff --git a/azure_monitor/tests/auto_collection/test_request_metrics.py b/azure_monitor/tests/auto_collection/test_request_metrics.py index d426ec9..2d0b121 100644 --- a/azure_monitor/tests/auto_collection/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/test_request_metrics.py @@ -11,7 +11,6 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) -from azure_monitor.sdk.auto_collection.utils import AutoCollectionType # pylint: disable=protected-access @@ -30,13 +29,12 @@ def tearDown(cls): def setUp(self): request_metrics.requests_map.clear() - def test_constructor_standard_metrics(self): + def test_constructor(self): mock_meter = mock.Mock() request_metrics_collector = request_metrics.RequestMetrics( meter=mock_meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(request_metrics_collector._meter, mock_meter) self.assertEqual(request_metrics_collector._labels, self._test_labels) @@ -47,7 +45,7 @@ def test_constructor_standard_metrics(self): name="\\ApplicationInsights\\Requests Failed/Sec", description="Incoming Requests Failed Rate", unit="rps", - value_type=int, + value_type=float, ) create_metric_calls[1].assert_called_with( callback=request_metrics_collector._track_request_duration, @@ -62,27 +60,7 @@ def test_constructor_standard_metrics(self): name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", description="Incoming Requests Rate", unit="rps", - value_type=int, - ) - - def test_constructor_live_metrics(self): - mock_meter = mock.Mock() - request_metrics_collector = request_metrics.RequestMetrics( - meter=mock_meter, - labels=self._test_labels, - span_processor=self._span_processor, - collection_type=AutoCollectionType.LIVE_METRICS, - ) - self.assertEqual(request_metrics_collector._meter, mock_meter) - self.assertEqual(request_metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 1) - create_metric_calls = mock_meter.register_observer.call_args_list - create_metric_calls[0].assert_called_with( - callback=request_metrics_collector._track_request_failed_rate, - name="\\ApplicationInsights\\Requests Failed/Sec", - description="Incoming Requests Failed Rate", - unit="rps", - value_type=int, + value_type=float, ) def test_track_request_duration(self): @@ -90,9 +68,8 @@ def test_track_request_duration(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) - self._span_processor.request_duration = 0.1 + self._span_processor.request_duration = 100 self._span_processor.request_count = 10 request_metrics.requests_map["last_count"] = 5 obs = Observer( @@ -105,7 +82,7 @@ def test_track_request_duration(self): ) request_metrics_collector._track_request_duration(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 20 + obs.aggregators[tuple(self._test_labels.items())].current, 20.0 ) def test_track_request_duration_error(self): @@ -113,9 +90,8 @@ def test_track_request_duration_error(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) - self._span_processor.request_duration = 0.1 + self._span_processor.request_duration = 100 self._span_processor.request_count = 10 request_metrics.requests_map["last_count"] = 10 obs = Observer( @@ -128,7 +104,7 @@ def test_track_request_duration_error(self): ) request_metrics_collector._track_request_duration(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 + obs.aggregators[tuple(self._test_labels.items())].current, 0.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -137,7 +113,6 @@ def test_track_request_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_time"] = 98 @@ -147,12 +122,12 @@ def test_track_request_rate(self, time_mock): name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", description="Incoming Requests Average Execution Rate", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2 + obs.aggregators[tuple(self._test_labels.items())].current, 2.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -162,7 +137,6 @@ def test_track_request_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) request_metrics.requests_map["last_time"] = None obs = Observer( @@ -170,12 +144,12 @@ def test_track_request_rate_time_none(self, time_mock): name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", description="Incoming Requests Average Execution Rate", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 + obs.aggregators[tuple(self._test_labels.items())].current, 0.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -184,22 +158,21 @@ def test_track_request_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 - request_metrics.requests_map["last_rate"] = 5 + request_metrics.requests_map["last_rate"] = 5.0 request_metrics.requests_map["last_time"] = 100 obs = Observer( callback=request_metrics_collector._track_request_rate, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", description="Incoming Requests Average Execution Rate", unit="rps", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 + obs.aggregators[tuple(self._test_labels.items())].current, 5.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -208,7 +181,6 @@ def test_track_request_failed_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.LIVE_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_time"] = 98 @@ -218,12 +190,12 @@ def test_track_request_failed_rate(self, time_mock): name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_failed_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2 + obs.aggregators[tuple(self._test_labels.items())].current, 2.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -233,7 +205,6 @@ def test_track_request_failed_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.LIVE_METRICS, ) request_metrics.requests_map["last_time"] = None obs = Observer( @@ -241,12 +212,12 @@ def test_track_request_failed_rate_time_none(self, time_mock): name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_failed_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0 + obs.aggregators[tuple(self._test_labels.items())].current, 0.0 ) @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") @@ -255,20 +226,19 @@ def test_track_request_failed_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, - collection_type=AutoCollectionType.LIVE_METRICS, ) time_mock.time.return_value = 100 - request_metrics.requests_map["last_rate"] = 5 + request_metrics.requests_map["last_rate"] = 5.0 request_metrics.requests_map["last_time"] = 100 obs = Observer( callback=request_metrics_collector._track_request_failed_rate, name="test", description="test", unit="test", - value_type=int, + value_type=float, meter=self._meter, ) request_metrics_collector._track_request_failed_rate(obs) self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5 + obs.aggregators[tuple(self._test_labels.items())].current, 5.0 ) From 0a5a2e21e3d7329d252d77fde841e25214bee7d1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Guzman Date: Tue, 9 Jun 2020 16:59:23 -0700 Subject: [PATCH 10/39] Fixing issue with documents parsing --- azure_monitor/src/azure_monitor/protocol.py | 19 ++++----------- .../auto_collection/live_metrics/exporter.py | 14 ++++------- .../live_metrics/test_exporter.py | 24 +++++++------------ 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/azure_monitor/src/azure_monitor/protocol.py b/azure_monitor/src/azure_monitor/protocol.py index 053ccf3..d482b77 100644 --- a/azure_monitor/src/azure_monitor/protocol.py +++ b/azure_monitor/src/azure_monitor/protocol.py @@ -563,15 +563,6 @@ def to_dict(self): } -class LiveMetricDocumentProperty(BaseObject): - - __slots__ = ("key", "value") - - def __init__(self, key: str, value: str) -> None: - self.key = key - self.value = value - - class LiveMetricDocument(BaseObject): __slots__ = ( @@ -588,7 +579,7 @@ def __init__( document_type: str = "", version: str = "", operation_id: str = "", - properties: typing.List[LiveMetricDocumentProperty] = None, + properties: typing.Dict[str, any] = None, ) -> None: self.quickpulse_type = quickpulse_type self.document_type = document_type @@ -602,9 +593,7 @@ def to_dict(self): "DocumentType": self.document_type, "Version": self.version, "OperationId": self.operation_id, - "Properties": self.properties.to_dict() - if self.properties - else None, + "Properties": self.properties, } @@ -675,7 +664,9 @@ def __init__( def to_dict(self): return { - "Documents": self.documents.to_dict() if self.documents else None, + "Documents": list(map(lambda x: x.to_dict(), self.documents)) + if self.documents + else None, "Instance": self.instance, "InstrumentationKey": self.instrumentation_key, "InvariantVersion": self.invariant_version, diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py index 0372ea1..7a265d7 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py @@ -15,7 +15,6 @@ Envelope, LiveMetric, LiveMetricDocument, - LiveMetricDocumentProperty, LiveMetricEnvelope, ) from azure_monitor.sdk.auto_collection.live_metrics import utils @@ -148,25 +147,20 @@ def _get_live_metric_document_type(self, base_type: str) -> str: return "Availability" return "" - def _get_aggregated_properties( - self, envelope: Envelope - ) -> typing.List[LiveMetricDocumentProperty]: - - aggregated_properties = [] + def _get_aggregated_properties(self, envelope: Envelope) -> typing.Dict: + aggregated_properties = {} measurements = ( envelope.data.base_data.measurements if envelope.data.base_data and envelope.data.base_data.measurements else [] ) for key in measurements: - prop = LiveMetricDocumentProperty(key=key, value=measurements[key]) - aggregated_properties.append(prop) + aggregated_properties[key] = measurements[key] properties = ( envelope.data.base_data.properties if envelope.data.base_data and envelope.data.base_data.properties else [] ) for key in properties: - prop = LiveMetricDocumentProperty(key=key, value=properties[key]) - aggregated_properties.append(prop) + aggregated_properties[key] = properties[key] return aggregated_properties diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index b82f1d7..17cbb07 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -199,26 +199,18 @@ def test_live_metric_envelope_documents(self): self.assertEqual(envelope.documents[0].operation_id, "") self.assertEqual(len(envelope.documents[0].properties), 4) self.assertEqual( - envelope.documents[0].properties[0].key, "test_measurement1" + envelope.documents[0].properties["test_measurement1"], + "test_measurement1Value", ) self.assertEqual( - envelope.documents[0].properties[1].key, "test_measurement2" + envelope.documents[0].properties["test_measurement2"], + "test_measurement2Value", ) self.assertEqual( - envelope.documents[0].properties[2].key, "test_property1" + envelope.documents[0].properties["test_property1"], + "test_property1Value", ) self.assertEqual( - envelope.documents[0].properties[3].key, "test_property2" - ) - self.assertEqual( - envelope.documents[0].properties[0].value, "test_measurement1Value" - ) - self.assertEqual( - envelope.documents[0].properties[1].value, "test_measurement2Value" - ) - self.assertEqual( - envelope.documents[0].properties[2].value, "test_property1Value" - ) - self.assertEqual( - envelope.documents[0].properties[3].value, "test_property2Value" + envelope.documents[0].properties["test_property2"], + "test_property2Value", ) From 32d510461709efec5d66e94a3f29a376d1be7f9a Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 16 Jun 2020 15:42:02 -0700 Subject: [PATCH 11/39] fix breaking --- azure_monitor/setup.cfg | 4 +- .../src/azure_monitor/export/__init__.py | 102 ----------------- .../azure_monitor/export/metrics/__init__.py | 33 ++++-- .../azure_monitor/export/trace/__init__.py | 103 +++++++++++++++++- .../sdk/auto_collection/dependency_metrics.py | 4 + .../auto_collection/live_metrics/exporter.py | 20 ++-- .../auto_collection/metrics_span_processor.py | 2 +- .../auto_collection/performance_metrics.py | 8 +- .../sdk/auto_collection/request_metrics.py | 4 + .../live_metrics/test_exporter.py | 50 +++++++-- azure_monitor/tests/metrics/test_metrics.py | 33 +++--- 11 files changed, 210 insertions(+), 153 deletions(-) diff --git a/azure_monitor/setup.cfg b/azure_monitor/setup.cfg index fca7b78..35b6b72 100644 --- a/azure_monitor/setup.cfg +++ b/azure_monitor/setup.cfg @@ -27,8 +27,8 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api ~= 0.7b1 - opentelemetry-sdk ~= 0.7b1 + opentelemetry-api == 0.9b0 + opentelemetry-sdk == 0.9b0 psutil >= 5.6.3 requests ~= 2.0 diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 8460a5d..6de99d8 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -209,105 +209,3 @@ def get_metrics_export_result(result: ExportResult) -> MetricsExportResult: ): return MetricsExportResult.FAILURE return None - - -# pylint: disable=too-many-statements -# pylint: disable=too-many-branches -def convert_span_to_envelope(span: Span) -> protocol.Envelope: - if not span: - return None - envelope = protocol.Envelope( - ikey="", - tags=dict(utils.azure_monitor_context), - time=ns_to_iso_str(span.start_time), - ) - envelope.tags["ai.operation.id"] = "{:032x}".format(span.context.trace_id) - parent = span.parent - if isinstance(parent, Span): - parent = parent.context - if parent: - envelope.tags["ai.operation.parentId"] = "{:016x}".format( - parent.span_id - ) - if span.kind in (SpanKind.CONSUMER, SpanKind.SERVER): - envelope.name = "Microsoft.ApplicationInsights.Request" - data = protocol.Request( - id="{:016x}".format(span.context.span_id), - duration=utils.ns_to_duration(span.end_time - span.start_time), - response_code=str(span.status.canonical_code.value), - success=span.status.canonical_code - == StatusCanonicalCode.OK, # Modify based off attributes or Status - properties={}, - ) - envelope.data = protocol.Data(base_data=data, base_type="RequestData") - if "http.method" in span.attributes: - data.name = span.attributes["http.method"] - if "http.route" in span.attributes: - data.name = data.name + " " + span.attributes["http.route"] - envelope.tags["ai.operation.name"] = data.name - data.properties["request.name"] = data.name - elif "http.path" in span.attributes: - data.properties["request.name"] = ( - data.name + " " + span.attributes["http.path"] - ) - if "http.url" in span.attributes: - data.url = span.attributes["http.url"] - data.properties["request.url"] = span.attributes["http.url"] - if "http.status_code" in span.attributes: - status_code = span.attributes["http.status_code"] - data.response_code = str(status_code) - data.success = 200 <= status_code < 400 - else: - envelope.name = "Microsoft.ApplicationInsights.RemoteDependency" - data = protocol.RemoteDependency( - name=span.name, - id="{:016x}".format(span.context.span_id), - result_code=str(span.status.canonical_code.value), - duration=utils.ns_to_duration(span.end_time - span.start_time), - success=span.status.canonical_code - == StatusCanonicalCode.OK, # Modify based off attributes or Status - properties={}, - ) - envelope.data = protocol.Data( - base_data=data, base_type="RemoteDependencyData" - ) - if span.kind in (SpanKind.CLIENT, SpanKind.PRODUCER): - if ( - "component" in span.attributes - and span.attributes["component"] == "http" - ): - data.type = "HTTP" - if "http.url" in span.attributes: - url = span.attributes["http.url"] - # data is the url - data.data = url - parse_url = urlparse(url) - # TODO: error handling, probably put scheme as well - # target matches authority (host:port) - data.target = parse_url.netloc - if "http.method" in span.attributes: - # name is METHOD/path - data.name = ( - span.attributes["http.method"] + "/" + parse_url.path - ) - if "http.status_code" in span.attributes: - status_code = span.attributes["http.status_code"] - data.result_code = str(status_code) - data.success = 200 <= status_code < 400 - else: # SpanKind.INTERNAL - data.type = "InProc" - data.success = True - for key in span.attributes: - # This removes redundant data from ApplicationInsights - if key.startswith("http."): - continue - data.properties[key] = span.attributes[key] - if span.links: - links = [] - for link in span.links: - operation_id = "{:032x}".format(link.context.trace_id) - span_id = "{:016x}".format(link.context.span_id) - links.append({"operation_Id": operation_id, "id": span_id}) - data.properties["_MS.links"] = json.dumps(links) - # TODO: tracestate, tags - return envelope diff --git a/azure_monitor/src/azure_monitor/export/metrics/__init__.py b/azure_monitor/src/azure_monitor/export/metrics/__init__.py index c0ac9c3..abc0aae 100644 --- a/azure_monitor/src/azure_monitor/export/metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/export/metrics/__init__.py @@ -5,7 +5,14 @@ from typing import Sequence from urllib.parse import urlparse -from opentelemetry.sdk.metrics import Counter, Metric, Observer +from opentelemetry.sdk.metrics import ( + Counter, + SumObserver, + UpDownCounter, + UpDownSumObserver, + ValueRecorder, + ValueObserver +) from opentelemetry.sdk.metrics.export import ( MetricRecord, MetricsExporter, @@ -66,20 +73,22 @@ def _metric_to_envelope( ) envelope.name = "Microsoft.ApplicationInsights.Metric" value = 0 - metric = metric_record.metric - if isinstance(metric, Counter): - value = metric_record.aggregator.checkpoint - elif isinstance(metric, Observer): + metric = metric_record.instrument + if isinstance(metric, ValueObserver): + # mmscl value = metric_record.aggregator.checkpoint.last - if not value: - value = 0 + elif isinstance(metric, ValueRecorder): + # mmsc + value = metric_record.aggregator.checkpoint.count else: - # TODO: What do measure aggregations look like in AI? - logger.warning("Measure metric recorded.") - + # sum or lv + value = metric_record.aggregator.checkpoint + if not value: + logger.warning("Value is none. Default to 0.") + value = 0 data_point = protocol.DataPoint( - ns=metric_record.metric.description, - name=metric_record.metric.name, + ns=metric.description, + name=metric.name, value=value, kind=protocol.DataPointType.MEASUREMENT.value, ) diff --git a/azure_monitor/src/azure_monitor/export/trace/__init__.py b/azure_monitor/src/azure_monitor/export/trace/__init__.py index 1a0e32a..dd433be 100644 --- a/azure_monitor/src/azure_monitor/export/trace/__init__.py +++ b/azure_monitor/src/azure_monitor/export/trace/__init__.py @@ -14,7 +14,6 @@ from azure_monitor.export import ( BaseExporter, ExportResult, - convert_span_to_envelope, get_trace_export_result, ) @@ -56,3 +55,105 @@ def _span_to_envelope(self, span: Span) -> protocol.Envelope: envelope = convert_span_to_envelope(span) envelope.ikey = self.options.instrumentation_key return envelope + + +# pylint: disable=too-many-statements +# pylint: disable=too-many-branches +def convert_span_to_envelope(span: Span) -> protocol.Envelope: + if not span: + return None + envelope = protocol.Envelope( + ikey="", + tags=dict(utils.azure_monitor_context), + time=ns_to_iso_str(span.start_time), + ) + envelope.tags["ai.operation.id"] = "{:032x}".format(span.context.trace_id) + parent = span.parent + if isinstance(parent, Span): + parent = parent.context + if parent: + envelope.tags["ai.operation.parentId"] = "{:016x}".format( + parent.span_id + ) + if span.kind in (SpanKind.CONSUMER, SpanKind.SERVER): + envelope.name = "Microsoft.ApplicationInsights.Request" + data = protocol.Request( + id="{:016x}".format(span.context.span_id), + duration=utils.ns_to_duration(span.end_time - span.start_time), + response_code=str(span.status.canonical_code.value), + success=span.status.canonical_code + == StatusCanonicalCode.OK, # Modify based off attributes or Status + properties={}, + ) + envelope.data = protocol.Data(base_data=data, base_type="RequestData") + if "http.method" in span.attributes: + data.name = span.attributes["http.method"] + if "http.route" in span.attributes: + data.name = data.name + " " + span.attributes["http.route"] + envelope.tags["ai.operation.name"] = data.name + data.properties["request.name"] = data.name + elif "http.path" in span.attributes: + data.properties["request.name"] = ( + data.name + " " + span.attributes["http.path"] + ) + if "http.url" in span.attributes: + data.url = span.attributes["http.url"] + data.properties["request.url"] = span.attributes["http.url"] + if "http.status_code" in span.attributes: + status_code = span.attributes["http.status_code"] + data.response_code = str(status_code) + data.success = 200 <= status_code < 400 + else: + envelope.name = "Microsoft.ApplicationInsights.RemoteDependency" + data = protocol.RemoteDependency( + name=span.name, + id="{:016x}".format(span.context.span_id), + result_code=str(span.status.canonical_code.value), + duration=utils.ns_to_duration(span.end_time - span.start_time), + success=span.status.canonical_code + == StatusCanonicalCode.OK, # Modify based off attributes or Status + properties={}, + ) + envelope.data = protocol.Data( + base_data=data, base_type="RemoteDependencyData" + ) + if span.kind in (SpanKind.CLIENT, SpanKind.PRODUCER): + if ( + "component" in span.attributes + and span.attributes["component"] == "http" + ): + data.type = "HTTP" + if "http.url" in span.attributes: + url = span.attributes["http.url"] + # data is the url + data.data = url + parse_url = urlparse(url) + # TODO: error handling, probably put scheme as well + # target matches authority (host:port) + data.target = parse_url.netloc + if "http.method" in span.attributes: + # name is METHOD/path + data.name = ( + span.attributes["http.method"] + "/" + parse_url.path + ) + if "http.status_code" in span.attributes: + status_code = span.attributes["http.status_code"] + data.result_code = str(status_code) + data.success = 200 <= status_code < 400 + else: # SpanKind.INTERNAL + data.type = "InProc" + data.success = True + for key in span.attributes: + # This removes redundant data from ApplicationInsights + if key.startswith("http."): + continue + data.properties[key] = span.attributes[key] + if span.links: + links = [] + for link in span.links: + operation_id = "{:032x}".format(link.context.trace_id) + span_id = "{:016x}".format(link.context.span_id) + links.append({"operation_Id": operation_id, "id": span_id}) + data.properties["_MS.links"] = json.dumps(links) + # TODO: tracestate, tags + return envelope diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py index 47850c3..b6dea9e 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/dependency_metrics.py @@ -4,6 +4,7 @@ from typing import Dict from opentelemetry.metrics import Meter, Observer +from opentelemetry.sdk.metrics import UpDownSumObserver from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, @@ -39,6 +40,7 @@ def __init__( description="Average Outgoing Requests duration", unit="milliseconds", value_type=int, + observer_type=UpDownSumObserver, ) meter.register_observer( callback=self._track_failure_rate, @@ -46,6 +48,7 @@ def __init__( description="Failed Outgoing Requests per second", unit="rps", value_type=float, + observer_type=UpDownSumObserver, ) meter.register_observer( callback=self._track_dependency_rate, @@ -53,6 +56,7 @@ def __init__( description="Outgoing Requests per second", unit="rps", value_type=float, + observer_type=UpDownSumObserver, ) def _track_dependency_rate(self, observer: Observer) -> None: diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py index 7a265d7..b1d61d0 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py @@ -4,7 +4,7 @@ import logging import typing -from opentelemetry.sdk.metrics import Counter, Observer +from opentelemetry.sdk.metrics import ValueObserver, ValueRecorder from opentelemetry.sdk.metrics.export import ( MetricRecord, MetricsExporter, @@ -76,13 +76,19 @@ def _metric_to_live_metrics_envelope( # Add metrics for metric_record in metric_records: value = 0 - metric = metric_record.metric - if isinstance(metric, Counter): - value = metric_record.aggregator.checkpoint - elif isinstance(metric, Observer): + metric = metric_record.instrument + if isinstance(metric, ValueObserver): + # mmscl value = metric_record.aggregator.checkpoint.last - if not value: - value = 0 + elif isinstance(metric, ValueRecorder): + # mmsc + value = metric_record.aggregator.checkpoint.count + else: + # sum or lv + value = metric_record.aggregator.checkpoint + if not value: + logger.warning("Value is none. Default to 0.") + value = 0 envelope.metrics.append( LiveMetric(name=metric.name, value=value, weight=1) ) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py index 1d007dd..660809e 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/metrics_span_processor.py @@ -6,7 +6,7 @@ from opentelemetry.sdk.trace import Span, SpanProcessor from opentelemetry.trace import SpanKind -from azure_monitor.export import convert_span_to_envelope +from azure_monitor.export.trace import convert_span_to_envelope logger = logging.getLogger(__name__) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py index 59c335b..7c5f458 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/performance_metrics.py @@ -5,6 +5,7 @@ import psutil from opentelemetry.metrics import Meter, Observer +from opentelemetry.sdk.metrics import UpDownSumObserver from azure_monitor.sdk.auto_collection.utils import AutoCollectionType @@ -39,6 +40,7 @@ def __init__( description="Processor time as a percentage", unit="percentage", value_type=float, + observer_type=UpDownSumObserver, ) if collection_type == AutoCollectionType.STANDARD_METRICS: @@ -48,6 +50,7 @@ def __init__( description="Amount of available memory in bytes", unit="byte", value_type=int, + observer_type=UpDownSumObserver, ) self._meter.register_observer( callback=self._track_process_cpu, @@ -55,6 +58,7 @@ def __init__( description="Process CPU usage as a percentage", unit="percentage", value_type=float, + observer_type=UpDownSumObserver, ) self._meter.register_observer( callback=self._track_process_memory, @@ -62,14 +66,16 @@ def __init__( description="Amount of memory process has used in bytes", unit="byte", value_type=int, + observer_type=UpDownSumObserver, ) if collection_type == AutoCollectionType.LIVE_METRICS: self._meter.register_observer( callback=self._track_commited_memory, name="\\Memory\\Committed Bytes", - description="Amount of commited memory in bytes", + description="Amount of committed memory in bytes", unit="byte", value_type=int, + observer_type=UpDownSumObserver, ) def _track_cpu(self, observer: Observer) -> None: diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index e1a3809..24d70de 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -5,6 +5,7 @@ from typing import Dict from opentelemetry.metrics import Meter, Observer +from opentelemetry.sdk.metrics import UpDownSumObserver from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, @@ -42,6 +43,7 @@ def __init__( description="Incoming Requests Failed Rate", unit="rps", value_type=float, + observer_type=UpDownSumObserver, ) meter.register_observer( callback=self._track_request_duration, @@ -49,6 +51,7 @@ def __init__( description="Incoming Requests Average Execution Time", unit="milliseconds", value_type=int, + observer_type=UpDownSumObserver, ) meter.register_observer( callback=self._track_request_rate, @@ -56,6 +59,7 @@ def __init__( description="Incoming Requests Rate", unit="rps", value_type=float, + observer_type=UpDownSumObserver, ) def _track_request_duration(self, observer: Observer) -> None: diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index 17cbb07..a2ae092 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -6,11 +6,17 @@ import requests from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics import ( + Counter, + MeterProvider, + ValueObserver, + ValueRecorder +) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( CounterAggregator, - ObserverAggregator, + ValueObserverAggregator, + MinMaxSumCountAggregator, ) from azure_monitor.protocol import ( @@ -44,13 +50,16 @@ def setUpClass(cls): cls._test_metric = cls._meter.create_metric( "testname", "testdesc", "unit", int, Counter, ["environment"] ) + cls._test_metric2 = cls._meter.create_metric( + "testname", "testdesc", "unit", int, ValueRecorder, ["environment"] + ) cls._test_obs = cls._meter.register_observer( lambda x: x, "testname", "testdesc", "unit", int, - Counter, + ValueObserver, ["environment"], ) cls._test_labels = tuple({"environment": "staging"}.items()) @@ -70,7 +79,7 @@ def test_constructor(self): def test_export(self): """Test export.""" record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + self._test_metric, self._test_labels, CounterAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -87,7 +96,7 @@ def test_export(self): def test_export_failed(self): record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + self._test_metric, self._test_labels, CounterAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -104,7 +113,7 @@ def test_export_failed(self): def test_export_exception(self): record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + self._test_metric, self._test_labels, CounterAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -117,11 +126,11 @@ def test_export_exception(self): result = exporter.export([record]) self.assertEqual(result, MetricsExportResult.FAILURE) - def test_live_metric_envelope_counter(self): - aggregator = ObserverAggregator() + def test_live_metric_envelope_observer(self): + aggregator = ValueObserverAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_obs) + record = MetricRecord(self._test_obs, self._test_labels, aggregator) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, span_processor=self._span_processor, @@ -138,11 +147,11 @@ def test_live_metric_envelope_counter(self): self.assertEqual(envelope.metrics[0].value, 123) self.assertEqual(envelope.metrics[0].weight, 1) - def test_live_metric_envelope_observer(self): + def test_live_metric_envelope_counter(self): aggregator = CounterAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_metric) + record = MetricRecord(self._test_metric, self._test_labels, aggregator) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, span_processor=self._span_processor, @@ -155,11 +164,28 @@ def test_live_metric_envelope_observer(self): self.assertEqual(envelope.metrics[0].value, 123) self.assertEqual(envelope.metrics[0].weight, 1) + def test_live_metric_envelope_value_recorder(self): + aggregator = MinMaxSumCountAggregator() + aggregator.update(123) + aggregator.take_checkpoint() + record = MetricRecord(self._test_metric2, self._test_labels, aggregator) + exporter = LiveMetricsExporter( + instrumentation_key=self._instrumentation_key, + span_processor=self._span_processor, + ) + + envelope = exporter._metric_to_live_metrics_envelope([record]) + self.assertIsInstance(envelope, LiveMetricEnvelope) + self.assertEqual(envelope.documents, []) + self.assertEqual(envelope.metrics[0].name, "testname") + self.assertEqual(envelope.metrics[0].value, 1) + self.assertEqual(envelope.metrics[0].weight, 1) + def test_live_metric_envelope_documents(self): aggregator = CounterAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_metric) + record = MetricRecord(self._test_metric, self._test_labels, aggregator) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, span_processor=self._span_processor, diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index aa7910d..62c6bb2 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -6,12 +6,17 @@ from unittest import mock from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, Measure, MeterProvider +from opentelemetry.sdk.metrics import ( + Counter, + ValueRecorder, + MeterProvider, + ValueObserver +) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( CounterAggregator, MinMaxSumCountAggregator, - ObserverAggregator, + ValueObserverAggregator, ) from opentelemetry.sdk.util import ns_to_iso_str @@ -55,8 +60,8 @@ def setUpClass(cls): cls._test_metric = cls._meter.create_metric( "testname", "testdesc", "unit", int, Counter, ["environment"] ) - cls._test_measure = cls._meter.create_metric( - "testname", "testdesc", "unit", int, Measure, ["environment"] + cls._test_value_recorder = cls._meter.create_metric( + "testname", "testdesc", "unit", int, ValueRecorder, ["environment"] ) cls._test_obs = cls._meter.register_observer( lambda x: x, @@ -64,7 +69,7 @@ def setUpClass(cls): "testdesc", "unit", int, - Counter, + ValueObserver, ["environment"], ) cls._test_labels = tuple({"environment": "staging"}.items()) @@ -157,7 +162,7 @@ def test_metric_to_envelope(self): aggregator = CounterAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_metric) + record = MetricRecord(self._test_metric, self._test_labels, aggregator) exporter = self._exporter envelope = exporter._metric_to_envelope(record) self.assertIsInstance(envelope, Envelope) @@ -191,10 +196,10 @@ def test_metric_to_envelope(self): self.assertIsNotNone(envelope.tags["ai.internal.sdkVersion"]) def test_observer_to_envelope(self): - aggregator = ObserverAggregator() + aggregator = ValueObserverAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_obs) + record = MetricRecord(self._test_obs, self._test_labels, aggregator) print(record.labels) exporter = self._exporter envelope = exporter._metric_to_envelope(record) @@ -229,10 +234,10 @@ def test_observer_to_envelope(self): self.assertIsNotNone(envelope.tags["ai.internal.sdkVersion"]) def test_observer_to_envelope_value_none(self): - aggregator = ObserverAggregator() + aggregator = ValueObserverAggregator() aggregator.update(None) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._test_labels, self._test_obs) + record = MetricRecord(self._test_obs, self._test_labels, aggregator) exporter = self._exporter envelope = exporter._metric_to_envelope(record) self.assertIsInstance(envelope, Envelope) @@ -266,13 +271,12 @@ def test_observer_to_envelope_value_none(self): self.assertIsNotNone(envelope.tags["ai.device.type"]) self.assertIsNotNone(envelope.tags["ai.internal.sdkVersion"]) - @mock.patch("azure_monitor.export.metrics.logger") - def test_measure_to_envelope(self, logger_mock): + def test_value_recorder_to_envelope(self): aggregator = MinMaxSumCountAggregator() aggregator.update(123) aggregator.take_checkpoint() record = MetricRecord( - aggregator, self._test_labels, self._test_measure + self._test_value_recorder, self._test_labels, aggregator ) exporter = self._exporter envelope = exporter._metric_to_envelope(record) @@ -294,7 +298,7 @@ def test_measure_to_envelope(self, logger_mock): self.assertIsInstance(envelope.data.base_data.metrics[0], DataPoint) self.assertEqual(envelope.data.base_data.metrics[0].ns, "testdesc") self.assertEqual(envelope.data.base_data.metrics[0].name, "testname") - self.assertEqual(envelope.data.base_data.metrics[0].value, 0) + self.assertEqual(envelope.data.base_data.metrics[0].value, 1) self.assertEqual( envelope.data.base_data.properties["environment"], "staging" ) @@ -305,4 +309,3 @@ def test_measure_to_envelope(self, logger_mock): self.assertIsNotNone(envelope.tags["ai.device.osVersion"]) self.assertIsNotNone(envelope.tags["ai.device.type"]) self.assertIsNotNone(envelope.tags["ai.internal.sdkVersion"]) - self.assertEqual(logger_mock.warning.called, True) From 031847d0f9f2c05e4a1fe89c7368205f3303ab6c Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 16 Jun 2020 23:16:44 -0700 Subject: [PATCH 12/39] fix examples --- azure_monitor/examples/metrics/auto_collector.py | 13 ++++--------- azure_monitor/examples/metrics/observer.py | 7 ++++--- azure_monitor/examples/metrics/simple.py | 3 +-- .../src/azure_monitor/export/metrics/__init__.py | 2 +- .../sdk/auto_collection/live_metrics/exporter.py | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/azure_monitor/examples/metrics/auto_collector.py b/azure_monitor/examples/metrics/auto_collector.py index d473c7e..fd7617c 100644 --- a/azure_monitor/examples/metrics/auto_collector.py +++ b/azure_monitor/examples/metrics/auto_collector.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from opentelemetry import metrics, trace from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export.controller import PushController from opentelemetry.sdk.trace import TracerProvider from azure_monitor import AzureMonitorMetricsExporter @@ -12,17 +11,16 @@ ) # Add Span Processor to get metrics about traces +trace.set_tracer_provider(TracerProvider()) +tracer = trace.get_tracer_provider().get_tracer(__name__) span_processor = AzureMetricsSpanProcessor() -tracer_provider = TracerProvider() -tracer_provider.add_span_processor(span_processor) -trace.set_tracer_provider(tracer_provider) +trace.get_tracer_provider().add_span_processor(span_processor) metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) exporter = AzureMonitorMetricsExporter( connection_string="InstrumentationKey=" ) -controller = PushController(meter, exporter, 5) testing_label_set = {"environment": "testing"} @@ -31,9 +29,6 @@ meter=meter, labels=testing_label_set, span_processor=span_processor ) -# To configure a separate export interval specific for standard metrics -# meter_standard = metrics.get_meter(__name__ + "_standard") -# controller _standard = PushController(meter_standard, exporter, 30) -# _auto_collection = AutoCollection(meter=meter_standard, label_set=testing_label_set) +metrics.get_meter_provider().start_pipeline(meter, exporter, 2) input("Press any key to exit...") diff --git a/azure_monitor/examples/metrics/observer.py b/azure_monitor/examples/metrics/observer.py index 1ce8332..52f21ac 100644 --- a/azure_monitor/examples/metrics/observer.py +++ b/azure_monitor/examples/metrics/observer.py @@ -2,8 +2,7 @@ # Licensed under the MIT License. import psutil from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export.controller import PushController +from opentelemetry.sdk.metrics import MeterProvider, UpDownSumObserver from azure_monitor import AzureMonitorMetricsExporter @@ -12,7 +11,7 @@ exporter = AzureMonitorMetricsExporter( connection_string="InstrumentationKey=" ) -controller = PushController(meter=meter, exporter=exporter, interval=2) +metrics.get_meter_provider().start_pipeline(meter, exporter, 2) # Callback to gather cpu usage @@ -28,6 +27,7 @@ def get_cpu_usage_callback(observer): description="per-cpu usage", unit="1", value_type=float, + observer_type=UpDownSumObserver, label_keys=("cpu_number",), ) @@ -44,6 +44,7 @@ def get_ram_usage_callback(observer): description="RAM memory usage", unit="1", value_type=float, + observer_type=UpDownSumObserver, label_keys=(), ) diff --git a/azure_monitor/examples/metrics/simple.py b/azure_monitor/examples/metrics/simple.py index 5c11f79..34c7fd1 100644 --- a/azure_monitor/examples/metrics/simple.py +++ b/azure_monitor/examples/metrics/simple.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from opentelemetry import metrics from opentelemetry.sdk.metrics import Counter, MeterProvider -from opentelemetry.sdk.metrics.export.controller import PushController from azure_monitor import AzureMonitorMetricsExporter @@ -11,7 +10,7 @@ exporter = AzureMonitorMetricsExporter( connection_string="InstrumentationKey=" ) -controller = PushController(meter, exporter, 5) +metrics.get_meter_provider().start_pipeline(meter, exporter, 5) requests_counter = meter.create_metric( name="requests", diff --git a/azure_monitor/src/azure_monitor/export/metrics/__init__.py b/azure_monitor/src/azure_monitor/export/metrics/__init__.py index abc0aae..6740b38 100644 --- a/azure_monitor/src/azure_monitor/export/metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/export/metrics/__init__.py @@ -83,7 +83,7 @@ def _metric_to_envelope( else: # sum or lv value = metric_record.aggregator.checkpoint - if not value: + if value is None: logger.warning("Value is none. Default to 0.") value = 0 data_point = protocol.DataPoint( diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py index b1d61d0..4c9e859 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py @@ -86,7 +86,7 @@ def _metric_to_live_metrics_envelope( else: # sum or lv value = metric_record.aggregator.checkpoint - if not value: + if value is None: logger.warning("Value is none. Default to 0.") value = 0 envelope.metrics.append( From 03eb98e6e6fbc50a9bcc5170c317b7d15ef30820 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 16 Jun 2020 23:21:29 -0700 Subject: [PATCH 13/39] black --- azure_monitor/src/azure_monitor/export/metrics/__init__.py | 2 +- .../tests/auto_collection/live_metrics/test_exporter.py | 6 ++++-- azure_monitor/tests/metrics/test_metrics.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/metrics/__init__.py b/azure_monitor/src/azure_monitor/export/metrics/__init__.py index 6740b38..4ddd5c7 100644 --- a/azure_monitor/src/azure_monitor/export/metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/export/metrics/__init__.py @@ -11,7 +11,7 @@ UpDownCounter, UpDownSumObserver, ValueRecorder, - ValueObserver + ValueObserver, ) from opentelemetry.sdk.metrics.export import ( MetricRecord, diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index a2ae092..cdad4d8 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -10,7 +10,7 @@ Counter, MeterProvider, ValueObserver, - ValueRecorder + ValueRecorder, ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( @@ -168,7 +168,9 @@ def test_live_metric_envelope_value_recorder(self): aggregator = MinMaxSumCountAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(self._test_metric2, self._test_labels, aggregator) + record = MetricRecord( + self._test_metric2, self._test_labels, aggregator + ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, span_processor=self._span_processor, diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index 62c6bb2..645167c 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -10,7 +10,7 @@ Counter, ValueRecorder, MeterProvider, - ValueObserver + ValueObserver, ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( From b27d6b860164b569d773d94ef62b2b413cafadf7 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 16 Jun 2020 23:25:14 -0700 Subject: [PATCH 14/39] isort --- azure_monitor/src/azure_monitor/export/metrics/__init__.py | 2 +- .../tests/auto_collection/live_metrics/test_exporter.py | 2 +- azure_monitor/tests/metrics/test_metrics.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/metrics/__init__.py b/azure_monitor/src/azure_monitor/export/metrics/__init__.py index 4ddd5c7..6aab0f6 100644 --- a/azure_monitor/src/azure_monitor/export/metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/export/metrics/__init__.py @@ -10,8 +10,8 @@ SumObserver, UpDownCounter, UpDownSumObserver, - ValueRecorder, ValueObserver, + ValueRecorder, ) from opentelemetry.sdk.metrics.export import ( MetricRecord, diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index cdad4d8..21b1f90 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -15,8 +15,8 @@ from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( CounterAggregator, - ValueObserverAggregator, MinMaxSumCountAggregator, + ValueObserverAggregator, ) from azure_monitor.protocol import ( diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index 645167c..a6c9ea1 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -8,9 +8,9 @@ from opentelemetry import metrics from opentelemetry.sdk.metrics import ( Counter, - ValueRecorder, MeterProvider, ValueObserver, + ValueRecorder, ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( From 732df5e7968e8c7165cb06397b4b7c74f9c7e0c4 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 13:34:29 -0700 Subject: [PATCH 15/39] remove dependency metrics --- azure_monitor/CHANGELOG.md | 3 +++ .../src/azure_monitor/sdk/auto_collection/__init__.py | 3 --- azure_monitor/tests/auto_collection/test_auto_collection.py | 6 ------ 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 7534ca4..2c0131e 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Remove dependency metrics from auto-collection +- ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) + ## 0.3b.1 Released 2020-05-21 diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 6c4d05e..4c33a14 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -44,7 +44,4 @@ def __init__( ): col_type = AutoCollectionType.STANDARD_METRICS self._performance_metrics = PerformanceMetrics(meter, labels, col_type) - self._dependency_metrics = DependencyMetrics( - meter, labels, span_processor - ) self._request_metrics = RequestMetrics(meter, labels, span_processor) diff --git a/azure_monitor/tests/auto_collection/test_auto_collection.py b/azure_monitor/tests/auto_collection/test_auto_collection.py index 25c1c81..e853598 100644 --- a/azure_monitor/tests/auto_collection/test_auto_collection.py +++ b/azure_monitor/tests/auto_collection/test_auto_collection.py @@ -29,9 +29,6 @@ def tearDownClass(cls): @mock.patch( "azure_monitor.sdk.auto_collection.PerformanceMetrics", autospec=True ) - @mock.patch( - "azure_monitor.sdk.auto_collection.DependencyMetrics", autospec=True - ) @mock.patch( "azure_monitor.sdk.auto_collection.RequestMetrics", autospec=True ) @@ -46,11 +43,8 @@ def test_constructor( span_processor=self._span_processor, ) self.assertEqual(mock_performance.called, True) - self.assertEqual(mock_dependencies.called, True) self.assertEqual(mock_requests.called, True) self.assertEqual(mock_performance.call_args[0][0], self._meter) self.assertEqual(mock_performance.call_args[0][1], self._test_labels) - self.assertEqual(mock_dependencies.call_args[0][0], self._meter) - self.assertEqual(mock_dependencies.call_args[0][1], self._test_labels) self.assertEqual(mock_requests.call_args[0][0], self._meter) self.assertEqual(mock_requests.call_args[0][1], self._test_labels) From 24f8a591dc513589bde79b3472cc7b6005769c38 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 13:51:35 -0700 Subject: [PATCH 16/39] fix test --- azure_monitor/tests/auto_collection/test_auto_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_monitor/tests/auto_collection/test_auto_collection.py b/azure_monitor/tests/auto_collection/test_auto_collection.py index e853598..d9bc07e 100644 --- a/azure_monitor/tests/auto_collection/test_auto_collection.py +++ b/azure_monitor/tests/auto_collection/test_auto_collection.py @@ -33,7 +33,7 @@ def tearDownClass(cls): "azure_monitor.sdk.auto_collection.RequestMetrics", autospec=True ) def test_constructor( - self, mock_performance, mock_dependencies, mock_requests + self, mock_requests, mock_performance ): """Test the constructor.""" From 039fc584732ed52e9667ea4417e10e864b82ddf9 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 13:54:46 -0700 Subject: [PATCH 17/39] lint --- azure_monitor/tests/auto_collection/test_auto_collection.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/azure_monitor/tests/auto_collection/test_auto_collection.py b/azure_monitor/tests/auto_collection/test_auto_collection.py index d9bc07e..40c2f8d 100644 --- a/azure_monitor/tests/auto_collection/test_auto_collection.py +++ b/azure_monitor/tests/auto_collection/test_auto_collection.py @@ -32,9 +32,7 @@ def tearDownClass(cls): @mock.patch( "azure_monitor.sdk.auto_collection.RequestMetrics", autospec=True ) - def test_constructor( - self, mock_requests, mock_performance - ): + def test_constructor(self, mock_requests, mock_performance): """Test the constructor.""" AutoCollection( From 62d607d314313d3d06d2ccab62fa13052eebbd01 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 17:05:06 -0700 Subject: [PATCH 18/39] storage --- .../src/azure_monitor/export/__init__.py | 12 +++-- azure_monitor/src/azure_monitor/options.py | 18 +++++--- azure_monitor/src/azure_monitor/storage.py | 34 ++++++-------- azure_monitor/tests/test_base_exporter.py | 12 +++++ azure_monitor/tests/test_storage.py | 46 +++++++------------ 5 files changed, 63 insertions(+), 59 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 6de99d8..2e09f36 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -95,12 +95,12 @@ def _transmit_from_storage(self) -> None: # give a few more seconds for blob lease operation # to reduce the chance of race (for perf consideration) if blob.lease(self.options.timeout + 5): - envelopes = blob.get() # TODO: handle error + envelopes = blob.get() result = self._transmit(envelopes) if result == ExportResult.FAILED_RETRYABLE: blob.lease(1) else: - blob.delete(silent=True) + blob.delete() # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks @@ -122,8 +122,14 @@ def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult: }, timeout=self.options.timeout, ) + except requests.Timeout: + logger.warning( + 'Request time out. Ingestion may be backed up. Retrying.') + return ExportResult.FAILED_RETRYABLE except Exception as ex: - logger.warning("Transient client side error: %s.", ex) + logger.warning( + 'Retrying due to transient client side error %s.', ex) + # client side error (retryable) return ExportResult.FAILED_RETRYABLE text = "N/A" diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 739ad37..86668ea 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -9,6 +9,7 @@ INGESTION_ENDPOINT = "ingestionendpoint" INSTRUMENTATION_KEY = "instrumentationkey" +TEMPDIR_PREFIX = "opentelemetry-python-" # Validate UUID format # Specs taken from https://tools.ietf.org/html/rfc4122 @@ -55,13 +56,6 @@ def __init__( storage_retention_period: int = 7 * 24 * 60 * 60, timeout: int = 10.0, # networking timeout in seconds ) -> None: - if storage_path is None: - storage_path = os.path.join( - os.path.expanduser("~"), - ".opentelemetry", - ".azure", - os.path.basename(sys.argv[0]) or ".console", - ) self.connection_string = connection_string self.instrumentation_key = instrumentation_key self.storage_maintenance_period = storage_maintenance_period @@ -74,6 +68,7 @@ def __init__( self._validate_instrumentation_key() def _initialize(self) -> None: + # cs and ikey code_cs = parse_connection_string(self.connection_string) code_ikey = self.instrumentation_key env_cs = parse_connection_string( @@ -103,6 +98,15 @@ def _initialize(self) -> None: ) self.endpoint = endpoint + "/v2/track" + # storage path + if self.storage_path is None: + TEMPDIR_SUFFIX = self.instrumentation_key or "" + self.storage_path = os.path.join( + tempfile.gettempdir(), + TEMPDIR_PREFIX + TEMPDIR_SUFFIX + ) + + def _validate_instrumentation_key(self) -> None: """Validates the instrumentation key used for Azure Monitor. An instrumentation key cannot be null or empty. An instrumentation key diff --git a/azure_monitor/src/azure_monitor/storage.py b/azure_monitor/src/azure_monitor/storage.py index 08ff2d0..714aed7 100644 --- a/azure_monitor/src/azure_monitor/storage.py +++ b/azure_monitor/src/azure_monitor/storage.py @@ -29,24 +29,22 @@ class LocalFileBlob: def __init__(self, fullpath): self.fullpath = fullpath - def delete(self, silent=False): + def delete(self): try: os.remove(self.fullpath) except Exception: - if not silent: - raise + pass # keep silent - def get(self, silent=False): + def get(self): try: with open(self.fullpath, "r") as file: return tuple( json.loads(line.strip()) for line in file.readlines() ) except Exception: - if not silent: - raise + pass # keep silent - def put(self, data, lease_period=0, silent=False): + def put(self, data, lease_period=0): try: fullpath = self.fullpath + ".tmp" with open(fullpath, "w") as file: @@ -62,8 +60,7 @@ def put(self, data, lease_period=0, silent=False): os.rename(fullpath, self.fullpath) return self except Exception: - if not silent: - raise + pass # keep silent def lease(self, period): timestamp = _now() + _seconds(period) @@ -94,11 +91,10 @@ def __init__( self.maintenance_period = maintenance_period self.retention_period = retention_period self.write_timeout = write_timeout - self._maintenance_routine(silent=False) + self._maintenance_routine() self._maintenance_task = PeriodicTask( interval=self.maintenance_period, function=self._maintenance_routine, - kwargs={"silent": True}, ) self._maintenance_task.daemon = True self._maintenance_task.start() @@ -115,20 +111,18 @@ def __exit__(self, type, value, traceback): self.close() # pylint: disable=unused-variable - def _maintenance_routine(self, silent=False): + def _maintenance_routine(self): try: if not os.path.isdir(self.path): - os.makedirs(self.path) + os.makedirs(self.path, exist_ok=True) except Exception: - if not silent: - raise + pass # keep silent try: # pylint: disable=unused-variable for blob in self.gets(): - pass + pass # keep silent except Exception: - if not silent: - raise + pass # keep silent def gets(self): now = _now() @@ -171,7 +165,7 @@ def get(self): pass return None - def put(self, data, lease_period=0, silent=False): + def put(self, data, lease_period=0): if not self._check_storage_size(): return None blob = LocalFileBlob( @@ -185,7 +179,7 @@ def put(self, data, lease_period=0, silent=False): ), ) ) - return blob.put(data, lease_period=lease_period, silent=silent) + return blob.put(data, lease_period=lease_period) def _check_storage_size(self): size = 0 diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index 5907939..1c4d760 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -3,6 +3,7 @@ import json import os +import requests import shutil import unittest from unittest import mock @@ -165,6 +166,17 @@ def test_transmission_nothing(self): post.return_value = None exporter._transmit_from_storage() + def test_transmit_request_timeout(self): + exporter = BaseExporter( + storage_path=os.path.join(TEST_FOLDER, self.id()) + ) + envelopes_to_export = map(lambda x: x.to_dict(), tuple([Envelope()])) + exporter.storage.put(envelopes_to_export) + with mock.patch("requests.post", throw(requests.Timeout)): + exporter._transmit_from_storage() + self.assertIsNone(exporter.storage.get()) + self.assertEqual(len(os.listdir(exporter.storage.path)), 1) + def test_transmit_request_exception(self): exporter = BaseExporter( storage_path=os.path.join(TEST_FOLDER, self.id()) diff --git a/azure_monitor/tests/test_storage.py b/azure_monitor/tests/test_storage.py index 4399444..480f261 100644 --- a/azure_monitor/tests/test_storage.py +++ b/azure_monitor/tests/test_storage.py @@ -36,39 +36,37 @@ def func(*_args, **_kwargs): class TestLocalFileBlob(unittest.TestCase): def test_delete(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar")) - blob.delete(silent=True) - self.assertRaises(Exception, blob.delete) - self.assertRaises(Exception, lambda: blob.delete(silent=False)) + blob.delete() + blob.delete() def test_get(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar")) - self.assertIsNone(blob.get(silent=True)) - self.assertRaises(Exception, blob.get) - self.assertRaises(Exception, lambda: blob.get(silent=False)) + self.assertIsNone(blob.get()) + blob.get() def test_put_error(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar")) with mock.patch("os.rename", side_effect=throw(Exception)): - self.assertRaises(Exception, lambda: blob.put([1, 2, 3])) + blob.put([1, 2, 3]) def test_put_without_lease(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar.blob")) test_input = (1, 2, 3) - blob.delete(silent=True) + blob.delete() blob.put(test_input) self.assertEqual(blob.get(), test_input) def test_put_with_lease(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar.blob")) test_input = (1, 2, 3) - blob.delete(silent=True) + blob.delete() blob.put(test_input, lease_period=0.01) blob.lease(0.01) self.assertEqual(blob.get(), test_input) def test_lease_error(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar.blob")) - blob.delete(silent=True) + blob.delete() self.assertEqual(blob.lease(0.01), None) @@ -105,8 +103,7 @@ def test_put(self): with LocalFileStorage(os.path.join(TEST_FOLDER, "bar")) as stor: self.assertEqual(stor.get().get(), test_input) with mock.patch("os.rename", side_effect=throw(Exception)): - self.assertIsNone(stor.put(test_input, silent=True)) - self.assertRaises(Exception, lambda: stor.put(test_input)) + self.assertIsNone(stor.put(test_input)) def test_put_max_size(self): test_input = (1, 2, 3) @@ -152,25 +149,16 @@ def test_check_storage_size_error(self): def test_maintanence_routine(self): with mock.patch("os.makedirs") as m: - m.return_value = None - self.assertRaises( - Exception, - lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")), - ) + with LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) as stor: + m.return_value = None with mock.patch("os.makedirs", side_effect=throw(Exception)): - self.assertRaises( - Exception, - lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")), - ) + stor = LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) + stor.close() with mock.patch("os.listdir", side_effect=throw(Exception)): - self.assertRaises( - Exception, - lambda: LocalFileStorage(os.path.join(TEST_FOLDER, "baz")), - ) + stor = LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) + stor.close() with LocalFileStorage(os.path.join(TEST_FOLDER, "baz")) as stor: with mock.patch("os.listdir", side_effect=throw(Exception)): - stor._maintenance_routine(silent=True) - self.assertRaises(Exception, stor._maintenance_routine) + stor._maintenance_routine() with mock.patch("os.path.isdir", side_effect=throw(Exception)): - stor._maintenance_routine(silent=True) - self.assertRaises(Exception, stor._maintenance_routine) + stor._maintenance_routine() From 3c72faea18c1eb0c80502989825cd40e52b6bbb7 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 17:10:11 -0700 Subject: [PATCH 19/39] black --- azure_monitor/src/azure_monitor/export/__init__.py | 6 ++++-- azure_monitor/src/azure_monitor/options.py | 7 +++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 2e09f36..81fba2a 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -124,11 +124,13 @@ def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult: ) except requests.Timeout: logger.warning( - 'Request time out. Ingestion may be backed up. Retrying.') + "Request time out. Ingestion may be backed up. Retrying." + ) return ExportResult.FAILED_RETRYABLE except Exception as ex: logger.warning( - 'Retrying due to transient client side error %s.', ex) + "Retrying due to transient client side error %s.", ex + ) # client side error (retryable) return ExportResult.FAILED_RETRYABLE diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 86668ea..5dc80aa 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -3,6 +3,7 @@ import os import re import sys +import tempfile import typing from azure_monitor.protocol import BaseObject @@ -102,10 +103,8 @@ def _initialize(self) -> None: if self.storage_path is None: TEMPDIR_SUFFIX = self.instrumentation_key or "" self.storage_path = os.path.join( - tempfile.gettempdir(), - TEMPDIR_PREFIX + TEMPDIR_SUFFIX - ) - + tempfile.gettempdir(), TEMPDIR_PREFIX + TEMPDIR_SUFFIX + ) def _validate_instrumentation_key(self) -> None: """Validates the instrumentation key used for Azure Monitor. From 106e3a9e67e586162b2a6c0e33918fa8329966eb Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 17:24:18 -0700 Subject: [PATCH 20/39] isort --- azure_monitor/tests/test_base_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index 1c4d760..8ba87a3 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -3,11 +3,11 @@ import json import os -import requests import shutil import unittest from unittest import mock +import requests from opentelemetry.sdk.metrics.export import MetricsExportResult from opentelemetry.sdk.trace.export import SpanExportResult From 88574680d740a6bd5bb8a51dd46cde550ab1cd45 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 18:26:38 -0700 Subject: [PATCH 21/39] lint --- azure_monitor/CHANGELOG.md | 4 +++- azure_monitor/src/azure_monitor/export/__init__.py | 1 + azure_monitor/src/azure_monitor/options.py | 5 ++--- azure_monitor/tests/test_storage.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 2c0131e..42596cd 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -3,7 +3,9 @@ ## Unreleased - Remove dependency metrics from auto-collection -- ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) + ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) +- Change default local storage directory + ([#100](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/100)) ## 0.3b.1 Released 2020-05-21 diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 81fba2a..d5ba392 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -104,6 +104,7 @@ def _transmit_from_storage(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks + # pylint: disable=too-many-return-statements def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult: """ Transmit the data envelopes to the ingestion service. diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 5dc80aa..54c7fc5 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. import os import re -import sys import tempfile import typing @@ -101,9 +100,9 @@ def _initialize(self) -> None: # storage path if self.storage_path is None: - TEMPDIR_SUFFIX = self.instrumentation_key or "" + temp_suffix = self.instrumentation_key or "" self.storage_path = os.path.join( - tempfile.gettempdir(), TEMPDIR_PREFIX + TEMPDIR_SUFFIX + tempfile.gettempdir(), temp_suffix + TEMPDIR_SUFFIX ) def _validate_instrumentation_key(self) -> None: diff --git a/azure_monitor/tests/test_storage.py b/azure_monitor/tests/test_storage.py index 480f261..cd8659f 100644 --- a/azure_monitor/tests/test_storage.py +++ b/azure_monitor/tests/test_storage.py @@ -32,7 +32,7 @@ def func(*_args, **_kwargs): return func - +# pylint: disable=no-self-use class TestLocalFileBlob(unittest.TestCase): def test_delete(self): blob = LocalFileBlob(os.path.join(TEST_FOLDER, "foobar")) From b579b3dd4bf9e824166ade960a1e90eb950c12b8 Mon Sep 17 00:00:00 2001 From: Leighton Date: Thu, 18 Jun 2020 19:44:35 -0700 Subject: [PATCH 22/39] lint --- azure_monitor/src/azure_monitor/options.py | 2 +- azure_monitor/tests/test_storage.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 54c7fc5..1384fd4 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -102,7 +102,7 @@ def _initialize(self) -> None: if self.storage_path is None: temp_suffix = self.instrumentation_key or "" self.storage_path = os.path.join( - tempfile.gettempdir(), temp_suffix + TEMPDIR_SUFFIX + tempfile.gettempdir(), TEMPDIR_PREFIX + temp_suffix ) def _validate_instrumentation_key(self) -> None: diff --git a/azure_monitor/tests/test_storage.py b/azure_monitor/tests/test_storage.py index cd8659f..d4f2364 100644 --- a/azure_monitor/tests/test_storage.py +++ b/azure_monitor/tests/test_storage.py @@ -32,6 +32,7 @@ def func(*_args, **_kwargs): return func + # pylint: disable=no-self-use class TestLocalFileBlob(unittest.TestCase): def test_delete(self): From b95bbab061496788caac1bd807c670e2a5494e9f Mon Sep 17 00:00:00 2001 From: Leighton Date: Mon, 22 Jun 2020 09:50:59 -0700 Subject: [PATCH 23/39] proxies --- azure_monitor/CHANGELOG.md | 2 ++ .../src/azure_monitor/export/__init__.py | 1 + azure_monitor/src/azure_monitor/options.py | 11 ++++++++++- azure_monitor/tests/test_base_exporter.py | 5 +++++ azure_monitor/tests/test_options.py | 18 ++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 2c0131e..fc5111d 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Implement proxies in exporter configuration +- ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) - Remove dependency metrics from auto-collection - ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 6de99d8..66fd28f 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -121,6 +121,7 @@ def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult: "Content-Type": "application/json; charset=utf-8", }, timeout=self.options.timeout, + proxies=json.loads(self.options.proxies), ) except Exception as ex: logger.warning("Transient client side error: %s.", ex) diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 739ad37..1199372 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -27,6 +27,7 @@ class ExporterOptions(BaseObject): Args: connection_string: Azure Connection String. instrumentation_key: Azure Instrumentation Key. + proxies: Proxies to pass Azure Monitor request through. storage_maintenance_period: Local storage maintenance interval in seconds. storage_max_size: Local storage maximum size in bytes. storage_path: Local storage file path. @@ -36,8 +37,8 @@ class ExporterOptions(BaseObject): __slots__ = ( "connection_string", - "endpoint", "instrumentation_key", + "proxies", "storage_maintenance_period", "storage_max_size", "storage_path", @@ -49,6 +50,7 @@ def __init__( self, connection_string: str = None, instrumentation_key: str = None, + proxies: typing.Dict[str, str] = None, storage_maintenance_period: int = 60, storage_max_size: int = 50 * 1024 * 1024, storage_path: str = None, @@ -64,6 +66,7 @@ def __init__( ) self.connection_string = connection_string self.instrumentation_key = instrumentation_key + self.proxies = proxies self.storage_maintenance_period = storage_maintenance_period self.storage_max_size = storage_max_size self.storage_path = storage_path @@ -74,6 +77,7 @@ def __init__( self._validate_instrumentation_key() def _initialize(self) -> None: + # connection string and ikey code_cs = parse_connection_string(self.connection_string) code_ikey = self.instrumentation_key env_cs = parse_connection_string( @@ -103,6 +107,11 @@ def _initialize(self) -> None: ) self.endpoint = endpoint + "/v2/track" + # proxies + if self.proxies is None: + self.proxies = '{}' + + def _validate_instrumentation_key(self) -> None: """Validates the instrumentation key used for Azure Monitor. An instrumentation key cannot be null or empty. An instrumentation key diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index 5907939..6f039cb 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -66,6 +66,7 @@ def test_constructor(self): """Test the constructor.""" base = BaseExporter( instrumentation_key="4321abcd-5678-4efa-8abc-1234567890ab", + proxies='{"https":"https://test-proxy.com"}', storage_maintenance_period=2, storage_max_size=3, storage_path=os.path.join(TEST_FOLDER, self.id()), @@ -77,6 +78,10 @@ def test_constructor(self): base.options.instrumentation_key, "4321abcd-5678-4efa-8abc-1234567890ab", ) + self.assertEqual( + base.options.proxies, + '{"https":"https://test-proxy.com"}', + ) self.assertEqual(base.options.storage_maintenance_period, 2) self.assertEqual(base.options.storage_max_size, 3) self.assertEqual(base.options.storage_retention_period, 4) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index 89b4368..3df8cd8 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -241,6 +241,24 @@ def test_process_options_endpoint_default(self): options.endpoint, "https://dc.services.visualstudio.com/v2/track" ) + def test_process_options_proxies_default(self): + options = ExporterOptions() + options.proxies = "{}" + common.process_options(options) + + self.assertEqual(options.proxies, "{}") + + def test_process_options_proxies_set_proxies(self): + options = ExporterOptions() + options.connection_string = None + options.proxies = '{"https": "https://test-proxy.com"}' + common.process_options(options) + + self.assertEqual( + options.proxies, + '{"https": "https://test-proxy.com"}' + ) + def test_parse_connection_string_invalid(self): self.assertRaises( ValueError, lambda: ExporterOptions(connection_string="asd") From e6eb689926fd98551716b367e6b7da668dae9550 Mon Sep 17 00:00:00 2001 From: Leighton Date: Mon, 22 Jun 2020 09:56:40 -0700 Subject: [PATCH 24/39] lint --- azure_monitor/CHANGELOG.md | 2 +- azure_monitor/src/azure_monitor/options.py | 4 ++-- azure_monitor/tests/test_base_exporter.py | 3 +-- azure_monitor/tests/test_options.py | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index fc5111d..5cd23bd 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased - Implement proxies in exporter configuration -- ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) +- ([#101](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/101)) - Remove dependency metrics from auto-collection - ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) diff --git a/azure_monitor/src/azure_monitor/options.py b/azure_monitor/src/azure_monitor/options.py index 1199372..064cc1d 100644 --- a/azure_monitor/src/azure_monitor/options.py +++ b/azure_monitor/src/azure_monitor/options.py @@ -37,6 +37,7 @@ class ExporterOptions(BaseObject): __slots__ = ( "connection_string", + "endpoint", "instrumentation_key", "proxies", "storage_maintenance_period", @@ -109,8 +110,7 @@ def _initialize(self) -> None: # proxies if self.proxies is None: - self.proxies = '{}' - + self.proxies = "{}" def _validate_instrumentation_key(self) -> None: """Validates the instrumentation key used for Azure Monitor. diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index 6f039cb..4b0647d 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -79,8 +79,7 @@ def test_constructor(self): "4321abcd-5678-4efa-8abc-1234567890ab", ) self.assertEqual( - base.options.proxies, - '{"https":"https://test-proxy.com"}', + base.options.proxies, '{"https":"https://test-proxy.com"}', ) self.assertEqual(base.options.storage_maintenance_period, 2) self.assertEqual(base.options.storage_max_size, 3) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index 3df8cd8..0cdce46 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -255,8 +255,7 @@ def test_process_options_proxies_set_proxies(self): common.process_options(options) self.assertEqual( - options.proxies, - '{"https": "https://test-proxy.com"}' + options.proxies, '{"https": "https://test-proxy.com"}' ) def test_parse_connection_string_invalid(self): From 74cac2e707c633e0fd571e96c8775f2d8915e1c6 Mon Sep 17 00:00:00 2001 From: Leighton Date: Mon, 22 Jun 2020 10:01:53 -0700 Subject: [PATCH 25/39] fix tests --- azure_monitor/tests/test_options.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index 0cdce46..a948c06 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -242,18 +242,19 @@ def test_process_options_endpoint_default(self): ) def test_process_options_proxies_default(self): - options = ExporterOptions() - options.proxies = "{}" - common.process_options(options) - + options = ExporterOptions( + connection_string=None, + instrumentation_key=self._valid_instrumentation_key, + proxies='{}' + ) self.assertEqual(options.proxies, "{}") def test_process_options_proxies_set_proxies(self): - options = ExporterOptions() - options.connection_string = None - options.proxies = '{"https": "https://test-proxy.com"}' - common.process_options(options) - + options = ExporterOptions( + connection_string=None, + instrumentation_key=self._valid_instrumentation_key, + proxies='{"https": "https://test-proxy.com"}' + ) self.assertEqual( options.proxies, '{"https": "https://test-proxy.com"}' ) From 90d4d8fd5a2b477ee5e69e162135a68ad285811d Mon Sep 17 00:00:00 2001 From: Leighton Date: Mon, 22 Jun 2020 10:06:56 -0700 Subject: [PATCH 26/39] lint --- azure_monitor/tests/test_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index a948c06..7507a01 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -245,7 +245,7 @@ def test_process_options_proxies_default(self): options = ExporterOptions( connection_string=None, instrumentation_key=self._valid_instrumentation_key, - proxies='{}' + proxies="{}", ) self.assertEqual(options.proxies, "{}") @@ -253,7 +253,7 @@ def test_process_options_proxies_set_proxies(self): options = ExporterOptions( connection_string=None, instrumentation_key=self._valid_instrumentation_key, - proxies='{"https": "https://test-proxy.com"}' + proxies='{"https": "https://test-proxy.com"}', ) self.assertEqual( options.proxies, '{"https": "https://test-proxy.com"}' From 833b1b7681ab1857dab1c05933d8090cad0a846e Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 14:19:12 -0700 Subject: [PATCH 27/39] remove --- README.md | 15 +++++++------- azure_monitor/setup.cfg | 1 + .../sdk/auto_collection/__init__.py | 2 +- .../sdk/auto_collection/request_metrics.py | 20 +++++++++++-------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 248dece..e9c47a6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The online documentation is available at https://opentelemetry-azure-monitor-pyt ### Trace -The **Azure Monitor Trace Exporter** allows you to export [OpenTelemetry](https://opentelemetry.io/) traces to [Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/). +The **Azure Monitor Span Exporter** allows you to export [OpenTelemetry](https://opentelemetry.io/) traces to [Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/). This example shows how to send a span "hello" to Azure Monitor. @@ -33,17 +33,13 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor trace.set_tracer_provider(TracerProvider()) - -# We tell OpenTelemetry who it is that is creating spans. In this case, we have -# no real name (no setup.py), so we make one up. If we had a version, we would -# also specify it here. tracer = trace.get_tracer(__name__) +# SpanExporter receives the spans and send them to the target location exporter = AzureMonitorSpanExporter( connection_string='InstrumentationKey=', ) -# SpanExporter receives the spans and send them to the target location. span_processor = BatchExportSpanProcessor(exporter) trace.get_tracer_provider().add_span_processor(span_processor) @@ -75,12 +71,14 @@ trace.set_tracer_provider(TracerProvider()) tracer_provider = trace.get_tracer_provider() exporter = AzureMonitorSpanExporter( - connection_string='InstrumentationKey=', - ) + connection_string='InstrumentationKey=', +) span_processor = BatchExportSpanProcessor(exporter) tracer_provider.add_span_processor(span_processor) RequestsInstrumentor().instrument() + +# This request will be traced response = requests.get(url="https://azure.microsoft.com/") ``` @@ -106,6 +104,7 @@ def callback_function(envelope): exporter = AzureMonitorSpanExporter( connection_string='InstrumentationKey=' ) +# This line will modify telemetry exporter.add_telemetry_processor(callback_function) trace.set_tracer_provider(TracerProvider()) diff --git a/azure_monitor/setup.cfg b/azure_monitor/setup.cfg index 35b6b72..3728d63 100644 --- a/azure_monitor/setup.cfg +++ b/azure_monitor/setup.cfg @@ -20,6 +20,7 @@ classifiers = Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [options] python_requires = >=3.4 diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 4c33a14..9811358 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -44,4 +44,4 @@ def __init__( ): col_type = AutoCollectionType.STANDARD_METRICS self._performance_metrics = PerformanceMetrics(meter, labels, col_type) - self._request_metrics = RequestMetrics(meter, labels, span_processor) + self._request_metrics = RequestMetrics(meter, labels, span_processor, col_type) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index 24d70de..9177f06 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -11,6 +11,8 @@ AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType + logger = logging.getLogger(__name__) requests_map = dict() @@ -32,19 +34,21 @@ def __init__( meter: Meter, labels: Dict[str, str], span_processor: AzureMetricsSpanProcessor, + collection_type: AutoCollectionType, ): self._meter = meter self._labels = labels self._span_processor = span_processor - meter.register_observer( - callback=self._track_request_failed_rate, - name="\\ApplicationInsights\\Requests Failed/Sec", - description="Incoming Requests Failed Rate", - unit="rps", - value_type=float, - observer_type=UpDownSumObserver, - ) + if collection_type == AutoCollectionType.LIVE_METRICS: + meter.register_observer( + callback=self._track_request_failed_rate, + name="\\ApplicationInsights\\Requests Failed/Sec", + description="Incoming Requests Failed Rate", + unit="rps", + value_type=float, + observer_type=UpDownSumObserver, + ) meter.register_observer( callback=self._track_request_duration, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", From 1d9d5e6de4cbf9a49b9c5339940bd27a4ae61607 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 14:36:13 -0700 Subject: [PATCH 28/39] fix tests --- .../sdk/auto_collection/__init__.py | 4 +- .../auto_collection/test_request_metrics.py | 80 ++----------------- 2 files changed, 8 insertions(+), 76 deletions(-) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py index 9811358..59e0705 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/__init__.py @@ -44,4 +44,6 @@ def __init__( ): col_type = AutoCollectionType.STANDARD_METRICS self._performance_metrics = PerformanceMetrics(meter, labels, col_type) - self._request_metrics = RequestMetrics(meter, labels, span_processor, col_type) + self._request_metrics = RequestMetrics( + meter, labels, span_processor, col_type + ) diff --git a/azure_monitor/tests/auto_collection/test_request_metrics.py b/azure_monitor/tests/auto_collection/test_request_metrics.py index 2d0b121..ec2f613 100644 --- a/azure_monitor/tests/auto_collection/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/test_request_metrics.py @@ -11,6 +11,7 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) +from azure_monitor.sdk.auto_collection.utils import AutoCollectionType # pylint: disable=protected-access @@ -35,18 +36,12 @@ def test_constructor(self): meter=mock_meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self.assertEqual(request_metrics_collector._meter, mock_meter) self.assertEqual(request_metrics_collector._labels, self._test_labels) self.assertEqual(mock_meter.register_observer.call_count, 3) create_metric_calls = mock_meter.register_observer.call_args_list - create_metric_calls[0].assert_called_with( - callback=request_metrics_collector._track_request_failed_rate, - name="\\ApplicationInsights\\Requests Failed/Sec", - description="Incoming Requests Failed Rate", - unit="rps", - value_type=float, - ) create_metric_calls[1].assert_called_with( callback=request_metrics_collector._track_request_duration, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", @@ -68,6 +63,7 @@ def test_track_request_duration(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self._span_processor.request_duration = 100 self._span_processor.request_count = 10 @@ -90,6 +86,7 @@ def test_track_request_duration_error(self): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) self._span_processor.request_duration = 100 self._span_processor.request_count = 10 @@ -113,6 +110,7 @@ def test_track_request_rate(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_time"] = 98 @@ -174,71 +172,3 @@ def test_track_request_rate_error(self, time_mock): self.assertEqual( obs.aggregators[tuple(self._test_labels.items())].current, 5.0 ) - - @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") - def test_track_request_failed_rate(self, time_mock): - request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - time_mock.time.return_value = 100 - request_metrics.requests_map["last_time"] = 98 - self._span_processor.failed_request_count = 4 - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=float, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 2.0 - ) - - @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") - def test_track_request_failed_rate_time_none(self, time_mock): - time_mock.time.return_value = 100 - request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - request_metrics.requests_map["last_time"] = None - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=float, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 0.0 - ) - - @mock.patch("azure_monitor.sdk.auto_collection.request_metrics.time") - def test_track_request_failed_rate_error(self, time_mock): - request_metrics_collector = request_metrics.RequestMetrics( - meter=self._meter, - labels=self._test_labels, - span_processor=self._span_processor, - ) - time_mock.time.return_value = 100 - request_metrics.requests_map["last_rate"] = 5.0 - request_metrics.requests_map["last_time"] = 100 - obs = Observer( - callback=request_metrics_collector._track_request_failed_rate, - name="test", - description="test", - unit="test", - value_type=float, - meter=self._meter, - ) - request_metrics_collector._track_request_failed_rate(obs) - self.assertEqual( - obs.aggregators[tuple(self._test_labels.items())].current, 5.0 - ) From 8caf678d4c2b41116ed2709f8c3fd75ac33feb6c Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 14:51:15 -0700 Subject: [PATCH 29/39] fix tests --- .../azure_monitor/sdk/auto_collection/request_metrics.py | 1 - .../tests/auto_collection/test_request_metrics.py | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py index 9177f06..c78294d 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/request_metrics.py @@ -10,7 +10,6 @@ from azure_monitor.sdk.auto_collection.metrics_span_processor import ( AzureMetricsSpanProcessor, ) - from azure_monitor.sdk.auto_collection.utils import AutoCollectionType logger = logging.getLogger(__name__) diff --git a/azure_monitor/tests/auto_collection/test_request_metrics.py b/azure_monitor/tests/auto_collection/test_request_metrics.py index ec2f613..a367834 100644 --- a/azure_monitor/tests/auto_collection/test_request_metrics.py +++ b/azure_monitor/tests/auto_collection/test_request_metrics.py @@ -40,9 +40,9 @@ def test_constructor(self): ) self.assertEqual(request_metrics_collector._meter, mock_meter) self.assertEqual(request_metrics_collector._labels, self._test_labels) - self.assertEqual(mock_meter.register_observer.call_count, 3) + self.assertEqual(mock_meter.register_observer.call_count, 2) create_metric_calls = mock_meter.register_observer.call_args_list - create_metric_calls[1].assert_called_with( + create_metric_calls[0].assert_called_with( callback=request_metrics_collector._track_request_duration, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", description="Incoming Requests Average Execution Time", @@ -50,7 +50,7 @@ def test_constructor(self): value_type=int, ) - create_metric_calls[2].assert_called_with( + create_metric_calls[1].assert_called_with( callback=request_metrics_collector._track_request_rate, name="\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", description="Incoming Requests Rate", @@ -135,6 +135,7 @@ def test_track_request_rate_time_none(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) request_metrics.requests_map["last_time"] = None obs = Observer( @@ -156,6 +157,7 @@ def test_track_request_rate_error(self, time_mock): meter=self._meter, labels=self._test_labels, span_processor=self._span_processor, + collection_type=AutoCollectionType.STANDARD_METRICS, ) time_mock.time.return_value = 100 request_metrics.requests_map["last_rate"] = 5.0 From 8f123dc9b5958ad8313a76ec7c8745046ab80208 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 15:01:34 -0700 Subject: [PATCH 30/39] fix test --- .../azure_monitor/sdk/auto_collection/live_metrics/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index 8c2cb46..cf0b343 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -44,7 +44,7 @@ def __init__( self._dependency_metrics = DependencyMetrics( meter, labels, span_processor ) - self._request_metrics = RequestMetrics(meter, labels, span_processor) + self._request_metrics = RequestMetrics(meter, labels, span_processor, col_type) self._manager = LiveMetricsManager( meter, instrumentation_key, span_processor ) From a8fb8f73a5478f4bece3d45fd83bb84d57e4f995 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 15:18:57 -0700 Subject: [PATCH 31/39] black --- .../sdk/auto_collection/live_metrics/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py index cf0b343..0bd1428 100644 --- a/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py +++ b/azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py @@ -44,7 +44,9 @@ def __init__( self._dependency_metrics = DependencyMetrics( meter, labels, span_processor ) - self._request_metrics = RequestMetrics(meter, labels, span_processor, col_type) + self._request_metrics = RequestMetrics( + meter, labels, span_processor, col_type + ) self._manager = LiveMetricsManager( meter, instrumentation_key, span_processor ) From 10f8c5106ee4f0cc4ef15f72e82566206d20df94 Mon Sep 17 00:00:00 2001 From: Leighton Date: Tue, 23 Jun 2020 15:24:45 -0700 Subject: [PATCH 32/39] changelog --- azure_monitor/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 2c0131e..30cd696 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,8 +2,10 @@ ## Unreleased +- Remove request failed per second metrics from auto-collection + ([#102](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/102)) - Remove dependency metrics from auto-collection -- ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) + ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) ## 0.3b.1 Released 2020-05-21 From c284309598176526a9aa5c3b43f4c5887c4ee9da Mon Sep 17 00:00:00 2001 From: Leighton Date: Wed, 24 Jun 2020 13:12:34 -0700 Subject: [PATCH 33/39] fix type --- azure_monitor/setup.cfg | 4 ++-- azure_monitor/src/azure_monitor/export/__init__.py | 2 +- azure_monitor/tests/test_base_exporter.py | 4 ++-- azure_monitor/tests/test_options.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/azure_monitor/setup.cfg b/azure_monitor/setup.cfg index 3728d63..b0f48bb 100644 --- a/azure_monitor/setup.cfg +++ b/azure_monitor/setup.cfg @@ -28,8 +28,8 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9b0 - opentelemetry-sdk == 0.9b0 + opentelemetry-api == 0.10b0 + opentelemetry-sdk == 0.10b0 psutil >= 5.6.3 requests ~= 2.0 diff --git a/azure_monitor/src/azure_monitor/export/__init__.py b/azure_monitor/src/azure_monitor/export/__init__.py index 584f40d..3c2501e 100644 --- a/azure_monitor/src/azure_monitor/export/__init__.py +++ b/azure_monitor/src/azure_monitor/export/__init__.py @@ -122,7 +122,7 @@ def _transmit(self, envelopes: typing.List[Envelope]) -> ExportResult: "Content-Type": "application/json; charset=utf-8", }, timeout=self.options.timeout, - proxies=json.loads(self.options.proxies), + proxies=self.options.proxies, ) except requests.Timeout: logger.warning( diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index adb6770..bbebd2b 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -67,7 +67,7 @@ def test_constructor(self): """Test the constructor.""" base = BaseExporter( instrumentation_key="4321abcd-5678-4efa-8abc-1234567890ab", - proxies='{"https":"https://test-proxy.com"}', + proxies={"https":"https://test-proxy.com"}, storage_maintenance_period=2, storage_max_size=3, storage_path=os.path.join(TEST_FOLDER, self.id()), @@ -80,7 +80,7 @@ def test_constructor(self): "4321abcd-5678-4efa-8abc-1234567890ab", ) self.assertEqual( - base.options.proxies, '{"https":"https://test-proxy.com"}', + base.options.proxies, {"https":"https://test-proxy.com"}, ) self.assertEqual(base.options.storage_maintenance_period, 2) self.assertEqual(base.options.storage_max_size, 3) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index 7507a01..8039080 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -245,18 +245,18 @@ def test_process_options_proxies_default(self): options = ExporterOptions( connection_string=None, instrumentation_key=self._valid_instrumentation_key, - proxies="{}", + proxies={}, ) - self.assertEqual(options.proxies, "{}") + self.assertEqual(options.proxies, {}) def test_process_options_proxies_set_proxies(self): options = ExporterOptions( connection_string=None, instrumentation_key=self._valid_instrumentation_key, - proxies='{"https": "https://test-proxy.com"}', + proxies={"https": "https://test-proxy.com"}, ) self.assertEqual( - options.proxies, '{"https": "https://test-proxy.com"}' + options.proxies, {"https": "https://test-proxy.com"} ) def test_parse_connection_string_invalid(self): From 866b0f8c9fa3013d592a9b0ea1bd9897b9852c27 Mon Sep 17 00:00:00 2001 From: Leighton Date: Wed, 24 Jun 2020 13:17:50 -0700 Subject: [PATCH 34/39] update version --- .../auto_collection/live_metrics/test_exporter.py | 12 ++++++------ azure_monitor/tests/metrics/test_metrics.py | 10 +++++----- azure_monitor/tests/test_base_exporter.py | 4 ++-- azure_monitor/tests/test_options.py | 4 +--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index 21b1f90..673fab3 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -14,7 +14,7 @@ ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( - CounterAggregator, + SumAggregator, MinMaxSumCountAggregator, ValueObserverAggregator, ) @@ -79,7 +79,7 @@ def test_constructor(self): def test_export(self): """Test export.""" record = MetricRecord( - self._test_metric, self._test_labels, CounterAggregator() + self._test_metric, self._test_labels, SumAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -96,7 +96,7 @@ def test_export(self): def test_export_failed(self): record = MetricRecord( - self._test_metric, self._test_labels, CounterAggregator() + self._test_metric, self._test_labels, SumAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -113,7 +113,7 @@ def test_export_failed(self): def test_export_exception(self): record = MetricRecord( - self._test_metric, self._test_labels, CounterAggregator() + self._test_metric, self._test_labels, SumAggregator() ) exporter = LiveMetricsExporter( instrumentation_key=self._instrumentation_key, @@ -148,7 +148,7 @@ def test_live_metric_envelope_observer(self): self.assertEqual(envelope.metrics[0].weight, 1) def test_live_metric_envelope_counter(self): - aggregator = CounterAggregator() + aggregator = SumAggregator() aggregator.update(123) aggregator.take_checkpoint() record = MetricRecord(self._test_metric, self._test_labels, aggregator) @@ -184,7 +184,7 @@ def test_live_metric_envelope_value_recorder(self): self.assertEqual(envelope.metrics[0].weight, 1) def test_live_metric_envelope_documents(self): - aggregator = CounterAggregator() + aggregator = SumAggregator() aggregator.update(123) aggregator.take_checkpoint() record = MetricRecord(self._test_metric, self._test_labels, aggregator) diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index a6c9ea1..c37e641 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -14,7 +14,7 @@ ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( - CounterAggregator, + SumAggregator, MinMaxSumCountAggregator, ValueObserverAggregator, ) @@ -109,7 +109,7 @@ def test_constructor(self): ) def test_export(self, mte, transmit): record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + SumAggregator(), self._test_labels, self._test_metric ) exporter = self._exporter mte.return_value = Envelope() @@ -125,7 +125,7 @@ def test_export(self, mte, transmit): ) def test_export_failed_retryable(self, mte, transmit): record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + SumAggregator(), self._test_labels, self._test_metric ) exporter = self._exporter transmit.return_value = ExportResult.FAILED_RETRYABLE @@ -145,7 +145,7 @@ def test_export_failed_retryable(self, mte, transmit): ) def test_export_exception(self, mte, transmit, logger_mock): record = MetricRecord( - CounterAggregator(), self._test_labels, self._test_metric + SumAggregator(), self._test_labels, self._test_metric ) exporter = self._exporter mte.return_value = Envelope() @@ -159,7 +159,7 @@ def test_metric_to_envelope_none(self): self.assertIsNone(exporter._metric_to_envelope(None)) def test_metric_to_envelope(self): - aggregator = CounterAggregator() + aggregator = SumAggregator() aggregator.update(123) aggregator.take_checkpoint() record = MetricRecord(self._test_metric, self._test_labels, aggregator) diff --git a/azure_monitor/tests/test_base_exporter.py b/azure_monitor/tests/test_base_exporter.py index bbebd2b..d197d47 100644 --- a/azure_monitor/tests/test_base_exporter.py +++ b/azure_monitor/tests/test_base_exporter.py @@ -67,7 +67,7 @@ def test_constructor(self): """Test the constructor.""" base = BaseExporter( instrumentation_key="4321abcd-5678-4efa-8abc-1234567890ab", - proxies={"https":"https://test-proxy.com"}, + proxies={"https": "https://test-proxy.com"}, storage_maintenance_period=2, storage_max_size=3, storage_path=os.path.join(TEST_FOLDER, self.id()), @@ -80,7 +80,7 @@ def test_constructor(self): "4321abcd-5678-4efa-8abc-1234567890ab", ) self.assertEqual( - base.options.proxies, {"https":"https://test-proxy.com"}, + base.options.proxies, {"https": "https://test-proxy.com"}, ) self.assertEqual(base.options.storage_maintenance_period, 2) self.assertEqual(base.options.storage_max_size, 3) diff --git a/azure_monitor/tests/test_options.py b/azure_monitor/tests/test_options.py index 8039080..1cee999 100644 --- a/azure_monitor/tests/test_options.py +++ b/azure_monitor/tests/test_options.py @@ -255,9 +255,7 @@ def test_process_options_proxies_set_proxies(self): instrumentation_key=self._valid_instrumentation_key, proxies={"https": "https://test-proxy.com"}, ) - self.assertEqual( - options.proxies, {"https": "https://test-proxy.com"} - ) + self.assertEqual(options.proxies, {"https": "https://test-proxy.com"}) def test_parse_connection_string_invalid(self): self.assertRaises( From e795e7c4b2222124a9f38d746bd020570ae66093 Mon Sep 17 00:00:00 2001 From: Leighton Date: Wed, 24 Jun 2020 13:20:43 -0700 Subject: [PATCH 35/39] lint --- azure_monitor/examples/traces/server.py | 2 +- .../tests/auto_collection/live_metrics/test_exporter.py | 2 +- azure_monitor/tests/metrics/test_metrics.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure_monitor/examples/traces/server.py b/azure_monitor/examples/traces/server.py index 51039c8..8f3c9a9 100644 --- a/azure_monitor/examples/traces/server.py +++ b/azure_monitor/examples/traces/server.py @@ -3,6 +3,7 @@ # pylint: disable=import-error # pylint: disable=no-member # pylint: disable=no-name-in-module +import flask import requests from opentelemetry import trace from opentelemetry.ext.flask import FlaskInstrumentor @@ -10,7 +11,6 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor -import flask from azure_monitor import AzureMonitorSpanExporter # The preferred tracer implementation must be set, as the opentelemetry-api diff --git a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py index 673fab3..c480a0d 100644 --- a/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py +++ b/azure_monitor/tests/auto_collection/live_metrics/test_exporter.py @@ -14,8 +14,8 @@ ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( - SumAggregator, MinMaxSumCountAggregator, + SumAggregator, ValueObserverAggregator, ) diff --git a/azure_monitor/tests/metrics/test_metrics.py b/azure_monitor/tests/metrics/test_metrics.py index c37e641..0f8f5ff 100644 --- a/azure_monitor/tests/metrics/test_metrics.py +++ b/azure_monitor/tests/metrics/test_metrics.py @@ -14,8 +14,8 @@ ) from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import ( - SumAggregator, MinMaxSumCountAggregator, + SumAggregator, ValueObserverAggregator, ) from opentelemetry.sdk.util import ns_to_iso_str From 89668883ddebf93960389575029f3a7d06e5e401 Mon Sep 17 00:00:00 2001 From: Leighton Date: Wed, 24 Jun 2020 13:25:56 -0700 Subject: [PATCH 36/39] lint --- azure_monitor/examples/traces/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_monitor/examples/traces/server.py b/azure_monitor/examples/traces/server.py index 8f3c9a9..51039c8 100644 --- a/azure_monitor/examples/traces/server.py +++ b/azure_monitor/examples/traces/server.py @@ -3,7 +3,6 @@ # pylint: disable=import-error # pylint: disable=no-member # pylint: disable=no-name-in-module -import flask import requests from opentelemetry import trace from opentelemetry.ext.flask import FlaskInstrumentor @@ -11,6 +10,7 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor +import flask from azure_monitor import AzureMonitorSpanExporter # The preferred tracer implementation must be set, as the opentelemetry-api From 7adca485fccb2ec3b2cea94faa2162226234a629 Mon Sep 17 00:00:00 2001 From: Leighton Date: Wed, 24 Jun 2020 13:31:46 -0700 Subject: [PATCH 37/39] fix changelog --- azure_monitor/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index 79b79ad..efd4f8a 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,12 +2,12 @@ ## Unreleased -- Implement proxies in exporter configuration -- ([#101](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/101)) - Remove dependency metrics from auto-collection ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) - Change default local storage directory ([#100](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/100)) +- Implement proxies in exporter configuration + ([#101](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/101)) - Remove request failed per second metrics from auto-collection ([#102](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/102)) From 5b4bf8c800615b91c9e86867e2292c31e60bff11 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Mon, 29 Jun 2020 10:18:25 -0700 Subject: [PATCH 38/39] changelog --- azure_monitor/CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/azure_monitor/CHANGELOG.md b/azure_monitor/CHANGELOG.md index efd4f8a..b8c03a5 100644 --- a/azure_monitor/CHANGELOG.md +++ b/azure_monitor/CHANGELOG.md @@ -2,8 +2,13 @@ ## Unreleased +## 0.4b.0 +Released 2020-06-29 + +- Added live metrics + ([#96](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/96)) - Remove dependency metrics from auto-collection - ([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92)) + ([#99](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/99)) - Change default local storage directory ([#100](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/100)) - Implement proxies in exporter configuration From eb00c0fa3f806ef8a20ccc2f787860200afac8ef Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Mon, 29 Jun 2020 10:29:13 -0700 Subject: [PATCH 39/39] conf --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 2bfad2e..c72e032 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ author = "Microsoft" # The full version, including alpha/beta/rc tags -release = "0.3b.0" +release = "0.4b.0" # -- General configuration ---------------------------------------------------