diff --git a/CHANGELOG.md b/CHANGELOG.md index 22fd5e9fed..877a0b6b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ Increment the: * [SDK] Update default exemplar reservoir size for exponential histograms [#3551](https://github.com/open-telemetry/opentelemetry-cpp/pull/3551) +* [SDK] Implements options for the ParentBasedSampler with default values + [#3553](https://github.com/open-telemetry/opentelemetry-cpp/pull/3553) + ## [1.22 2025-07-11] * [DOC] Udpate link to membership document diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/parent.h b/sdk/include/opentelemetry/sdk/trace/samplers/parent.h index e44e9fa320..5917273df6 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/parent.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/parent.h @@ -9,6 +9,8 @@ #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_metadata.h" #include "opentelemetry/trace/trace_id.h" @@ -21,16 +23,36 @@ namespace trace { /** - * The ParentBased sampler is a composite sampler. ParentBased(delegateSampler) either respects - * the parent span's sampling decision or delegates to delegateSampler for root spans. + * The ParentBased sampler is a composite sampler that delegates sampling decisions based on the + * parent span's context. + * + * The decision is delegated to one of five configurable samplers: + * - No parent exists (root span): delegates to `root sampler`. + * - A remote parent exists and was sampled: delegates to `remote_parent_sampled_sampler` (default + * to AlwaysOnSampler). + * - A remote parent exists and was not sampled: delegates to `remote_parent_nonsampled_sampler` + * (default to AlwaysOffSampler). + * - A local parent exists and was sampled: delegates to `local_parent_sampled_sampler` (default to + * AlwaysOnSampler). + * - A local parent exists and was not sampled: delegates to `local_parent_nonsampled_sampler` + * (default to AlwaysOffSampler). */ class ParentBasedSampler : public Sampler { public: - explicit ParentBasedSampler(const std::shared_ptr &delegate_sampler) noexcept; - /** The decision either respects the parent span's sampling decision or delegates to - * delegateSampler for root spans - * @return Returns DROP always + explicit ParentBasedSampler(const std::shared_ptr &root_sampler, + const std::shared_ptr &remote_parent_sampled_sampler = + std::make_shared(), + const std::shared_ptr &remote_parent_nonsampled_sampler = + std::make_shared(), + const std::shared_ptr &local_parent_sampled_sampler = + std::make_shared(), + const std::shared_ptr &local_parent_nonsampled_sampler = + std::make_shared()) noexcept; + + /** Implements the decision logic by checking the parent context and delegating to the appropriate + * configured sampler + * @return The SamplingResult from the delegated sampler */ SamplingResult ShouldSample( const opentelemetry::trace::SpanContext &parent_context, @@ -46,9 +68,14 @@ class ParentBasedSampler : public Sampler nostd::string_view GetDescription() const noexcept override; private: - const std::shared_ptr delegate_sampler_; + const std::shared_ptr root_sampler_; + const std::shared_ptr remote_parent_sampled_sampler_; + const std::shared_ptr remote_parent_nonsampled_sampler_; + const std::shared_ptr local_parent_sampled_sampler_; + const std::shared_ptr local_parent_nonsampled_sampler_; const std::string description_; }; + } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/parent_factory.h b/sdk/include/opentelemetry/sdk/trace/samplers/parent_factory.h index d5cf598311..d07e4c1c0c 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/parent_factory.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/parent_factory.h @@ -23,7 +23,17 @@ class ParentBasedSamplerFactory /** * Create a ParentBasedSampler. */ - static std::unique_ptr Create(const std::shared_ptr &delegate_sampler); + static std::unique_ptr Create(const std::shared_ptr &root_sampler); + + /** + * Create a ParentBasedSampler. + */ + static std::unique_ptr Create( + const std::shared_ptr &root_sampler, + const std::shared_ptr &remote_parent_sampled_sampler, + const std::shared_ptr &remote_parent_nonsampled_sampler, + const std::shared_ptr &local_parent_sampled_sampler, + const std::shared_ptr &local_parent_nonsampled_sampler); }; } // namespace trace diff --git a/sdk/src/trace/samplers/parent.cc b/sdk/src/trace/samplers/parent.cc index 1e7dd9ba3d..e77db2e4ae 100644 --- a/sdk/src/trace/samplers/parent.cc +++ b/sdk/src/trace/samplers/parent.cc @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include #include #include @@ -21,9 +20,18 @@ namespace sdk { namespace trace { -ParentBasedSampler::ParentBasedSampler(const std::shared_ptr &delegate_sampler) noexcept - : delegate_sampler_(delegate_sampler), - description_("ParentBased{" + std::string{delegate_sampler->GetDescription()} + "}") +ParentBasedSampler::ParentBasedSampler( + const std::shared_ptr &root_sampler, + const std::shared_ptr &remote_parent_sampled_sampler, + const std::shared_ptr &remote_parent_nonsampled_sampler, + const std::shared_ptr &local_parent_sampled_sampler, + const std::shared_ptr &local_parent_nonsampled_sampler) noexcept + : root_sampler_(root_sampler), + remote_parent_sampled_sampler_(remote_parent_sampled_sampler), + remote_parent_nonsampled_sampler_(remote_parent_nonsampled_sampler), + local_parent_sampled_sampler_(local_parent_sampled_sampler), + local_parent_nonsampled_sampler_(local_parent_nonsampled_sampler), + description_("ParentBased{" + std::string{root_sampler->GetDescription()} + "}") {} SamplingResult ParentBasedSampler::ShouldSample( @@ -36,24 +44,38 @@ SamplingResult ParentBasedSampler::ShouldSample( { if (!parent_context.IsValid()) { - // If no parent (root span) exists returns the result of the delegateSampler - return delegate_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, attributes, - links); + // If no parent (root span) exists returns the result of the root_sampler + return root_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, attributes, + links); } // If parent exists: if (parent_context.IsSampled()) { - return {Decision::RECORD_AND_SAMPLE, nullptr, parent_context.trace_state()}; + if (parent_context.IsRemote()) + { + return remote_parent_sampled_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, + attributes, links); + } + return local_parent_sampled_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, + attributes, links); } - return {Decision::DROP, nullptr, parent_context.trace_state()}; + // Parent is not sampled + if (parent_context.IsRemote()) + { + return remote_parent_nonsampled_sampler_->ShouldSample(parent_context, trace_id, name, + span_kind, attributes, links); + } + return local_parent_nonsampled_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, + attributes, links); } nostd::string_view ParentBasedSampler::GetDescription() const noexcept { return description_; } + } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/samplers/parent_factory.cc b/sdk/src/trace/samplers/parent_factory.cc index 25a2e94f65..a927ee7b2e 100644 --- a/sdk/src/trace/samplers/parent_factory.cc +++ b/sdk/src/trace/samplers/parent_factory.cc @@ -4,6 +4,8 @@ #include #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/samplers/parent.h" #include "opentelemetry/sdk/trace/samplers/parent_factory.h" #include "opentelemetry/version.h" @@ -15,9 +17,24 @@ namespace trace { std::unique_ptr ParentBasedSamplerFactory::Create( - const std::shared_ptr &delegate_sampler) + const std::shared_ptr &root_sampler) { - std::unique_ptr sampler(new ParentBasedSampler(delegate_sampler)); + std::unique_ptr sampler = ParentBasedSamplerFactory::Create( + root_sampler, std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared()); + return sampler; +} + +std::unique_ptr ParentBasedSamplerFactory::Create( + const std::shared_ptr &root_sampler, + const std::shared_ptr &remote_parent_sampled_sampler, + const std::shared_ptr &remote_parent_nonsampled_sampler, + const std::shared_ptr &local_parent_sampled_sampler, + const std::shared_ptr &local_parent_nonsampled_sampler) +{ + std::unique_ptr sampler(new ParentBasedSampler( + root_sampler, remote_parent_sampled_sampler, remote_parent_nonsampled_sampler, + local_parent_sampled_sampler, local_parent_nonsampled_sampler)); return sampler; } diff --git a/sdk/test/trace/parent_sampler_test.cc b/sdk/test/trace/parent_sampler_test.cc index e5ac16ff14..db179c9695 100644 --- a/sdk/test/trace/parent_sampler_test.cc +++ b/sdk/test/trace/parent_sampler_test.cc @@ -51,10 +51,14 @@ TEST(ParentBasedSampler, ShouldSample) opentelemetry::common::KeyValueIterableView view{m1}; trace_api::SpanContextKeyValueIterableView links{l1}; auto trace_state = trace_api::TraceState::FromHeader("congo=t61rcWkgMzE"); - trace_api::SpanContext parent_context_sampled(trace_id, span_id, trace_api::TraceFlags{1}, false, - trace_state); - trace_api::SpanContext parent_context_nonsampled(trace_id, span_id, trace_api::TraceFlags{0}, - false, trace_state); + trace_api::SpanContext parent_context_sampled_local(trace_id, span_id, trace_api::TraceFlags{1}, + false, trace_state); + trace_api::SpanContext parent_context_nonsampled_local( + trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + trace_api::SpanContext parent_context_sampled_remote(trace_id, span_id, trace_api::TraceFlags{1}, + true, trace_state); + trace_api::SpanContext parent_context_nonsampled_remote( + trace_id, span_id, trace_api::TraceFlags{0}, true, trace_state); // Case 1: Parent doesn't exist. Return result of delegateSampler() auto sampling_result = sampler_off.ShouldSample(trace_api::SpanContext::GetInvalid(), trace_id, @@ -67,17 +71,29 @@ TEST(ParentBasedSampler, ShouldSample) ASSERT_EQ("", sampling_result.trace_state->ToHeader()); ASSERT_EQ("", sampling_result2.trace_state->ToHeader()); - // Case 2: Parent exists and SampledFlag is true + // Case 2: Parent exists and SampledFlag is true and RemoteFlag is false auto sampling_result3 = - sampler_off.ShouldSample(parent_context_sampled, trace_id, "", span_kind, view, links); + sampler_off.ShouldSample(parent_context_sampled_local, trace_id, "", span_kind, view, links); ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result3.decision); ASSERT_EQ("congo=t61rcWkgMzE", sampling_result3.trace_state->ToHeader()); - // Case 3: Parent exists and SampledFlag is false - auto sampling_result4 = - sampler_on.ShouldSample(parent_context_nonsampled, trace_id, "", span_kind, view, links); + // Case 3: Parent exists and SampledFlag is false and RemoteFlag is false + auto sampling_result4 = sampler_on.ShouldSample(parent_context_nonsampled_local, trace_id, "", + span_kind, view, links); ASSERT_EQ(Decision::DROP, sampling_result4.decision); ASSERT_EQ("congo=t61rcWkgMzE", sampling_result4.trace_state->ToHeader()); + + // Case 4: Parent exists, SampledFlag is true and RemoteFlag is true + auto sampling_result5 = + sampler_off.ShouldSample(parent_context_sampled_remote, trace_id, "", span_kind, view, links); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result5.decision); + ASSERT_EQ("congo=t61rcWkgMzE", sampling_result5.trace_state->ToHeader()); + + // Case 5: Parent exists, SampledFlag is false and RemoteFlag is true + auto sampling_result6 = sampler_on.ShouldSample(parent_context_nonsampled_remote, trace_id, "", + span_kind, view, links); + ASSERT_EQ(Decision::DROP, sampling_result6.decision); + ASSERT_EQ("congo=t61rcWkgMzE", sampling_result6.trace_state->ToHeader()); } TEST(ParentBasedSampler, GetDescription)