From e60e605b3da1e6b04dd30bd917afa5d791ed9cdb Mon Sep 17 00:00:00 2001 From: Johannes Liebermann Date: Mon, 7 Oct 2019 16:45:38 +0200 Subject: [PATCH] Handle time conversion between OT and OTel Handle time format differences between OpenTracing and OpenTelemetry. OpenTracing expects time values in seconds since the epoch represented as floats. OpenTelemetry expects time values in nanoseconds since the epoch represented as ints. --- .../src/opentracingshim/__init__.py | 20 +++++++++----- opentracing-shim/src/opentracingshim/util.py | 26 +++++++++++++++++++ opentracing-shim/tests/test_shim.py | 17 ++++++++++-- opentracing-shim/tests/test_util.py | 13 ++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/opentracing-shim/src/opentracingshim/__init__.py b/opentracing-shim/src/opentracingshim/__init__.py index 7c5066f44d1..c0f59de937a 100644 --- a/opentracing-shim/src/opentracingshim/__init__.py +++ b/opentracing-shim/src/opentracingshim/__init__.py @@ -61,12 +61,11 @@ def set_operation_name(self, operation_name): self._otel_span.update_name(operation_name) return self - def finish(self, finish_time=None): - self._otel_span.end() - # TODO: Handle finish_time. The OpenTelemetry API doesn't currently - # support setting end time on a span and we cannot assume that all - # OpenTelemetry Tracer implementations have an `end_time` field. - # https://github.com/open-telemetry/opentelemetry-python/issues/134 + def finish(self, finish_time: float = None): + end_time = finish_time + if end_time is not None: + end_time = util.time_seconds_to_ns(finish_time) + self._otel_span.end(end_time=end_time) def set_tag(self, key, value): self._otel_span.set_attribute(key, value) @@ -233,7 +232,14 @@ def start_span( for key, value in tags.items(): span.set_attribute(key, value) - span.start(start_time=start_time) + # The OpenTracing API expects time values to be `float` values which + # represent the number of seconds since the epoch. OpenTelemetry + # represents time values as nanoseconds since the epoch. + start_time_ns = start_time + if start_time_ns is not None: + start_time_ns = util.time_seconds_to_ns(start_time) + + span.start(start_time=start_time_ns) context = SpanContextWrapper(span.get_context()) return SpanWrapper(self, context, span) diff --git a/opentracing-shim/src/opentracingshim/util.py b/opentracing-shim/src/opentracingshim/util.py index 48a1a108c45..6f7b80fe020 100644 --- a/opentracing-shim/src/opentracingshim/util.py +++ b/opentracing-shim/src/opentracingshim/util.py @@ -28,6 +28,32 @@ def time_ns(): DEFAULT_EVENT_NAME = "log" +def time_seconds_to_ns(time_seconds: float) -> int: + """Converts a time value in seconds to a time value in nanoseconds. + + `time_seconds` is a `float` as returned by `time.time()` which represents + the number of seconds since the epoch. + + The returned value is an `int` representing the number of nanoseconds since + the epoch. + """ + + return int(time_seconds * 1e9) + + +def time_seconds_from_ns(time_nanoseconds: int) -> float: + """Converts a time value in nanoseconds to a time value in seconds. + + `time_nanoseconds` is an `int` representing the number of nanoseconds since + the epoch. + + The returned value is a `float` representing the number of seconds since + the epoch. + """ + + return time_nanoseconds / 1e9 + + def event_name_from_kv(key_values: dict) -> str: """A helper function which returns an event name from the given dict, or a default event name. diff --git a/opentracing-shim/tests/test_shim.py b/opentracing-shim/tests/test_shim.py index 2e2a71c8e7c..c9a9c06a720 100644 --- a/opentracing-shim/tests/test_shim.py +++ b/opentracing-shim/tests/test_shim.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time import unittest import opentracing @@ -19,6 +20,7 @@ import opentracingshim from opentelemetry import trace from opentelemetry.sdk.trace import Tracer +from opentracingshim import util class TestShim(unittest.TestCase): @@ -112,9 +114,20 @@ def test_explicit_span_finish(self): def test_explicit_start_time(self): """Test `start_time` argument.""" - now = opentracingshim.util.time_ns() + now = time.time() with self.shim.start_active_span("TestSpan", start_time=now) as scope: - self.assertEqual(scope.span.unwrap().start_time, now) + result = util.time_seconds_from_ns(scope.span.unwrap().start_time) + self.assertEqual(result, now) + + def test_explicit_end_time(self): + """Test `end_time` argument of `finish()` method.""" + + span = self.shim.start_span("TestSpan") + now = time.time() + span.finish(now) + + end_time = util.time_seconds_from_ns(span.unwrap().end_time) + self.assertEqual(end_time, now) def test_explicit_span_activation(self): """Test manual activation and deactivation of a span.""" diff --git a/opentracing-shim/tests/test_util.py b/opentracing-shim/tests/test_util.py index 3b2dabcc89f..99e0b61ddce 100644 --- a/opentracing-shim/tests/test_util.py +++ b/opentracing-shim/tests/test_util.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time import unittest from opentracingshim import util @@ -35,3 +36,15 @@ def test_event_name_from_kv(self): # Test missing `event` field. res = util.event_name_from_kv({"foo": "bar"}) self.assertEqual(res, util.DEFAULT_EVENT_NAME) + + def test_time_seconds_to_ns(self): + time_seconds = time.time() + result = util.time_seconds_to_ns(time_seconds) + + self.assertEqual(result, int(time_seconds * 1e9)) + + def test_time_seconds_from_ns(self): + time_nanoseconds = util.time_ns() + result = util.time_seconds_from_ns(time_nanoseconds) + + self.assertEqual(result, time_nanoseconds / 1e9)