Skip to content

Commit f4d87b0

Browse files
authored
feat: add DD_TRACE_API_VERSION environment variable (#2918)
* feat: add ``DD_TRACE_ENCODING`` environment variable The new variable can be used to activate the v0.5 trace encoding on demand. * rename env var * better docs and Tracer.configure API extension
1 parent b1e5902 commit f4d87b0

File tree

16 files changed

+382
-162
lines changed

16 files changed

+382
-162
lines changed

benchmarks/encoder/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ one-trace: &base_variant
55
ltags: 0
66
nmetrics: 0
77
dd_origin: false
8+
encoding: "v0.4"
89
many-traces:
910
<<: *base_variant
1011
ntraces: 100

benchmarks/encoder/scenario.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ class Encoder(bm.Scenario):
99
ltags = bm.var(type=int)
1010
nmetrics = bm.var(type=int)
1111
dd_origin = bm.var(type=bool)
12+
encoding = bm.var(type=str)
1213

1314
def run(self):
14-
encoder = utils.init_encoder()
15+
encoder = utils.init_encoder(self.encoding)
1516
traces = utils.gen_traces(self)
1617

1718
def _(loops):

benchmarks/encoder/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import random
22
import string
33

4-
from ddtrace.internal.encoding import Encoder
4+
from ddtrace.internal.encoding import MSGPACK_ENCODERS
55
from ddtrace.span import Span
66

77

@@ -10,14 +10,14 @@
1010
# see https://github.com/DataDog/dd-trace-py/pull/2422
1111
from ddtrace.internal._encoding import BufferedEncoder # noqa: F401
1212

13-
def init_encoder(max_size=8 << 20, max_item_size=8 << 20):
14-
return Encoder(max_size, max_item_size)
13+
def init_encoder(encoding, max_size=8 << 20, max_item_size=8 << 20):
14+
return MSGPACK_ENCODERS[encoding](max_size, max_item_size)
1515

1616

1717
except ImportError:
1818

19-
def init_encoder():
20-
return Encoder()
19+
def init_encoder(encoding):
20+
return MSGPACK_ENCODERS[encoding]()
2121

2222

2323
def _rands(size=6, chars=string.ascii_uppercase + string.digits):

ddtrace/encoding.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from .internal.encoding import Encoder
21
from .internal.encoding import JSONEncoder
32
from .internal.encoding import JSONEncoderV2
43
from .internal.encoding import MsgpackEncoderV03 as MsgpackEncoder
54
from .utils.deprecation import deprecation
65

76

7+
Encoder = MsgpackEncoder
8+
9+
810
__all__ = (
911
"Encoder",
1012
"JSONEncoder",

ddtrace/internal/encoding.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .logger import get_logger
1212

1313

14-
__all__ = ["MsgpackEncoderV03", "MsgpackEncoderV05", "ListStringTable", "Encoder"]
14+
__all__ = ["MsgpackEncoderV03", "MsgpackEncoderV05", "ListStringTable", "MSGPACK_ENCODERS"]
1515

1616

1717
if TYPE_CHECKING:
@@ -30,27 +30,31 @@ def encode_traces(self, traces):
3030
# type: (List[List[Span]]) -> str
3131
"""
3232
Encodes a list of traces, expecting a list of items where each items
33-
is a list of spans. Before dump the string in a serialized format all
34-
traces are normalized, calling the ``to_dict()`` method. The traces
33+
is a list of spans. Before dumping the string in a serialized format all
34+
traces are normalized according to the encoding format. The trace
3535
nesting is not changed.
3636
3737
:param traces: A list of traces that should be serialized
3838
"""
39-
normalized_traces = [[span.to_dict() for span in trace] for trace in traces]
40-
return self.encode(normalized_traces)
39+
raise NotImplementedError()
4140

42-
@staticmethod
43-
def encode(obj):
41+
def encode(self, obj):
42+
# type: (List[List[Any]]) -> str
4443
"""
4544
Defines the underlying format used during traces or services encoding.
46-
This method must be implemented and should only be used by the internal functions.
45+
This method must be implemented and should only be used by the internal
46+
functions.
4747
"""
48-
raise NotImplementedError
48+
raise NotImplementedError()
4949

5050

5151
class JSONEncoder(_EncoderBase):
5252
content_type = "application/json"
5353

54+
def encode_traces(self, traces):
55+
normalized_traces = [[span.to_dict() for span in trace] for trace in traces]
56+
return self.encode(normalized_traces)
57+
5458
@staticmethod
5559
def encode(obj):
5660
# type: (Any) -> str
@@ -93,4 +97,8 @@ def _decode_id_to_hex(hex_id):
9397
return int(hex_id, 16)
9498

9599

96-
Encoder = MsgpackEncoderV03
100+
MSGPACK_ENCODERS = {
101+
"v0.3": MsgpackEncoderV03,
102+
"v0.4": MsgpackEncoderV03,
103+
"v0.5": MsgpackEncoderV05,
104+
}

ddtrace/internal/writer.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
from ._encoding import BufferFull
2929
from ._encoding import BufferItemTooLarge
3030
from .agent import get_connection
31-
from .encoding import Encoder
3231
from .encoding import JSONEncoderV2
32+
from .encoding import MSGPACK_ENCODERS
3333
from .logger import get_logger
3434
from .runtime import container
3535
from .sma import SimpleMovingAverage
@@ -234,6 +234,7 @@ def __init__(
234234
dogstatsd=None, # type: Optional[DogStatsd]
235235
report_metrics=False, # type: bool
236236
sync_mode=False, # type: bool
237+
api_version=None, # type: Optional[str]
237238
):
238239
# type: (...) -> None
239240
# Pre-conditions:
@@ -256,10 +257,18 @@ def __init__(
256257
}
257258
self._timeout = timeout
258259

259-
if priority_sampler is not None:
260-
self._endpoint = "v0.4/traces"
261-
else:
262-
self._endpoint = "v0.3/traces"
260+
encoding_version = (
261+
api_version or os.getenv("DD_TRACE_API_VERSION") or ("v0.4" if priority_sampler is not None else "v0.3")
262+
)
263+
try:
264+
Encoder = MSGPACK_ENCODERS[encoding_version]
265+
except KeyError:
266+
raise ValueError(
267+
"Unsupported encoding version: '%s'. The supported versions are: %r"
268+
% (encoding_version, ", ".join(sorted(MSGPACK_ENCODERS.keys())))
269+
)
270+
271+
self._endpoint = "%s/traces" % encoding_version
263272

264273
self._container_info = container.get_container_info()
265274
if self._container_info and self._container_info.container_id:
@@ -348,10 +357,27 @@ def _put(self, data, headers):
348357
conn.close()
349358

350359
def _downgrade(self, payload, response):
360+
if self._endpoint == "v0.5/traces":
361+
self._endpoint = "v0.4/traces"
362+
self._encoder = MSGPACK_ENCODERS["v0.4"](
363+
max_size=self._buffer_size,
364+
max_item_size=self._max_payload_size,
365+
)
366+
# Since we have to change the encoding in this case, the payload
367+
# would need to be converted to the downgraded encoding before
368+
# sending it, but we chuck it away instead.
369+
log.warning(
370+
"Dropping trace payload due to the downgrade to an incompatible API version (from v0.5 to v0.4). To "
371+
"avoid this from happening in the future, either ensure that the Datadog agent has a v0.5/traces "
372+
"endpoint available, or explicitly set the trace API version to, e.g., v0.4."
373+
)
374+
return None
351375
if self._endpoint == "v0.4/traces":
352376
self._endpoint = "v0.3/traces"
377+
# These endpoints share the same encoding, so we can try sending the
378+
# same payload over the downgraded endpoint.
353379
return payload
354-
raise ValueError
380+
raise ValueError()
355381

356382
def _send_payload(self, payload, count):
357383
headers = self._headers.copy()
@@ -378,7 +404,8 @@ def _send_payload(self, payload, count):
378404
self.agent_url,
379405
)
380406
else:
381-
return self._send_payload(payload, count)
407+
if payload is not None:
408+
self._send_payload(payload, count)
382409
elif response.status >= 400:
383410
log.error(
384411
"failed to send traces to Datadog Agent at %s: HTTP error status %s, reason %s",

ddtrace/tracer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def configure(
297297
writer=None, # type: Optional[TraceWriter]
298298
partial_flush_enabled=None, # type: Optional[bool]
299299
partial_flush_min_spans=None, # type: Optional[int]
300+
api_version=None, # type: Optional[str]
300301
):
301302
# type: (...) -> None
302303
"""
@@ -393,6 +394,7 @@ def configure(
393394
dogstatsd=get_dogstatsd_client(self._dogstatsd_url),
394395
report_metrics=config.health_metrics_enabled,
395396
sync_mode=self._use_sync_mode(),
397+
api_version=api_version,
396398
)
397399
elif writer is None and isinstance(self.writer, LogWriter):
398400
# No need to do anything for the LogWriter.

docs/configuration.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ below:
170170
- A map of case-insensitive header keys to tag names. Automatically applies matching header values as tags on root spans.
171171
For example, ``User-Agent:http.useragent,content-type:http.content_type``.
172172

173+
.. _dd-trace-api-version:
174+
* - ``DD_TRACE_API_VERSION``
175+
- String
176+
- ``v0.4`` if priority sampling is enabled, else ``v0.3``
177+
- The trace API version to use when sending traces to the Datadog agent.
178+
Currently, the supported versions are: ``v0.3``, ``v0.4`` and ``v0.5``.
179+
173180
.. _dd-profiling-enabled:
174181
* - ``DD_PROFILING_ENABLED``
175182
- Boolean
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
prelude: >
3+
The ``v0.5`` trace API version has been made available and can be selected
4+
via the newly introduced ``DD_TRACE_API_VERSION`` environment variable by
5+
setting ``DD_TRACE_API_VERSION="v0.5"``. This newer API version generates
6+
smaller payloads, thus increasing the trace throughput to the Datadog agent,
7+
for better performance.
8+
features:
9+
- |
10+
Added the ``DD_TRACE_API_VERSION`` environment variable for requesting a
11+
specific trace API version.

scripts/profiles/encoders/run.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
import sys
22

3-
from ddtrace.internal.encoding import Encoder
4-
from ddtrace.internal.encoding import MsgpackEncoderV03
5-
from ddtrace.internal.encoding import MsgpackEncoderV05
3+
from ddtrace.internal.encoding import MSGPACK_ENCODERS as ENCODERS
64
from tests.tracer.test_encoders import gen_trace
75

86

9-
ENCODERS = {
10-
"v0.3": MsgpackEncoderV03,
11-
"v0.5": MsgpackEncoderV05,
12-
}
13-
14-
try:
15-
encoder = ENCODERS.get(sys.argv[1], Encoder)(8 << 20, 8 << 20)
16-
except IndexError:
17-
encoder = Encoder(8 << 20, 8 << 20)
7+
encoder = ENCODERS[sys.argv[1]](8 << 20, 8 << 20)
188

199

2010
trace = gen_trace(nspans=1000)

0 commit comments

Comments
 (0)