Skip to content
This repository was archived by the owner on Jul 11, 2022. It is now read-only.

Commit a1c7e9d

Browse files
bhavin192yurishkuro
authored andcommitted
Add 128bit trace_id support (#230)
* Add 128bit trace_id support - Creates a new property in Config 'generate_128bit_trace_id' if set to True then 128bit trace_ids are generated - Adds support for environment variable JAEGER_TRACEID_128BIT. If the value is 'true' then 128bit trace_ids are generated - Tracer takes variable generate_128bit_trace_id, if the value is True then max_trace_id_bits is set to constants._max_trace_id_bits - Adds new function _random_id(bitsize) - Deprecate random_id() in favor of _random_id(bitsize) - Deprecate MAX_ID_BITS in favor of _max_trace_id_bits and _max_id_bits - Set low and high bits of traceId correctly - Uses _id_to_high() and _id_to_low() to do that Signed-off-by: Bhavin Gandhi <[email protected]> * Add tests for 128bit trace_id support Signed-off-by: Bhavin Gandhi <[email protected]> * Update ProbabilisticSampler to trim the trace_id to 64bits Signed-off-by: Bhavin Gandhi <[email protected]> * Update test_round_trip to test with 128bit trace_id - Uses itertools.product to create a list with all possible combinations for tracer i.e. tracer with 64bit trace_id and tracer with 128bit trace_id Signed-off-by: Bhavin Gandhi <[email protected]> * Add test_inject_with_128bit_trace_id to test_codecs.py - This tests the behavior of inject for different codes when it receives 64bit/128bit trace_id on wire when tracer is configured to genereate 64/128bit trace_ids - Also tests the behavior of inject when tracer is configured to generate 64/128bit trace_ids Signed-off-by: Bhavin Gandhi <[email protected]>
1 parent 0ce73e3 commit a1c7e9d

File tree

10 files changed

+184
-26
lines changed

10 files changed

+184
-26
lines changed

jaeger_client/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def _validate_config(self, config):
122122
'reporter_flush_interval',
123123
'sampling_refresh_interval',
124124
'trace_id_header',
125+
'generate_128bit_trace_id',
125126
'baggage_header_prefix',
126127
'service_name',
127128
'throttler']
@@ -166,6 +167,16 @@ def trace_id_header(self):
166167
"""
167168
return self.config.get('trace_id_header', TRACE_ID_HEADER)
168169

170+
@property
171+
def generate_128bit_trace_id(self):
172+
"""
173+
:return: Returns boolean value to indicate if 128bit trace_id
174+
generation is enabled
175+
"""
176+
if 'generate_128bit_trace_id' in self.config:
177+
return get_boolean(self.config['generate_128bit_trace_id'], False)
178+
return os.getenv('JAEGER_TRACEID_128BIT') == 'true'
179+
169180
@property
170181
def baggage_header_prefix(self):
171182
"""
@@ -395,6 +406,7 @@ def create_tracer(self, reporter, sampler, throttler=None):
395406
sampler=sampler,
396407
metrics_factory=self._metrics_factory,
397408
trace_id_header=self.trace_id_header,
409+
generate_128bit_trace_id=self.generate_128bit_trace_id,
398410
baggage_header_prefix=self.baggage_header_prefix,
399411
debug_id_header=self.debug_id_header,
400412
tags=self.tags,

jaeger_client/constants.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@
1818

1919
import six
2020

21-
# Max number of bits to use when generating random ID
21+
# DEPRECATED: max number of bits to use when generating random ID
2222
MAX_ID_BITS = 64
2323

24+
# Max number of bits allowed to use when generating Trace ID
25+
_max_trace_id_bits = 128
26+
27+
# Max number of bits to use when generating random ID
28+
_max_id_bits = 64
29+
2430
# How often remotely controlled sampler polls for sampling strategy
2531
DEFAULT_SAMPLING_INTERVAL = 60
2632

jaeger_client/sampler.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from threading import Lock
2323
from tornado.ioloop import PeriodicCallback
2424
from .constants import (
25-
MAX_ID_BITS,
25+
_max_id_bits,
2626
DEFAULT_SAMPLING_INTERVAL,
2727
SAMPLER_TYPE_CONST,
2828
SAMPLER_TYPE_PROBABILISTIC,
@@ -121,10 +121,11 @@ def __init__(self, rate):
121121
)
122122
assert 0.0 <= rate <= 1.0, 'Sampling rate must be between 0.0 and 1.0'
123123
self.rate = rate
124-
self.max_number = 1 << MAX_ID_BITS
124+
self.max_number = 1 << _max_id_bits
125125
self.boundary = rate * self.max_number
126126

127127
def is_sampled(self, trace_id, operation=''):
128+
trace_id = trace_id & (self.max_number - 1)
128129
return trace_id < self.boundary, self._tags
129130

