This repository was archived by the owner on Nov 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Adding live metrics manager #78
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
60fe4a4
Adding live metrics capabilities
hectorhdzg c18e589
Fix shutdown
hectorhdzg 09a623f
Addressing comments
hectorhdzg 5f1f5b6
Adding more tests
hectorhdzg 7ded8cb
Addressing comments
hectorhdzg 09c6cb5
Fixing tests
hectorhdzg 38bcc07
Fix lint issues
hectorhdzg 3db7f4c
Lint
hectorhdzg bd9e600
Lint
hectorhdzg 695c760
Merge branch 'master' into hectorh/livemetrics
hectorhdzg 688eca0
Addressing comments
hectorhdzg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. |
169 changes: 169 additions & 0 deletions
169
azure_monitor/src/azure_monitor/sdk/auto_collection/live_metrics/exporter.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. | ||
# | ||
import collections | ||
import logging | ||
import typing | ||
|
||
from opentelemetry.sdk.metrics import Counter, Observer | ||
from opentelemetry.sdk.metrics.export import ( | ||
MetricRecord, | ||
MetricsExporter, | ||
MetricsExportResult, | ||
) | ||
|
||
from azure_monitor.protocol import ( | ||
Envelope, | ||
LiveMetric, | ||
LiveMetricDocument, | ||
LiveMetricDocumentProperty, | ||
LiveMetricEnvelope, | ||
) | ||
from azure_monitor.sdk.auto_collection.live_metrics import utils | ||
from azure_monitor.sdk.auto_collection.live_metrics.sender import ( | ||
LiveMetricsSender, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
# pylint: disable=no-self-use | ||
# pylint: disable=too-many-statements | ||
# pylint: disable=too-many-return-statements | ||
class LiveMetricsExporter(MetricsExporter): | ||
"""Live Metrics Exporter | ||
|
||
Export data to Azure Live Metrics service and determine if user is subscribed. | ||
""" | ||
|
||
def __init__(self, instrumentation_key): | ||
self._instrumentation_key = instrumentation_key | ||
self._sender = LiveMetricsSender(self._instrumentation_key) | ||
self.subscribed = True | ||
self._document_envelopes = collections.deque() | ||
|
||
def add_document(self, envelope: Envelope): | ||
lzchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._document_envelopes.append(envelope) | ||
|
||
def export( | ||
self, metric_records: typing.Sequence[MetricRecord] | ||
) -> MetricsExportResult: | ||
envelope = self._metric_to_live_metrics_envelope(metric_records) | ||
try: | ||
response = self._sender.post(envelope) | ||
if response.ok: | ||
self.subscribed = ( | ||
response.headers.get(utils.LIVE_METRICS_SUBSCRIBED_HEADER) | ||
== "true" | ||
) | ||
return MetricsExportResult.SUCCESS | ||
|
||
except Exception: # pylint: disable=broad-except | ||
logger.exception("Exception occurred while exporting the data.") | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return MetricsExportResult.FAILED_NOT_RETRYABLE | ||
|
||
def _metric_to_live_metrics_envelope( | ||
self, metric_records: typing.Sequence[MetricRecord] | ||
) -> LiveMetricEnvelope: | ||
|
||
envelope = utils.create_metric_envelope(self._instrumentation_key) | ||
envelope.documents = self._get_live_metric_documents() | ||
envelope.metrics = [] | ||
|
||
# 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): | ||
value = metric_record.aggregator.checkpoint.last | ||
if not value: | ||
value = 0 | ||
envelope.metrics.append( | ||
LiveMetric(name=metric.name, value=value, weight=1) | ||
) | ||
|
||
return envelope | ||
|
||
def _get_live_metric_documents( | ||
self, | ||
) -> typing.Sequence[LiveMetricDocument]: | ||
live_metric_documents = [] | ||
while self._document_envelopes: | ||
for envelope in self._document_envelopes.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" | ||
) | ||
|
||
return live_metric_documents | ||
|
||
def _get_live_metric_type(self, base_type: str) -> str: | ||
if base_type == "EventData": | ||
return "EventTelemetryDocument" | ||
if base_type == "ExceptionData": | ||
return "ExceptionTelemetryDocument" | ||
if base_type == "MessageData": | ||
return "TraceTelemetryDocument" | ||
if base_type == "MetricData": | ||
return "MetricTelemetryDocument" | ||
if base_type == "RequestData": | ||
return "RequestTelemetryDocument" | ||
if base_type == "RemoteDependencyData": | ||
return "DependencyTelemetryDocument" | ||
if base_type == "AvailabilityData": | ||
return "AvailabilityTelemetryDocument" | ||
return "" | ||
|
||
def _get_live_metric_document_type(self, base_type: str) -> str: | ||
if base_type == "EventData": | ||
return "Event" | ||
if base_type == "ExceptionData": | ||
return "Exception" | ||
if base_type == "MessageData": | ||
return "Trace" | ||
if base_type == "MetricData": | ||
return "Metric" | ||
if base_type == "RequestData": | ||
return "Request" | ||
if base_type == "RemoteDependencyData": | ||
return "RemoteDependency" | ||
if base_type == "AvailabilityData": | ||
return "Availability" | ||
return "" | ||
|
||
def _get_aggregated_properties( | ||
self, envelope: Envelope | ||
) -> typing.List[LiveMetricDocumentProperty]: | ||
|
||
aggregated_properties = [] | ||
measurements = ( | ||
envelope.data.base_data.measurements | ||
if envelope.data.base_data.measurements | ||
else [] | ||
) | ||
for key in measurements: | ||
prop = LiveMetricDocumentProperty(key=key, value=measurements[key]) | ||
aggregated_properties.append(prop) | ||
properties = ( | ||
envelope.data.base_data.properties | ||
if envelope.data.base_data.properties | ||
else [] | ||
) | ||
for key in properties: | ||
prop = LiveMetricDocumentProperty(key=key, value=properties[key]) | ||
aggregated_properties.append(prop) | ||
return aggregated_properties |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the exporter keeping the state of subscribed, however there doesn't seem to be a nice way to do this. One way is to use the
context
, where the key is LIVE_METRICS_{x} where x is the unique UUID for that manager and the value is subscribed or not. Another (ugly) way is to have a reference to the manager from the exporter and keep the state of subscribed in the manager.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since UUID is unique per application and not manager instance, I think it is fine to have a global variable in the context keeping track of the state of subscribed. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem is with internal threads, we have 3 different threads that would be using this global context variable, making this code easier to break in specific cases, my point is that is not worth to add this logic only because subscribed flag feels out of place.