Skip to content

Commit c435600

Browse files
authoredAug 31, 2020
Align sampling specs in SDK (#1034)
1 parent dd47cd4 commit c435600

File tree

12 files changed

+261
-165
lines changed

12 files changed

+261
-165
lines changed
 

Diff for: ‎exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/exporter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def _get_sampling_rate(span):
247247
return (
248248
span.sampler.rate
249249
if ctx.trace_flags.sampled
250-
and isinstance(span.sampler, sampling.ProbabilitySampler)
250+
and isinstance(span.sampler, sampling.TraceIdRatioBased)
251251
else None
252252
)
253253

Diff for: ‎exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def test_sampling_rate(self):
498498
is_remote=False,
499499
trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
500500
)
501-
sampler = sampling.ProbabilitySampler(0.5)
501+
sampler = sampling.TraceIdRatioBased(0.5)
502502

503503
span = trace.Span(
504504
name="sampled", context=context, parent=None, sampler=sampler

Diff for: ‎opentelemetry-api/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
([#1023](https://github.com/open-telemetry/opentelemetry-python/pull/1023))
99
- Change return value type of `correlationcontext.get_correlations` to immutable `MappingProxyType`
1010
([#1024](https://github.com/open-telemetry/opentelemetry-python/pull/1024))
11+
- Change is_recording_events to is_recording
12+
([#1034](https://github.com/open-telemetry/opentelemetry-python/pull/1034))
1113
- Remove lazy Event and Link API from Span interface
1214
([#1045](https://github.com/open-telemetry/opentelemetry-python/pull/1045))
1315

Diff for: ‎opentelemetry-api/src/opentelemetry/trace/__init__.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def start_span(
229229
name: str,
230230
parent: ParentSpan = CURRENT_SPAN,
231231
kind: SpanKind = SpanKind.INTERNAL,
232-
attributes: typing.Optional[types.Attributes] = None,
232+
attributes: types.Attributes = None,
233233
links: typing.Sequence[Link] = (),
234234
start_time: typing.Optional[int] = None,
235235
set_status_on_exception: bool = True,
@@ -281,7 +281,7 @@ def start_as_current_span(
281281
name: str,
282282
parent: ParentSpan = CURRENT_SPAN,
283283
kind: SpanKind = SpanKind.INTERNAL,
284-
attributes: typing.Optional[types.Attributes] = None,
284+
attributes: types.Attributes = None,
285285
links: typing.Sequence[Link] = (),
286286
) -> typing.Iterator["Span"]:
287287
"""Context manager for creating a new span and set it
@@ -357,7 +357,7 @@ def start_span(
357357
name: str,
358358
parent: ParentSpan = Tracer.CURRENT_SPAN,
359359
kind: SpanKind = SpanKind.INTERNAL,
360-
attributes: typing.Optional[types.Attributes] = None,
360+
attributes: types.Attributes = None,
361361
links: typing.Sequence[Link] = (),
362362
start_time: typing.Optional[int] = None,
363363
set_status_on_exception: bool = True,
@@ -371,7 +371,7 @@ def start_as_current_span(
371371
name: str,
372372
parent: ParentSpan = Tracer.CURRENT_SPAN,
373373
kind: SpanKind = SpanKind.INTERNAL,
374-
attributes: typing.Optional[types.Attributes] = None,
374+
attributes: types.Attributes = None,
375375
links: typing.Sequence[Link] = (),
376376
) -> typing.Iterator["Span"]:
377377
# pylint: disable=unused-argument,no-self-use

Diff for: ‎opentelemetry-api/src/opentelemetry/trace/span.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def update_name(self, name: str) -> None:
6262
"""
6363

6464
@abc.abstractmethod
65-
def is_recording_events(self) -> bool:
65+
def is_recording(self) -> bool:
6666
"""Returns whether this span will be recorded.
6767
6868
Returns true if this Span is active and recording information like
@@ -203,7 +203,7 @@ def __init__(self, context: "SpanContext") -> None:
203203
def get_context(self) -> "SpanContext":
204204
return self._context
205205

206-
def is_recording_events(self) -> bool:
206+
def is_recording(self) -> bool:
207207
return False
208208

209209
def end(self, end_time: typing.Optional[int] = None) -> None:

Diff for: ‎opentelemetry-api/tests/test_implementation.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ def test_default_tracer(self):
3939
with tracer.start_span("test") as span:
4040
self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
4141
self.assertEqual(span, trace.INVALID_SPAN)
42-
self.assertIs(span.is_recording_events(), False)
42+
self.assertIs(span.is_recording(), False)
4343
with tracer.start_span("test2") as span2:
4444
self.assertEqual(
4545
span2.get_context(), trace.INVALID_SPAN_CONTEXT
4646
)
4747
self.assertEqual(span2, trace.INVALID_SPAN)
48-
self.assertIs(span2.is_recording_events(), False)
48+
self.assertIs(span2.is_recording(), False)
4949

5050
def test_span(self):
5151
with self.assertRaises(TypeError):
@@ -55,7 +55,7 @@ def test_span(self):
5555
def test_default_span(self):
5656
span = trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT)
5757
self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
58-
self.assertIs(span.is_recording_events(), False)
58+
self.assertIs(span.is_recording(), False)
5959

6060
# METER
6161

Diff for: ‎opentelemetry-sdk/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Moved samplers from API to SDK
66
([#1023](https://github.com/open-telemetry/opentelemetry-python/pull/1023))
7+
- Sampling spec changes
8+
([#1034](https://github.com/open-telemetry/opentelemetry-python/pull/1034))
79
- Remove lazy Event and Link API from Span interface
810
([#1045](https://github.com/open-telemetry/opentelemetry-python/pull/1045))
911

Diff for: ‎opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

+24-31
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def get_context(self):
515515

516516
def set_attribute(self, key: str, value: types.AttributeValue) -> None:
517517
with self._lock:
518-
if not self.is_recording_events():
518+
if not self.is_recording():
519519
return
520520
has_ended = self.end_time is not None
521521
if has_ended:
@@ -541,7 +541,7 @@ def set_attribute(self, key: str, value: types.AttributeValue) -> None:
541541

542542
def _add_event(self, event: EventBase) -> None:
543543
with self._lock:
544-
if not self.is_recording_events():
544+
if not self.is_recording():
545545
return
546546
has_ended = self.end_time is not None
547547

@@ -569,7 +569,7 @@ def add_event(
569569

570570
def start(self, start_time: Optional[int] = None) -> None:
571571
with self._lock:
572-
if not self.is_recording_events():
572+
if not self.is_recording():
573573
return
574574
has_started = self.start_time is not None
575575
if not has_started:
@@ -583,7 +583,7 @@ def start(self, start_time: Optional[int] = None) -> None:
583583

584584
def end(self, end_time: Optional[int] = None) -> None:
585585
with self._lock:
586-
if not self.is_recording_events():
586+
if not self.is_recording():
587587
return
588588
if self.start_time is None:
589589
raise RuntimeError("Calling end() on a not started span.")
@@ -610,7 +610,7 @@ def update_name(self, name: str) -> None:
610610
return
611611
self.name = name
612612

613-
def is_recording_events(self) -> bool:
613+
def is_recording(self) -> bool:
614614
return True
615615

616616
def set_status(self, status: trace_api.Status) -> None:
@@ -703,7 +703,7 @@ def start_as_current_span(
703703
name: str,
704704
parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN,
705705
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
706-
attributes: Optional[types.Attributes] = None,
706+
attributes: types.Attributes = None,
707707
links: Sequence[trace_api.Link] = (),
708708
) -> Iterator[trace_api.Span]:
709709
span = self.start_span(name, parent, kind, attributes, links)
@@ -714,7 +714,7 @@ def start_span( # pylint: disable=too-many-locals
714714
name: str,
715715
parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN,
716716
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
717-
attributes: Optional[types.Attributes] = None,
717+
attributes: types.Attributes = None,
718718
links: Sequence[trace_api.Link] = (),
719719
start_time: Optional[int] = None,
720720
set_status_on_exception: bool = True,
@@ -741,6 +741,20 @@ def start_span( # pylint: disable=too-many-locals
741741
trace_flags = parent_context.trace_flags
742742
trace_state = parent_context.trace_state
743743

744+
# The sampler decides whether to create a real or no-op span at the
745+
# time of span creation. No-op spans do not record events, and are not
746+
# exported.
747+
# The sampler may also add attributes to the newly-created span, e.g.
748+
# to include information about the sampling result.
749+
sampling_result = self.source.sampler.should_sample(
750+
parent_context, trace_id, name, attributes, links,
751+
)
752+
753+
trace_flags = (
754+
trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED)
755+
if sampling_result.decision.is_sampled()
756+
else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
757+
)
744758
context = trace_api.SpanContext(
745759
trace_id,
746760
generate_span_id(),
@@ -749,37 +763,16 @@ def start_span( # pylint: disable=too-many-locals
749763
trace_state=trace_state,
750764
)
751765

752-
# The sampler decides whether to create a real or no-op span at the
753-
# time of span creation. No-op spans do not record events, and are not
754-
# exported.
755-
# The sampler may also add attributes to the newly-created span, e.g.
756-
# to include information about the sampling decision.
757-
sampling_decision = self.source.sampler.should_sample(
758-
parent_context,
759-
context.trace_id,
760-
context.span_id,
761-
name,
762-
attributes,
763-
links,
764-
)
765-
766-
if sampling_decision.sampled:
767-
options = context.trace_flags | trace_api.TraceFlags.SAMPLED
768-
context.trace_flags = trace_api.TraceFlags(options)
769-
if attributes is None:
770-
span_attributes = sampling_decision.attributes
771-
else:
772-
# apply sampling decision attributes after initial attributes
773-
span_attributes = attributes.copy()
774-
span_attributes.update(sampling_decision.attributes)
766+
# Only record if is_recording() is true
767+
if sampling_result.decision.is_recording():
775768
# pylint:disable=protected-access
776769
span = Span(
777770
name=name,
778771
context=context,
779772
parent=parent_context,
780773
sampler=self.source.sampler,
781774
resource=self.source.resource,
782-
attributes=span_attributes,
775+
attributes=sampling_result.attributes,
783776
span_processor=self.source._active_span_processor,
784777
kind=kind,
785778
links=links,

Diff for: ‎opentelemetry-sdk/src/opentelemetry/sdk/trace/sampling.py

+113-40
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@
1818
OpenTelemetry provides two types of samplers:
1919
2020
- `StaticSampler`
21-
- `ProbabilitySampler`
21+
- `TraceIdRatioBased`
2222
23-
A `StaticSampler` always returns the same sampling decision regardless of the conditions. Both possible StaticSamplers are already created:
23+
A `StaticSampler` always returns the same sampling result regardless of the conditions. Both possible StaticSamplers are already created:
2424
25-
- Always sample spans: `ALWAYS_ON`
26-
- Never sample spans: `ALWAYS_OFF`
25+
- Always sample spans: ALWAYS_ON
26+
- Never sample spans: ALWAYS_OFF
2727
28-
A `ProbabilitySampler` makes a random sampling decision based on the sampling probability given. If the span being sampled has a parent, `ProbabilitySampler` will respect the parent span's sampling decision.
28+
A `TraceIdRatioBased` sampler makes a random sampling result based on the sampling probability given.
2929
30-
Currently, sampling decisions are always made during the creation of the span. However, this might not always be the case in the future (see `OTEP #115 <https://github.com/open-telemetry/oteps/pull/115>`_).
30+
If the span being sampled has a parent, `ParentBased` will respect the parent span's sampling result. Otherwise, it returns the sampling result from the given delegate sampler.
3131
32-
Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample`.
32+
Currently, sampling results are always made during the creation of the span. However, this might not always be the case in the future (see `OTEP #115 <https://github.com/open-telemetry/oteps/pull/115>`_).
33+
34+
Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample` as well as `Sampler.get_description`.
3335
3436
To use a sampler, pass it into the tracer provider constructor. For example:
3537
@@ -41,10 +43,10 @@
4143
ConsoleSpanExporter,
4244
SimpleExportSpanProcessor,
4345
)
44-
from opentelemetry.sdk.trace.sampling import ProbabilitySampler
46+
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
4547
4648
# sample 1 in every 1000 traces
47-
sampler = ProbabilitySampler(1/1000)
49+
sampler = TraceIdRatioBased(1/1000)
4850
4951
# set the sampler onto the global tracer provider
5052
trace.set_tracer_provider(TracerProvider(sampler=sampler))
@@ -54,41 +56,57 @@
5456
SimpleExportSpanProcessor(ConsoleSpanExporter())
5557
)
5658
57-
# created spans will now be sampled by the ProbabilitySampler
59+
# created spans will now be sampled by the TraceIdRatioBased sampler
5860
with trace.get_tracer(__name__).start_as_current_span("Test Span"):
5961
...
6062
"""
6163
import abc
62-
from typing import Dict, Mapping, Optional, Sequence
64+
import enum
65+
from typing import Optional, Sequence
6366

6467
# pylint: disable=unused-import
6568
from opentelemetry.trace import Link, SpanContext
66-
from opentelemetry.util.types import Attributes, AttributeValue
69+
from opentelemetry.util.types import Attributes
70+
71+
72+
class Decision(enum.Enum):
73+
# IsRecording() == false, span will not be recorded and all events and attributes will be dropped.
74+
NOT_RECORD = 0
75+
# IsRecording() == true, but Sampled flag MUST NOT be set.
76+
RECORD = 1
77+
# IsRecording() == true AND Sampled flag` MUST be set.
78+
RECORD_AND_SAMPLED = 2
79+
80+
def is_recording(self):
81+
return self in (Decision.RECORD, Decision.RECORD_AND_SAMPLED)
6782

83+
def is_sampled(self):
84+
return self is Decision.RECORD_AND_SAMPLED
6885

69-
class Decision:
70-
"""A sampling decision as applied to a newly-created Span.
86+
87+
class SamplingResult:
88+
"""A sampling result as applied to a newly-created Span.
7189
7290
Args:
73-
sampled: Whether the `opentelemetry.trace.Span` should be sampled.
91+
decision: A sampling decision based off of whether the span is recorded
92+
and the sampled flag in trace flags in the span context.
7493
attributes: Attributes to add to the `opentelemetry.trace.Span`.
7594
"""
7695

7796
def __repr__(self) -> str:
7897
return "{}({}, attributes={})".format(
79-
type(self).__name__, str(self.sampled), str(self.attributes)
98+
type(self).__name__, str(self.decision), str(self.attributes)
8099
)
81100

82101
def __init__(
83-
self,
84-
sampled: bool = False,
85-
attributes: Optional[Mapping[str, "AttributeValue"]] = None,
102+
self, decision: Decision, attributes: Attributes = None,
86103
) -> None:
87-
self.sampled = sampled # type: bool
104+
self.decision = decision
105+
# TODO: attributes must be immutable
88106
if attributes is None:
89-
self.attributes = {} # type: Dict[str, "AttributeValue"]
107+
self.attributes = {}
90108
else:
91-
self.attributes = dict(attributes)
109+
self.attributes = attributes
92110

93111

94112
class Sampler(abc.ABC):
@@ -97,11 +115,14 @@ def should_sample(
97115
self,
98116
parent_context: Optional["SpanContext"],
99117
trace_id: int,
100-
span_id: int,
101118
name: str,
102-
attributes: Optional[Attributes] = None,
119+
attributes: Attributes = None,
103120
links: Sequence["Link"] = (),
104-
) -> "Decision":
121+
) -> "SamplingResult":
122+
pass
123+
124+
@abc.abstractmethod
125+
def get_description(self) -> str:
105126
pass
106127

107128

@@ -115,15 +136,21 @@ def should_sample(
115136
self,
116137
parent_context: Optional["SpanContext"],
117138
trace_id: int,
118-
span_id: int,
119139
name: str,
120-
attributes: Optional[Attributes] = None,
140+
attributes: Attributes = None,
121141
links: Sequence["Link"] = (),
122-
) -> "Decision":
123-
return self._decision
142+
) -> "SamplingResult":
143+
if self._decision is Decision.NOT_RECORD:
144+
return SamplingResult(self._decision)
145+
return SamplingResult(self._decision, attributes)
124146

147+
def get_description(self) -> str:
148+
if self._decision is Decision.NOT_RECORD:
149+
return "AlwaysOffSampler"
150+
return "AlwaysOnSampler"
125151

126-
class ProbabilitySampler(Sampler):
152+
153+
class TraceIdRatioBased(Sampler):
127154
"""
128155
Sampler that makes sampling decisions probabalistically based on `rate`,
129156
while also respecting the parent span sampling decision.
@@ -133,6 +160,8 @@ class ProbabilitySampler(Sampler):
133160
"""
134161

135162
def __init__(self, rate: float):
163+
if rate < 0.0 or rate > 1.0:
164+
raise ValueError("Probability must be in range [0.0, 1.0].")
136165
self._rate = rate
137166
self._bound = self.get_bound_for_rate(self._rate)
138167

@@ -161,26 +190,70 @@ def should_sample(
161190
self,
162191
parent_context: Optional["SpanContext"],
163192
trace_id: int,
164-
span_id: int,
165193
name: str,
166-
attributes: Optional[Attributes] = None, # TODO
194+
attributes: Attributes = None, # TODO
167195
links: Sequence["Link"] = (),
168-
) -> "Decision":
196+
) -> "SamplingResult":
197+
decision = Decision.NOT_RECORD
198+
if trace_id & self.TRACE_ID_LIMIT < self.bound:
199+
decision = Decision.RECORD_AND_SAMPLED
200+
if decision is Decision.NOT_RECORD:
201+
return SamplingResult(decision)
202+
return SamplingResult(decision, attributes)
203+
204+
def get_description(self) -> str:
205+
return "TraceIdRatioBased{{{}}}".format(self._rate)
206+
207+
208+
class ParentBased(Sampler):
209+
"""
210+
If a parent is set, follows the same sampling decision as the parent.
211+
Otherwise, uses the delegate provided at initialization to make a
212+
decision.
213+
214+
Args:
215+
delegate: The delegate sampler to use if parent is not set.
216+
"""
217+
218+
def __init__(self, delegate: Sampler):
219+
self._delegate = delegate
220+
221+
def should_sample(
222+
self,
223+
parent_context: Optional["SpanContext"],
224+
trace_id: int,
225+
name: str,
226+
attributes: Attributes = None, # TODO
227+
links: Sequence["Link"] = (),
228+
) -> "SamplingResult":
169229
if parent_context is not None:
170-
return Decision(parent_context.trace_flags.sampled)
230+
if (
231+
not parent_context.is_valid
232+
or not parent_context.trace_flags.sampled
233+
):
234+
return SamplingResult(Decision.NOT_RECORD)
235+
return SamplingResult(Decision.RECORD_AND_SAMPLED, attributes)
236+
237+
return self._delegate.should_sample(
238+
parent_context=parent_context,
239+
trace_id=trace_id,
240+
name=name,
241+
attributes=attributes,
242+
links=links,
243+
)
171244

172-
return Decision(trace_id & self.TRACE_ID_LIMIT < self.bound)
245+
def get_description(self):
246+
return "ParentBased{{{}}}".format(self._delegate.get_description())
173247

174248

175-
ALWAYS_OFF = StaticSampler(Decision(False))
249+
ALWAYS_OFF = StaticSampler(Decision.NOT_RECORD)
176250
"""Sampler that never samples spans, regardless of the parent span's sampling decision."""
177251

178-
ALWAYS_ON = StaticSampler(Decision(True))
252+
ALWAYS_ON = StaticSampler(Decision.RECORD_AND_SAMPLED)
179253
"""Sampler that always samples spans, regardless of the parent span's sampling decision."""
180254

181-
182-
DEFAULT_OFF = ProbabilitySampler(0.0)
255+
DEFAULT_OFF = ParentBased(ALWAYS_OFF)
183256
"""Sampler that respects its parent span's sampling decision, but otherwise never samples."""
184257

185-
DEFAULT_ON = ProbabilitySampler(1.0)
258+
DEFAULT_ON = ParentBased(ALWAYS_ON)
186259
"""Sampler that respects its parent span's sampling decision, but otherwise always samples."""

Diff for: ‎opentelemetry-sdk/tests/trace/test_implementation.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ def test_tracer(self):
3131
with tracer.start_span("test") as span:
3232
self.assertNotEqual(span.get_context(), INVALID_SPAN_CONTEXT)
3333
self.assertNotEqual(span, INVALID_SPAN)
34-
self.assertIs(span.is_recording_events(), True)
34+
self.assertIs(span.is_recording(), True)
3535
with tracer.start_span("test2") as span2:
3636
self.assertNotEqual(span2.get_context(), INVALID_SPAN_CONTEXT)
3737
self.assertNotEqual(span2, INVALID_SPAN)
38-
self.assertIs(span2.is_recording_events(), True)
38+
self.assertIs(span2.is_recording(), True)
3939

4040
def test_span(self):
4141
with self.assertRaises(Exception):
@@ -44,4 +44,4 @@ def test_span(self):
4444

4545
span = trace.Span("name", INVALID_SPAN_CONTEXT)
4646
self.assertEqual(span.get_context(), INVALID_SPAN_CONTEXT)
47-
self.assertIs(span.is_recording_events(), True)
47+
self.assertIs(span.is_recording(), True)

Diff for: ‎opentelemetry-sdk/tests/trace/test_sampling.py

+86-61
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,25 @@ def test_always_on(self):
3030
),
3131
0xDEADBEF1,
3232
0xDEADBEF2,
33-
"unsampled parent, sampling on",
33+
{"unsampled parent": "sampling on"},
34+
)
35+
self.assertTrue(no_record_always_on.decision.is_sampled())
36+
self.assertEqual(
37+
no_record_always_on.attributes, {"unsampled parent": "sampling on"}
3438
)
35-
self.assertTrue(no_record_always_on.sampled)
36-
self.assertEqual(no_record_always_on.attributes, {})
3739

3840
sampled_always_on = sampling.ALWAYS_ON.should_sample(
3941
trace.SpanContext(
4042
0xDEADBEEF, 0xDEADBEF0, is_remote=False, trace_flags=TO_SAMPLED
4143
),
4244
0xDEADBEF1,
4345
0xDEADBEF2,
44-
"sampled parent, sampling on",
46+
{"sampled parent": "sampling on"},
47+
)
48+
self.assertTrue(no_record_always_on.decision.is_sampled())
49+
self.assertEqual(
50+
sampled_always_on.attributes, {"sampled parent": "sampling on"}
4551
)
46-
self.assertTrue(sampled_always_on.sampled)
47-
self.assertEqual(sampled_always_on.attributes, {})
4852

4953
def test_always_off(self):
5054
no_record_always_off = sampling.ALWAYS_OFF.should_sample(
@@ -55,7 +59,7 @@ def test_always_off(self):
5559
0xDEADBEF2,
5660
"unsampled parent, sampling off",
5761
)
58-
self.assertFalse(no_record_always_off.sampled)
62+
self.assertFalse(no_record_always_off.decision.is_sampled())
5963
self.assertEqual(no_record_always_off.attributes, {})
6064

6165
sampled_always_on = sampling.ALWAYS_OFF.should_sample(
@@ -66,7 +70,7 @@ def test_always_off(self):
6670
0xDEADBEF2,
6771
"sampled parent, sampling off",
6872
)
69-
self.assertFalse(sampled_always_on.sampled)
73+
self.assertFalse(sampled_always_on.decision.is_sampled())
7074
self.assertEqual(sampled_always_on.attributes, {})
7175

7276
def test_default_on(self):
@@ -78,7 +82,7 @@ def test_default_on(self):
7882
0xDEADBEF2,
7983
"unsampled parent, sampling on",
8084
)
81-
self.assertFalse(no_record_default_on.sampled)
85+
self.assertFalse(no_record_default_on.decision.is_sampled())
8286
self.assertEqual(no_record_default_on.attributes, {})
8387

8488
sampled_default_on = sampling.DEFAULT_ON.should_sample(
@@ -87,10 +91,20 @@ def test_default_on(self):
8791
),
8892
0xDEADBEF1,
8993
0xDEADBEF2,
90-
"sampled parent, sampling on",
94+
{"sampled parent": "sampling on"},
95+
)
96+
self.assertTrue(sampled_default_on.decision.is_sampled())
97+
self.assertEqual(
98+
sampled_default_on.attributes, {"sampled parent": "sampling on"}
99+
)
100+
101+
default_on = sampling.DEFAULT_ON.should_sample(
102+
None, 0xDEADBEF1, 0xDEADBEF2, {"sampled parent": "sampling on"},
103+
)
104+
self.assertTrue(default_on.decision.is_sampled())
105+
self.assertEqual(
106+
default_on.attributes, {"sampled parent": "sampling on"}
91107
)
92-
self.assertTrue(sampled_default_on.sampled)
93-
self.assertEqual(sampled_default_on.attributes, {})
94108

95109
def test_default_off(self):
96110
no_record_default_off = sampling.DEFAULT_OFF.should_sample(
@@ -101,7 +115,7 @@ def test_default_off(self):
101115
0xDEADBEF2,
102116
"unsampled parent, sampling off",
103117
)
104-
self.assertFalse(no_record_default_off.sampled)
118+
self.assertFalse(no_record_default_off.decision.is_sampled())
105119
self.assertEqual(no_record_default_off.attributes, {})
106120

107121
sampled_default_off = sampling.DEFAULT_OFF.should_sample(
@@ -110,90 +124,69 @@ def test_default_off(self):
110124
),
111125
0xDEADBEF1,
112126
0xDEADBEF2,
113-
"sampled parent, sampling off",
127+
{"sampled parent": "sampling on"},
128+
)
129+
self.assertTrue(sampled_default_off.decision.is_sampled())
130+
self.assertEqual(
131+
sampled_default_off.attributes, {"sampled parent": "sampling on"}
132+
)
133+
134+
default_off = sampling.DEFAULT_OFF.should_sample(
135+
None, 0xDEADBEF1, 0xDEADBEF2, "unsampled parent, sampling off",
114136
)
115-
self.assertTrue(sampled_default_off.sampled)
116-
self.assertEqual(sampled_default_off.attributes, {})
137+
self.assertFalse(default_off.decision.is_sampled())
138+
self.assertEqual(default_off.attributes, {})
117139

118140
def test_probability_sampler(self):
119-
sampler = sampling.ProbabilitySampler(0.5)
141+
sampler = sampling.TraceIdRatioBased(0.5)
120142

121143
# Check that we sample based on the trace ID if the parent context is
122144
# null
123145
self.assertTrue(
124146
sampler.should_sample(
125147
None, 0x7FFFFFFFFFFFFFFF, 0xDEADBEEF, "span name"
126-
).sampled
148+
).decision.is_sampled()
127149
)
128150
self.assertFalse(
129151
sampler.should_sample(
130152
None, 0x8000000000000000, 0xDEADBEEF, "span name"
131-
).sampled
132-
)
133-
134-
# Check that the sampling decision matches the parent context if given,
135-
# and that the sampler ignores the trace ID
136-
self.assertFalse(
137-
sampler.should_sample(
138-
trace.SpanContext(
139-
0xDEADBEF0,
140-
0xDEADBEF1,
141-
is_remote=False,
142-
trace_flags=TO_DEFAULT,
143-
),
144-
0x7FFFFFFFFFFFFFFF,
145-
0xDEADBEEF,
146-
"span name",
147-
).sampled
148-
)
149-
self.assertTrue(
150-
sampler.should_sample(
151-
trace.SpanContext(
152-
0xDEADBEF0,
153-
0xDEADBEF1,
154-
is_remote=False,
155-
trace_flags=TO_SAMPLED,
156-
),
157-
0x8000000000000000,
158-
0xDEADBEEF,
159-
"span name",
160-
).sampled
153+
).decision.is_sampled()
161154
)
162155

163156
def test_probability_sampler_zero(self):
164-
default_off = sampling.ProbabilitySampler(0.0)
157+
default_off = sampling.TraceIdRatioBased(0.0)
165158
self.assertFalse(
166159
default_off.should_sample(
167160
None, 0x0, 0xDEADBEEF, "span name"
168-
).sampled
161+
).decision.is_sampled()
169162
)
170163

171164
def test_probability_sampler_one(self):
172-
default_off = sampling.ProbabilitySampler(1.0)
165+
default_off = sampling.TraceIdRatioBased(1.0)
173166
self.assertTrue(
174167
default_off.should_sample(
175168
None, 0xFFFFFFFFFFFFFFFF, 0xDEADBEEF, "span name"
176-
).sampled
169+
).decision.is_sampled()
177170
)
178171

179172
def test_probability_sampler_limits(self):
180173

181174
# Sample one of every 2^64 (= 5e-20) traces. This is the lowest
182175
# possible meaningful sampling rate, only traces with trace ID 0x0
183176
# should get sampled.
184-
almost_always_off = sampling.ProbabilitySampler(2 ** -64)
177+
almost_always_off = sampling.TraceIdRatioBased(2 ** -64)
185178
self.assertTrue(
186179
almost_always_off.should_sample(
187180
None, 0x0, 0xDEADBEEF, "span name"
188-
).sampled
181+
).decision.is_sampled()
189182
)
190183
self.assertFalse(
191184
almost_always_off.should_sample(
192185
None, 0x1, 0xDEADBEEF, "span name"
193-
).sampled
186+
).decision.is_sampled()
194187
)
195188
self.assertEqual(
196-
sampling.ProbabilitySampler.get_bound_for_rate(2 ** -64), 0x1
189+
sampling.TraceIdRatioBased.get_bound_for_rate(2 ** -64), 0x1
197190
)
198191

