diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 86b26c57be..25ecebdd86 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -1040,6 +1040,7 @@ otel_cc_benchmark( "//sdk/src/metrics", "//sdk/src/resource", "//sdk/src/trace", + "//test_common:headers", "@com_github_opentelemetry_proto//:trace_service_proto_cc", ], ) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 89e229ca4a..eef251a1de 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -1225,7 +1225,11 @@ if(WITH_BENCHMARK) target_link_libraries( otlp_recordable_benchmark - PRIVATE opentelemetry_otlp_recordable opentelemetry_trace - opentelemetry_metrics opentelemetry_resources benchmark::benchmark + PRIVATE opentelemetry_otlp_recordable + opentelemetry_trace + opentelemetry_metrics + opentelemetry_resources + benchmark::benchmark + opentelemetry_test_common ${CMAKE_THREAD_LIBS_INIT}) endif() diff --git a/exporters/otlp/test/otlp_recordable_benchmark.cc b/exporters/otlp/test/otlp_recordable_benchmark.cc index 6c672c9217..2d5202d71d 100644 --- a/exporters/otlp/test/otlp_recordable_benchmark.cc +++ b/exporters/otlp/test/otlp_recordable_benchmark.cc @@ -3,38 +3,72 @@ // clang-format off // -// 2026-06-18T21:05:11+00:00 +// ~/build/exporters/otlp/otlp_recordable_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-01T15:57:22+00:00 // Running /home/devuser/build/exporters/otlp/otlp_recordable_benchmark -// Run on (32 X 5175.97 MHz CPU s) +// 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.61, 1.30, 1.33 -// ----------------------------------------------------------------------------------- -// Benchmark Time CPU Iterations -// ----------------------------------------------------------------------------------- -// OtlpSpanFixture/Minimal_mean 355 ns 357 ns 5 -// OtlpSpanFixture/Minimal_median 355 ns 358 ns 5 -// OtlpSpanFixture/Minimal_stddev 0.489 ns 0.790 ns 5 -// OtlpSpanFixture/Minimal_cv 0.14 % 0.22 % 5 -// OtlpSpanFixture/Nominal_mean 617 ns 619 ns 5 -// OtlpSpanFixture/Nominal_median 617 ns 620 ns 5 -// OtlpSpanFixture/Nominal_stddev 0.960 ns 1.25 ns 5 -// OtlpSpanFixture/Nominal_cv 0.16 % 0.20 % 5 -// OtlpSpanFixture/MaxAttributes_mean 8014 ns 8016 ns 5 items_per_second=16.0018M/s -// OtlpSpanFixture/MaxAttributes_median 7737 ns 7739 ns 5 items_per_second=16.5389M/s -// OtlpSpanFixture/MaxAttributes_stddev 419 ns 418 ns 5 items_per_second=819.876k/s -// OtlpSpanFixture/MaxAttributes_cv 5.23 % 5.22 % 5 items_per_second=5.12% -// BM_PopulateRequest/span_count:1_mean 0.398 us 0.398 us 5 items_per_second=2.51517M/s -// BM_PopulateRequest/span_count:1_median 0.397 us 0.397 us 5 items_per_second=2.52167M/s -// BM_PopulateRequest/span_count:1_stddev 0.002 us 0.002 us 5 items_per_second=12.1068k/s -// BM_PopulateRequest/span_count:1_cv 0.48 % 0.48 % 5 items_per_second=0.48% -// BM_PopulateRequest/span_count:512_mean 26.3 us 26.3 us 5 items_per_second=19.4714M/s -// BM_PopulateRequest/span_count:512_median 26.3 us 26.3 us 5 items_per_second=19.4746M/s -// BM_PopulateRequest/span_count:512_stddev 0.078 us 0.077 us 5 items_per_second=56.7323k/s -// BM_PopulateRequest/span_count:512_cv 0.30 % 0.29 % 5 items_per_second=0.29% +// Load Average: 2.05, 1.65, 1.67 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// -------------------------------------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// -------------------------------------------------------------------------------------------------------------------- +// OtlpRecordableFixture/RecordMinimalSpan_mean 362 ns 364 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_median 363 ns 365 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_stddev 1.77 ns 1.97 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_cv 0.49 % 0.54 % 5 +// OtlpRecordableFixture/RecordNominalSpan_mean 638 ns 639 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_median 636 ns 637 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_stddev 8.01 ns 8.04 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_cv 1.26 % 1.26 % 5 +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_mean 414 ns 416 ns 5 items_per_second=2.40287M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_median 414 ns 416 ns 5 items_per_second=2.40294M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_stddev 1.94 ns 2.14 ns 5 items_per_second=12.3138k/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_cv 0.47 % 0.51 % 5 items_per_second=0.51% +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_mean 880 ns 881 ns 5 items_per_second=11.3516M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_median 883 ns 883 ns 5 items_per_second=11.3193M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_stddev 12.8 ns 13.0 ns 5 items_per_second=167.444k/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_cv 1.46 % 1.47 % 5 items_per_second=1.48% +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_mean 7293 ns 7297 ns 5 items_per_second=17.7189M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_median 6834 ns 6839 ns 5 items_per_second=18.7175M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_stddev 842 ns 841 ns 5 items_per_second=1.922M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_cv 11.55 % 11.53 % 5 items_per_second=10.85% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_mean 502 ns 503 ns 5 items_per_second=1.98937M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_median 501 ns 503 ns 5 items_per_second=1.99001M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_stddev 1.04 ns 0.917 ns 5 items_per_second=3.63084k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_cv 0.21 % 0.18 % 5 items_per_second=0.18% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_mean 1705 ns 1707 ns 5 items_per_second=5.86103M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_median 1699 ns 1700 ns 5 items_per_second=5.88169M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_stddev 29.0 ns 29.0 ns 5 items_per_second=99.4244k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_cv 1.70 % 1.70 % 5 items_per_second=1.70% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_mean 16750 ns 16750 ns 5 items_per_second=7.64354M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_median 16757 ns 16756 ns 5 items_per_second=7.63925M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_stddev 287 ns 287 ns 5 items_per_second=130.651k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_cv 1.71 % 1.71 % 5 items_per_second=1.71% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_mean 511 ns 513 ns 5 items_per_second=1.94877M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_median 510 ns 512 ns 5 items_per_second=1.95458M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_stddev 5.76 ns 6.17 ns 5 items_per_second=23.304k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_cv 1.13 % 1.20 % 5 items_per_second=1.20% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_mean 1784 ns 1786 ns 5 items_per_second=5.60068M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_median 1785 ns 1787 ns 5 items_per_second=5.59477M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_stddev 6.08 ns 6.23 ns 5 items_per_second=19.5651k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_cv 0.34 % 0.35 % 5 items_per_second=0.35% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_mean 19515 ns 19517 ns 5 items_per_second=6.62769M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_median 18663 ns 18666 ns 5 items_per_second=6.85722M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_stddev 2403 ns 2403 ns 5 items_per_second=703.536k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_cv 12.31 % 12.31 % 5 items_per_second=10.62% +// BM_OtlpPopulateRequest/span_count:1_mean 0.387 us 0.387 us 5 items_per_second=2.58274M/s +// BM_OtlpPopulateRequest/span_count:1_median 0.387 us 0.387 us 5 items_per_second=2.58201M/s +// BM_OtlpPopulateRequest/span_count:1_stddev 0.004 us 0.004 us 5 items_per_second=23.6738k/s +// BM_OtlpPopulateRequest/span_count:1_cv 0.92 % 0.92 % 5 items_per_second=0.92% +// BM_OtlpPopulateRequest/span_count:512_mean 23.6 us 23.6 us 5 items_per_second=21.7343M/s +// BM_OtlpPopulateRequest/span_count:512_median 23.5 us 23.5 us 5 items_per_second=21.7479M/s +// BM_OtlpPopulateRequest/span_count:512_stddev 0.260 us 0.259 us 5 items_per_second=238.588k/s +// BM_OtlpPopulateRequest/span_count:512_cv 1.10 % 1.10 % 5 items_per_second=1.10% // // clang-format on @@ -47,7 +81,6 @@ #include #include -#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" #include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" @@ -55,21 +88,15 @@ #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/span.h" -#include "opentelemetry/nostd/string_view.h" -#include "opentelemetry/nostd/utility.h" #include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" -#include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/exporter.h" -#include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/test_common/sdk/trace/test_utils.h" #include "opentelemetry/trace/span.h" -#include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" -#include "opentelemetry/trace/trace_flags.h" -#include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/tracer.h" // clang-format off @@ -79,9 +106,10 @@ #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep // clang-format on -namespace otlp = opentelemetry::exporter::otlp; -namespace trace_sdk = opentelemetry::sdk::trace; -namespace trace_api = opentelemetry::trace; +namespace otlp = opentelemetry::exporter::otlp; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace trace_api = opentelemetry::trace; +namespace test_utils = opentelemetry::test_common; namespace { @@ -107,69 +135,22 @@ class NullSpanExporter final : public trace_sdk::SpanExporter bool Shutdown(std::chrono::microseconds) noexcept override { return true; } }; -// A span processor that queues completed recordables into an internal buffer -// without serialising or destroying them mirroring the batch processor. -class BufferingProcessor final : public trace_sdk::SpanProcessor -{ -public: - using Buffer = std::vector>; - - explicit BufferingProcessor(std::unique_ptr exporter) - : exporter_(std::move(exporter)) - {} - - std::unique_ptr MakeRecordable() noexcept override - { - return exporter_->MakeRecordable(); - } - - void OnStart(trace_sdk::Recordable &, const opentelemetry::trace::SpanContext &) noexcept override - {} - - void OnEnd(std::unique_ptr &&span) noexcept override - { - buffer_.push_back(std::move(span)); - } - - bool ForceFlush(std::chrono::microseconds) noexcept override - { - buffer_.clear(); - return true; - } - bool Shutdown(std::chrono::microseconds) noexcept override { return true; } - -private: - std::unique_ptr exporter_; - Buffer buffer_; -}; - -const opentelemetry::sdk::resource::Resource &TestResource() -{ - static const auto resource = opentelemetry::sdk::resource::Resource::Create( - {{"service.name", opentelemetry::nostd::string_view{"benchmark_service"}}, - {"service.version", opentelemetry::nostd::string_view{"1.0.0"}}}); - return resource; -} - -const opentelemetry::sdk::instrumentationscope::InstrumentationScope &TestScope() -{ - static auto scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create( - "benchmark_scope", "1.0.0", "https://opentelemetry.io/schemas/1.24.0", - {{"scope.source", "benchmark"}}); - return *scope; -} - // Benchmark fixture: each benchmark function gets its own TracerProvider backed -// by a BufferingProcessor with a NullSpanExporter. -class OtlpSpanFixture : public benchmark::Fixture +// by a BufferingSpanProcessor with a NullSpanExporter. +class OtlpRecordableFixture : public benchmark::Fixture { public: + using benchmark::Fixture::SetUp; + void SetUp(benchmark::State &) override { - auto processor = std::make_unique(std::make_unique()); - provider_ = std::make_shared(std::move(processor)); - tracer_ = provider_->GetTracer(TestScope().GetName(), TestScope().GetVersion(), - TestScope().GetSchemaURL()); + auto processor = + std::make_unique(std::make_unique()); + provider_ = std::make_shared(std::move(processor)); + tracer_ = provider_->GetTracer(test_utils::TestScope().GetName(), + test_utils::TestScope().GetVersion(), + test_utils::TestScope().GetSchemaURL()); + test_utils::InitializeSpanTestData(); } protected: @@ -178,39 +159,13 @@ class OtlpSpanFixture : public benchmark::Fixture }; // Benchmark argument values. -constexpr int kMaxAttributes = 128; // default attribute count limit -constexpr int kBatchSmall = 1; // single span -constexpr int kBatchLarge = 512; // default batch size - -// Pre-built keys for the max-attributes benchmark (matches SDK default attribute limit). -const std::vector &MaxAttributeKeys() -{ - static const std::vector keys = []() { - std::vector result; - result.reserve(kMaxAttributes); - for (int key_idx = 0; key_idx < kMaxAttributes; ++key_idx) - { - result.push_back("attribute." + std::to_string(key_idx)); - } - return result; - }(); - return keys; -} +constexpr std::size_t kBatchSmall = 1; // single span +constexpr std::size_t kBatchLarge = 512; // default batch size // Build a batch of N fully-populated OtlpRecordable spans for PopulateRequest benchmarks. std::vector> MakeSpanBatch( std::size_t span_count) { - constexpr uint8_t kTraceIdBytes[opentelemetry::trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8}; - constexpr uint8_t kSpanIdBytes[opentelemetry::trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; - constexpr uint8_t kParentIdBytes[opentelemetry::trace::SpanId::kSize] = {8, 7, 6, 5, 4, 3, 2, 1}; - const opentelemetry::trace::TraceId trace_id{kTraceIdBytes}; - const opentelemetry::trace::SpanId span_id{kSpanIdBytes}; - const opentelemetry::trace::SpanId parent_id{kParentIdBytes}; - const opentelemetry::trace::SpanContext ctx{ - trace_id, span_id, - opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, true}; const auto now = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); std::vector> batch; @@ -218,13 +173,13 @@ std::vector> MakeSpanBatc for (std::size_t span_idx = 0; span_idx < span_count; ++span_idx) { auto recordable = std::make_unique(); - recordable->SetIdentity(ctx, parent_id); + recordable->SetIdentity(test_utils::TestSpanContext(), opentelemetry::trace::SpanId{}); recordable->SetName("benchmark_span"); recordable->SetSpanKind(trace_api::SpanKind::kServer); recordable->SetStartTime(now); recordable->SetDuration(std::chrono::milliseconds(5)); - recordable->SetResource(TestResource()); - recordable->SetInstrumentationScope(TestScope()); + recordable->SetResource(test_utils::TestResource()); + recordable->SetInstrumentationScope(test_utils::TestScope()); batch.push_back(std::move(recordable)); } return batch; @@ -240,80 +195,110 @@ std::unique_ptr CreateArena() } // namespace -// Baseline: start a span and end it immediately with no attributes. -BENCHMARK_DEFINE_F(OtlpSpanFixture, Minimal)(benchmark::State &state) +// Baseline: start a span and end it immediately with no attributes, links, or events. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordMinimalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartMinimalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordMinimalSpan)->Unit(benchmark::kNanosecond); + +// RecordNominalSpan span: starts a span with a representative mix of attribute types then ends the +// span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordNominalSpan)(benchmark::State &state) { for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span"); + auto span = test_utils::StartNominalSpan(*tracer_); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } } -BENCHMARK_REGISTER_F(OtlpSpanFixture, Minimal)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordNominalSpan)->Unit(benchmark::kNanosecond); -// Nominal span: representative mix of attribute types passed at creation. -BENCHMARK_DEFINE_F(OtlpSpanFixture, Nominal)(benchmark::State &state) +// RecordSpanWithAttributes: starts a span with N attributes (all string values) then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithAttributes)(benchmark::State &state) { - using SpanAttribute = - std::pair; - static const std::vector kAttributes = { - {"benchmark.string.short", opentelemetry::nostd::string_view{"abcdefgh"}}, - {"benchmark.string.medium", - opentelemetry::nostd::string_view{"abcdefghijklmnopqrstuvwxyz012345"}}, - {"benchmark.string.long", - opentelemetry::nostd::string_view{ - "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"}}, - {"benchmark.int", opentelemetry::common::AttributeValue{static_cast(1)}}, - {"benchmark.double", opentelemetry::common::AttributeValue{3.14159265358979}}, - {"benchmark.bool", opentelemetry::common::AttributeValue{true}}, - }; + const int64_t attribute_count = state.range(0); + const std::vector attributes = + test_utils::MakeAttributes(static_cast(attribute_count)); for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span", kAttributes); + auto span = test_utils::StartSpanWithAttributes(*tracer_, attributes); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } + state.SetItemsProcessed(state.iterations() * attribute_count); } -BENCHMARK_REGISTER_F(OtlpSpanFixture, Nominal)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithAttributes) + ->ArgName("attribute_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanAttributeLimit) + ->Unit(benchmark::kNanosecond); + +// RecordSpanWithEvents: starts a span via the tracer API with N events, then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithEvents)(benchmark::State &state) +{ + const int64_t event_count = state.range(0); -// Max-attributes span: fills the recordable to its attribute limit (128) with string values. -BENCHMARK_DEFINE_F(OtlpSpanFixture, MaxAttributes)(benchmark::State &state) + for (auto _ : state) + { + auto span = test_utils::StartSpanWithEvents(*tracer_, static_cast(event_count)); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * event_count); +} +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithEvents) + ->ArgName("event_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanEventLimit) + ->Unit(benchmark::kNanosecond); + +// RecordSpanWithLinks: starts a span via the tracer API with N links, then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithLinks)(benchmark::State &state) { - using SpanAttribute = - std::pair; - static const std::vector kAttributes = []() { - const auto &keys = MaxAttributeKeys(); - std::vector attributes; - attributes.reserve(kMaxAttributes); - for (const auto &key : keys) - { - attributes.emplace_back(key, opentelemetry::nostd::string_view{"value-string-attribute"}); - } - return attributes; - }(); + const int64_t link_count = state.range(0); + const std::vector link_entries = + test_utils::MakeLinkEntries(static_cast(link_count)); + for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span", kAttributes); + auto span = test_utils::StartSpanWithLinks(*tracer_, link_entries); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } - state.SetItemsProcessed(state.iterations() * kMaxAttributes); + state.SetItemsProcessed(state.iterations() * link_count); } -BENCHMARK_REGISTER_F(OtlpSpanFixture, MaxAttributes)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithLinks) + ->ArgName("link_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanLinkLimit) + ->Unit(benchmark::kNanosecond); // Benchmark the arena allocation + OtlpRecordableUtils::PopulateRequest for a // batch of N spans. This mirrors the path in OtlpGrpcExporter::Export. -static void BM_PopulateRequest(benchmark::State &state) +static void BM_OtlpPopulateRequest(benchmark::State &state) { - const std::size_t span_count = static_cast(state.range(0)); - auto batch = MakeSpanBatch(span_count); + const int64_t span_count = state.range(0); + auto batch = MakeSpanBatch(static_cast(span_count)); for (auto _ : state) { auto arena = CreateArena(); @@ -326,9 +311,9 @@ static void BM_PopulateRequest(benchmark::State &state) request); benchmark::DoNotOptimize(request); } - state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetItemsProcessed(state.iterations() * span_count); } -BENCHMARK(BM_PopulateRequest) +BENCHMARK(BM_OtlpPopulateRequest) ->ArgName("span_count") ->Arg(kBatchSmall) ->Arg(kBatchLarge) diff --git a/sdk/src/trace/span_data.cc b/sdk/src/trace/span_data.cc index 9969b44c30..8b1503ee5d 100644 --- a/sdk/src/trace/span_data.cc +++ b/sdk/src/trace/span_data.cc @@ -100,15 +100,13 @@ void SpanData::AddEvent(nostd::string_view name, opentelemetry::common::SystemTimestamp timestamp, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataEvent event(std::string(name), timestamp, attributes); - events_.push_back(event); + events_.emplace_back(std::string(name), timestamp, attributes); } void SpanData::AddLink(const opentelemetry::trace::SpanContext &span_context, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataLink link(span_context, attributes); - links_.push_back(link); + links_.emplace_back(span_context, attributes); } void SpanData::SetStatus(opentelemetry::trace::StatusCode code, diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 7f536dc57e..3e4393345a 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -205,3 +205,19 @@ otel_cc_benchmark( "//sdk/src/trace", ], ) + +otel_cc_benchmark( + name = "span_data_benchmark", + srcs = ["span_data_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 ca0ae383bc..e299deb502 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -36,4 +36,14 @@ if(WITH_BENCHMARK) sampler_benchmark benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace opentelemetry_resources opentelemetry_exporter_in_memory) + + add_executable(span_data_benchmark span_data_benchmark.cc) + target_link_libraries( + span_data_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/span_data_benchmark.cc b/sdk/test/trace/span_data_benchmark.cc new file mode 100644 index 0000000000..6bf0418a01 --- /dev/null +++ b/sdk/test/trace/span_data_benchmark.cc @@ -0,0 +1,213 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// clang-format off +// +// ~/build/sdk/test/trace/span_data_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-01T15:59:10+00:00 +// Running /home/devuser/build/sdk/test/trace/span_data_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.35, 1.52, 1.62 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// -------------------------------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// -------------------------------------------------------------------------------------------------------------- +// SpanDataFixture/RecordMinimalSpan_mean 341 ns 343 ns 5 +// SpanDataFixture/RecordMinimalSpan_median 341 ns 343 ns 5 +// SpanDataFixture/RecordMinimalSpan_stddev 0.950 ns 0.988 ns 5 +// SpanDataFixture/RecordMinimalSpan_cv 0.28 % 0.29 % 5 +// SpanDataFixture/RecordNominalSpan_mean 581 ns 584 ns 5 +// SpanDataFixture/RecordNominalSpan_median 588 ns 590 ns 5 +// SpanDataFixture/RecordNominalSpan_stddev 15.3 ns 15.1 ns 5 +// SpanDataFixture/RecordNominalSpan_cv 2.63 % 2.59 % 5 +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_mean 390 ns 394 ns 5 items_per_second=2.5415M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_median 391 ns 395 ns 5 items_per_second=2.53367M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_stddev 4.17 ns 4.40 ns 5 items_per_second=28.4487k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_cv 1.07 % 1.12 % 5 items_per_second=1.12% +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_mean 705 ns 708 ns 5 items_per_second=14.1257M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_median 707 ns 711 ns 5 items_per_second=14.0739M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_stddev 6.35 ns 6.36 ns 5 items_per_second=127.942k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_cv 0.90 % 0.90 % 5 items_per_second=0.91% +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_mean 7479 ns 7484 ns 5 items_per_second=17.1122M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_median 7539 ns 7543 ns 5 items_per_second=16.969M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_stddev 189 ns 189 ns 5 items_per_second=438.424k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_cv 2.53 % 2.53 % 5 items_per_second=2.56% +// SpanDataFixture/RecordSpanWithEvents/event_count:1_mean 456 ns 459 ns 5 items_per_second=2.17751M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_median 456 ns 459 ns 5 items_per_second=2.178M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_stddev 2.89 ns 3.02 ns 5 items_per_second=14.3182k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_cv 0.63 % 0.66 % 5 items_per_second=0.66% +// SpanDataFixture/RecordSpanWithEvents/event_count:10_mean 1584 ns 1587 ns 5 items_per_second=6.30345M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_median 1580 ns 1583 ns 5 items_per_second=6.318M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_stddev 24.3 ns 24.4 ns 5 items_per_second=95.4207k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_cv 1.54 % 1.54 % 5 items_per_second=1.51% +// SpanDataFixture/RecordSpanWithEvents/event_count:128_mean 16434 ns 16438 ns 5 items_per_second=7.78791M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_median 16429 ns 16432 ns 5 items_per_second=7.78954M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_stddev 211 ns 211 ns 5 items_per_second=100.089k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_cv 1.28 % 1.28 % 5 items_per_second=1.29% +// SpanDataFixture/RecordSpanWithLinks/link_count:1_mean 435 ns 437 ns 5 items_per_second=2.29077M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_median 435 ns 436 ns 5 items_per_second=2.29368M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_stddev 2.12 ns 2.39 ns 5 items_per_second=12.5108k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_cv 0.49 % 0.55 % 5 items_per_second=0.55% +// SpanDataFixture/RecordSpanWithLinks/link_count:10_mean 1368 ns 1371 ns 5 items_per_second=7.2967M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_median 1375 ns 1378 ns 5 items_per_second=7.25823M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_stddev 22.3 ns 22.4 ns 5 items_per_second=121.346k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_cv 1.63 % 1.63 % 5 items_per_second=1.66% +// SpanDataFixture/RecordSpanWithLinks/link_count:128_mean 15304 ns 15308 ns 5 items_per_second=8.38477M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_median 14933 ns 14937 ns 5 items_per_second=8.56932M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_stddev 930 ns 930 ns 5 items_per_second=474.386k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_cv 6.08 % 6.08 % 5 items_per_second=5.66% +// +// clang-format on + +#include + +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/test_common/sdk/trace/test_utils.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" + +namespace trace_sdk = opentelemetry::sdk::trace; +namespace test_utils = opentelemetry::test_common; + +namespace +{ + +class SpanDataFixture : public benchmark::Fixture +{ +public: + using benchmark::Fixture::SetUp; + + void SetUp(benchmark::State &) override + { + auto exporter = std::make_unique(); + auto processor = std::make_unique(std::move(exporter)); + provider_ = std::make_shared(std::move(processor)); + tracer_ = provider_->GetTracer(test_utils::TestScope().GetName(), + test_utils::TestScope().GetVersion(), + test_utils::TestScope().GetSchemaURL()); + test_utils::InitializeSpanTestData(); + } + +protected: + std::shared_ptr provider_; + opentelemetry::nostd::shared_ptr tracer_; +}; + +} // namespace + +// Baseline: start a span and end it immediately with no attributes, links, or events. +BENCHMARK_DEFINE_F(SpanDataFixture, RecordMinimalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartMinimalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordMinimalSpan)->Unit(benchmark::kNanosecond); + +// RecordNominalSpan span: representative mix of attribute types passed at creation. +BENCHMARK_DEFINE_F(SpanDataFixture, RecordNominalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartNominalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordNominalSpan)->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithAttributes)(benchmark::State &state) +{ + const int64_t attribute_count = state.range(0); + const std::vector attributes = + test_utils::MakeAttributes(attribute_count); + for (auto _ : state) + { + auto span = test_utils::StartSpanWithAttributes(*tracer_, attributes); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * attribute_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithAttributes) + ->ArgName("attribute_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanAttributeLimit) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithEvents)(benchmark::State &state) +{ + const int64_t event_count = state.range(0); + + for (auto _ : state) + { + auto span = test_utils::StartSpanWithEvents(*tracer_, static_cast(event_count)); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * event_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithEvents) + ->ArgName("event_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanEventLimit) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithLinks)(benchmark::State &state) +{ + const int64_t link_count = state.range(0); + const std::vector link_entries = + test_utils::MakeLinkEntries(static_cast(link_count)); + + for (auto _ : state) + { + auto span = test_utils::StartSpanWithLinks(*tracer_, link_entries); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * link_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithLinks) + ->ArgName("link_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanLinkLimit) + ->Unit(benchmark::kNanosecond); + +int main(int argc, char **argv) +{ + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h b/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h new file mode 100644 index 0000000000..78f0305fbb --- /dev/null +++ b/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h @@ -0,0 +1,228 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_flags.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace test_common +{ + +// A span processor that queues completed recordables into an internal buffer +// without exporting or destroying them, mirroring the batch processor's +// recording path. ForceFlush clears the buffer so benchmarks can drop recorded +// spans between iterations without measuring deallocation. +class BufferingSpanProcessor final : public sdk::trace::SpanProcessor +{ +public: + using Buffer = std::vector>; + + explicit BufferingSpanProcessor(std::unique_ptr exporter) + : exporter_(std::move(exporter)) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return exporter_->MakeRecordable(); + } + + void OnStart(sdk::trace::Recordable &, const trace::SpanContext &) noexcept override {} + + void OnEnd(std::unique_ptr &&span) noexcept override + { + buffer_.push_back(std::move(span)); + } + + bool ForceFlush(std::chrono::microseconds) noexcept override + { + buffer_.clear(); + return true; + } + + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::unique_ptr exporter_; + Buffer buffer_; +}; + +inline const sdk::resource::Resource &TestResource() +{ + static const auto kResource = + sdk::resource::Resource::Create({{"service.name", nostd::string_view{"benchmark_service"}}, + {"service.version", nostd::string_view{"1.0.0"}}}); + return kResource; +} + +inline const sdk::instrumentationscope::InstrumentationScope &TestScope() +{ + static const auto kScope = sdk::instrumentationscope::InstrumentationScope::Create( + "benchmark_scope", "1.0.0", "https://opentelemetry.io/schemas/1.24.0", + {{"scope.source", "benchmark"}}); + return *kScope; +} + +constexpr std::size_t kSpanAttributeLimit = 128; +constexpr std::size_t kSpanEventLimit = 128; +constexpr std::size_t kSpanLinkLimit = 128; +constexpr std::size_t kSpanEventAttributesCount = 2; +constexpr std::size_t kSpanLinkAttributesCount = 2; + +// Pre-built unique attribute keys (up to the default limit) for attribute sweeps. +inline const std::vector &AttributeKeys() +{ + static const std::vector kAttributeKeys = []() { + std::vector result; + result.reserve(kSpanAttributeLimit); + for (std::size_t key_idx = 0; key_idx < kSpanAttributeLimit; ++key_idx) + { + result.push_back("attribute." + std::to_string(key_idx)); + } + return result; + }(); + return kAttributeKeys; +} + +// A representative mix of attribute types used by the nominal span benchmark. +using SpanAttribute = std::pair; +inline const std::vector &NominalAttributes() +{ + static const std::vector kNominalAttributes = { + {"benchmark.string.short", nostd::string_view{"abcdefgh"}}, + {"benchmark.string.medium", nostd::string_view{"abcdefghijklmnopqrstuvwxyz012345"}}, + {"benchmark.string.long", + nostd::string_view{"abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"}}, + {"benchmark.int", common::AttributeValue{static_cast(1)}}, + {"benchmark.double", common::AttributeValue{3.14159265358979}}, + {"benchmark.bool", common::AttributeValue{true}}, + }; + return kNominalAttributes; +} + +inline std::vector MakeAttributes(std::size_t count) +{ + const auto &keys = AttributeKeys(); + std::vector attributes; + attributes.reserve(count); + for (std::size_t i = 0; i < count; ++i) + { + attributes.emplace_back(keys.at(i), nostd::string_view{"value-string-attribute"}); + } + return attributes; +} + +inline const std::vector &SpanEventAttributes() +{ + static const std::vector kSpanEventAttributes = + MakeAttributes(kSpanEventAttributesCount); + return kSpanEventAttributes; +} + +inline const trace::SpanContext &TestSpanContext() +{ + static constexpr uint8_t kTraceId[trace::TraceId::kSize] = {2, 3, 4, 5, 6, 7, 8, 9, + 2, 3, 4, 5, 6, 7, 8, 9}; + static constexpr uint8_t kSpanId[trace::SpanId::kSize] = {9, 8, 7, 6, 5, 4, 3, 2}; + static const trace::SpanContext kTestSpanContext{trace::TraceId{kTraceId}, trace::SpanId{kSpanId}, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, + true}; + return kTestSpanContext; +} + +inline const std::vector &SpanLinkAttributes() +{ + static const std::vector kSpanLinkAttributes = + MakeAttributes(kSpanLinkAttributesCount); + return kSpanLinkAttributes; +} + +using LinkEntry = std::pair>; + +inline std::vector MakeLinkEntries(std::size_t count) +{ + std::vector entries; + entries.reserve(count); + for (std::size_t i = 0; i < count; ++i) + { + entries.emplace_back(TestSpanContext(), SpanLinkAttributes()); + } + return entries; +} + +// Starts a span with no attributes, events, or links +inline nostd::shared_ptr StartMinimalSpan(trace::Tracer &tracer) +{ + return tracer.StartSpan("benchmark_span"); +} + +// Starts a span with a representative mix of attributes +inline nostd::shared_ptr StartNominalSpan(trace::Tracer &tracer) +{ + return tracer.StartSpan("benchmark_span", NominalAttributes()); +} + +// Starts a span with pre-built attributes +inline nostd::shared_ptr StartSpanWithAttributes( + trace::Tracer &tracer, + const std::vector &attributes) +{ + return tracer.StartSpan("benchmark_span", attributes); +} + +// Starts a span with pre-built links +inline nostd::shared_ptr StartSpanWithLinks(trace::Tracer &tracer, + const std::vector &link_entries) +{ + return tracer.StartSpan("benchmark_span", nostd::span{}, link_entries); +} + +// Starts a span and adds `num_events` +inline nostd::shared_ptr StartSpanWithEvents(trace::Tracer &tracer, + std::size_t num_events) +{ + auto span = tracer.StartSpan("benchmark_span"); + for (std::size_t i = 0; i < num_events; ++i) + { + span->AddEvent("benchmark_event", SpanEventAttributes()); + } + return span; +} + +// Initialize all the static test data in singletons above. To be called in test fixture SetUp() +inline void InitializeSpanTestData() +{ + TestResource(); + TestScope(); + AttributeKeys(); + NominalAttributes(); + SpanEventAttributes(); + SpanLinkAttributes(); + TestSpanContext(); +} + +} // namespace test_common +OPENTELEMETRY_END_NAMESPACE