Skip to content

Commit 43ad608

Browse files
authored
Merge pull request #1172 from DataDog/brettlangdon/fallback.sampling
core: Update default sampler to new DatadogSampler
2 parents 21dda97 + a87d66d commit 43ad608

File tree

4 files changed

+188
-45
lines changed

4 files changed

+188
-45
lines changed

ddtrace/internal/writer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .. import api
77
from .. import _worker
88
from ..internal.logger import get_logger
9+
from ..sampler import BasePrioritySampler
910
from ..settings import config
1011
from ..vendor import monotonic
1112
from ddtrace.vendor.six.moves.queue import Queue, Full, Empty
@@ -25,13 +26,14 @@ class AgentWriter(_worker.PeriodicWorkerThread):
2526

2627
def __init__(self, hostname='localhost', port=8126, uds_path=None, https=False,
2728
shutdown_timeout=DEFAULT_TIMEOUT,
28-
filters=None, priority_sampler=None,
29+
filters=None, sampler=None, priority_sampler=None,
2930
dogstatsd=None):
3031
super(AgentWriter, self).__init__(interval=self.QUEUE_PROCESSING_INTERVAL,
3132
exit_timeout=shutdown_timeout,
3233
name=self.__class__.__name__)
3334
self._trace_queue = Q(maxsize=MAX_TRACES)
3435
self._filters = filters
36+
self._sampler = sampler
3537
self._priority_sampler = priority_sampler
3638
self._last_error_ts = 0
3739
self.dogstatsd = dogstatsd
@@ -94,10 +96,17 @@ def flush_queue(self):
9496
for response in traces_responses:
9597
if isinstance(response, Exception) or response.status >= 400:
9698
self._log_error_status(response)
97-
elif self._priority_sampler:
99+
elif self._priority_sampler or isinstance(self._sampler, BasePrioritySampler):
98100
result_traces_json = response.get_json()
99101
if result_traces_json and 'rate_by_service' in result_traces_json:
100-
self._priority_sampler.set_sample_rate_by_service(result_traces_json['rate_by_service'])
102+
if self._priority_sampler:
103+
self._priority_sampler.update_rate_by_service_sample_rates(
104+
result_traces_json['rate_by_service'],
105+
)
106+
if isinstance(self._sampler, BasePrioritySampler):
107+
self._sampler.update_rate_by_service_sample_rates(
108+
result_traces_json['rate_by_service'],
109+
)
101110

102111
# Dump statistics
103112
# NOTE: Do not use the buffering of dogstatsd as it's not thread-safe

ddtrace/sampler.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def sample(self, span):
2727
pass
2828

2929

30+
class BasePrioritySampler(six.with_metaclass(abc.ABCMeta)):
31+
@abc.abstractmethod
32+
def update_rate_by_service_sample_rates(self, sample_rates):
33+
pass
34+
35+
3036
class AllSampler(BaseSampler):
3137
"""Sampler sampling all the traces"""
3238

@@ -60,7 +66,7 @@ def sample(self, span):
6066
return ((span.trace_id * KNUTH_FACTOR) % MAX_TRACE_ID) <= self.sampling_id_threshold
6167

6268