199192
# Sample every trace with trace ID less than 0xffffffffffffffff. In
@@ -204,11 +197,11 @@ def test_probability_sampler_limits(self):
204197
#
205198
# 1 - sys.float_info.epsilon
206199

207-
almost_always_on = sampling.ProbabilitySampler(1 - 2 ** -64)
200+
almost_always_on = sampling.TraceIdRatioBased(1 - 2 ** -64)
208201
self.assertTrue(
209202
almost_always_on.should_sample(
210203
None, 0xFFFFFFFFFFFFFFFE, 0xDEADBEEF, "span name"
211-
).sampled
204+
).decision.is_sampled()
212205
)
213206

214207
# These tests are logically consistent, but fail because of the float
@@ -224,23 +217,55 @@ def test_probability_sampler_limits(self):
224217
# ).sampled
225218
# )
226219
# self.assertEqual(
227-
# sampling.ProbabilitySampler.get_bound_for_rate(1 - 2 ** -64)),
220+
# sampling.TraceIdRatioBased.get_bound_for_rate(1 - 2 ** -64)),
228221
# 0xFFFFFFFFFFFFFFFF,
229222
# )
230223

231224
# Check that a sampler with the highest effective sampling rate < 1
232225
# refuses to sample traces with trace ID 0xffffffffffffffff.
233-
almost_almost_always_on = sampling.ProbabilitySampler(
226+
almost_almost_always_on = sampling.TraceIdRatioBased(
234227
1 - sys.float_info.epsilon
235228
)
236229
self.assertFalse(
237230
almost_almost_always_on.should_sample(
238231
None, 0xFFFFFFFFFFFFFFFF, 0xDEADBEEF, "span name"
239-
).sampled
232+
).decision.is_sampled()
240233
)
241234
# Check that the higest effective sampling rate is actually lower than
242235
# the highest theoretical sampling rate. If this test fails the test
243236
# above is wrong.
244237
self.assertLess(
245238
almost_almost_always_on.bound, 0xFFFFFFFFFFFFFFFF,
246239
)
240+
241+
def test_parent_based(self):
242+
sampler = sampling.ParentBased(sampling.ALWAYS_ON)
243+
# Check that the sampling decision matches the parent context if given
244+
self.assertFalse(
245+
sampler.should_sample(
246+
trace.SpanContext(
247+
0xDEADBEF0,
248+
0xDEADBEF1,
249+
is_remote=False,
250+
trace_flags=TO_DEFAULT,
251+
),
252+
0x7FFFFFFFFFFFFFFF,
253+
0xDEADBEEF,
254+
"span name",
255+
).decision.is_sampled()
256+
)
257+
258+
sampler2 = sampling.ParentBased(sampling.ALWAYS_OFF)
259+
self.assertTrue(
260+
sampler2.should_sample(
261+
trace.SpanContext(
262+
0xDEADBEF0,
263+
0xDEADBEF1,
264+
is_remote=False,
265+
trace_flags=TO_SAMPLED,
266+
),
267+
0x8000000000000000,
268+
0xDEADBEEF,
269+
"span name",
270+
).decision.is_sampled()
271+
)

