Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit d0f9965

Browse files
authored
Fix exporting of zero value networks statsbeat (#1155)
1 parent 891666a commit d0f9965

File tree

7 files changed

+226
-56
lines changed

7 files changed

+226
-56
lines changed

contrib/opencensus-ext-azure/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
- Add storage existence checks to storing and transmitting in exporter
66
([#1150](https://github.com/census-instrumentation/opencensus-python/pull/1150))
77
- Add 502 and 504 status codes as retriable
8-
([#1150](https://github.com/census-instrumentation/opencensus-python/pull/1150))
8+
([#1153](https://github.com/census-instrumentation/opencensus-python/pull/1153))
9+
- Fix statsbeat bug - exporting zero values for network statsbeat
10+
([#1155](https://github.com/census-instrumentation/opencensus-python/pull/1155))
911

1012
## 1.1.6
1113
Released 2022-08-03

contrib/opencensus-ext-azure/opencensus/ext/azure/common/transport.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class TransportStatusCode:
5959

6060
class TransportMixin(object):
6161

62-
# check to see if collecting requests information related to statsbeats
62+
# check to see whether its the case of stats collection
6363
def _check_stats_collection(self):
6464
return state.is_statsbeat_enabled() and \
6565
not state.get_statsbeat_shutdown() and \
@@ -334,15 +334,16 @@ def _statsbeat_failure_reached_threshold():
334334

335335
def _update_requests_map(type, value=None):
336336
if value is None:
337-
value = 0 # error state
337+
value = 0
338338
with _requests_lock:
339339
if type == "count":
340340
_requests_map['count'] = _requests_map.get('count', 0) + 1 # noqa: E501
341-
elif type == "duration":
341+
elif type == "duration": # value will be duration
342342
_requests_map['duration'] = _requests_map.get('duration', 0) + value # noqa: E501
343343
elif type == "success":
344344
_requests_map['success'] = _requests_map.get('success', 0) + 1 # noqa: E501
345345
else:
346+
# value will be a key (status_code/error message)
346347
prev = 0
347348
if _requests_map.get(type):
348349
prev = _requests_map.get(type).get(value, 0)

contrib/opencensus-ext-azure/opencensus/ext/azure/log_exporter/__init__.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
import logging
16-
import os
1716
import random
1817
import threading
1918
import time
@@ -66,9 +65,6 @@ def __init__(self, **options):
6665
self._queue = Queue(capacity=self.options.queue_capacity)
6766
self._worker = Worker(self._queue, self)
6867
self._worker.start()
69-
# start statsbeat on exporter instantiation
70-
if not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"):
71-
statsbeat.collect_statsbeat_metrics(self.options)
7268
# For redirects
7369
self._consecutive_redirects = 0 # To prevent circular redirects
7470

@@ -187,9 +183,15 @@ def filter(self, record):
187183
return random.random() < self.probability
188184

189185

190-
class AzureLogHandler(TransportMixin, ProcessorMixin, BaseLogHandler):
186+
class AzureLogHandler(BaseLogHandler, TransportMixin, ProcessorMixin):
191187
"""Handler for logging to Microsoft Azure Monitor."""
192188

189+
def __init__(self, **options):
190+
super(AzureLogHandler, self).__init__(**options)
191+
# start statsbeat on exporter instantiation
192+
if self._check_stats_collection():
193+
statsbeat.collect_statsbeat_metrics(self.options)
194+
193195
def log_record_to_envelope(self, record):
194196
envelope = create_envelope(self.options.instrumentation_key, record)
195197

@@ -257,6 +259,12 @@ def log_record_to_envelope(self, record):
257259
class AzureEventHandler(TransportMixin, ProcessorMixin, BaseLogHandler):
258260
"""Handler for sending custom events to Microsoft Azure Monitor."""
259261

262+
def __init__(self, **options):
263+
super(AzureEventHandler, self).__init__(**options)
264+
# start statsbeat on exporter instantiation
265+
if self._check_stats_collection():
266+
statsbeat.collect_statsbeat_metrics(self.options)
267+
260268
def log_record_to_envelope(self, record):
261269
envelope = create_envelope(self.options.instrumentation_key, record)
262270

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/__init__.py

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
import atexit
16-
import os
1716

1817
from opencensus.common import utils as common_utils
1918
from opencensus.ext.azure.common import Options, utils
@@ -30,6 +29,9 @@
3029
TransportStatusCode,
3130
)
3231
from opencensus.ext.azure.metrics_exporter import standard_metrics
32+
from opencensus.ext.azure.statsbeat.statsbeat_metrics import (
33+
_NETWORK_STATSBEAT_NAMES,
34+
)
3335
from opencensus.metrics import transport
3436
from opencensus.metrics.export.metric_descriptor import MetricDescriptorType
3537
from opencensus.stats import stats as stats_module
@@ -99,44 +101,47 @@ def metric_to_envelopes(self, metric):
99101
# Each time series will be uniquely identified by its
100102
# label values
101103
for time_series in metric.time_series:
102-
# Using stats, time_series should only have one
103-
# point which contains the aggregated value
104-
data_point = self._create_data_points(
105-
time_series, md)[0]
106-
# if statsbeat exporter, ignore points with 0 value
107-
if self._is_stats and data_point.value == 0:
108-
continue
104+
# time_series should only have one point which
105+
# contains the aggregated value
106+
# time_series point list is never empty
107+
point = time_series.points[0]
108+
# we ignore None and 0 values for network statsbeats
109+
if self._is_stats_exporter():
110+
if md.name in _NETWORK_STATSBEAT_NAMES:
111+
if not point.value.value:
112+
continue
113+
data_point = DataPoint(
114+
ns=md.name,
115+
name=md.name,
116+
value=point.value.value
117+
)
109118
# The timestamp is when the metric was recorded
110-
timestamp = time_series.points[0].timestamp
119+
timestamp = point.timestamp
111120
# Get the properties using label keys from metric
112121
# and label values of the time series
113-
properties = self._create_properties(time_series, md)
114-
envelopes.append(self._create_envelope(data_point,
115-
timestamp,
116-
properties))
122+
properties = self._create_properties(
123+
time_series,
124+
md.label_keys
125+
)
126+
envelopes.append(
127+
self._create_envelope(
128+
data_point,
129+
timestamp,
130+
properties
131+
)
132+
)
117133
return envelopes
118134

119-
def _create_data_points(self, time_series, metric_descriptor):
120-
"""Convert a metric's OC time series to list of Azure data points."""
121-
data_points = []
122-
for point in time_series.points:
123-
# TODO: Possibly encode namespace in name
124-
data_point = DataPoint(ns=metric_descriptor.name,
125-
name=metric_descriptor.name,
126-
value=point.value.value)
127-
data_points.append(data_point)
128-
return data_points
129-
130-
def _create_properties(self, time_series, metric_descriptor):
135+
def _create_properties(self, time_series, label_keys):
131136
properties = {}
132137
# We construct a properties map from the label keys and values. We
133138
# assume the ordering is already correct
134-
for i in range(len(metric_descriptor.label_keys)):
139+
for i in range(len(label_keys)):
135140
if time_series.label_values[i].value is None:
136141
value = "null"
137142
else:
138143
value = time_series.label_values[i].value
139-
properties[metric_descriptor.label_keys[i].key] = value
144+
properties[label_keys[i].key] = value
140145
return properties
141146

142147
def _create_envelope(self, data_point, timestamp, properties):
@@ -177,8 +182,9 @@ def new_metrics_exporter(**options):
177182
producers,
178183
exporter,
179184
interval=exporter.options.export_interval)
180-
if not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"):
185+
# start statsbeat on exporter instantiation
186+
if exporter._check_stats_collection():
187+
# Import here to avoid circular dependencies
181188
from opencensus.ext.azure.statsbeat import statsbeat
182-
# Stats will track the user's ikey
183189
statsbeat.collect_statsbeat_metrics(exporter.options)
184190
return exporter

contrib/opencensus-ext-azure/opencensus/ext/azure/statsbeat/statsbeat_metrics.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@
6262
_REQ_THROTTLE_NAME = "Throttle Count"
6363
_REQ_EXCEPTION_NAME = "Exception Count"
6464

65+
_NETWORK_STATSBEAT_NAMES = (
66+
_REQ_SUCCESS_NAME,
67+
_REQ_FAILURE_NAME,
68+
_REQ_DURATION_NAME,
69+
_REQ_RETRY_NAME,
70+
_REQ_THROTTLE_NAME,
71+
_REQ_EXCEPTION_NAME,
72+
)
73+
6574
_ENDPOINT_TYPES = ["breeze"]
6675
_RP_NAMES = ["appsvc", "functions", "vm", "unknown"]
6776

@@ -368,13 +377,15 @@ def _get_network_metrics(self):
368377
properties.pop()
369378

370379
stats_metric = metric.get_metric(datetime.datetime.utcnow())
371-
# Only export metric if status/exc_type was present
380+
# metric will be None if status_code or exc_type is invalid
381+
# for success count, this will never be None
372382
if stats_metric is not None:
383+
# we handle not exporting of None and 0 values in the exporter
373384
metrics.append(stats_metric)
374385
return metrics
375386

376387
def _get_feature_metric(self):
377-
# Don't export if value is 0
388+
# Don't export if feature list is None
378389
if self._feature is _StatsbeatFeature.NONE:
379390
return None
380391
properties = self._get_common_properties()
@@ -385,7 +396,7 @@ def _get_feature_metric(self):
385396

386397
def _get_instrumentation_metric(self):
387398
integrations = get_integrations()
388-
# Don't export if value is 0
399+
# Don't export if instrumentation list is None
389400
if integrations is _Integrations.NONE:
390401
return None
391402
properties = self._get_common_properties()

contrib/opencensus-ext-azure/opencensus/ext/azure/trace_exporter/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import atexit
1616
import json
1717
import logging
18-
import os
1918

2019
from opencensus.common.schedule import QueueExitEvent
2120
from opencensus.ext.azure.common import Options, utils
@@ -56,13 +55,14 @@
5655
STACKTRACE = attributes_helper.COMMON_ATTRIBUTES['STACKTRACE']
5756

5857

59-
class AzureExporter(BaseExporter, ProcessorMixin, TransportMixin):
58+
class AzureExporter(BaseExporter, TransportMixin, ProcessorMixin):
6059
"""An exporter that sends traces to Microsoft Azure Monitor.
6160
6261
:param options: Options for the exporter.
6362
"""
6463

6564
def __init__(self, **options):
65+
super(AzureExporter, self).__init__(**options)
6666
self.options = Options(**options)
6767
utils.validate_instrumentation_key(self.options.instrumentation_key)
6868
self.storage = None
@@ -75,10 +75,9 @@ def __init__(self, **options):
7575
source=self.__class__.__name__,
7676
)
7777
self._telemetry_processors = []
78-
super(AzureExporter, self).__init__(**options)
7978
atexit.register(self._stop, self.options.grace_period)
8079
# start statsbeat on exporter instantiation
81-
if not os.environ.get("APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"):
80+
if self._check_stats_collection():
8281
statsbeat.collect_statsbeat_metrics(self.options)
8382
# For redirects
8483
self._consecutive_redirects = 0 # To prevent circular redirects

0 commit comments

Comments
 (0)