diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h b/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h new file mode 100644 index 0000000000..c562277957 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h @@ -0,0 +1,57 @@ +#pragma once + +#include "opentelemetry/sdk/common/atomic_shared_ptr.h" +#include "opentelemetry/sdk/trace/sampler.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/** + * A placeholder class that stores the states of a span. + * Will be replaced by the real SpanContext class once it is implemented. + */ +class Sampler::SpanContext +{ +public: + inline explicit SpanContext(bool is_recording, bool sampled_flag) + : is_recording(is_recording), sampled_flag(sampled_flag) + {} + + bool is_recording; + bool sampled_flag; +}; + +/** + * The parent or else sampler is a composite sampler. ParentOrElse(delegateSampler) either respects + * the parent span's sampling decision or delegates to delegateSampler for root spans. + */ +class ParentOrElseSampler : public Sampler +{ +public: + explicit ParentOrElseSampler(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 NOT_RECORD always + */ + SamplingResult ShouldSample(const SpanContext * parent_context, + trace_api::TraceId trace_id, + nostd::string_view name, + trace_api::SpanKind span_kind, + const trace_api::KeyValueIterable & attributes) noexcept override; + + /** + * @return Description MUST be ParentOrElse{delegate_sampler_.getDescription()} + */ + std::string GetDescription() const noexcept override; + +private: + const std::shared_ptr delegate_sampler_; + const std::string description_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index 455665be68..98f949997e 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -1 +1 @@ -add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc) +add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc samplers/parent_or_else.cc) diff --git a/sdk/src/trace/samplers/parent_or_else.cc b/sdk/src/trace/samplers/parent_or_else.cc new file mode 100644 index 0000000000..fd0d909e84 --- /dev/null +++ b/sdk/src/trace/samplers/parent_or_else.cc @@ -0,0 +1,41 @@ +#include "opentelemetry/sdk/trace/samplers/parent_or_else.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +ParentOrElseSampler::ParentOrElseSampler(std::shared_ptr delegate_sampler) noexcept + : delegate_sampler_(delegate_sampler), + description_("ParentOrElse{" + delegate_sampler->GetDescription() + "}") +{} + +SamplingResult ParentOrElseSampler::ShouldSample( + const SpanContext *parent_context, + trace_api::TraceId trace_id, + nostd::string_view name, + trace_api::SpanKind span_kind, + const trace_api::KeyValueIterable &attributes) noexcept +{ + if (parent_context == nullptr) + { + // If no parent (root span) exists returns the result of the delegateSampler + return delegate_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, attributes); + } + + // If parent exists: + if (parent_context->sampled_flag) + { + return {Decision::RECORD_AND_SAMPLE, nullptr}; + } + + return {Decision::NOT_RECORD, nullptr}; +} + +std::string ParentOrElseSampler::GetDescription() const noexcept +{ + return description_; +} +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 37018d7eeb..a3c3c77f91 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -53,8 +53,6 @@ cc_test( ], ) - - cc_test( name = "always_off_sampler_test", srcs = [ @@ -64,4 +62,15 @@ cc_test( "//sdk/src/trace", "@com_google_googletest//:gtest_main", ], -) \ No newline at end of file +) + +cc_test( + name = "parent_or_else_sampler_test", + srcs = [ + "parent_or_else_sampler_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index 75bfd4f97a..b4524da0f2 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -1,5 +1,6 @@ foreach(testname tracer_provider_test span_data_test simple_processor_test - tracer_test always_off_sampler_test always_on_sampler_test) + tracer_test always_off_sampler_test always_on_sampler_test + parent_or_else_sampler_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace) diff --git a/sdk/test/trace/parent_or_else_sampler_test.cc b/sdk/test/trace/parent_or_else_sampler_test.cc new file mode 100644 index 0000000000..152121f4c3 --- /dev/null +++ b/sdk/test/trace/parent_or_else_sampler_test.cc @@ -0,0 +1,50 @@ +#include +#include +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/samplers/parent_or_else.h" + +using opentelemetry::sdk::trace::AlwaysOffSampler; +using opentelemetry::sdk::trace::AlwaysOnSampler; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::sdk::trace::ParentOrElseSampler; + +TEST(ParentOrElseSampler, ShouldSample) +{ + ParentOrElseSampler sampler_off(std::make_shared()); + ParentOrElseSampler sampler_on(std::make_shared()); + + // Set up parameters + opentelemetry::trace::TraceId trace_id; + opentelemetry::trace::SpanKind span_kind = opentelemetry::trace::SpanKind::kInternal; + using M = std::map; + M m1 = {{}}; + opentelemetry::trace::KeyValueIterableView view{m1}; + opentelemetry::sdk::trace::Sampler::SpanContext parent_context_sampled(true, true); + opentelemetry::sdk::trace::Sampler::SpanContext parent_context_nonsampled(true, false); + + // Case 1: Parent doesn't exist. Return result of delegateSampler() + auto sampling_result = sampler_off.ShouldSample(nullptr, trace_id, "", span_kind, view); + auto sampling_result2 = sampler_on.ShouldSample(nullptr, trace_id, "", span_kind, view); + + ASSERT_EQ(Decision::NOT_RECORD, sampling_result.decision); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result2.decision); + + // Case 2: Parent exists and SampledFlag is true + auto sampling_result3 = + sampler_off.ShouldSample(&parent_context_sampled, trace_id, "", span_kind, view); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result3.decision); + + // Case 3: Parent exists and SampledFlag is false + auto sampling_result4 = + sampler_on.ShouldSample(&parent_context_nonsampled, trace_id, "", span_kind, view); + ASSERT_EQ(Decision::NOT_RECORD, sampling_result4.decision); +} + +TEST(ParentOrElseSampler, GetDescription) +{ + ParentOrElseSampler sampler(std::make_shared()); + ASSERT_EQ("ParentOrElse{AlwaysOffSampler}", sampler.GetDescription()); + ParentOrElseSampler sampler2(std::make_shared()); + ASSERT_EQ("ParentOrElse{AlwaysOnSampler}", sampler2.GetDescription()); +}