aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py')
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py453
1 files changed, 453 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py b/.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py
new file mode 100644
index 00000000..fb6990a0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/sdk/trace/sampling.py
@@ -0,0 +1,453 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+For general information about sampling, see `the specification <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling>`_.
+
+OpenTelemetry provides two types of samplers:
+
+- `StaticSampler`
+- `TraceIdRatioBased`
+
+A `StaticSampler` always returns the same sampling result regardless of the conditions. Both possible StaticSamplers are already created:
+
+- Always sample spans: ALWAYS_ON
+- Never sample spans: ALWAYS_OFF
+
+A `TraceIdRatioBased` sampler makes a random sampling result based on the sampling probability given.
+
+If the span being sampled has a parent, `ParentBased` will respect the parent delegate sampler. Otherwise, it returns the sampling result from the given root sampler.
+
+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>`_).
+
+Custom samplers can be created by subclassing `Sampler` and implementing `Sampler.should_sample` as well as `Sampler.get_description`.
+
+Samplers are able to modify the `opentelemetry.trace.span.TraceState` of the parent of the span being created. For custom samplers, it is suggested to implement `Sampler.should_sample` to utilize the
+parent span context's `opentelemetry.trace.span.TraceState` and pass into the `SamplingResult` instead of the explicit trace_state field passed into the parameter of `Sampler.should_sample`.
+
+To use a sampler, pass it into the tracer provider constructor. For example:
+
+.. code:: python
+
+ from opentelemetry import trace
+ from opentelemetry.sdk.trace import TracerProvider
+ from opentelemetry.sdk.trace.export import (
+ ConsoleSpanExporter,
+ SimpleSpanProcessor,
+ )
+ from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
+
+ # sample 1 in every 1000 traces
+ sampler = TraceIdRatioBased(1/1000)
+
+ # set the sampler onto the global tracer provider
+ trace.set_tracer_provider(TracerProvider(sampler=sampler))
+
+ # set up an exporter for sampled spans
+ trace.get_tracer_provider().add_span_processor(
+ SimpleSpanProcessor(ConsoleSpanExporter())
+ )
+
+ # created spans will now be sampled by the TraceIdRatioBased sampler
+ with trace.get_tracer(__name__).start_as_current_span("Test Span"):
+ ...
+
+The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
+The list of built-in values for ``OTEL_TRACES_SAMPLER`` are:
+
+ * always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
+ * always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
+ * traceidratio - Sampler that samples probabilistically based on rate.
+ * parentbased_always_on - (default) Sampler that respects its parent span's sampling decision, but otherwise always samples.
+ * parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
+ * parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabilistically based on rate.
+
+Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio. Rate must be in the range [0.0,1.0]. When not provided rate will be set to
+1.0 (maximum rate possible).
+
+Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.
+
+.. code:: python
+
+ from opentelemetry import trace
+ from opentelemetry.sdk.trace import TracerProvider
+ from opentelemetry.sdk.trace.export import (
+ ConsoleSpanExporter,
+ SimpleSpanProcessor,
+ )
+
+ trace.set_tracer_provider(TracerProvider())
+
+ # set up an exporter for sampled spans
+ trace.get_tracer_provider().add_span_processor(
+ SimpleSpanProcessor(ConsoleSpanExporter())
+ )
+
+ # created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
+ with trace.get_tracer(__name__).start_as_current_span("Test Span"):
+ ...
+
+When utilizing a configurator, you can configure a custom sampler. In order to create a configurable custom sampler, create an entry point for the custom sampler
+factory method or function under the entry point group, ``opentelemetry_traces_sampler``. The custom sampler factory method must be of type ``Callable[[str], Sampler]``, taking a single string argument and
+returning a Sampler object. The single input will come from the string value of the ``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will
+be an empty string. For example:
+
+.. code:: python
+
+ setup(
+ ...
+ entry_points={
+ ...
+ "opentelemetry_traces_sampler": [
+ "custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler"
+ ]
+ }
+ )
+ # ...
+ class CustomRatioSampler(Sampler):
+ def __init__(rate):
+ # ...
+ # ...
+ class CustomSamplerFactory:
+ @staticmethod
+ def get_sampler(sampler_argument):
+ try:
+ rate = float(sampler_argument)
+ return CustomSampler(rate)
+ except ValueError: # In case argument is empty string.
+ return CustomSampler(0.5)
+
+In order to configure you application with a custom sampler's entry point, set the ``OTEL_TRACES_SAMPLER`` environment variable to the key name of the entry point. For example, to configured the
+above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``.
+"""
+
+import abc
+import enum
+import os
+from logging import getLogger
+from types import MappingProxyType
+from typing import Optional, Sequence
+
+# pylint: disable=unused-import
+from opentelemetry.context import Context
+from opentelemetry.sdk.environment_variables import (
+ OTEL_TRACES_SAMPLER,
+ OTEL_TRACES_SAMPLER_ARG,
+)
+from opentelemetry.trace import Link, SpanKind, get_current_span
+from opentelemetry.trace.span import TraceState
+from opentelemetry.util.types import Attributes
+
+_logger = getLogger(__name__)
+
+
+class Decision(enum.Enum):
+ # IsRecording() == false, span will not be recorded and all events and attributes will be dropped.
+ DROP = 0
+ # IsRecording() == true, but Sampled flag MUST NOT be set.
+ RECORD_ONLY = 1
+ # IsRecording() == true AND Sampled flag` MUST be set.
+ RECORD_AND_SAMPLE = 2
+
+ def is_recording(self):
+ return self in (Decision.RECORD_ONLY, Decision.RECORD_AND_SAMPLE)
+
+ def is_sampled(self):
+ return self is Decision.RECORD_AND_SAMPLE
+
+
+class SamplingResult:
+ """A sampling result as applied to a newly-created Span.
+
+ Args:
+ decision: A sampling decision based off of whether the span is recorded
+ and the sampled flag in trace flags in the span context.
+ attributes: Attributes to add to the `opentelemetry.trace.Span`.
+ trace_state: The tracestate used for the `opentelemetry.trace.Span`.
+ Could possibly have been modified by the sampler.
+ """
+
+ def __repr__(self) -> str:
+ return f"{type(self).__name__}({str(self.decision)}, attributes={str(self.attributes)})"
+
+ def __init__(
+ self,
+ decision: Decision,
+ attributes: "Attributes" = None,
+ trace_state: Optional["TraceState"] = None,
+ ) -> None:
+ self.decision = decision
+ if attributes is None:
+ self.attributes = MappingProxyType({})
+ else:
+ self.attributes = MappingProxyType(attributes)
+ self.trace_state = trace_state
+
+
+class Sampler(abc.ABC):
+ @abc.abstractmethod
+ def should_sample(
+ self,
+ parent_context: Optional["Context"],
+ trace_id: int,
+ name: str,
+ kind: Optional[SpanKind] = None,
+ attributes: Attributes = None,
+ links: Optional[Sequence["Link"]] = None,
+ trace_state: Optional["TraceState"] = None,
+ ) -> "SamplingResult":
+ pass
+
+ @abc.abstractmethod
+ def get_description(self) -> str:
+ pass
+
+
+class StaticSampler(Sampler):
+ """Sampler that always returns the same decision."""
+
+ def __init__(self, decision: "Decision") -> None:
+ self._decision = decision
+
+ def should_sample(
+ self,
+ parent_context: Optional["Context"],
+ trace_id: int,
+ name: str,
+ kind: Optional[SpanKind] = None,
+ attributes: Attributes = None,
+ links: Optional[Sequence["Link"]] = None,
+ trace_state: Optional["TraceState"] = None,
+ ) -> "SamplingResult":
+ if self._decision is Decision.DROP:
+ attributes = None
+ return SamplingResult(
+ self._decision,
+ attributes,
+ _get_parent_trace_state(parent_context),
+ )
+
+ def get_description(self) -> str:
+ if self._decision is Decision.DROP:
+ return "AlwaysOffSampler"
+ return "AlwaysOnSampler"
+
+
+ALWAYS_OFF = StaticSampler(Decision.DROP)
+"""Sampler that never samples spans, regardless of the parent span's sampling decision."""
+
+ALWAYS_ON = StaticSampler(Decision.RECORD_AND_SAMPLE)
+"""Sampler that always samples spans, regardless of the parent span's sampling decision."""
+
+
+class TraceIdRatioBased(Sampler):
+ """
+ Sampler that makes sampling decisions probabilistically based on `rate`.
+
+ Args:
+ rate: Probability (between 0 and 1) that a span will be sampled
+ """
+
+ def __init__(self, rate: float):
+ if rate < 0.0 or rate > 1.0:
+ raise ValueError("Probability must be in range [0.0, 1.0].")
+ self._rate = rate
+ self._bound = self.get_bound_for_rate(self._rate)
+
+ # For compatibility with 64 bit trace IDs, the sampler checks the 64
+ # low-order bits of the trace ID to decide whether to sample a given trace.
+ TRACE_ID_LIMIT = (1 << 64) - 1
+
+ @classmethod
+ def get_bound_for_rate(cls, rate: float) -> int:
+ return round(rate * (cls.TRACE_ID_LIMIT + 1))
+
+ @property
+ def rate(self) -> float:
+ return self._rate
+
+ @property
+ def bound(self) -> int:
+ return self._bound
+
+ def should_sample(
+ self,
+ parent_context: Optional["Context"],
+ trace_id: int,
+ name: str,
+ kind: Optional[SpanKind] = None,
+ attributes: Attributes = None,
+ links: Optional[Sequence["Link"]] = None,
+ trace_state: Optional["TraceState"] = None,
+ ) -> "SamplingResult":
+ decision = Decision.DROP
+ if trace_id & self.TRACE_ID_LIMIT < self.bound:
+ decision = Decision.RECORD_AND_SAMPLE
+ if decision is Decision.DROP:
+ attributes = None
+ return SamplingResult(
+ decision,
+ attributes,
+ _get_parent_trace_state(parent_context),
+ )
+
+ def get_description(self) -> str:
+ return f"TraceIdRatioBased{{{self._rate}}}"
+
+
+class ParentBased(Sampler):
+ """
+ If a parent is set, applies the respective delegate sampler.
+ Otherwise, uses the root provided at initialization to make a
+ decision.
+
+ Args:
+ root: Sampler called for spans with no parent (root spans).
+ remote_parent_sampled: Sampler called for a remote sampled parent.
+ remote_parent_not_sampled: Sampler called for a remote parent that is
+ not sampled.
+ local_parent_sampled: Sampler called for a local sampled parent.
+ local_parent_not_sampled: Sampler called for a local parent that is
+ not sampled.
+ """
+
+ def __init__(
+ self,
+ root: Sampler,
+ remote_parent_sampled: Sampler = ALWAYS_ON,
+ remote_parent_not_sampled: Sampler = ALWAYS_OFF,
+ local_parent_sampled: Sampler = ALWAYS_ON,
+ local_parent_not_sampled: Sampler = ALWAYS_OFF,
+ ):
+ self._root = root
+ self._remote_parent_sampled = remote_parent_sampled
+ self._remote_parent_not_sampled = remote_parent_not_sampled
+ self._local_parent_sampled = local_parent_sampled
+ self._local_parent_not_sampled = local_parent_not_sampled
+
+ def should_sample(
+ self,
+ parent_context: Optional["Context"],
+ trace_id: int,
+ name: str,
+ kind: Optional[SpanKind] = None,
+ attributes: Attributes = None,
+ links: Optional[Sequence["Link"]] = None,
+ trace_state: Optional["TraceState"] = None,
+ ) -> "SamplingResult":
+ parent_span_context = get_current_span(
+ parent_context
+ ).get_span_context()
+ # default to the root sampler
+ sampler = self._root
+ # respect the sampling and remote flag of the parent if present
+ if parent_span_context is not None and parent_span_context.is_valid:
+ if parent_span_context.is_remote:
+ if parent_span_context.trace_flags.sampled:
+ sampler = self._remote_parent_sampled
+ else:
+ sampler = self._remote_parent_not_sampled
+ else:
+ if parent_span_context.trace_flags.sampled:
+ sampler = self._local_parent_sampled
+ else:
+ sampler = self._local_parent_not_sampled
+
+ return sampler.should_sample(
+ parent_context=parent_context,
+ trace_id=trace_id,
+ name=name,
+ kind=kind,
+ attributes=attributes,
+ links=links,
+ )
+
+ def get_description(self):
+ return f"ParentBased{{root:{self._root.get_description()},remoteParentSampled:{self._remote_parent_sampled.get_description()},remoteParentNotSampled:{self._remote_parent_not_sampled.get_description()},localParentSampled:{self._local_parent_sampled.get_description()},localParentNotSampled:{self._local_parent_not_sampled.get_description()}}}"
+
+
+DEFAULT_OFF = ParentBased(ALWAYS_OFF)
+"""Sampler that respects its parent span's sampling decision, but otherwise never samples."""
+
+DEFAULT_ON = ParentBased(ALWAYS_ON)
+"""Sampler that respects its parent span's sampling decision, but otherwise always samples."""
+
+
+class ParentBasedTraceIdRatio(ParentBased):
+ """
+ Sampler that respects its parent span's sampling decision, but otherwise
+ samples probabilistically based on `rate`.
+ """
+
+ def __init__(self, rate: float):
+ root = TraceIdRatioBased(rate=rate)
+ super().__init__(root=root)
+
+
+class _AlwaysOff(StaticSampler):
+ def __init__(self, _):
+ super().__init__(Decision.DROP)
+
+
+class _AlwaysOn(StaticSampler):
+ def __init__(self, _):
+ super().__init__(Decision.RECORD_AND_SAMPLE)
+
+
+class _ParentBasedAlwaysOff(ParentBased):
+ def __init__(self, _):
+ super().__init__(ALWAYS_OFF)
+
+
+class _ParentBasedAlwaysOn(ParentBased):
+ def __init__(self, _):
+ super().__init__(ALWAYS_ON)
+
+
+_KNOWN_SAMPLERS = {
+ "always_on": ALWAYS_ON,
+ "always_off": ALWAYS_OFF,
+ "parentbased_always_on": DEFAULT_ON,
+ "parentbased_always_off": DEFAULT_OFF,
+ "traceidratio": TraceIdRatioBased,
+ "parentbased_traceidratio": ParentBasedTraceIdRatio,
+}
+
+
+def _get_from_env_or_default() -> Sampler:
+ trace_sampler = os.getenv(
+ OTEL_TRACES_SAMPLER, "parentbased_always_on"
+ ).lower()
+ if trace_sampler not in _KNOWN_SAMPLERS:
+ _logger.warning("Couldn't recognize sampler %s.", trace_sampler)
+ trace_sampler = "parentbased_always_on"
+
+ if trace_sampler in ("traceidratio", "parentbased_traceidratio"):
+ try:
+ rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
+ except (ValueError, TypeError):
+ _logger.warning("Could not convert TRACES_SAMPLER_ARG to float.")
+ rate = 1.0
+ return _KNOWN_SAMPLERS[trace_sampler](rate)
+
+ return _KNOWN_SAMPLERS[trace_sampler]
+
+
+def _get_parent_trace_state(
+ parent_context: Optional[Context],
+) -> Optional["TraceState"]:
+ parent_span_context = get_current_span(parent_context).get_span_context()
+ if parent_span_context is None or not parent_span_context.is_valid:
+ return None
+ return parent_span_context.trace_state