Skip to content

Commit d2e868c

Browse files
authored
Update live metrics to use typespec generated swagger (#34840)
1 parent 9bf99d3 commit d2e868c

File tree

16 files changed

+1010
-976
lines changed

16 files changed

+1010
-976
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_exporter.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
import logging
34
from typing import Any, Optional
45

56
from opentelemetry.context import (
@@ -43,6 +44,9 @@
4344
from azure.monitor.opentelemetry.exporter._utils import _ticks_since_dot_net_epoch, PeriodicTask
4445

4546

47+
_logger = logging.getLogger(__name__)
48+
49+
4650
_QUICKPULSE_METRIC_TEMPORALITIES = {
4751
# Use DELTA temporalities because we want to reset the counts every collection interval
4852
Counter: AggregationTemporality.DELTA,
@@ -77,8 +81,8 @@ def __init__(self, connection_string: Optional[str]) -> None:
7781
self._live_endpoint = parsed_connection_string.live_endpoint
7882
self._instrumentation_key = parsed_connection_string.instrumentation_key
7983
# TODO: Support AADaudience (scope)/credentials
80-
81-
self._client = QuickpulseClient(host=self._live_endpoint)
84+
# Pass `None` for now until swagger definition is fixed
85+
self._client = QuickpulseClient(credential=None, endpoint=self._live_endpoint) # type: ignore
8286
# TODO: Support redirect
8387

8488
MetricExporter.__init__(
@@ -113,10 +117,11 @@ def export(
113117

114118
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
115119
try:
116-
post_response = self._client.post( # type: ignore
120+
post_response = self._client.publish( # type: ignore
121+
endpoint=self._live_endpoint,
117122
monitoring_data_points=data_points,
118123
ikey=self._instrumentation_key,
119-
x_ms_qps_transmission_time=_ticks_since_dot_net_epoch(),
124+
transmission_time=_ticks_since_dot_net_epoch(),
120125
cls=_Response,
121126
)
122127
if not post_response:
@@ -128,7 +133,7 @@ def export(
128133
# User leaving the live metrics page will be treated as an unsuccessful
129134
result = MetricExportResult.FAILURE
130135
except Exception: # pylint: disable=broad-except,invalid-name
131-
# Errors are not reported and assumed as unsuccessful
136+
_logger.exception("Exception occurred while publishing live metrics.")
132137
result = MetricExportResult.FAILURE
133138
finally:
134139
detach(token)
@@ -164,20 +169,25 @@ def shutdown(
164169
"""
165170

166171

167-
def _ping(self, monitoring_data_point) -> Optional[_Response]:
172+
def _ping(self, monitoring_data_point: MonitoringDataPoint) -> Optional[_Response]:
168173
ping_response = None
169174
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
170175
try:
171-
ping_response = self._client.ping( # type: ignore
176+
ping_response = self._client.is_subscribed( # type: ignore
177+
endpoint=self._live_endpoint,
172178
monitoring_data_point=monitoring_data_point,
173179
ikey=self._instrumentation_key,
174-
x_ms_qps_transmission_time=_ticks_since_dot_net_epoch(),
180+
transmission_time=_ticks_since_dot_net_epoch(),
181+
machine_name=monitoring_data_point.machine_name,
182+
instance_name=monitoring_data_point.instance,
183+
stream_id=monitoring_data_point.stream_id,
184+
role_name=monitoring_data_point.role_name,
185+
invariant_version=monitoring_data_point.invariant_version,
175186
cls=_Response,
176187
)
177188
return ping_response # type: ignore
178189
except HttpResponseError:
179-
# Errors are not reported
180-
pass
190+
_logger.exception("Exception occurred while pinging live metrics.")
181191
detach(token)
182192
return ping_response
183193

@@ -208,41 +218,35 @@ def _ticker(self) -> None:
208218
if _is_ping_state():
209219
# Send a ping if elapsed number of request meets the threshold
210220
if self._elapsed_num_seconds % _get_global_quickpulse_state().value == 0:
211-
print("pinging...")
212221
ping_response = self._exporter._ping( # pylint: disable=protected-access
213222
self._base_monitoring_data_point,
214223
)
215224
if ping_response:
216225
header = ping_response._response_headers.get("x-ms-qps-subscribed") # pylint: disable=protected-access
217226
if header and header == "true":
218-
print("ping succeeded: switching to post")
219227
# Switch state to post if subscribed
220228
_set_global_quickpulse_state(_QuickpulseState.POST_SHORT)
221229
self._elapsed_num_seconds = 0
222230
else:
223231
# Backoff after _LONG_PING_INTERVAL_SECONDS (60s) of no successful requests
224232
if _get_global_quickpulse_state() is _QuickpulseState.PING_SHORT and \
225233
self._elapsed_num_seconds >= _LONG_PING_INTERVAL_SECONDS:
226-
print("ping failed for 60s, switching to pinging every 60s")
227234
_set_global_quickpulse_state(_QuickpulseState.PING_LONG)
228235
# TODO: Implement redirect
229236
else:
230237
# Erroneous ping responses instigate backoff logic
231238
# Backoff after _LONG_PING_INTERVAL_SECONDS (60s) of no successful requests
232239
if _get_global_quickpulse_state() is _QuickpulseState.PING_SHORT and \
233240
self._elapsed_num_seconds >= _LONG_PING_INTERVAL_SECONDS:
234-
print("ping failed for 60s, switching to pinging every 60s")
235241
_set_global_quickpulse_state(_QuickpulseState.PING_LONG)
236242
else:
237-
print("posting...")
238243
try:
239244
self.collect()
240245
except _UnsuccessfulQuickPulsePostError:
241246
# Unsuccessful posts instigate backoff logic
242247
# Backoff after _POST_CANCEL_INTERVAL_SECONDS (20s) of no successful requests
243248
# And resume pinging
244249
if self._elapsed_num_seconds >= _POST_CANCEL_INTERVAL_SECONDS:
245-
print("post failed for 20s, switching to pinging")
246250
_set_global_quickpulse_state(_QuickpulseState.PING_SHORT)
247251
self._elapsed_num_seconds = 0
248252

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_generated/_client.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# --------------------------------------------------------------------------
88

99
from copy import deepcopy
10-
from typing import Any
10+
from typing import Any, TYPE_CHECKING
1111

1212
from azure.core import PipelineClient
1313
from azure.core.pipeline import policies
@@ -18,20 +18,24 @@
1818
from ._operations import QuickpulseClientOperationsMixin
1919
from ._serialization import Deserializer, Serializer
2020

21+
if TYPE_CHECKING:
22+
# pylint: disable=unused-import,ungrouped-imports
23+
from azure.core.credentials import TokenCredential
24+
2125

2226
class QuickpulseClient(QuickpulseClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword
2327
"""Quickpulse Client.
2428
25-
:param host: QuickPulse endpoint: https://rt.services.visualstudio.com. Default value is
26-
"https://rt.services.visualstudio.com".
27-
:type host: str
29+
:param credential: Credential needed for the client to connect to Azure. Required.
30+
:type credential: ~azure.core.credentials.TokenCredential
31+
:keyword api_version: Api Version. Default value is "2024-04-01-preview". Note that overriding
32+
this default value may result in unsupported behavior.
33+
:paramtype api_version: str
2834
"""
2935

30-
def __init__( # pylint: disable=missing-client-constructor-parameter-credential
31-
self, host: str = "https://rt.services.visualstudio.com", **kwargs: Any
32-
) -> None:
33-
_endpoint = "{Host}"
34-
self._config = QuickpulseClientConfiguration(host=host, **kwargs)
36+
def __init__(self, credential: "TokenCredential", **kwargs: Any) -> None:
37+
_endpoint = "{endpoint}"
38+
self._config = QuickpulseClientConfiguration(credential=credential, **kwargs)
3539
_policies = kwargs.pop("policies", None)
3640
if _policies is None:
3741
_policies = [
@@ -75,11 +79,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs:
7579
"""
7680

7781
request_copy = deepcopy(request)
78-
path_format_arguments = {
79-
"Host": self._serialize.url("self._config.host", self._config.host, "str", skip_quote=True),
80-
}
81-
82-
request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments)
82+
request_copy.url = self._client.format_url(request_copy.url)
8383
return self._client.send_request(request_copy, stream=stream, **kwargs) # type: ignore
8484

8585
def close(self) -> None:

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_generated/_configuration.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
from typing import Any
9+
from typing import Any, TYPE_CHECKING
1010

1111
from azure.core.pipeline import policies
1212

13+
if TYPE_CHECKING:
14+
# pylint: disable=unused-import,ungrouped-imports
15+
from azure.core.credentials import TokenCredential
16+
1317
VERSION = "unknown"
1418

1519

@@ -19,16 +23,22 @@ class QuickpulseClientConfiguration: # pylint: disable=too-many-instance-attrib
1923
Note that all parameters used to create this instance are saved as instance
2024
attributes.
2125
22-
:param host: QuickPulse endpoint: https://rt.services.visualstudio.com. Default value is
23-
"https://rt.services.visualstudio.com".
24-
:type host: str
26+
:param credential: Credential needed for the client to connect to Azure. Required.
27+
:type credential: ~azure.core.credentials.TokenCredential
28+
:keyword api_version: Api Version. Default value is "2024-04-01-preview". Note that overriding
29+
this default value may result in unsupported behavior.
30+
:paramtype api_version: str
2531
"""
2632

27-
def __init__(self, host: str = "https://rt.services.visualstudio.com", **kwargs: Any) -> None:
28-
if host is None:
29-
raise ValueError("Parameter 'host' must not be None.")
33+
def __init__(self, credential: "TokenCredential", **kwargs: Any) -> None:
34+
api_version: str = kwargs.pop("api_version", "2024-04-01-preview")
35+
36+
# if credential is None:
37+
# raise ValueError("Parameter 'credential' must not be None.")
3038

31-
self.host = host
39+
self.credential = credential
40+
self.api_version = api_version
41+
self.credential_scopes = kwargs.pop("credential_scopes", ["https://monitor.azure.com/.default"])
3242
kwargs.setdefault("sdk_moniker", "quickpulseclient/{}".format(VERSION))
3343
self.polling_interval = kwargs.get("polling_interval", 30)
3444
self._configure(**kwargs)
@@ -43,3 +53,7 @@ def _configure(self, **kwargs: Any) -> None:
4353
self.redirect_policy = kwargs.get("redirect_policy") or policies.RedirectPolicy(**kwargs)
4454
self.retry_policy = kwargs.get("retry_policy") or policies.RetryPolicy(**kwargs)
4555
self.authentication_policy = kwargs.get("authentication_policy")
56+
# if self.credential and not self.authentication_policy:
57+
# self.authentication_policy = policies.BearerTokenCredentialPolicy(
58+
# self.credential, *self.credential_scopes, **kwargs
59+
# )

0 commit comments

Comments
 (0)