Diff for: ‎opentelemetry-sdk/tests/trace/test_trace.py

+20-19
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ def test_default_sampler(self):
140140
child_span = tracer.start_span(name="child span", parent=root_span)
141141
self.assertIsInstance(child_span, trace.Span)
142142
self.assertTrue(root_span.context.trace_flags.sampled)
143+
self.assertEqual(
144+
root_span.get_context().trace_flags, trace_api.TraceFlags.SAMPLED
145+
)
146+
self.assertEqual(
147+
child_span.get_context().trace_flags, trace_api.TraceFlags.SAMPLED
148+
)
143149

144150
def test_sampler_no_sampling(self):
145151
tracer_provider = trace.TracerProvider(sampling.ALWAYS_OFF)
@@ -151,6 +157,12 @@ def test_sampler_no_sampling(self):
151157
self.assertIsInstance(root_span, trace_api.DefaultSpan)
152158
child_span = tracer.start_span(name="child span", parent=root_span)
153159
self.assertIsInstance(child_span, trace_api.DefaultSpan)
160+
self.assertEqual(
161+
root_span.get_context().trace_flags, trace_api.TraceFlags.DEFAULT
162+
)
163+
self.assertEqual(
164+
child_span.get_context().trace_flags, trace_api.TraceFlags.DEFAULT
165+
)
154166

