diff --git a/CHANGELOG.md b/CHANGELOG.md index 906d4c3e2f..1524a326e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,9 @@ Breaking changes: ## [1.28.0] 2026-07-16 +* [SDK] Add Tracer::StartSpan benchmark and optimizations + [#4248](https://github.com/open-telemetry/opentelemetry-cpp/pull/4248) + * [RELEASE] Bump main branch to 1.28.0-dev [#4081](https://github.com/open-telemetry/opentelemetry-cpp/pull/4081) diff --git a/api/include/opentelemetry/trace/context.h b/api/include/opentelemetry/trace/context.h index 90ef5adc41..a55b63b515 100644 --- a/api/include/opentelemetry/trace/context.h +++ b/api/include/opentelemetry/trace/context.h @@ -27,13 +27,14 @@ inline nostd::shared_ptr GetSpan(const context::Context &context) noexcept // stored directly in the context, and returns an invalid SpanContext if neither is present. inline SpanContext GetSpanContext(const context::Context &context) noexcept { - if (!context.HasKey(kSpanKey)) + const context::ContextValue context_value = context.GetValue(kSpanKey); + + if (nostd::holds_alternative(context_value)) { + // The context does not have a span or span context return SpanContext::GetInvalid(); } - const context::ContextValue context_value = context.GetValue(kSpanKey); - // Get the span metadata from the active span in the context. if (const nostd::shared_ptr *maybe_span = nostd::get_if>(&context_value)) diff --git a/api/include/opentelemetry/trace/default_span.h b/api/include/opentelemetry/trace/default_span.h index a6e7b1d6b8..3eb909ca5a 100644 --- a/api/include/opentelemetry/trace/default_span.h +++ b/api/include/opentelemetry/trace/default_span.h @@ -14,7 +14,7 @@ namespace trace { /** - * DefaultSpan provides a non-operational Span that propagates + * DefaultSpan provides a non-recording Span that propagates * the tracer context by wrapping it inside the Span object. */ @@ -28,6 +28,7 @@ class DefaultSpan : public Span trace::SpanContext GetContext() const noexcept override { return span_context_; } + // Returns false, as DefaultSpan is a non-recording span. bool IsRecording() const noexcept override { return false; } void SetAttribute(nostd::string_view /* key */, diff --git a/api/include/opentelemetry/trace/span_startoptions.h b/api/include/opentelemetry/trace/span_startoptions.h index 8a2165b0f0..ca4d73128a 100644 --- a/api/include/opentelemetry/trace/span_startoptions.h +++ b/api/include/opentelemetry/trace/span_startoptions.h @@ -59,7 +59,7 @@ struct StartSpanOptions // - If the `parent` field is not set, the newly created Span will inherit the // parent of the currently active Span (if any) in the current context. // - nostd::variant parent = SpanContext::GetInvalid(); + nostd::variant parent = context::Context{}; // TODO: // SpanContext remote_parent; diff --git a/opentracing-shim/test/shim_utils_test.cc b/opentracing-shim/test/shim_utils_test.cc index 84a76b906d..cd53ff48b5 100644 --- a/opentracing-shim/test/shim_utils_test.cc +++ b/opentracing-shim/test/shim_utils_test.cc @@ -26,6 +26,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/common/timestamp.h" +#include "opentelemetry/context/context.h" #include "opentelemetry/context/runtime_context.h" #include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/shared_ptr.h" @@ -34,13 +35,13 @@ #include "opentelemetry/opentracingshim/shim_utils.h" #include "opentelemetry/opentracingshim/span_context_shim.h" #include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_metadata.h" #include "opentelemetry/trace/span_startoptions.h" -#include "shim_mocks.h" - namespace trace_api = opentelemetry::trace; namespace baggage = opentelemetry::baggage; namespace common = opentelemetry::common; +namespace context = opentelemetry::context; namespace nostd = opentelemetry::nostd; namespace shim = opentelemetry::opentracingshim; @@ -138,8 +139,7 @@ TEST(ShimUtilsTest, MakeOptionsShim_EmptyRefs) common::SystemTimestamp{options.start_system_timestamp}); ASSERT_EQ(options_shim.start_steady_time, common::SteadyTimestamp{options.start_steady_timestamp}); - ASSERT_EQ(nostd::get(options_shim.parent), - trace_api::SpanContext::GetInvalid()); + ASSERT_FALSE(nostd::get(options_shim.parent).HasKey(trace_api::kSpanKey)); } TEST(ShimUtilsTest, MakeOptionsShim_InvalidSpanContext) @@ -154,8 +154,8 @@ TEST(ShimUtilsTest, MakeOptionsShim_InvalidSpanContext) common::SystemTimestamp{options.start_system_timestamp}); ASSERT_EQ(options_shim.start_steady_time, common::SteadyTimestamp{options.start_steady_timestamp}); - ASSERT_EQ(nostd::get(options_shim.parent), - trace_api::SpanContext::GetInvalid()); + + ASSERT_FALSE(nostd::get(options_shim.parent).HasKey(trace_api::kSpanKey)); } TEST(ShimUtilsTest, MakeOptionsShim_FirstChildOf) diff --git a/sdk/include/opentelemetry/sdk/trace/sampler.h b/sdk/include/opentelemetry/sdk/trace/sampler.h index 888a44383b..b9afc00024 100644 --- a/sdk/include/opentelemetry/sdk/trace/sampler.h +++ b/sdk/include/opentelemetry/sdk/trace/sampler.h @@ -58,11 +58,11 @@ struct SamplingResult // The tracestate used by the span. nostd::shared_ptr trace_state; - inline bool IsRecording() + inline bool IsRecording() const noexcept { return decision == Decision::RECORD_ONLY || decision == Decision::RECORD_AND_SAMPLE; } - inline bool IsSampled() { return decision == Decision::RECORD_AND_SAMPLE; } + inline bool IsSampled() const noexcept { return decision == Decision::RECORD_AND_SAMPLE; } }; /** diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 601cfc1b7c..4cc2f50850 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -141,6 +141,7 @@ class Tracer final : public opentelemetry::trace::Tracer, #if OPENTELEMETRY_ABI_VERSION_NO < 2 std::atomic is_enabled_{false}; #endif + nostd::shared_ptr noop_span_; }; } // namespace trace } // namespace sdk diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index 1233e3361d..84877eb9aa 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include "opentelemetry/nostd/function_ref.h" @@ -55,8 +56,9 @@ Span::Span(std::shared_ptr &&tracer, const common::KeyValueIterable &attributes, const opentelemetry::trace::SpanContextKeyValueIterable &links, const opentelemetry::trace::StartSpanOptions &options, + const opentelemetry::sdk::trace::SamplingResult &sampling_result, const opentelemetry::trace::SpanContext &parent_span_context, - std::unique_ptr span_context) noexcept + opentelemetry::trace::SpanContext span_context) noexcept : tracer_{std::move(tracer)}, recordable_{tracer_->GetProcessor().MakeRecordable()}, start_steady_time{options.start_steady_time}, @@ -69,11 +71,11 @@ Span::Span(std::shared_ptr &&tracer, recordable_->SetSpanLimits(tracer_->GetSpanLimits()); recordable_->SetName(name); recordable_->SetInstrumentationScope(tracer_->GetInstrumentationScope()); - recordable_->SetIdentity(*span_context_, parent_span_context.IsValid() - ? parent_span_context.span_id() - : opentelemetry::trace::SpanId()); + recordable_->SetIdentity(span_context_, parent_span_context.IsValid() + ? parent_span_context.span_id() + : opentelemetry::trace::SpanId()); - recordable_->SetTraceFlags(span_context_->trace_flags()); + recordable_->SetTraceFlags(span_context_.trace_flags()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { recordable_->SetAttribute(key, value); @@ -86,6 +88,14 @@ Span::Span(std::shared_ptr &&tracer, return true; }); + if (sampling_result.attributes != nullptr) + { + for (const auto &kv : *sampling_result.attributes) + { + recordable_->SetAttribute(kv.first, kv.second); + } + } + recordable_->SetSpanKind(options.kind); recordable_->SetStartTime(NowOr(options.start_system_time)); start_steady_time = NowOr(options.start_steady_time); diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index 2a5614821d..a780b5df03 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -11,6 +11,7 @@ #include "opentelemetry/common/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" @@ -31,8 +32,9 @@ class Span final : public opentelemetry::trace::Span const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::trace::SpanContextKeyValueIterable &links, const opentelemetry::trace::StartSpanOptions &options, + const opentelemetry::sdk::trace::SamplingResult &sampling_result, const opentelemetry::trace::SpanContext &parent_span_context, - std::unique_ptr span_context) noexcept; + opentelemetry::trace::SpanContext span_context) noexcept; Span(const Span &) = delete; Span(Span &&) = delete; @@ -73,17 +75,14 @@ class Span final : public opentelemetry::trace::Span bool IsRecording() const noexcept override; - opentelemetry::trace::SpanContext GetContext() const noexcept override - { - return *span_context_.get(); - } + opentelemetry::trace::SpanContext GetContext() const noexcept override { return span_context_; } private: std::shared_ptr tracer_; mutable std::mutex mu_; std::unique_ptr recordable_; opentelemetry::common::SteadyTimestamp start_steady_time; - std::unique_ptr span_context_; + opentelemetry::trace::SpanContext span_context_; bool has_ended_{false}; }; } // namespace trace diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index 96b2d81fc9..0aa2e415ab 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -1,14 +1,15 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include #include -#include +#include #include #include #include +#include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/context/context.h" +#include "opentelemetry/context/runtime_context.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" @@ -20,9 +21,10 @@ #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/context.h" -#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/default_span.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_context_kv_iterable.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_startoptions.h" #include "opentelemetry/trace/trace_flags.h" @@ -37,11 +39,68 @@ namespace sdk { namespace trace { + +namespace +{ + +nostd::shared_ptr MakeNonRecordingSpan( + opentelemetry::trace::SpanContext &&span_context) noexcept +{ +#if OPENTELEMETRY_HAVE_EXCEPTIONS + try + { +#endif + return {std::make_shared(std::move(span_context))}; +#if OPENTELEMETRY_HAVE_EXCEPTIONS + } + catch (const std::bad_alloc &) + { + return {}; + } +#else + return nostd::shared_ptr{ + new (std::nothrow) opentelemetry::trace::DefaultSpan(std::move(span_context))}; +#endif +} + +nostd::shared_ptr MakeSpan( + std::shared_ptr &&tracer, + nostd::string_view name, + const opentelemetry::common::KeyValueIterable &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links, + const opentelemetry::trace::StartSpanOptions &options, + const opentelemetry::sdk::trace::SamplingResult &sampling_result, + const opentelemetry::trace::SpanContext &parent_context, + opentelemetry::trace::SpanContext &&span_context) noexcept +{ +#if OPENTELEMETRY_HAVE_EXCEPTIONS + try + { +#endif + return {std::make_shared(std::move(tracer), name, attributes, links, options, + sampling_result, parent_context, std::move(span_context))}; +#if OPENTELEMETRY_HAVE_EXCEPTIONS + } + catch (const std::bad_alloc &) + { + return {}; + } +#else + return nostd::shared_ptr{ + new (std::nothrow) Span{std::move(tracer), name, attributes, links, options, sampling_result, + parent_context, std::move(span_context)}}; +#endif +} + +} // namespace + Tracer::Tracer(std::shared_ptr context, std::unique_ptr instrumentation_scope) noexcept : instrumentation_scope_{std::move(instrumentation_scope)}, context_{std::move(context)}, - tracer_config_(context_->GetTracerConfigurator().ComputeConfig(*instrumentation_scope_)) + tracer_config_(context_->GetTracerConfigurator().ComputeConfig(*instrumentation_scope_)), + noop_span_{std::make_shared( + opentelemetry::trace::SpanContext::GetInvalid())} { UpdateEnabled(tracer_config_.IsEnabled()); } @@ -55,126 +114,110 @@ nostd::shared_ptr Tracer::StartSpan( // Check if the tracer is enabled using the API Tracer::Enabled() accessor if available. if (!Enabled()) { - static const std::shared_ptr kNoopTracer = - std::make_shared(); - return kNoopTracer->StartSpan(name, attributes, links, options); + return noop_span_; } - // make sure to always overwrite this parent_context - bool get_current_context = true; - opentelemetry::trace::SpanContext parent_context(false, false); - if (const opentelemetry::trace::SpanContext *span_context = - nostd::get_if(&options.parent)) - { - if (span_context->IsValid()) + // Resolve parent span context from options or fall back to the current runtime context. + const auto parent_context = [&options]() noexcept -> opentelemetry::trace::SpanContext { + // 1. If the parent is a valid SpanContext, use it directly. + // 2. If the parent is a Context with a span, extract the SpanContext. + // 3. If the parent is a Context with the `is_root_span` flag set, return an invalid + // SpanContext. + // 4. If the parent is not provided, use the current runtime context to get the SpanContext. + if (const auto *span_context = + nostd::get_if(&options.parent)) { - parent_context = *span_context; - get_current_context = false; - } - } - else if (const context::Context *context = nostd::get_if(&options.parent)) - { - // fetch span context from parent span stored in the context - auto parent_span_context = opentelemetry::trace::GetSpanContext(*context); - if (parent_span_context.IsValid()) - { - parent_context = parent_span_context; - get_current_context = false; + if (span_context->IsValid()) + { + return *span_context; + } } - else + else if (const auto *context = nostd::get_if(&options.parent)) { - if (opentelemetry::trace::IsRootSpan(*context)) + const auto ctx_span_context = opentelemetry::trace::GetSpanContext(*context); + if (ctx_span_context.IsValid()) + { + return ctx_span_context; + } + else if (opentelemetry::trace::IsRootSpan(*context)) { - get_current_context = false; + return opentelemetry::trace::SpanContext::GetInvalid(); } } - } - if (get_current_context) - { - parent_context = GetCurrentSpan()->GetContext(); - } + return opentelemetry::trace::GetSpanContext( + opentelemetry::context::RuntimeContext::GetCurrent()); + }(); - IdGenerator &generator = GetIdGenerator(); - opentelemetry::trace::TraceId trace_id; - opentelemetry::trace::SpanId span_id = generator.GenerateSpanId(); - bool is_parent_span_valid = false; - uint8_t flags = 0; + IdGenerator &generator = GetIdGenerator(); + const opentelemetry::trace::SpanId span_id = generator.GenerateSpanId(); + const opentelemetry::trace::TraceId trace_id = + parent_context.IsValid() ? parent_context.trace_id() : generator.GenerateTraceId(); - if (parent_context.IsValid()) - { - trace_id = parent_context.trace_id(); - flags = parent_context.trace_flags().flags(); - is_parent_span_valid = true; - } - else - { - trace_id = generator.GenerateTraceId(); - if (generator.IsRandom()) + const auto sampling_result = context_->GetSampler().ShouldSample(parent_context, trace_id, name, + options.kind, attributes, links); + + const opentelemetry::trace::TraceFlags trace_flags = + [&]() noexcept -> opentelemetry::trace::TraceFlags { + std::uint8_t flags = 0; + if (parent_context.IsValid()) + { + flags = parent_context.trace_flags().flags(); + } + else if (generator.IsRandom()) { flags = opentelemetry::trace::TraceFlags::kIsRandom; } - } - auto sampling_result = context_->GetSampler().ShouldSample(parent_context, trace_id, name, - options.kind, attributes, links); - if (sampling_result.IsSampled()) - { - flags |= opentelemetry::trace::TraceFlags::kIsSampled; - } - else - { - flags &= ~opentelemetry::trace::TraceFlags::kIsSampled; - } + if (sampling_result.IsSampled()) + { + flags |= opentelemetry::trace::TraceFlags::kIsSampled; + } + else + { + flags &= ~opentelemetry::trace::TraceFlags::kIsSampled; + } -#if 0 - /* https://github.com/open-telemetry/opentelemetry-specification as of v1.29.0 */ - /* Support W3C Trace Context version 1. */ - flags &= opentelemetry::trace::TraceFlags::kAllW3CTraceContext1Flags; -#endif + /* Support W3C Trace Context version 2. */ + flags &= opentelemetry::trace::TraceFlags::kAllW3CTraceContext2Flags; -#if 1 - /* Waiting for https://github.com/open-telemetry/opentelemetry-specification/issues/3411 */ - /* Support W3C Trace Context version 2. */ - flags &= opentelemetry::trace::TraceFlags::kAllW3CTraceContext2Flags; -#endif + return opentelemetry::trace::TraceFlags(flags); + }(); - opentelemetry::trace::TraceFlags trace_flags(flags); + const auto get_trace_state = + [&sampling_result, &parent_context]() -> nostd::shared_ptr { + if (sampling_result.trace_state) + { + return sampling_result.trace_state; + } + if (parent_context.IsValid()) + { + return parent_context.trace_state(); + } + return opentelemetry::trace::TraceState::GetDefault(); + }; - auto span_context = - std::unique_ptr(new opentelemetry::trace::SpanContext( - trace_id, span_id, trace_flags, false, - sampling_result.trace_state ? sampling_result.trace_state - : is_parent_span_valid ? parent_context.trace_state() - : opentelemetry::trace::TraceState::GetDefault())); + opentelemetry::trace::SpanContext span_context(trace_id, span_id, trace_flags, false, + get_trace_state()); if (!sampling_result.IsRecording()) { - // create no-op span with valid span-context. + auto non_recording_span = MakeNonRecordingSpan(std::move(span_context)); - auto noop_span = nostd::shared_ptr{ - new (std::nothrow) - opentelemetry::trace::NoopSpan(this->shared_from_this(), std::move(span_context))}; - return noop_span; - } - else - { - - auto span = nostd::shared_ptr{ - new (std::nothrow) Span{this->shared_from_this(), name, attributes, links, options, - parent_context, std::move(span_context)}}; - - // if the attributes is not nullptr, add attributes to the span. - if (sampling_result.attributes) + if (!non_recording_span) { - for (auto &kv : *sampling_result.attributes) - { - span->SetAttribute(kv.first, kv.second); - } + return noop_span_; } + return non_recording_span; + } - return span; + auto span = MakeSpan(shared_from_this(), name, attributes, links, options, sampling_result, + parent_context, std::move(span_context)); + if (!span) + { + return noop_span_; } + return span; } void Tracer::ForceFlushWithMicroseconds(uint64_t timeout) noexcept diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 3e4393345a..468e1400dc 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -221,3 +221,19 @@ otel_cc_benchmark( "//test_common:headers", ], ) + +otel_cc_benchmark( + name = "tracer_benchmark", + srcs = ["tracer_benchmark.cc"], + tags = [ + "benchmark", + "test", + "trace", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/resource", + "//sdk/src/trace", + "//test_common:headers", + ], +) diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index e299deb502..59a77b95b2 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -46,4 +46,14 @@ if(WITH_BENCHMARK) opentelemetry_resources opentelemetry_exporter_in_memory opentelemetry_test_common) + + add_executable(tracer_benchmark tracer_benchmark.cc) + target_link_libraries( + tracer_benchmark + benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_trace + opentelemetry_resources + opentelemetry_exporter_in_memory + opentelemetry_test_common) endif() diff --git a/sdk/test/trace/sampler_benchmark.cc b/sdk/test/trace/sampler_benchmark.cc index aff81aef8c..1fbd3583fe 100644 --- a/sdk/test/trace/sampler_benchmark.cc +++ b/sdk/test/trace/sampler_benchmark.cc @@ -1,12 +1,77 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +// clang-format off +// +// ~/build/sdk/test/trace/sampler_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-18T14:58:59+00:00 +// Running /home/devuser/build/sdk/test/trace/sampler_benchmark +// Run on (32 X 5700 MHz CPU s) +// CPU Caches: +// L1 Data 48 KiB (x16) +// L1 Instruction 32 KiB (x16) +// L2 Unified 2048 KiB (x16) +// L3 Unified 36864 KiB (x1) +// Load Average: 0.76, 1.44, 1.40 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// ------------------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// ------------------------------------------------------------------------------------------------- +// BM_AlwaysOffSamplerConstruction_mean 0.336 ns 0.336 ns 5 +// BM_AlwaysOffSamplerConstruction_median 0.335 ns 0.335 ns 5 +// BM_AlwaysOffSamplerConstruction_stddev 0.001 ns 0.001 ns 5 +// BM_AlwaysOffSamplerConstruction_cv 0.32 % 0.32 % 5 +// BM_AlwaysOnSamplerConstruction_mean 0.335 ns 0.335 ns 5 +// BM_AlwaysOnSamplerConstruction_median 0.335 ns 0.335 ns 5 +// BM_AlwaysOnSamplerConstruction_stddev 0.001 ns 0.001 ns 5 +// BM_AlwaysOnSamplerConstruction_cv 0.20 % 0.20 % 5 +// BM_AlwaysOffSamplerShouldSample_mean 4.14 ns 4.14 ns 5 +// BM_AlwaysOffSamplerShouldSample_median 4.09 ns 4.09 ns 5 +// BM_AlwaysOffSamplerShouldSample_stddev 0.071 ns 0.071 ns 5 +// BM_AlwaysOffSamplerShouldSample_cv 1.72 % 1.72 % 5 +// BM_AlwaysOnSamplerShouldSample_mean 4.45 ns 4.45 ns 5 +// BM_AlwaysOnSamplerShouldSample_median 4.45 ns 4.45 ns 5 +// BM_AlwaysOnSamplerShouldSample_stddev 0.013 ns 0.013 ns 5 +// BM_AlwaysOnSamplerShouldSample_cv 0.30 % 0.30 % 5 +// BM_ParentBasedSamplerShouldSample_mean 7.37 ns 7.36 ns 5 +// BM_ParentBasedSamplerShouldSample_median 7.36 ns 7.36 ns 5 +// BM_ParentBasedSamplerShouldSample_stddev 0.010 ns 0.009 ns 5 +// BM_ParentBasedSamplerShouldSample_cv 0.14 % 0.12 % 5 +// BM_TraceIdRatioBasedSamplerShouldSample_mean 3.97 ns 3.97 ns 5 +// BM_TraceIdRatioBasedSamplerShouldSample_median 3.97 ns 3.97 ns 5 +// BM_TraceIdRatioBasedSamplerShouldSample_stddev 0.010 ns 0.010 ns 5 +// BM_TraceIdRatioBasedSamplerShouldSample_cv 0.25 % 0.25 % 5 +// BM_SpanCreation_mean 215 ns 215 ns 5 +// BM_SpanCreation_median 214 ns 214 ns 5 +// BM_SpanCreation_stddev 4.74 ns 4.74 ns 5 +// BM_SpanCreation_cv 2.21 % 2.21 % 5 +// BM_NoopSpanCreation_mean 31.1 ns 31.1 ns 5 +// BM_NoopSpanCreation_median 31.1 ns 31.1 ns 5 +// BM_NoopSpanCreation_stddev 0.028 ns 0.027 ns 5 +// BM_NoopSpanCreation_cv 0.09 % 0.09 % 5 +// BM_SpanCreationWithSamplingResultAttributes/1_mean 253 ns 253 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/1_median 251 ns 251 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/1_stddev 4.27 ns 4.27 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/1_cv 1.69 % 1.69 % 5 +// BM_SpanCreationWithSamplingResultAttributes/10_mean 588 ns 588 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/10_median 589 ns 589 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/10_stddev 3.18 ns 3.17 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/10_cv 0.54 % 0.54 % 5 +// BM_SpanCreationWithSamplingResultAttributes/128_mean 8602 ns 8600 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/128_median 8702 ns 8701 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/128_stddev 299 ns 299 ns 5 +// BM_SpanCreationWithSamplingResultAttributes/128_cv 3.48 % 3.48 % 5 +// +// clang-format on + #include +#include #include #include #include #include +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable_view.h" #include "opentelemetry/context/context_value.h" // IWYU pragma: keep #include "opentelemetry/exporters/memory/in_memory_span_exporter.h" @@ -35,6 +100,8 @@ using namespace opentelemetry::sdk::trace; using opentelemetry::exporter::memory::InMemorySpanExporter; using opentelemetry::trace::SpanContext; +namespace nostd = opentelemetry::nostd; + namespace { // Sampler constructor used as a baseline to compare with other samplers @@ -178,5 +245,58 @@ void BM_NoopSpanCreation(benchmark::State &state) } BENCHMARK(BM_NoopSpanCreation); +namespace +{ +class AttributeContributingSampler : public Sampler +{ +public: + AttributeContributingSampler(std::size_t num_attributes) + : attributes_(CreateAttributes(num_attributes)) + {} + + static std::map CreateAttributes( + std::size_t num_attributes) + { + auto attributes_map = std::map(); + for (std::size_t i = 0; i < num_attributes; ++i) + { + attributes_map.emplace("attr" + std::to_string(i), static_cast(i)); + } + return attributes_map; + } + + SamplingResult ShouldSample( + const opentelemetry::trace::SpanContext & /*parent_context*/, + opentelemetry::trace::TraceId /*trace_id*/, + nostd::string_view /*name*/, + opentelemetry::trace::SpanKind /*span_kind*/, + const opentelemetry::common::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::SpanContextKeyValueIterable & /*links*/) noexcept override + { + return SamplingResult{ + Decision::RECORD_AND_SAMPLE, + std::make_unique>(attributes_), + {}}; + } + + nostd::string_view GetDescription() const noexcept override + { + return "AttributeContributingSampler"; + } + +private: + std::map attributes_; +}; +} // namespace + +// Test to measure performance for a sampler that adds attributes to the span +void BM_SpanCreationWithSamplingResultAttributes(benchmark::State &state) +{ + std::size_t num_attributes = static_cast(state.range(0)); + std::unique_ptr sampler(new AttributeContributingSampler(num_attributes)); + BenchmarkSpanCreation(std::move(sampler), state); +} +BENCHMARK(BM_SpanCreationWithSamplingResultAttributes)->Arg(1)->Arg(10)->Arg(128); + } // namespace BENCHMARK_MAIN(); diff --git a/sdk/test/trace/tracer_benchmark.cc b/sdk/test/trace/tracer_benchmark.cc new file mode 100644 index 0000000000..3fafb7ce6e --- /dev/null +++ b/sdk/test/trace/tracer_benchmark.cc @@ -0,0 +1,198 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// clang-format off +// +// ~/build/sdk/test/trace/tracer_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-17T17:30:15+00:00 +// Running /home/devuser/build/sdk/test/trace/tracer_benchmark +// Run on (32 X 5700 MHz CPU s) +// CPU Caches: +// L1 Data 48 KiB (x16) +// L1 Instruction 32 KiB (x16) +// L2 Unified 2048 KiB (x16) +// L3 Unified 36864 KiB (x1) +// Load Average: 1.06, 3.28, 4.23 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// --------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// --------------------------------------------------------------------------------------- +// BM_StartSpanTracerDisabled_mean 5.29 ns 5.29 ns 5 +// BM_StartSpanTracerDisabled_median 5.30 ns 5.30 ns 5 +// BM_StartSpanTracerDisabled_stddev 0.030 ns 0.030 ns 5 +// BM_StartSpanTracerDisabled_cv 0.58 % 0.57 % 5 +// BM_StartSpan_mean 158 ns 158 ns 5 +// BM_StartSpan_median 158 ns 158 ns 5 +// BM_StartSpan_stddev 1.56 ns 1.55 ns 5 +// BM_StartSpan_cv 0.99 % 0.98 % 5 +// BM_StartSpanWithScope_mean 225 ns 225 ns 5 +// BM_StartSpanWithScope_median 224 ns 224 ns 5 +// BM_StartSpanWithScope_stddev 2.61 ns 2.60 ns 5 +// BM_StartSpanWithScope_cv 1.16 % 1.16 % 5 +// BM_StartSpanWithImplicitParent_mean 160 ns 160 ns 5 +// BM_StartSpanWithImplicitParent_median 161 ns 161 ns 5 +// BM_StartSpanWithImplicitParent_stddev 1.93 ns 1.93 ns 5 +// BM_StartSpanWithImplicitParent_cv 1.20 % 1.20 % 5 +// BM_StartSpanWithExplicitParentContext_mean 154 ns 154 ns 5 +// BM_StartSpanWithExplicitParentContext_median 154 ns 154 ns 5 +// BM_StartSpanWithExplicitParentContext_stddev 2.25 ns 2.25 ns 5 +// BM_StartSpanWithExplicitParentContext_cv 1.46 % 1.46 % 5 +// BM_StartSpanWithExplicitRootContext_mean 157 ns 157 ns 5 +// BM_StartSpanWithExplicitRootContext_median 157 ns 157 ns 5 +// BM_StartSpanWithExplicitRootContext_stddev 0.310 ns 0.301 ns 5 +// BM_StartSpanWithExplicitRootContext_cv 0.20 % 0.19 % 5 +// +// clang-format on + +#include + +#include +#include +#include + +#include "opentelemetry/context/context.h" +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/sdk/trace/tracer_config.h" +#include "opentelemetry/sdk/trace/tracer_context.h" +#include "opentelemetry/trace/context.h" +#include "opentelemetry/trace/scope.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_metadata.h" +#include "opentelemetry/trace/span_startoptions.h" +#include "opentelemetry/trace/tracer.h" + +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace context = opentelemetry::context; +using opentelemetry::exporter::memory::InMemorySpanExporter; + +namespace +{ + +std::shared_ptr CreateTracer(bool is_enabled = true) +{ + // Set the batch size to 0 so the InMemorySpanExporter's circular buffer has a capcity of 1 and + // rejects the span from the first iteration. This will force destruction of the span in the + // timing loop consistently. + constexpr std::size_t kExporterBufferSize = 0; + + auto exporter = std::make_unique(kExporterBufferSize); + auto processor = std::make_unique(std::move(exporter)); + std::vector> processors; + processors.push_back(std::move(processor)); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto context = std::make_shared(std::move(processors), resource); + + if (!is_enabled) + { + auto disabled_config = std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + trace_sdk::TracerConfig::Disabled()) + .Build()); + context->SetTracerConfigurator(std::move(disabled_config)); + } + auto tracer = std::make_shared(context); + + return tracer; +} + +// Test to measure performance for span creation +void BM_StartSpanTracerDisabled(benchmark::State &state) +{ + auto tracer = CreateTracer(false); + while (state.KeepRunning()) + { + auto span = tracer->StartSpan("span"); + span->End(); + } +} +BENCHMARK(BM_StartSpanTracerDisabled); + +// Test to measure performance for span creation +void BM_StartSpan(benchmark::State &state) +{ + auto tracer = CreateTracer(); + while (state.KeepRunning()) + { + auto span = tracer->StartSpan("span"); + span->End(); + } +} +BENCHMARK(BM_StartSpan); + +// Test to measure performance for single span creation with scope +void BM_StartSpanWithScope(benchmark::State &state) +{ + auto tracer = CreateTracer(); + while (state.KeepRunning()) + { + auto span = tracer->StartSpan("span"); + trace_api::Scope scope{span}; + span->End(); + } +} +BENCHMARK(BM_StartSpanWithScope); + +// Test to measure performance for nested span creation with scope +void BM_StartSpanWithImplicitParent(benchmark::State &state) +{ + auto tracer = CreateTracer(); + auto parent_span = tracer->StartSpan("parent"); + trace_api::Scope parent_scope{parent_span}; + while (state.KeepRunning()) + { + auto span = tracer->StartSpan("span"); + span->End(); + } +} + +BENCHMARK(BM_StartSpanWithImplicitParent); + +// Test to measure performance for nested span creation with manual span context management +void BM_StartSpanWithExplicitParentContext(benchmark::State &state) +{ + auto tracer = CreateTracer(); + + auto parent_span = tracer->StartSpan("parent"); + + auto init_context = context::Context{}; + auto parent_context = trace_api::SetSpan(init_context, parent_span); + + while (state.KeepRunning()) + { + trace_api::StartSpanOptions options; + options.parent = parent_context; + auto span = tracer->StartSpan("span", options); + span->End(); + } +} +BENCHMARK(BM_StartSpanWithExplicitParentContext); + +void BM_StartSpanWithExplicitRootContext(benchmark::State &state) +{ + auto tracer = CreateTracer(); + + auto root_context = context::Context{trace_api::kIsRootSpanKey, true}; + + while (state.KeepRunning()) + { + trace_api::StartSpanOptions options; + options.parent = root_context; + auto span = tracer->StartSpan("span", options); + span->End(); + } +} +BENCHMARK(BM_StartSpanWithExplicitRootContext); + +} // namespace +BENCHMARK_MAIN(); diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index d3107db47a..da91c14e28 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -43,7 +43,6 @@ #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/context.h" -#include "opentelemetry/trace/noop.h" #include "opentelemetry/trace/scope.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" @@ -67,6 +66,8 @@ using opentelemetry::exporter::memory::InMemorySpanData; using opentelemetry::exporter::memory::InMemorySpanExporter; using opentelemetry::trace::SpanContext; +namespace +{ /** * A mock sampler with ShouldSample returning: * Decision::RECORD_AND_SAMPLE if trace_id is valid @@ -171,8 +172,6 @@ class MockIdGenerator : public IdGenerator uint8_t buf_trace[16] = {1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}; }; -namespace -{ std::shared_ptr initTracer(std::unique_ptr &&exporter) { auto processor = std::unique_ptr(new SimpleSpanProcessor(std::move(exporter))); @@ -591,13 +590,9 @@ TEST(Tracer, StartSpanWithDisabledConfig) new RandomIdGenerator(), disable_tracer); auto span = tracer->StartSpan("span 1"); - std::shared_ptr noop_tracer = - std::make_shared(); - auto noop_span = noop_tracer->StartSpan("noop"); - EXPECT_TRUE(span.get() == noop_span.get()); + EXPECT_FALSE(span->IsRecording()); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - EXPECT_FALSE(noop_tracer->Enabled()); EXPECT_FALSE(tracer->Enabled()); #endif } @@ -612,22 +607,15 @@ TEST(Tracer, StartSpanWithEnabledConfig) new RandomIdGenerator(), enable_tracer); auto span = tracer->StartSpan("span 1"); - std::shared_ptr noop_tracer = - std::make_shared(); - auto noop_span = noop_tracer->StartSpan("noop"); - EXPECT_FALSE(span.get() == noop_span.get()); + EXPECT_TRUE(span->IsRecording()); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - EXPECT_FALSE(noop_tracer->Enabled()); EXPECT_TRUE(tracer->Enabled()); #endif } TEST(Tracer, StartSpanWithCustomConfig) { - std::shared_ptr noop_tracer = - std::make_shared(); - auto noop_span = noop_tracer->StartSpan("noop"); auto check_if_version_present = [](const InstrumentationScope &scope_info) { return !scope_info.GetVersion().empty(); }; @@ -642,31 +630,30 @@ TEST(Tracer, StartSpanWithCustomConfig) initTracer(std::unique_ptr{new InMemorySpanExporter()}, new AlwaysOnSampler(), new RandomIdGenerator(), custom_configurator); const auto span_default_scope = tracer_default_scope->StartSpan("span 1"); - EXPECT_TRUE(span_default_scope == noop_span); + EXPECT_FALSE(span_default_scope->IsRecording()); auto foo_scope = InstrumentationScope::Create("foo_library"); const auto tracer_foo_scope = initTracer(std::unique_ptr{new InMemorySpanExporter()}, new AlwaysOnSampler(), new RandomIdGenerator(), custom_configurator, std::move(foo_scope)); const auto span_foo_scope = tracer_foo_scope->StartSpan("span 1"); - EXPECT_TRUE(span_foo_scope == noop_span); + EXPECT_FALSE(span_foo_scope->IsRecording()); auto foo_scope_with_version = InstrumentationScope::Create("foo_library", "1.0.0"); const auto tracer_foo_scope_with_version = initTracer(std::unique_ptr{new InMemorySpanExporter()}, new AlwaysOnSampler(), new RandomIdGenerator(), custom_configurator, std::move(foo_scope_with_version)); const auto span_foo_scope_with_version = tracer_foo_scope_with_version->StartSpan("span 1"); - EXPECT_FALSE(span_foo_scope_with_version == noop_span); + EXPECT_TRUE(span_foo_scope_with_version->IsRecording()); auto bar_scope = InstrumentationScope::Create("bar_library"); auto tracer_bar_scope = initTracer(std::unique_ptr{new InMemorySpanExporter()}, new AlwaysOnSampler(), new RandomIdGenerator(), custom_configurator, std::move(bar_scope)); auto span_bar_scope = tracer_bar_scope->StartSpan("span 1"); - EXPECT_FALSE(span_bar_scope == noop_span); + EXPECT_TRUE(span_bar_scope->IsRecording()); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - EXPECT_FALSE(noop_tracer->Enabled()); EXPECT_FALSE(tracer_default_scope->Enabled()); EXPECT_FALSE(tracer_foo_scope->Enabled()); EXPECT_TRUE(tracer_foo_scope_with_version->Enabled()); @@ -676,9 +663,6 @@ TEST(Tracer, StartSpanWithCustomConfig) TEST(Tracer, StartSpanWithCustomConfigDifferingConditionOrder) { - std::shared_ptr noop_tracer = - std::make_shared(); - auto noop_span = noop_tracer->StartSpan("noop"); auto check_if_version_present = [](const InstrumentationScope &scope_info) { return !scope_info.GetVersion().empty(); }; @@ -707,12 +691,12 @@ TEST(Tracer, StartSpanWithCustomConfigDifferingConditionOrder) // Custom configurator 1 evaluates version first and enables the tracer const auto span_foo_scope_with_version_1 = tracer_foo_scope_with_version_1->StartSpan("span 1"); - EXPECT_FALSE(span_foo_scope_with_version_1 == noop_span); + EXPECT_TRUE(span_foo_scope_with_version_1->IsRecording()); // Custom configurator 2 evaluates the name first and therefore disables the tracer without // evaluating other condition const auto span_foo_scope_with_version_2 = tracer_foo_scope_with_version_2->StartSpan("span 1"); - EXPECT_TRUE(span_foo_scope_with_version_2 == noop_span); + EXPECT_FALSE(span_foo_scope_with_version_2->IsRecording()); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 EXPECT_TRUE(tracer_foo_scope_with_version_1->Enabled());