Skip to content

Commit

Permalink
Detect and backoff version in metrics exporter
Browse files Browse the repository at this point in the history
Since backoff==2.0.0, the API of the expo function has changed. The
"porcelain" methods of the backoff library (the decorators
backoff.on_exception and backoff.on_predicate) send a "None" value into
the generator and discard the first yielded value. This is for
compatibility with the more general wait generator API inside the
backoff package.

This commit allows the HTTP OTLP metrics exporter to automatically
detect the behavior of the installed backoff package and adapt
accordingly.
  • Loading branch information
nickstenning committed Oct 30, 2022
1 parent 2231d0b commit dcd6140
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
from opentelemetry.sdk.resources import Resource as SDKResource
from opentelemetry.util.re import parse_headers

import backoff
import requests
from backoff import expo

_logger = logging.getLogger(__name__)

Expand All @@ -77,6 +77,18 @@
DEFAULT_METRICS_EXPORT_PATH = "v1/metrics"
DEFAULT_TIMEOUT = 10 # in seconds

# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
# wait generator API requires a first .send(None) before reading the backoff
# values from the generator.
_is_backoff_v2 = next(backoff.expo()) is None


def _expo(*args, **kwargs):
gen = backoff.expo(*args, **kwargs)
if _is_backoff_v2:
gen.send(None)
return gen


class OTLPMetricExporter(MetricExporter):

Expand Down Expand Up @@ -319,7 +331,7 @@ def export(
**kwargs,
) -> MetricExportResult:
serialized_data = self._translate_data(metrics_data)
for delay in expo(max_value=self._MAX_RETRY_TIMEOUT):
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):

if delay == self._MAX_RETRY_TIMEOUT:
return MetricExportResult.FAILURE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from unittest.mock import patch

import requests
import responses

from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
Expand Down Expand Up @@ -269,3 +270,30 @@ def test_serialization(self, mock_post):
verify=exporter._certificate_file,
timeout=exporter._timeout,
)

@responses.activate
@patch("opentelemetry.exporter.otlp.proto.http.metric_exporter.backoff")
@patch("opentelemetry.exporter.otlp.proto.http.metric_exporter.sleep")
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
# In backoff ~= 2.0.0 the first value yielded from expo is None.
def generate_delays(*args, **kwargs):
yield None
yield 1

mock_backoff.expo.configure_mock(**{"side_effect": generate_delays})

# return a retryable error
responses.add(
responses.POST,
"http://metrics.example.com/export",
json={"error": "something exploded"},
status=500,
)

exporter = OTLPMetricExporter(
endpoint="http://metrics.example.com/export"
)
metrics_data = self.metrics["sum_int"]

exporter.export(metrics_data)
mock_sleep.assert_called_once_with(1)

0 comments on commit dcd6140

Please sign in to comment.