155167

156168
class TestSpanCreation(unittest.TestCase):
@@ -201,7 +213,7 @@ def test_invalid_instrumentation_info(self):
201213
tracer1.instrumentation_info, InstrumentationInfo
202214
)
203215
span1 = tracer1.start_span("foo")
204-
self.assertTrue(span1.is_recording_events())
216+
self.assertTrue(span1.is_recording())
205217
self.assertEqual(tracer1.instrumentation_info.version, "")
206218
self.assertEqual(
207219
tracer1.instrumentation_info.name, "ERROR:MISSING MODULE NAME"
@@ -521,36 +533,25 @@ def test_check_attribute_helper(self):
521533
self.assertTrue(trace._is_valid_attribute_value(15))
522534

523535
def test_sampling_attributes(self):
524-
decision_attributes = {
536+
sampling_attributes = {
525537
"sampler-attr": "sample-val",
526538
"attr-in-both": "decision-attr",
527539
}
528540
tracer_provider = trace.TracerProvider(
529-
sampling.StaticSampler(
530-
sampling.Decision(sampled=True, attributes=decision_attributes)
531-
)
541+
sampling.StaticSampler(sampling.Decision.RECORD_AND_SAMPLED,)
532542
)
533543

534544
self.tracer = tracer_provider.get_tracer(__name__)
535545

536-
with self.tracer.start_as_current_span("root2") as root:
537-
self.assertEqual(len(root.attributes), 2)
538-
self.assertEqual(root.attributes["sampler-attr"], "sample-val")
539-
self.assertEqual(root.attributes["attr-in-both"], "decision-attr")
540-
541-
attributes = {
542-
"attr-key": "val",
543-
"attr-key2": "val2",
544-
"attr-in-both": "span-attr",
545-
}
546546
with self.tracer.start_as_current_span(
547-
"root2", attributes=attributes
547+
name="root2", attributes=sampling_attributes
548548
) as root:
549-
self.assertEqual(len(root.attributes), 4)
550-
self.assertEqual(root.attributes["attr-key"], "val")
551-
self.assertEqual(root.attributes["attr-key2"], "val2")
549+
self.assertEqual(len(root.attributes), 2)
552550
self.assertEqual(root.attributes["sampler-attr"], "sample-val")
553551
self.assertEqual(root.attributes["attr-in-both"], "decision-attr")
552+
self.assertEqual(
553+
root.get_context().trace_flags, trace_api.TraceFlags.SAMPLED
554+
)
554555

555556
def test_events(self):
556557
self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN)

0 commit comments

Comments
 (0)
Please sign in to comment.