130131
def close(self):

jaeger_client/thrift.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@
2626
long = int
2727

2828

29+
def _id_to_low(big_id):
30+
"""
31+
:param big_id: id in integer
32+
:return: Returns the right most 64 bits of big_id
33+
"""
34+
if big_id is not None:
35+
return big_id & (_max_unsigned_id - 1)
36+
37+
38+
def _id_to_high(big_id):
39+
"""
40+
:param big_id: id in integer
41+
:return: Returns the left most 64 bits of big_id
42+
"""
43+
if big_id is not None:
44+
return (big_id >> 64) & (_max_unsigned_id - 1)
45+
46+
2947
def id_to_int(big_id):
3048
if big_id is None:
3149
return None
@@ -150,8 +168,8 @@ def make_jaeger_batch(spans, process):
150168
for span in spans:
151169
with span.update_lock:
152170
jaeger_span = ttypes.Span(
153-
traceIdLow=id_to_int(span.trace_id),
154-
traceIdHigh=0,
171+
traceIdLow=id_to_int(_id_to_low(span.trace_id)),
172+
traceIdHigh=id_to_int(_id_to_high(span.trace_id)),
155173
spanId=id_to_int(span.span_id),
156174
parentSpanId=id_to_int(span.parent_id) or 0,
157175
operationName=span.operation_name,

jaeger_client/tracer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(
4444
self, service_name, reporter, sampler, metrics=None,
4545
metrics_factory=None,
4646
trace_id_header=constants.TRACE_ID_HEADER,
47+
generate_128bit_trace_id=False,
4748
baggage_header_prefix=constants.BAGGAGE_HEADER_PREFIX,
4849
debug_id_header=constants.DEBUG_ID_HEADER_KEY,
4950
one_span_per_rpc=False, extra_codecs=None,
@@ -60,6 +61,8 @@ def __init__(
6061
self.debug_id_header = debug_id_header
6162
self.one_span_per_rpc = one_span_per_rpc
6263
self.max_tag_value_length = max_tag_value_length
64+
self.max_trace_id_bits = constants._max_trace_id_bits if generate_128bit_trace_id \
65+
else constants._max_id_bits
6366
self.codecs = {
6467
Format.TEXT_MAP: TextCodec(
6568
url_encoding=False,
@@ -144,8 +147,8 @@ def start_span(self,
144147
tags.get(ext_tags.SPAN_KIND) == ext_tags.SPAN_KIND_RPC_SERVER
145148

146149
if parent is None or not parent.has_trace:
147-
trace_id = self.random_id()
148-
span_id = trace_id
150+
trace_id = self._random_id(self.max_trace_id_bits)
151+
span_id = self._random_id(constants._max_id_bits)
149152
parent_id = None
150153
flags = 0
151154
baggage = None
@@ -170,7 +173,7 @@ def start_span(self,
170173
span_id = parent.span_id
171174
parent_id = parent.parent_id
172175
else:
173-
span_id = self.random_id()
176+
span_id = self._random_id(constants._max_id_bits)
174177
parent_id = parent.span_id
175178
flags = parent.flags
176179
baggage = dict(parent.baggage) # TODO do we need to clone?
@@ -238,8 +241,14 @@ def report_span(self, span):
238241
self.metrics.spans_finished(1)
239242

240243
def random_id(self):
244+
"""
245+
DEPRECATED: use _random_id() instead
246+
"""
241247
return self.random.getrandbits(constants.MAX_ID_BITS)
242248

249+
def _random_id(self, bitsize):
250+
return self.random.getrandbits(bitsize)
251+
243252
def is_debug_allowed(self, *args, **kwargs):
244253
if not self.throttler:
245254
return True

tests/test_codecs.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import unittest
1818
from collections import namedtuple
19+
from itertools import product
1920
import six
2021

2122
import mock
@@ -28,6 +29,7 @@
2829
)
2930
from jaeger_client.config import Config
3031
from jaeger_client.reporter import InMemoryReporter
32+
from jaeger_client import constants
3133
from opentracing import Format
3234
from opentracing.propagation import (
3335
InvalidCarrierException,
@@ -82,6 +84,8 @@ def test_trace_context_from_to_string(self):
8284
tests = [
8385
[(256, 127, None, 1), '100:7f:0:1'],
8486
[(256, 127, 256, 0), '100:7f:100:0'],
87+
[(0xffffffffffffffffffffffffffffffff, 127, 256, 0),
88+
'ffffffffffffffffffffffffffffffff:7f:100:0'],
8589
]
8690
for test in tests:
8791
ctx = test[0]
@@ -211,11 +215,11 @@ def test_context_from_large_ids(self):
211215
codec = TextCodec(trace_id_header='Trace_ID',
212216
baggage_header_prefix='Trace-Attr-')
213217
headers = {
214-
'Trace-ID': 'FFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:1',
218+
'Trace-ID': 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:1',
215219
}
216220
context = codec.extract(headers)
217-
assert context.trace_id == 0xFFFFFFFFFFFFFFFF
218-
assert context.trace_id == (1 << 64) - 1
221+
assert context.trace_id == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
222+
assert context.trace_id == (1 << 128) - 1
219223
assert context.trace_id > 0
220224
assert context.span_id == 0xFFFFFFFFFFFFFFFF
221225
assert context.span_id == (1 << 64) - 1
@@ -437,11 +441,64 @@ def _test_baggage_without_trace_id(tracer, trace_id_header, baggage_header_prefi
437441
(ZipkinSpanFormat, {}),
438442
])
439443
def test_round_trip(tracer, fmt, carrier):
440-
span = tracer.start_span('test-%s' % fmt)
441-
tracer.inject(span, fmt, carrier)
442-
context = tracer.extract(fmt, carrier)
443-
span2 = tracer.start_span('test-%s' % fmt, child_of=context)
444-
assert span.trace_id == span2.trace_id
444+
tracer_128bit = Tracer(
445+
service_name='test',
446+
reporter=InMemoryReporter(),
447+
sampler=ConstSampler(True),
448+
generate_128bit_trace_id=True)
449+
450+
for tracer1, tracer2 in product([tracer, tracer_128bit], repeat=2):
451+
span = tracer1.start_span('test-%s' % fmt)
452+
tracer1.inject(span, fmt, carrier)
453+
context = tracer2.extract(fmt, carrier)
454+
span2 = tracer2.start_span('test-%s' % fmt, child_of=context)
455+
assert span.trace_id == span2.trace_id
456+
457+
458+
def _text_codec_to_trace_id_string(carrier):
459+
return carrier[constants.TRACE_ID_HEADER].split(':')[0]
460+
461+
462+
def _zipkin_codec_to_trace_id_string(carrier):
463+
return '{:x}'.format(carrier['trace_id'])
464+
465+
466+
@pytest.mark.parametrize('fmt,carrier,get_trace_id', [
467+
(Format.TEXT_MAP, {}, _text_codec_to_trace_id_string),
468+
(Format.HTTP_HEADERS, {}, _text_codec_to_trace_id_string),
469+
(ZipkinSpanFormat, {}, _zipkin_codec_to_trace_id_string),
470+
])
471+
def test_inject_with_128bit_trace_id(tracer, fmt, carrier, get_trace_id):
472+
tracer_128bit = Tracer(
473+
service_name='test',
474+
reporter=InMemoryReporter(),
475+
sampler=ConstSampler(True),
476+
generate_128bit_trace_id=True)
477+
478+
for tracer in [tracer, tracer_128bit]:
479+
length = tracer.max_trace_id_bits / 4
480+
trace_id = (1 << 64) - 1 if length == 16 else (1 << 128) - 1
481+
ctx = SpanContext(trace_id=trace_id, span_id=127, parent_id=None,
482+
flags=1)
483+
span = Span(ctx, operation_name='test-%s' % fmt, tracer=None, start_time=1)
484+
tracer.inject(span, fmt, carrier)
485+
assert len(get_trace_id(carrier)) == length
486+
487+
# test if the trace_id arrived on wire remains same even if
488+
# the tracer is configured for 64bit ids or 128bit ids
489+
ctx = SpanContext(trace_id=(1 << 128) - 1, span_id=127, parent_id=None,
490+
flags=0)
491+
span = tracer.start_span('test-%s' % fmt, child_of=ctx)
492+
carrier = dict()
493+
tracer.inject(span, fmt, carrier)
494+
assert len(get_trace_id(carrier)) == 32
495+
496+
ctx = SpanContext(trace_id=(1 << 64) - 1, span_id=127, parent_id=None,
497+
flags=0)
498+
span = tracer.start_span('test-%s' % fmt, child_of=ctx)
499+
carrier = dict()
500+
tracer.inject(span, fmt, carrier)
501+
assert len(get_trace_id(carrier)) == 16
445502

446503

447504
def test_debug_id():

tests/test_config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,20 @@ def test_initialize_tracer(self):
156156
def test_default_local_agent_reporting_port(self):
157157
c = Config({}, service_name='x')
158158
assert c.local_agent_reporting_port == 6831
159+
160+
def test_generate_128bit_trace_id(self):
161+
c = Config({}, service_name='x')
162+
assert c.generate_128bit_trace_id is False
163+
164+
c = Config({'generate_128bit_trace_id': True}, service_name='x')
165+
assert c.generate_128bit_trace_id is True
166+
167+
os.environ['JAEGER_TRACEID_128BIT'] = 'true'
168+
c = Config({'generate_128bit_trace_id': False}, service_name='x')
169+
assert c.generate_128bit_trace_id is False
170+
171+
c = Config({}, service_name='x')
172+
assert c.generate_128bit_trace_id is True
173+
174+
os.environ.pop('JAEGER_TRACEID_128BIT')
175+
assert os.getenv('JAEGER_TRACEID_128BIT', None) is None

tests/test_sampler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def test_guaranteed_throughput_probabilistic_sampler():
195195
sampled, tags = sampler.is_sampled(MAX_INT - 10)
196196
assert sampled
197197
assert tags == get_tags('probabilistic', 0.51)
198-
sampled, tags = sampler.is_sampled(MAX_INT + (MAX_INT / 4))
198+
sampled, tags = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)))
199199
assert sampled
200200
assert tags == get_tags('lowerbound', 0.51)
201201

@@ -234,7 +234,7 @@ def test_adaptive_sampler():
234234
# Move time forward by a second to guarantee the rate limiter has enough credits
235235
mock_time.side_effect = lambda: ts + 1
236236

237-
sampled, tags = sampler.is_sampled(MAX_INT + (MAX_INT / 4), 'new_op')
237+
sampled, tags = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)), 'new_op')
238238
assert sampled
239239
assert tags == get_tags('lowerbound', 0.51)
240240