63-
class RateByServiceSampler(BaseSampler):
69+
class RateByServiceSampler(BaseSampler, BasePrioritySampler):
6470
"""Sampler based on a rate, by service
6571
6672
Keep (100 * `sample_rate`)% of the traces.
@@ -97,7 +103,7 @@ def sample(self, span):
97103
span.set_metric(SAMPLING_AGENT_DECISION, sampler.sample_rate)
98104
return sampler.sample(span)
99105

100-
def set_sample_rate_by_service(self, rate_by_service):
106+
def update_rate_by_service_sample_rates(self, rate_by_service):
101107
new_by_service_samplers = self._get_new_by_service_sampler()
102108
for key, sample_rate in iteritems(rate_by_service):
103109
new_by_service_samplers[key] = RateSampler(sample_rate)
@@ -109,30 +115,41 @@ def set_sample_rate_by_service(self, rate_by_service):
109115
RateByServiceSampler._default_key = RateByServiceSampler._key()
110116

111117

112-
class DatadogSampler(BaseSampler):
118+
class DatadogSampler(BaseSampler, BasePrioritySampler):
113119
"""
114120
This sampler is currently in ALPHA and it's API may change at any time, use at your own risk.
115121
"""
116122
__slots__ = ('default_sampler', 'limiter', 'rules')
117123

118124
NO_RATE_LIMIT = -1
119125
DEFAULT_RATE_LIMIT = 100
120-
DEFAULT_SAMPLE_RATE = 1.0
126+
DEFAULT_SAMPLE_RATE = None
121127

122128
def __init__(self, rules=None, default_sample_rate=None, rate_limit=None):
123129
"""
124130
Constructor for DatadogSampler sampler
125131
126132
:param rules: List of :class:`SamplingRule` rules to apply to the root span of every trace, default no rules
127133
:type rules: :obj:`list` of :class:`SamplingRule`
128-
:param default_sample_rate: The default sample rate to apply if no rules matched (default: 1.0)
134+
:param default_sample_rate: The default sample rate to apply if no rules matched (default: ``None`` /
135+
Use :class:`RateByServiceSampler` only)
129136
:type default_sample_rate: float 0 <= X <= 1.0
130137
:param rate_limit: Global rate limit (traces per second) to apply to all traces regardless of the rules
131138
applied to them, (default: ``100``)
132139
:type rate_limit: :obj:`int`
133140
"""
134141
if default_sample_rate is None:
135-
default_sample_rate = float(get_env('trace', 'sample_rate', default=self.DEFAULT_SAMPLE_RATE))
142+
# If no sample rate was provided explicitly in code, try to load from environment variable
143+
sample_rate = get_env('trace', 'sample_rate', default=self.DEFAULT_SAMPLE_RATE)
144+
145+
# If no env variable was found, just use the default
146+
if sample_rate is None:
147+
default_sample_rate = self.DEFAULT_SAMPLE_RATE
148+
149+
# Otherwise, try to convert it to a float
150+
else:
151+
default_sample_rate = float(sample_rate)
152+
136153
if rate_limit is None:
137154
rate_limit = int(get_env('trace', 'rate_limit', default=self.DEFAULT_RATE_LIMIT))
138155

@@ -148,7 +165,16 @@ def __init__(self, rules=None, default_sample_rate=None, rate_limit=None):
148165

149166
# Configure rate limiter
150167
self.limiter = RateLimiter(rate_limit)
151-
self.default_sampler = SamplingRule(sample_rate=default_sample_rate)
168+
169+
# Default to previous default behavior of RateByServiceSampler
170+
self.default_sampler = RateByServiceSampler()
171+
if default_sample_rate is not None:
172+
self.default_sampler = SamplingRule(sample_rate=default_sample_rate)
173+
174+
def update_rate_by_service_sample_rates(self, sample_rates):
175+
# Pass through the call to our RateByServiceSampler
176+
if isinstance(self.default_sampler, RateByServiceSampler):
177+
self.default_sampler.update_rate_by_service_sample_rates(sample_rates)
152178

153179
def _set_priority(self, span, priority):
154180
if span._context:
@@ -175,7 +201,16 @@ def sample(self, span):
175201
matching_rule = rule
176202
break
177203
else:
178-
# No rule matches, use the default sampler
204+
# If this is the old sampler, sample and return
205+
if isinstance(self.default_sampler, RateByServiceSampler):
206+
if self.default_sampler.sample(span):
207+
self._set_priority(span, AUTO_KEEP)
208+
return True
209+
else:
210+
self._set_priority(span, AUTO_REJECT)
211+
return False
212+
213+
# If no rules match, use our defualt sampler
179214
matching_rule = self.default_sampler
180215

181216
# Sample with the matching sampling rule
@@ -202,7 +237,7 @@ def sample(self, span):
202237
return True
203238

204239

205-
class SamplingRule(object):
240+
class SamplingRule(BaseSampler):
206241
"""
207242
Definition of a sampling rule used by :class:`DatadogSampler` for applying a sample rate on a span
208243
"""

ddtrace/tracer.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .internal.writer import AgentWriter
1313
from .provider import DefaultContextProvider
1414
from .context import Context
15-
from .sampler import AllSampler, DatadogSampler, RateSampler, RateByServiceSampler
15+
from .sampler import DatadogSampler, RateSampler, RateByServiceSampler
1616
from .span import Span
1717
from .utils.formats import get_env
1818
from .utils.deprecation import deprecated, RemovedInDDTrace10Warning
@@ -114,7 +114,7 @@ def __init__(self, url=DEFAULT_AGENT_URL, dogstatsd_url=DEFAULT_DOGSTATSD_URL):
114114
port=port,
115115
https=https,
116116
uds_path=uds_path,
117-
sampler=AllSampler(),
117+
sampler=DatadogSampler(),
118118
context_provider=DefaultContextProvider(),
119119
dogstatsd_url=dogstatsd_url,
120120
)
@@ -229,7 +229,7 @@ def configure(self, enabled=None, hostname=None, port=None, uds_path=None, https
229229
self._dogstatsd_client = DogStatsd(**dogstatsd_kwargs)
230230

231231
if hostname is not None or port is not None or uds_path is not None or https is not None or \
232-
filters is not None or priority_sampling is not None:
232+
filters is not None or priority_sampling is not None or sampler is not None:
233233
# Preserve hostname and port when overriding filters or priority sampling
234234
# This is clumsy and a good reason to get rid of this configure() API
235235
if hasattr(self, 'writer') and hasattr(self.writer, 'api'):
@@ -247,6 +247,7 @@ def configure(self, enabled=None, hostname=None, port=None, uds_path=None, https
247247
uds_path=uds_path,
248248
https=https,
249249
filters=filters,
250+
sampler=self.sampler,
250251
priority_sampler=self.priority_sampler,
251252
dogstatsd=self._dogstatsd_client,
252253
)
@@ -370,6 +371,8 @@ def start_span(self, name, child_of=None, service=None, resource=None, span_type
370371
context.sampling_priority = AUTO_REJECT
371372
else:
372373
context.sampling_priority = AUTO_KEEP if span.sampled else AUTO_REJECT
374+
# We must always mark the span as sampled so it is forwarded to the agent
375+
span.sampled = True
373376

374377
# add tags to root span to correlate trace with runtime metrics
375378
# only applied to spans with types that are internal to applications

0 commit comments

Comments
 (0)