23
23
MetricReader ,
24
24
)
25
25
26
- from azure .core .exceptions import HttpResponseError
27
26
from azure .core .pipeline .policies import ContentDecodePolicy
28
27
from azure .monitor .opentelemetry .exporter ._quickpulse ._constants import (
29
28
_LONG_PING_INTERVAL_SECONDS ,
30
29
_POST_CANCEL_INTERVAL_SECONDS ,
31
30
_POST_INTERVAL_SECONDS ,
31
+ _QUICKPULSE_ETAG_HEADER_NAME ,
32
32
_QUICKPULSE_SUBSCRIBED_HEADER_NAME ,
33
33
)
34
34
from azure .monitor .opentelemetry .exporter ._quickpulse ._generated ._configuration import QuickpulseClientConfiguration
35
35
from azure .monitor .opentelemetry .exporter ._quickpulse ._generated ._client import QuickpulseClient
36
36
from azure .monitor .opentelemetry .exporter ._quickpulse ._generated .models import MonitoringDataPoint
37
37
from azure .monitor .opentelemetry .exporter ._quickpulse ._policy import _QuickpulseRedirectPolicy
38
38
from azure .monitor .opentelemetry .exporter ._quickpulse ._state import (
39
+ _get_and_clear_quickpulse_documents ,
39
40
_get_global_quickpulse_state ,
41
+ _get_quickpulse_etag ,
40
42
_is_ping_state ,
41
43
_set_global_quickpulse_state ,
42
- _get_and_clear_quickpulse_documents ,
44
+ _set_quickpulse_etag ,
43
45
_QuickpulseState ,
44
46
)
45
47
from azure .monitor .opentelemetry .exporter ._quickpulse ._utils import (
46
48
_metric_to_quick_pulse_data_points ,
49
+ _update_filter_configuration ,
47
50
)
48
51
from azure .monitor .opentelemetry .exporter ._connection_string_parser import ConnectionStringParser
49
52
from azure .monitor .opentelemetry .exporter ._utils import (
@@ -143,13 +146,14 @@ def export(
143
146
base_monitoring_data_point = base_monitoring_data_point ,
144
147
documents = _get_and_clear_quickpulse_documents (),
145
148
)
146
-
149
+ configuration_etag = _get_quickpulse_etag () or ""
147
150
token = attach (set_value (_SUPPRESS_INSTRUMENTATION_KEY , True ))
148
151
try :
149
152
post_response = self ._client .publish ( # type: ignore
150
153
endpoint = self ._live_endpoint ,
151
154
monitoring_data_points = data_points ,
152
- ikey = self ._instrumentation_key ,
155
+ ikey = self ._instrumentation_key , # type: ignore
156
+ configuration_etag = configuration_etag ,
153
157
transmission_time = _ticks_since_dot_net_epoch (),
154
158
cls = _Response ,
155
159
)
@@ -163,6 +167,19 @@ def export(
163
167
if header != "true" :
164
168
# User leaving the live metrics page will be treated as an unsuccessful
165
169
result = MetricExportResult .FAILURE
170
+ else :
171
+ # Check if etag has changed
172
+ etag = post_response ._response_headers .get ( # pylint: disable=protected-access
173
+ _QUICKPULSE_ETAG_HEADER_NAME # pylint: disable=protected-access
174
+ )
175
+ if etag and etag != configuration_etag :
176
+ config = (
177
+ post_response ._pipeline_response .http_response .content # pylint: disable=protected-access
178
+ )
179
+ # Content will only be populated if configuration has changed (etag is different)
180
+ if config :
181
+ # Update and apply configuration changes
182
+ _update_filter_configuration (etag , config )
166
183
except Exception : # pylint: disable=broad-except,invalid-name
167
184
_logger .exception ("Exception occurred while publishing live metrics." )
168
185
result = MetricExportResult .FAILURE
@@ -201,21 +218,23 @@ def shutdown(
201
218
def _ping (self , monitoring_data_point : MonitoringDataPoint ) -> Optional [_Response ]:
202
219
ping_response = None
203
220
token = attach (set_value (_SUPPRESS_INSTRUMENTATION_KEY , True ))
221
+ etag = _get_quickpulse_etag () or ""
204
222
try :
205
223
ping_response = self ._client .is_subscribed ( # type: ignore
206
224
endpoint = self ._live_endpoint ,
207
225
monitoring_data_point = monitoring_data_point ,
208
- ikey = self ._instrumentation_key ,
226
+ ikey = self ._instrumentation_key , # type: ignore
209
227
transmission_time = _ticks_since_dot_net_epoch (),
210
228
machine_name = monitoring_data_point .machine_name ,
211
229
instance_name = monitoring_data_point .instance ,
212
230
stream_id = monitoring_data_point .stream_id ,
213
231
role_name = monitoring_data_point .role_name ,
214
- invariant_version = monitoring_data_point .invariant_version ,
232
+ invariant_version = monitoring_data_point .invariant_version , # type: ignore
233
+ configuration_etag = etag ,
215
234
cls = _Response ,
216
235
)
217
236
return ping_response # type: ignore
218
- except HttpResponseError :
237
+ except Exception : # pylint: disable=broad-except,invalid-name
219
238
_logger .exception ("Exception occurred while pinging live metrics." )
220
239
detach (token )
221
240
return ping_response
@@ -243,28 +262,42 @@ def __init__(
243
262
)
244
263
self ._worker .start ()
245
264
265
+ # pylint: disable=protected-access
266
+ # pylint: disable=too-many-nested-blocks
246
267
def _ticker (self ) -> None :
247
268
if _is_ping_state ():
248
269
# Send a ping if elapsed number of request meets the threshold
249
270
if self ._elapsed_num_seconds % _get_global_quickpulse_state ().value == 0 :
250
- ping_response = self ._exporter ._ping ( # pylint: disable=protected-access
271
+ ping_response = self ._exporter ._ping (
251
272
self ._base_monitoring_data_point ,
252
273
)
253
274
if ping_response :
254
- header = ping_response ._response_headers .get ( # pylint: disable=protected-access
255
- _QUICKPULSE_SUBSCRIBED_HEADER_NAME
256
- )
257
- if header and header == "true" :
258
- # Switch state to post if subscribed
259
- _set_global_quickpulse_state (_QuickpulseState .POST_SHORT )
260
- self ._elapsed_num_seconds = 0
261
- else :
262
- # Backoff after _LONG_PING_INTERVAL_SECONDS (60s) of no successful requests
263
- if (
264
- _get_global_quickpulse_state () is _QuickpulseState .PING_SHORT
265
- and self ._elapsed_num_seconds >= _LONG_PING_INTERVAL_SECONDS
266
- ):
267
- _set_global_quickpulse_state (_QuickpulseState .PING_LONG )
275
+ try :
276
+ subscribed = ping_response ._response_headers .get (_QUICKPULSE_SUBSCRIBED_HEADER_NAME )
277
+ if subscribed and subscribed == "true" :
278
+ # Switch state to post if subscribed
279
+ _set_global_quickpulse_state (_QuickpulseState .POST_SHORT )
280
+ self ._elapsed_num_seconds = 0
281
+ # Update config etag
282
+ etag = ping_response ._response_headers .get (_QUICKPULSE_ETAG_HEADER_NAME )
283
+ if etag is None :
284
+ etag = ""
285
+ if _get_quickpulse_etag () != etag :
286
+ _set_quickpulse_etag (etag )
287
+ # TODO: Set default document filter config from response body
288
+ # config = ping_response._pipeline_response.http_response.content
289
+ else :
290
+ # Backoff after _LONG_PING_INTERVAL_SECONDS (60s) of no successful requests
291
+ if (
292
+ _get_global_quickpulse_state () is _QuickpulseState .PING_SHORT
293
+ and self ._elapsed_num_seconds >= _LONG_PING_INTERVAL_SECONDS
294
+ ):
295
+ _set_global_quickpulse_state (_QuickpulseState .PING_LONG )
296
+ # Reset etag to default if not subscribed
297
+ _set_quickpulse_etag ("" )
298
+ except Exception : # pylint: disable=broad-except,invalid-name
299
+ _logger .exception ("Exception occurred while pinging live metrics." )
300
+ _set_quickpulse_etag ("" )
268
301
# TODO: Implement redirect
269
302
else :
270
303
# Erroneous ping responses instigate backoff logic
@@ -274,6 +307,8 @@ def _ticker(self) -> None:
274
307
and self ._elapsed_num_seconds >= _LONG_PING_INTERVAL_SECONDS
275
308
):
276
309
_set_global_quickpulse_state (_QuickpulseState .PING_LONG )
310
+ # Reset etag to default if error
311
+ _set_quickpulse_etag ("" )
277
312
else :
278
313
try :
279
314
self .collect ()
@@ -283,6 +318,8 @@ def _ticker(self) -> None:
283
318
# And resume pinging
284
319
if self ._elapsed_num_seconds >= _POST_CANCEL_INTERVAL_SECONDS :
285
320
_set_global_quickpulse_state (_QuickpulseState .PING_SHORT )
321
+ # Reset etag to default
322
+ _set_quickpulse_etag ("" )
286
323
self ._elapsed_num_seconds = 0
287
324
288
325
self ._elapsed_num_seconds += 1
0 commit comments