@@ -243,7 +243,7 @@ def test_adaptive_sampler():
243243
sampled, tags = sampler.is_sampled(MAX_INT - 10, 'new_op_2')
244244
assert sampled
245245
assert tags == get_tags('probabilistic', 0.51)
246-
sampled, _ = sampler.is_sampled(MAX_INT + (MAX_INT / 4), 'new_op_2')
246+
sampled, _ = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)), 'new_op_2')
247247
assert not sampled
248248
assert '%s' % sampler == 'AdaptiveSampler(0.510000, 3.000000, 2)'
249249

tests/test_thrift.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ def now_reading(self):
6060

6161
def test_large_ids(tracer):
6262

63-
def serialize(span_id):
63+
def serialize(trace_id, span_id):
6464
"""Checks that there are no exceptions during marshalling."""
65-
parent_ctx = SpanContext(trace_id=span_id, span_id=span_id,
65+
parent_ctx = SpanContext(trace_id=trace_id, span_id=span_id,
6666
parent_id=0, flags=1)
6767
parent = Span(context=parent_ctx, operation_name='x', tracer=tracer)
6868
span = tracer.start_span(operation_name='x',
@@ -71,25 +71,44 @@ def serialize(span_id):
7171
_marshall_span(span)
7272

7373
trace_id = 0x77fd53dc6b437681
74-
serialize(trace_id)
74+
serialize(trace_id, trace_id)
7575
assert thrift.id_to_int(trace_id) == 0x77fd53dc6b437681
7676

7777
trace_id = 0x7fffffffffffffff
78-
serialize(trace_id)
78+
serialize(trace_id, trace_id)
7979
assert thrift.id_to_int(trace_id) == 0x7fffffffffffffff
8080

8181
trace_id = 0x8000000000000000
82-
serialize(trace_id)
82+
serialize(trace_id, trace_id)
8383
assert thrift.id_to_int(trace_id) == -0x8000000000000000
8484

8585
trace_id = 0x97fd53dc6b437681
86-
serialize(trace_id)
86+
serialize(trace_id, trace_id)
8787

8888
trace_id = (1 << 64) - 1
8989
assert trace_id == 0xffffffffffffffff
90-
serialize(trace_id)
90+
serialize(trace_id, trace_id)
9191
assert thrift.id_to_int(trace_id) == -1
9292

93+
trace_id = (1 << 128) - 1
94+
span_id = 0xffffffffffffffff
95+
assert trace_id == 0xffffffffffffffffffffffffffffffff
96+
serialize(trace_id, span_id)
97+
assert thrift._id_to_low(trace_id) == 0xffffffffffffffff
98+
assert thrift._id_to_high(trace_id) == 0xffffffffffffffff
99+
100+
trace_id = 0xfb34678b8864f051e5c8c603484e57
101+
span_id = 0x77fd53dc6b437681
102+
serialize(trace_id, span_id)
103+
assert thrift._id_to_low(trace_id) == 0x51e5c8c603484e57
104+
assert thrift._id_to_high(trace_id) == 0xfb34678b8864f0
105+
106+
107+
def test_none_ids():
108+
assert thrift.id_to_int(None) is None
109+
assert thrift._id_to_low(None) is None
110+
assert thrift._id_to_high(None) is None
111+
93112

94113
def test_large_tags():
95114
tag = thrift.make_tag('x', 'y' * 300, max_length=256)

0 commit comments

Comments
 (0)