diff options
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/sdk/test/trace')
12 files changed, 2105 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/BUILD new file mode 100644 index 000000000..70e517684 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/BUILD @@ -0,0 +1,155 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "tracer_provider_test", + srcs = [ + "tracer_provider_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "span_data_test", + srcs = [ + "span_data_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "simple_processor_test", + srcs = [ + "simple_processor_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "batch_span_processor_test", + srcs = [ + "batch_span_processor_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tracer_test", + srcs = [ + "tracer_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "always_on_sampler_test", + srcs = [ + "always_on_sampler_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "always_off_sampler_test", + srcs = [ + "always_off_sampler_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "parent_sampler_test", + srcs = [ + "parent_sampler_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "trace_id_ratio_sampler_test", + srcs = [ + "trace_id_ratio_sampler_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/common:random", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "sampler_benchmark", + srcs = ["sampler_benchmark.cc"], + tags = [ + "test", + "trace", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/resource", + "//sdk/src/trace", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/CMakeLists.txt new file mode 100644 index 000000000..b02ff705f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/CMakeLists.txt @@ -0,0 +1,30 @@ +foreach( + testname + tracer_provider_test + span_data_test + simple_processor_test + tracer_test + always_off_sampler_test + always_on_sampler_test + parent_sampler_test + trace_id_ratio_sampler_test + batch_span_processor_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_common + opentelemetry_trace + opentelemetry_resources + opentelemetry_exporter_in_memory) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX trace. + TEST_LIST ${testname}) +endforeach() + +add_executable(sampler_benchmark sampler_benchmark.cc) +target_link_libraries( + sampler_benchmark benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_trace opentelemetry_resources opentelemetry_exporter_in_memory) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_off_sampler_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_off_sampler_test.cc new file mode 100644 index 000000000..1c32bd5a8 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_off_sampler_test.cc @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <gtest/gtest.h> +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" + +using opentelemetry::sdk::trace::AlwaysOffSampler; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::trace::SpanContext; +namespace trace_api = opentelemetry::trace; + +TEST(AlwaysOffSampler, ShouldSample) +{ + AlwaysOffSampler sampler; + + trace_api::TraceId trace_id; + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<SpanContext, std::map<std::string, std::string>>>; + L l1 = {{SpanContext(false, false), {}}, {SpanContext(false, false), {}}}; + + opentelemetry::common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> links{l1}; + + auto sampling_result = + sampler.ShouldSample(SpanContext::GetInvalid(), trace_id, "", span_kind, view, links); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + ASSERT_EQ("", sampling_result.trace_state->ToHeader()); +} + +TEST(AlwaysOffSampler, GetDescription) +{ + AlwaysOffSampler sampler; + + ASSERT_EQ("AlwaysOffSampler", sampler.GetDescription()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_on_sampler_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_on_sampler_test.cc new file mode 100644 index 000000000..c483f9b8b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/always_on_sampler_test.cc @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" + +#include <gtest/gtest.h> +#include <map> + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::nostd; +using opentelemetry::trace::SpanContext; +namespace trace_api = opentelemetry::trace; + +TEST(AlwaysOnSampler, ShouldSample) +{ + AlwaysOnSampler sampler; + + // A buffer of trace_id with no specific meaning + constexpr uint8_t buf[] = {0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}; + + trace_api::TraceId trace_id_invalid; + trace_api::TraceId trace_id_valid(buf); + std::map<std::string, int> key_value_container = {{"key", 0}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + opentelemetry::trace::SpanContextKeyValueIterableView<L> links{l1}; + + // Test with invalid (empty) trace id and empty parent context + auto sampling_result = sampler.ShouldSample( + SpanContext::GetInvalid(), trace_id_invalid, "invalid trace id test", + trace_api::SpanKind::kServer, + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>>(key_value_container), + links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + ASSERT_EQ("", sampling_result.trace_state->ToHeader()); + + // Test with a valid trace id and empty parent context + sampling_result = sampler.ShouldSample( + SpanContext::GetInvalid(), trace_id_valid, "valid trace id test", + trace_api::SpanKind::kServer, + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>>(key_value_container), + links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + ASSERT_EQ("", sampling_result.trace_state->ToHeader()); +} + +TEST(AlwaysOnSampler, GetDescription) +{ + AlwaysOnSampler sampler; + + ASSERT_EQ("AlwaysOnSampler", sampler.GetDescription()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/batch_span_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/batch_span_processor_test.cc new file mode 100644 index 000000000..0e6f9c35a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/batch_span_processor_test.cc @@ -0,0 +1,291 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer.h" + +#include <gtest/gtest.h> +#include <chrono> +#include <thread> + +OPENTELEMETRY_BEGIN_NAMESPACE + +/** + * Returns a mock span exporter meant exclusively for testing only + */ +class MockSpanExporter final : public sdk::trace::SpanExporter +{ +public: + MockSpanExporter( + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received, + std::shared_ptr<std::atomic<bool>> is_shutdown, + std::shared_ptr<std::atomic<bool>> is_export_completed = + std::shared_ptr<std::atomic<bool>>(new std::atomic<bool>(false)), + const std::chrono::milliseconds export_delay = std::chrono::milliseconds(0)) noexcept + : spans_received_(spans_received), + is_shutdown_(is_shutdown), + is_export_completed_(is_export_completed), + export_delay_(export_delay) + {} + + std::unique_ptr<sdk::trace::Recordable> MakeRecordable() noexcept override + { + return std::unique_ptr<sdk::trace::Recordable>(new sdk::trace::SpanData); + } + + sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &recordables) noexcept override + { + *is_export_completed_ = false; + + std::this_thread::sleep_for(export_delay_); + + for (auto &recordable : recordables) + { + auto span = std::unique_ptr<sdk::trace::SpanData>( + static_cast<sdk::trace::SpanData *>(recordable.release())); + + if (span != nullptr) + { + spans_received_->push_back(std::move(span)); + } + } + + *is_export_completed_ = true; + return sdk::common::ExportResult::kSuccess; + } + + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + { + *is_shutdown_ = true; + return true; + } + + bool IsExportCompleted() { return is_export_completed_->load(); } + +private: + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received_; + std::shared_ptr<std::atomic<bool>> is_shutdown_; + std::shared_ptr<std::atomic<bool>> is_export_completed_; + // Meant exclusively to test force flush timeout + const std::chrono::milliseconds export_delay_; +}; + +/** + * Fixture Class + */ +class BatchSpanProcessorTestPeer : public testing::Test +{ +public: + std::unique_ptr<std::vector<std::unique_ptr<sdk::trace::Recordable>>> GetTestSpans( + std::shared_ptr<sdk::trace::SpanProcessor> processor, + const int num_spans) + { + std::unique_ptr<std::vector<std::unique_ptr<sdk::trace::Recordable>>> test_spans( + new std::vector<std::unique_ptr<sdk::trace::Recordable>>); + + for (int i = 0; i < num_spans; ++i) + { + test_spans->push_back(processor->MakeRecordable()); + static_cast<sdk::trace::SpanData *>(test_spans->at(i).get()) + ->SetName("Span " + std::to_string(i)); + } + + return test_spans; + } +}; + +/* ################################## TESTS ############################################ */ + +TEST_F(BatchSpanProcessorTestPeer, TestShutdown) +{ + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received( + new std::vector<std::unique_ptr<sdk::trace::SpanData>>); + + auto batch_processor = + std::shared_ptr<sdk::trace::BatchSpanProcessor>(new sdk::trace::BatchSpanProcessor( + std::unique_ptr<MockSpanExporter>(new MockSpanExporter(spans_received, is_shutdown)), + sdk::trace::BatchSpanProcessorOptions())); + const int num_spans = 3; + + auto test_spans = GetTestSpans(batch_processor, num_spans); + + for (int i = 0; i < num_spans; ++i) + { + batch_processor->OnEnd(std::move(test_spans->at(i))); + } + + EXPECT_TRUE(batch_processor->Shutdown()); + // It's safe to shutdown again + EXPECT_TRUE(batch_processor->Shutdown()); + + EXPECT_EQ(num_spans, spans_received->size()); + for (int i = 0; i < num_spans; ++i) + { + EXPECT_EQ("Span " + std::to_string(i), spans_received->at(i)->GetName()); + } + + EXPECT_TRUE(is_shutdown->load()); +} + +TEST_F(BatchSpanProcessorTestPeer, TestForceFlush) +{ + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received( + new std::vector<std::unique_ptr<sdk::trace::SpanData>>); + + auto batch_processor = + std::shared_ptr<sdk::trace::BatchSpanProcessor>(new sdk::trace::BatchSpanProcessor( + std::unique_ptr<MockSpanExporter>(new MockSpanExporter(spans_received, is_shutdown)), + sdk::trace::BatchSpanProcessorOptions())); + const int num_spans = 2048; + + auto test_spans = GetTestSpans(batch_processor, num_spans); + + for (int i = 0; i < num_spans; ++i) + { + batch_processor->OnEnd(std::move(test_spans->at(i))); + } + + // Give some time to export + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_spans, spans_received->size()); + for (int i = 0; i < num_spans; ++i) + { + EXPECT_EQ("Span " + std::to_string(i), spans_received->at(i)->GetName()); + } + + // Create some more spans to make sure that the processor still works + auto more_test_spans = GetTestSpans(batch_processor, num_spans); + for (int i = 0; i < num_spans; ++i) + { + batch_processor->OnEnd(std::move(more_test_spans->at(i))); + } + + // Give some time to export the spans + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_spans * 2, spans_received->size()); + for (int i = 0; i < num_spans; ++i) + { + EXPECT_EQ("Span " + std::to_string(i % num_spans), + spans_received->at(num_spans + i)->GetName()); + } +} + +TEST_F(BatchSpanProcessorTestPeer, TestManySpansLoss) +{ + /* Test that when exporting more than max_queue_size spans, some are most likely lost*/ + + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received( + new std::vector<std::unique_ptr<sdk::trace::SpanData>>); + + const int max_queue_size = 4096; + + auto batch_processor = + std::shared_ptr<sdk::trace::BatchSpanProcessor>(new sdk::trace::BatchSpanProcessor( + std::unique_ptr<MockSpanExporter>(new MockSpanExporter(spans_received, is_shutdown)), + sdk::trace::BatchSpanProcessorOptions())); + + auto test_spans = GetTestSpans(batch_processor, max_queue_size); + + for (int i = 0; i < max_queue_size; ++i) + { + batch_processor->OnEnd(std::move(test_spans->at(i))); + } + + // Give some time to export the spans + std::this_thread::sleep_for(std::chrono::milliseconds(700)); + + EXPECT_TRUE(batch_processor->ForceFlush()); + + // Span should be exported by now + EXPECT_GE(max_queue_size, spans_received->size()); +} + +TEST_F(BatchSpanProcessorTestPeer, TestManySpansLossLess) +{ + /* Test that no spans are lost when sending max_queue_size spans */ + + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received( + new std::vector<std::unique_ptr<sdk::trace::SpanData>>); + + const int num_spans = 2048; + + auto batch_processor = + std::shared_ptr<sdk::trace::BatchSpanProcessor>(new sdk::trace::BatchSpanProcessor( + std::unique_ptr<MockSpanExporter>(new MockSpanExporter(spans_received, is_shutdown)), + sdk::trace::BatchSpanProcessorOptions())); + + auto test_spans = GetTestSpans(batch_processor, num_spans); + + for (int i = 0; i < num_spans; ++i) + { + batch_processor->OnEnd(std::move(test_spans->at(i))); + } + + // Give some time to export the spans + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_spans, spans_received->size()); + for (int i = 0; i < num_spans; ++i) + { + EXPECT_EQ("Span " + std::to_string(i), spans_received->at(i)->GetName()); + } +} + +TEST_F(BatchSpanProcessorTestPeer, TestScheduleDelayMillis) +{ + /* Test that max_export_batch_size spans are exported every schedule_delay_millis + seconds */ + + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::atomic<bool>> is_export_completed(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<sdk::trace::SpanData>>> spans_received( + new std::vector<std::unique_ptr<sdk::trace::SpanData>>); + const std::chrono::milliseconds export_delay(0); + const size_t max_export_batch_size = 512; + sdk::trace::BatchSpanProcessorOptions options{}; + options.schedule_delay_millis = std::chrono::milliseconds(2000); + + auto batch_processor = + std::shared_ptr<sdk::trace::BatchSpanProcessor>(new sdk::trace::BatchSpanProcessor( + std::unique_ptr<MockSpanExporter>( + new MockSpanExporter(spans_received, is_shutdown, is_export_completed, export_delay)), + options)); + + auto test_spans = GetTestSpans(batch_processor, max_export_batch_size); + + for (size_t i = 0; i < max_export_batch_size; ++i) + { + batch_processor->OnEnd(std::move(test_spans->at(i))); + } + + // Sleep for schedule_delay_millis milliseconds + std::this_thread::sleep_for(options.schedule_delay_millis); + + // small delay to give time to export + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // Spans should be exported by now + EXPECT_TRUE(is_export_completed->load()); + EXPECT_EQ(max_export_batch_size, spans_received->size()); + for (size_t i = 0; i < max_export_batch_size; ++i) + { + EXPECT_EQ("Span " + std::to_string(i), spans_received->at(i)->GetName()); + } +} + +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/parent_sampler_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/parent_sampler_test.cc new file mode 100644 index 000000000..124287598 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/parent_sampler_test.cc @@ -0,0 +1,73 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <gtest/gtest.h> +#include <memory> +#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/trace/span_context_kv_iterable_view.h" + +using opentelemetry::sdk::trace::AlwaysOffSampler; +using opentelemetry::sdk::trace::AlwaysOnSampler; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::sdk::trace::ParentBasedSampler; +namespace trace_api = opentelemetry::trace; + +TEST(ParentBasedSampler, ShouldSample) +{ + ParentBasedSampler sampler_off(std::make_shared<AlwaysOffSampler>()); + ParentBasedSampler sampler_on(std::make_shared<AlwaysOnSampler>()); + + // Set up parameters + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + opentelemetry::common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> 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); + + // Case 1: Parent doesn't exist. Return result of delegateSampler() + auto sampling_result = sampler_off.ShouldSample(trace_api::SpanContext::GetInvalid(), trace_id, + "", span_kind, view, links); + auto sampling_result2 = sampler_on.ShouldSample(trace_api::SpanContext::GetInvalid(), trace_id, + "", span_kind, view, links); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result2.decision); + ASSERT_EQ("", sampling_result.trace_state->ToHeader()); + ASSERT_EQ("", sampling_result2.trace_state->ToHeader()); + + // Case 2: Parent exists and SampledFlag is true + auto sampling_result3 = + sampler_off.ShouldSample(parent_context_sampled, 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); + ASSERT_EQ(Decision::DROP, sampling_result4.decision); + ASSERT_EQ("congo=t61rcWkgMzE", sampling_result4.trace_state->ToHeader()); +} + +TEST(ParentBasedSampler, GetDescription) +{ + ParentBasedSampler sampler(std::make_shared<AlwaysOffSampler>()); + ASSERT_EQ("ParentBased{AlwaysOffSampler}", sampler.GetDescription()); + ParentBasedSampler sampler2(std::make_shared<AlwaysOnSampler>()); + ASSERT_EQ("ParentBased{AlwaysOnSampler}", sampler2.GetDescription()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/sampler_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/sampler_benchmark.cc new file mode 100644 index 000000000..b09cb5b0c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/sampler_benchmark.cc @@ -0,0 +1,156 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/sdk/resource/resource.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/sdk/trace/samplers/parent.h" +#include "opentelemetry/sdk/trace/samplers/trace_id_ratio.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer.h" + +#include <cstdint> + +#include <benchmark/benchmark.h> + +using namespace opentelemetry::sdk::trace; +using opentelemetry::exporter::memory::InMemorySpanExporter; +using opentelemetry::trace::SpanContext; + +namespace +{ +// Sampler constructor used as a baseline to compare with other samplers +void BM_AlwaysOffSamplerConstruction(benchmark::State &state) +{ + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(AlwaysOffSampler()); + } +} +BENCHMARK(BM_AlwaysOffSamplerConstruction); + +// Sampler constructor used as a baseline to compare with other samplers +void BM_AlwaysOnSamplerConstruction(benchmark::State &state) +{ + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(AlwaysOnSampler()); + } +} +BENCHMARK(BM_AlwaysOnSamplerConstruction); + +void BM_ParentBasedSamplerConstruction(benchmark::State &state) +{ + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(ParentBasedSampler(std::make_shared<AlwaysOnSampler>())); + } +} +BENCHMARK(BM_ParentBasedSamplerConstruction); + +void BM_TraceIdRatioBasedSamplerConstruction(benchmark::State &state) +{ + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(TraceIdRatioBasedSampler(0.01)); + } +} +BENCHMARK(BM_TraceIdRatioBasedSamplerConstruction); + +// Sampler Helper Function +void BenchmarkShouldSampler(Sampler &sampler, benchmark::State &state) +{ + opentelemetry::trace::TraceId trace_id; + opentelemetry::trace::SpanKind span_kind = opentelemetry::trace::SpanKind::kInternal; + + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + opentelemetry::common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> links{l1}; + + while (state.KeepRunning()) + { + auto invalid_ctx = SpanContext::GetInvalid(); + benchmark::DoNotOptimize( + sampler.ShouldSample(invalid_ctx, trace_id, "", span_kind, view, links)); + } +} + +// Sampler used as a baseline to compare with other samplers +void BM_AlwaysOffSamplerShouldSample(benchmark::State &state) +{ + AlwaysOffSampler sampler; + + BenchmarkShouldSampler(sampler, state); +} +BENCHMARK(BM_AlwaysOffSamplerShouldSample); + +// Sampler used as a baseline to compare with other samplers +void BM_AlwaysOnSamplerShouldSample(benchmark::State &state) +{ + AlwaysOnSampler sampler; + + BenchmarkShouldSampler(sampler, state); +} +BENCHMARK(BM_AlwaysOnSamplerShouldSample); + +void BM_ParentBasedSamplerShouldSample(benchmark::State &state) +{ + ParentBasedSampler sampler(std::make_shared<AlwaysOnSampler>()); + + BenchmarkShouldSampler(sampler, state); +} +BENCHMARK(BM_ParentBasedSamplerShouldSample); + +void BM_TraceIdRatioBasedSamplerShouldSample(benchmark::State &state) +{ + TraceIdRatioBasedSampler sampler(0.01); + + BenchmarkShouldSampler(sampler, state); +} +BENCHMARK(BM_TraceIdRatioBasedSamplerShouldSample); + +// Sampler Helper Function +void BenchmarkSpanCreation(std::shared_ptr<Sampler> sampler, benchmark::State &state) +{ + std::unique_ptr<SpanExporter> exporter(new InMemorySpanExporter()); + std::unique_ptr<SpanProcessor> processor(new SimpleSpanProcessor(std::move(exporter))); + std::vector<std::unique_ptr<SpanProcessor>> processors; + processors.push_back(std::move(processor)); + auto context = std::make_shared<TracerContext>(std::move(processors)); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto tracer = std::shared_ptr<opentelemetry::trace::Tracer>(new Tracer(context)); + + while (state.KeepRunning()) + { + auto span = tracer->StartSpan("span"); + + span->SetAttribute("attr1", 3.1); + + span->End(); + } +} + +// Test to measure performance for span creation +void BM_SpanCreation(benchmark::State &state) +{ + BenchmarkSpanCreation(std::move(std::make_shared<AlwaysOnSampler>()), state); +} +BENCHMARK(BM_SpanCreation); + +// Test to measure performance overhead for no-op span creation +void BM_NoopSpanCreation(benchmark::State &state) +{ + BenchmarkSpanCreation(std::move(std::make_shared<AlwaysOffSampler>()), state); +} +BENCHMARK(BM_NoopSpanCreation); + +} // namespace +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/simple_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/simple_processor_test.cc new file mode 100644 index 000000000..9398b922a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/simple_processor_test.cc @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include <gtest/gtest.h> + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::sdk::common; +using opentelemetry::exporter::memory::InMemorySpanData; +using opentelemetry::exporter::memory::InMemorySpanExporter; +using opentelemetry::trace::SpanContext; + +TEST(SimpleProcessor, ToInMemorySpanExporter) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + SimpleSpanProcessor processor(std::move(exporter)); + + auto recordable = processor.MakeRecordable(); + + processor.OnStart(*recordable, SpanContext::GetInvalid()); + + ASSERT_EQ(0, span_data->GetSpans().size()); + + processor.OnEnd(std::move(recordable)); + + ASSERT_EQ(1, span_data->GetSpans().size()); + + EXPECT_TRUE(processor.Shutdown()); +} + +// An exporter that does nothing but record (and give back ) the # of times Shutdown was called. +class RecordShutdownExporter final : public SpanExporter +{ +public: + RecordShutdownExporter(int *shutdown_counter) : shutdown_counter_(shutdown_counter) {} + + std::unique_ptr<Recordable> MakeRecordable() noexcept override + { + return std::unique_ptr<Recordable>(new SpanData()); + } + + ExportResult Export( + const opentelemetry::nostd::span<std::unique_ptr<Recordable>> &recordables) noexcept override + { + return ExportResult::kSuccess; + } + + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + { + *shutdown_counter_ += 1; + return true; + } + +private: + int *shutdown_counter_; +}; + +TEST(SimpleSpanProcessor, ShutdownCalledOnce) +{ + int shutdowns = 0; + std::unique_ptr<RecordShutdownExporter> exporter(new RecordShutdownExporter(&shutdowns)); + SimpleSpanProcessor processor(std::move(exporter)); + EXPECT_EQ(0, shutdowns); + processor.Shutdown(); + EXPECT_EQ(1, shutdowns); + processor.Shutdown(); + EXPECT_EQ(1, shutdowns); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/span_data_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/span_data_test.cc new file mode 100644 index 000000000..7a6c66c91 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/span_data_test.cc @@ -0,0 +1,135 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" + +#include <gtest/gtest.h> + +using opentelemetry::sdk::trace::SpanData; +namespace trace_api = opentelemetry::trace; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; + +TEST(SpanData, DefaultValues) +{ + trace_api::SpanContext empty_span_context{false, false}; + trace_api::SpanId zero_span_id; + SpanData data; + + ASSERT_EQ(data.GetTraceId(), empty_span_context.trace_id()); + ASSERT_EQ(data.GetSpanId(), empty_span_context.span_id()); + ASSERT_EQ(data.GetSpanContext(), empty_span_context); + ASSERT_EQ(data.GetParentSpanId(), zero_span_id); + ASSERT_EQ(data.GetName(), ""); + ASSERT_EQ(data.GetStatus(), trace_api::StatusCode::kUnset); + ASSERT_EQ(data.GetDescription(), ""); + ASSERT_EQ(data.GetStartTime().time_since_epoch(), std::chrono::nanoseconds(0)); + ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(0)); + ASSERT_EQ(data.GetAttributes().size(), 0); + ASSERT_EQ(data.GetEvents().size(), 0); +} + +TEST(SpanData, Set) +{ + constexpr uint8_t trace_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; + constexpr uint8_t span_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8}; + constexpr uint8_t parent_span_id_buf[] = {8, 7, 6, 5, 4, 3, 2, 1}; + trace_api::TraceId trace_id{trace_id_buf}; + trace_api::SpanId span_id{span_id_buf}; + trace_api::SpanId parent_span_id{parent_span_id_buf}; + const auto trace_state = trace_api::TraceState::GetDefault()->Set("key1", "value"); + const trace_api::SpanContext span_context{ + trace_id, span_id, trace_api::TraceFlags{trace_api::TraceFlags::kIsSampled}, true, + trace_state}; + common::SystemTimestamp now(std::chrono::system_clock::now()); + + SpanData data; + data.SetIdentity(span_context, parent_span_id); + data.SetName("span name"); + data.SetSpanKind(trace_api::SpanKind::kServer); + data.SetStatus(trace_api::StatusCode::kOk, "description"); + data.SetStartTime(now); + data.SetDuration(std::chrono::nanoseconds(1000000)); + data.SetAttribute("attr1", (int64_t)314159); + data.AddEvent("event1", now); + + ASSERT_EQ(data.GetTraceId(), trace_id); + ASSERT_EQ(data.GetSpanId(), span_id); + ASSERT_EQ(data.GetSpanContext(), span_context); + std::string trace_state_key1_value; + ASSERT_EQ(data.GetSpanContext().trace_state()->Get("key1", trace_state_key1_value), true); + ASSERT_EQ(trace_state_key1_value, "value"); + ASSERT_EQ(data.GetParentSpanId(), parent_span_id); + ASSERT_EQ(data.GetName(), "span name"); + ASSERT_EQ(data.GetSpanKind(), trace_api::SpanKind::kServer); + ASSERT_EQ(data.GetStatus(), trace_api::StatusCode::kOk); + ASSERT_EQ(data.GetDescription(), "description"); + ASSERT_EQ(data.GetStartTime().time_since_epoch(), now.time_since_epoch()); + ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(1000000)); + ASSERT_EQ(nostd::get<int64_t>(data.GetAttributes().at("attr1")), 314159); + ASSERT_EQ(data.GetEvents().at(0).GetName(), "event1"); + ASSERT_EQ(data.GetEvents().at(0).GetTimestamp(), now); +} + +TEST(SpanData, EventAttributes) +{ + SpanData data; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int64_t values[kNumAttributes] = {3, 5, 20}; + std::map<std::string, int64_t> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + data.AddEvent("Test Event", std::chrono::system_clock::now(), + common::KeyValueIterableView<std::map<std::string, int64_t>>(attributes)); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(nostd::get<int64_t>(data.GetEvents().at(0).GetAttributes().at(keys[i])), values[i]); + } +} + +TEST(SpanData, Resources) +{ + SpanData data; + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto input_attr = resource.GetAttributes(); + data.SetResource(resource); + auto output_attr = data.GetResource().GetAttributes(); + EXPECT_EQ(input_attr, output_attr); +} + +TEST(SpanData, Links) +{ + SpanData data; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int64_t values[kNumAttributes] = {4, 12, 33}; + std::map<std::string, int64_t> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + // produce valid SpanContext with pseudo span and trace Id. + uint8_t span_id_buf[trace_api::SpanId::kSize] = { + 1, + }; + trace_api::SpanId span_id{span_id_buf}; + uint8_t trace_id_buf[trace_api::TraceId::kSize] = { + 2, + }; + trace_api::TraceId trace_id{trace_id_buf}; + const auto span_context = trace_api::SpanContext( + trace_id, span_id, trace_api::TraceFlags{trace_api::TraceFlags::kIsSampled}, true); + + data.AddLink(span_context, + common::KeyValueIterableView<std::map<std::string, int64_t>>(attributes)); + + EXPECT_EQ(data.GetLinks().at(0).GetSpanContext(), span_context); + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(nostd::get<int64_t>(data.GetLinks().at(0).GetAttributes().at(keys[i])), values[i]); + } +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/trace_id_ratio_sampler_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/trace_id_ratio_sampler_test.cc new file mode 100644 index 000000000..1b0088f13 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/trace_id_ratio_sampler_test.cc @@ -0,0 +1,258 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/samplers/trace_id_ratio.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "src/common/random.h" + +#include <gtest/gtest.h> +#include <cstdlib> +#include <ctime> + +using opentelemetry::sdk::common::Random; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::sdk::trace::TraceIdRatioBasedSampler; +namespace trace_api = opentelemetry::trace; +namespace common = opentelemetry::common; + +namespace +{ +/* + * Helper function for running TraceIdBased sampler tests. + * Given a span context, sampler, and number of iterations this function + * will return the number of RECORD_AND_SAMPLE decision based on randomly + * generated traces. + * + * @param context a required valid span context + * @param sampler a required valid sampler + * @param iterations a required number specifying the number of times to + * generate a random trace_id and check if it should sample using the provided + * provider and context + */ +int RunShouldSampleCountDecision(trace_api::SpanContext &context, + TraceIdRatioBasedSampler &sampler, + int iterations) +{ + int actual_count = 0; + + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> links{l1}; + + for (int i = 0; i < iterations; ++i) + { + uint8_t buf[16] = {0}; + Random::GenerateRandomBuffer(buf); + + trace_api::TraceId trace_id(buf); + + auto result = sampler.ShouldSample(context, trace_id, "", span_kind, view, links); + if (result.decision == Decision::RECORD_AND_SAMPLE) + { + ++actual_count; + } + } + + return actual_count; +} +} // namespace + +TEST(TraceIdRatioBasedSampler, ShouldSampleWithoutContext) +{ + trace_api::TraceId invalid_trace_id; + + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> links{l1}; + + TraceIdRatioBasedSampler s1(0.01); + + auto sampling_result = s1.ShouldSample(trace_api::SpanContext::GetInvalid(), invalid_trace_id, "", + span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + constexpr uint8_t buf[] = {0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0}; + trace_api::TraceId valid_trace_id(buf); + + sampling_result = s1.ShouldSample(trace_api::SpanContext::GetInvalid(), valid_trace_id, "", + span_kind, view, links); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + TraceIdRatioBasedSampler s2(0.50000001); + + sampling_result = s2.ShouldSample(trace_api::SpanContext::GetInvalid(), valid_trace_id, "", + span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + TraceIdRatioBasedSampler s3(0.49999999); + + sampling_result = s3.ShouldSample(trace_api::SpanContext::GetInvalid(), valid_trace_id, "", + span_kind, view, links); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + TraceIdRatioBasedSampler s4(0.50000000); + + sampling_result = s4.ShouldSample(trace_api::SpanContext::GetInvalid(), valid_trace_id, "", + span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); +} + +TEST(TraceIdRatioBasedSampler, ShouldSampleWithContext) +{ + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + trace_api::SpanContext c1(trace_id, span_id, trace_api::TraceFlags{0}, false); + trace_api::SpanContext c2(trace_id, span_id, trace_api::TraceFlags{1}, false); + trace_api::SpanContext c3(trace_id, span_id, trace_api::TraceFlags{0}, true); + trace_api::SpanContext c4(trace_id, span_id, trace_api::TraceFlags{1}, true); + + using M = std::map<std::string, int>; + M m1 = {{}}; + + using L = std::vector<std::pair<trace_api::SpanContext, std::map<std::string, std::string>>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + common::KeyValueIterableView<M> view{m1}; + trace_api::SpanContextKeyValueIterableView<L> links{l1}; + + TraceIdRatioBasedSampler s1(0.01); + + auto sampling_result = s1.ShouldSample(c1, trace_id, "", span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + sampling_result = s1.ShouldSample(c2, trace_id, "", span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + sampling_result = s1.ShouldSample(c3, trace_id, "", span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + sampling_result = s1.ShouldSample(c4, trace_id, "", span_kind, view, links); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); +} + +TEST(TraceIdRatioBasedSampler, TraceIdRatioBasedSamplerHalf) +{ + double ratio = 0.5; + int iterations = 100000; + int expected_count = static_cast<int>(iterations * ratio); + int variance = static_cast<int>(iterations * 0.01); + + trace_api::SpanContext c(true, true); + TraceIdRatioBasedSampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_TRUE(actual_count < (expected_count + variance)); + ASSERT_TRUE(actual_count > (expected_count - variance)); +} + +TEST(TraceIdRatioBasedSampler, TraceIdRatioBasedSamplerOnePercent) +{ + double ratio = 0.01; + int iterations = 100000; + int expected_count = static_cast<int>(iterations * ratio); + int variance = static_cast<int>(iterations * 0.01); + + trace_api::SpanContext c(true, true); + TraceIdRatioBasedSampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_TRUE(actual_count < (expected_count + variance)); + ASSERT_TRUE(actual_count > (expected_count - variance)); +} + +TEST(TraceIdRatioBasedSampler, TraceIdRatioBasedSamplerAll) +{ + double ratio = 1.0; + int iterations = 100000; + int expected_count = static_cast<int>(iterations * ratio); + + trace_api::SpanContext c(true, true); + TraceIdRatioBasedSampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_EQ(actual_count, expected_count); +} + +TEST(TraceIdRatioBasedSampler, TraceIdRatioBasedSamplerNone) +{ + double ratio = 0.0; + int iterations = 100000; + int expected_count = static_cast<int>(iterations * ratio); + + trace_api::SpanContext c(true, true); + TraceIdRatioBasedSampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_EQ(actual_count, expected_count); +} + +TEST(TraceIdRatioBasedSampler, GetDescription) +{ + TraceIdRatioBasedSampler s1(0.01); + ASSERT_EQ("TraceIdRatioBasedSampler{0.010000}", s1.GetDescription()); + + TraceIdRatioBasedSampler s2(0.00); + ASSERT_EQ("TraceIdRatioBasedSampler{0.000000}", s2.GetDescription()); + + TraceIdRatioBasedSampler s3(1.00); + ASSERT_EQ("TraceIdRatioBasedSampler{1.000000}", s3.GetDescription()); + + TraceIdRatioBasedSampler s4(0.102030405); + ASSERT_EQ("TraceIdRatioBasedSampler{0.102030}", s4.GetDescription()); + + TraceIdRatioBasedSampler s5(3.00); + ASSERT_EQ("TraceIdRatioBasedSampler{1.000000}", s5.GetDescription()); + + TraceIdRatioBasedSampler s6(-3.00); + ASSERT_EQ("TraceIdRatioBasedSampler{0.000000}", s6.GetDescription()); + + TraceIdRatioBasedSampler s7(1.00000000001); + ASSERT_EQ("TraceIdRatioBasedSampler{1.000000}", s7.GetDescription()); + + TraceIdRatioBasedSampler s8(-1.00000000001); + ASSERT_EQ("TraceIdRatioBasedSampler{0.000000}", s8.GetDescription()); + + TraceIdRatioBasedSampler s9(0.50); + ASSERT_EQ("TraceIdRatioBasedSampler{0.500000}", s9.GetDescription()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_provider_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_provider_test.cc new file mode 100644 index 000000000..498f66127 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_provider_test.cc @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer.h" + +#include <gtest/gtest.h> + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::sdk::resource; + +#include <iostream> + +TEST(TracerProvider, GetTracer) +{ + std::unique_ptr<SpanProcessor> processor(new SimpleSpanProcessor(nullptr)); + std::vector<std::unique_ptr<SpanProcessor>> processors; + processors.push_back(std::move(processor)); + TracerProvider tp1(std::make_shared<TracerContext>(std::move(processors), Resource::Create({}))); + auto t1 = tp1.GetTracer("test"); + auto t2 = tp1.GetTracer("test"); + auto t3 = tp1.GetTracer("different", "1.0.0"); + auto t4 = tp1.GetTracer(""); + auto t5 = tp1.GetTracer(opentelemetry::nostd::string_view{}); + auto t6 = tp1.GetTracer("different", "1.0.0", "https://opentelemetry.io/schemas/1.2.0"); + ASSERT_NE(nullptr, t1); + ASSERT_NE(nullptr, t2); + ASSERT_NE(nullptr, t3); + ASSERT_NE(nullptr, t6); + + // Should return the same instance each time. + ASSERT_EQ(t1, t2); + ASSERT_NE(t1, t3); + ASSERT_EQ(t4, t5); + ASSERT_NE(t3, t6); + + // Should be an sdk::trace::Tracer with the processor attached. +#ifdef OPENTELEMETRY_RTTI_ENABLED + auto sdkTracer1 = dynamic_cast<Tracer *>(t1.get()); +#else + auto sdkTracer1 = static_cast<Tracer *>(t1.get()); +#endif + ASSERT_NE(nullptr, sdkTracer1); + ASSERT_EQ("AlwaysOnSampler", sdkTracer1->GetSampler().GetDescription()); + std::unique_ptr<SpanProcessor> processor2(new SimpleSpanProcessor(nullptr)); + std::vector<std::unique_ptr<SpanProcessor>> processors2; + processors2.push_back(std::move(processor2)); + TracerProvider tp2( + std::make_shared<TracerContext>(std::move(processors2), Resource::Create({}), + std::unique_ptr<Sampler>(new AlwaysOffSampler()), + std::unique_ptr<IdGenerator>(new RandomIdGenerator))); +#ifdef OPENTELEMETRY_RTTI_ENABLED + auto sdkTracer2 = dynamic_cast<Tracer *>(tp2.GetTracer("test").get()); +#else + auto sdkTracer2 = static_cast<Tracer *>(tp2.GetTracer("test").get()); +#endif + ASSERT_EQ("AlwaysOffSampler", sdkTracer2->GetSampler().GetDescription()); + + auto instrumentation_library1 = sdkTracer1->GetInstrumentationLibrary(); + ASSERT_EQ(instrumentation_library1.GetName(), "test"); + ASSERT_EQ(instrumentation_library1.GetVersion(), ""); + + // Should be an sdk::trace::Tracer with the processor attached. +#ifdef OPENTELEMETRY_RTTI_ENABLED + auto sdkTracer3 = dynamic_cast<Tracer *>(t3.get()); +#else + auto sdkTracer3 = static_cast<Tracer *>(t3.get()); +#endif + auto instrumentation_library3 = sdkTracer3->GetInstrumentationLibrary(); + ASSERT_EQ(instrumentation_library3.GetName(), "different"); + ASSERT_EQ(instrumentation_library3.GetVersion(), "1.0.0"); +} + +TEST(TracerProvider, Shutdown) +{ + std::unique_ptr<SpanProcessor> processor(new SimpleSpanProcessor(nullptr)); + std::vector<std::unique_ptr<SpanProcessor>> processors; + processors.push_back(std::move(processor)); + + TracerProvider tp1(std::make_shared<TracerContext>(std::move(processors))); + + EXPECT_TRUE(tp1.Shutdown()); + + // It's safe to shutdown again + EXPECT_TRUE(tp1.Shutdown()); +} + +TEST(TracerProvider, ForceFlush) +{ + std::unique_ptr<SpanProcessor> processor1(new SimpleSpanProcessor(nullptr)); + + TracerProvider tp1(std::move(processor1)); + + EXPECT_TRUE(tp1.ForceFlush()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_test.cc new file mode 100644 index 000000000..15a7566b9 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/trace/tracer_test.cc @@ -0,0 +1,731 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/sdk/resource/resource.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/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/trace/context.h" + +#include <gtest/gtest.h> + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::sdk::resource; +using opentelemetry::common::SteadyTimestamp; +using opentelemetry::common::SystemTimestamp; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; +using opentelemetry::common::KeyValueIterableView; +using opentelemetry::exporter::memory::InMemorySpanData; +using opentelemetry::exporter::memory::InMemorySpanExporter; +using opentelemetry::trace::SpanContext; + +/** + * A mock sampler with ShouldSample returning: + * Decision::RECORD_AND_SAMPLE if trace_id is valid + * Decision::DROP otherwise. + */ +class MockSampler final : public Sampler +{ +public: + SamplingResult ShouldSample( + const SpanContext & /*parent_context*/, + trace_api::TraceId trace_id, + nostd::string_view /*name*/, + trace_api::SpanKind /*span_kind*/, + const opentelemetry::common::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::SpanContextKeyValueIterable & /*links*/) noexcept override + { + // Sample only if valid trace_id ( This is to test Sampler get's valid trace id) + if (trace_id.IsValid()) + { + // Return two pairs of attributes. These attributes should be added to the + // span attributes + return {Decision::RECORD_AND_SAMPLE, + nostd::unique_ptr<const std::map<std::string, opentelemetry::common::AttributeValue>>( + new const std::map<std::string, opentelemetry::common::AttributeValue>( + {{"sampling_attr1", 123}, {"sampling_attr2", "string"}}))}; + } + else + { + // we should never reach here + assert(false); + return {Decision::DROP}; + } + } + + nostd::string_view GetDescription() const noexcept override { return "MockSampler"; } +}; + +/** + * A Mock Custom Id Generator + */ +class MockIdGenerator : public IdGenerator +{ + opentelemetry::trace::SpanId GenerateSpanId() noexcept override + { + return opentelemetry::trace::SpanId(buf_span); + } + + opentelemetry::trace::TraceId GenerateTraceId() noexcept override + { + return opentelemetry::trace::TraceId(buf_trace); + } + uint8_t buf_span[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + 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<opentelemetry::trace::Tracer> initTracer(std::unique_ptr<SpanExporter> &&exporter) +{ + auto processor = std::unique_ptr<SpanProcessor>(new SimpleSpanProcessor(std::move(exporter))); + std::vector<std::unique_ptr<SpanProcessor>> processors; + processors.push_back(std::move(processor)); + auto context = std::make_shared<TracerContext>(std::move(processors)); + return std::shared_ptr<opentelemetry::trace::Tracer>(new Tracer(context)); +} + +std::shared_ptr<opentelemetry::trace::Tracer> initTracer( + std::unique_ptr<SpanExporter> &&exporter, + // For testing, just shove a pointer over, we'll take it over. + Sampler *sampler, + IdGenerator *id_generator = new RandomIdGenerator) +{ + auto processor = std::unique_ptr<SpanProcessor>(new SimpleSpanProcessor(std::move(exporter))); + std::vector<std::unique_ptr<SpanProcessor>> processors; + processors.push_back(std::move(processor)); + auto resource = Resource::Create({}); + auto context = std::make_shared<TracerContext>(std::move(processors), resource, + std::unique_ptr<Sampler>(sampler), + std::unique_ptr<IdGenerator>(id_generator)); + return std::shared_ptr<opentelemetry::trace::Tracer>(new Tracer(context)); +} + +} // namespace + +TEST(Tracer, ToInMemorySpanExporter) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + auto span_first = tracer->StartSpan("span 1"); + auto scope_first = tracer->WithActiveSpan(span_first); + auto span_second = tracer->StartSpan("span 2"); + + ASSERT_EQ(0, span_data->GetSpans().size()); + + span_second->End(); + + auto span2 = span_data->GetSpans(); + ASSERT_EQ(1, span2.size()); + ASSERT_EQ("span 2", span2.at(0)->GetName()); + EXPECT_TRUE(span2.at(0)->GetTraceId().IsValid()); + EXPECT_TRUE(span2.at(0)->GetSpanId().IsValid()); + EXPECT_TRUE(span2.at(0)->GetParentSpanId().IsValid()); + + span_first->End(); + + auto span1 = span_data->GetSpans(); + ASSERT_EQ(1, span1.size()); + ASSERT_EQ("span 1", span1.at(0)->GetName()); + EXPECT_TRUE(span1.at(0)->GetTraceId().IsValid()); + EXPECT_TRUE(span1.at(0)->GetSpanId().IsValid()); + EXPECT_FALSE(span1.at(0)->GetParentSpanId().IsValid()); + + // Verify trace and parent span id propagation + EXPECT_EQ(span1.at(0)->GetTraceId(), span2.at(0)->GetTraceId()); + EXPECT_EQ(span2.at(0)->GetParentSpanId(), span1.at(0)->GetSpanId()); +} + +TEST(Tracer, StartSpanSampleOn) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer_on = initTracer(std::move(exporter)); + + tracer_on->StartSpan("span 1")->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &cur_span_data = spans.at(0); + ASSERT_LT(std::chrono::nanoseconds(0), cur_span_data->GetStartTime().time_since_epoch()); + ASSERT_LT(std::chrono::nanoseconds(0), cur_span_data->GetDuration()); +} + +TEST(Tracer, StartSpanSampleOff) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer_off = initTracer(std::move(exporter), new AlwaysOffSampler()); + + // This span will not be recorded. + auto span = tracer_off->StartSpan("span 2"); + + // Always generate a valid span-context (span-id) + auto context = span->GetContext(); + EXPECT_TRUE(context.IsValid()); + EXPECT_FALSE(context.IsSampled()); + + span->End(); + // The span doesn't write any span data because the sampling decision is alway + // DROP. + ASSERT_EQ(0, span_data->GetSpans().size()); +} + +TEST(Tracer, StartSpanCustomIdGenerator) +{ + IdGenerator *id_generator = new MockIdGenerator(); + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter), new AlwaysOnSampler(), id_generator); + + tracer->StartSpan("span 1")->End(); + auto spans = span_data->GetSpans(); + auto &cur_span_data = spans.at(0); + + EXPECT_EQ(cur_span_data->GetTraceId(), id_generator->GenerateTraceId()); + EXPECT_EQ(cur_span_data->GetSpanId(), id_generator->GenerateSpanId()); +} + +TEST(Tracer, StartSpanWithOptionsTime) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + opentelemetry::trace::StartSpanOptions start; + start.start_system_time = SystemTimestamp(std::chrono::nanoseconds(300)); + start.start_steady_time = SteadyTimestamp(std::chrono::nanoseconds(10)); + + opentelemetry::trace::EndSpanOptions end; + end.end_steady_time = SteadyTimestamp(std::chrono::nanoseconds(40)); + + tracer->StartSpan("span 1", start)->End(end); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &cur_span_data = spans.at(0); + ASSERT_EQ(std::chrono::nanoseconds(300), cur_span_data->GetStartTime().time_since_epoch()); + ASSERT_EQ(std::chrono::nanoseconds(30), cur_span_data->GetDuration()); +} + +TEST(Tracer, StartSpanWithAttributes) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + // Start a span with all supported scalar attribute types. + tracer + ->StartSpan("span 1", {{"attr1", "string"}, + {"attr2", false}, + {"attr1", 314159}, + {"attr3", (unsigned int)314159}, + {"attr4", (int32_t)-20}, + {"attr5", (uint32_t)20}, + {"attr6", (int64_t)-20}, + {"attr7", (uint64_t)20}, + {"attr8", 3.1}, + {"attr9", "string"}}) + ->End(); + + // Start a span with all supported array attribute types. + int listInt[] = {1, 2, 3}; + unsigned int listUInt[] = {1, 2, 3}; + int32_t listInt32[] = {1, -2, 3}; + uint32_t listUInt32[] = {1, 2, 3}; + int64_t listInt64[] = {1, -2, 3}; + uint64_t listUInt64[] = {1, 2, 3}; + double listDouble[] = {1.1, 2.1, 3.1}; + bool listBool[] = {true, false}; + nostd::string_view listStringView[] = {"a", "b"}; + std::map<std::string, common::AttributeValue> m; + m["attr1"] = nostd::span<int>(listInt); + m["attr2"] = nostd::span<unsigned int>(listUInt); + m["attr3"] = nostd::span<int32_t>(listInt32); + m["attr4"] = nostd::span<uint32_t>(listUInt32); + m["attr5"] = nostd::span<int64_t>(listInt64); + m["attr6"] = nostd::span<uint64_t>(listUInt64); + m["attr7"] = nostd::span<double>(listDouble); + m["attr8"] = nostd::span<bool>(listBool); + m["attr9"] = nostd::span<nostd::string_view>(listStringView); + + tracer->StartSpan("span 2", m)->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(2, spans.size()); + + auto &cur_span_data = spans.at(0); + ASSERT_EQ(9, cur_span_data->GetAttributes().size()); + ASSERT_EQ(314159, nostd::get<int32_t>(cur_span_data->GetAttributes().at("attr1"))); + ASSERT_EQ(false, nostd::get<bool>(cur_span_data->GetAttributes().at("attr2"))); + ASSERT_EQ(314159, nostd::get<uint32_t>(cur_span_data->GetAttributes().at("attr3"))); + ASSERT_EQ(-20, nostd::get<int32_t>(cur_span_data->GetAttributes().at("attr4"))); + ASSERT_EQ(20, nostd::get<uint32_t>(cur_span_data->GetAttributes().at("attr5"))); + ASSERT_EQ(-20, nostd::get<int64_t>(cur_span_data->GetAttributes().at("attr6"))); + ASSERT_EQ(20, nostd::get<uint64_t>(cur_span_data->GetAttributes().at("attr7"))); + ASSERT_EQ(3.1, nostd::get<double>(cur_span_data->GetAttributes().at("attr8"))); + ASSERT_EQ("string", nostd::get<std::string>(cur_span_data->GetAttributes().at("attr9"))); + + auto &cur_span_data2 = spans.at(1); + ASSERT_EQ(9, cur_span_data2->GetAttributes().size()); + ASSERT_EQ(std::vector<int32_t>({1, 2, 3}), + nostd::get<std::vector<int32_t>>(cur_span_data2->GetAttributes().at("attr1"))); + ASSERT_EQ(std::vector<uint32_t>({1, 2, 3}), + nostd::get<std::vector<uint32_t>>(cur_span_data2->GetAttributes().at("attr2"))); + ASSERT_EQ(std::vector<int32_t>({1, -2, 3}), + nostd::get<std::vector<int32_t>>(cur_span_data2->GetAttributes().at("attr3"))); + ASSERT_EQ(std::vector<uint32_t>({1, 2, 3}), + nostd::get<std::vector<uint32_t>>(cur_span_data2->GetAttributes().at("attr4"))); + ASSERT_EQ(std::vector<int64_t>({1, -2, 3}), + nostd::get<std::vector<int64_t>>(cur_span_data2->GetAttributes().at("attr5"))); + ASSERT_EQ(std::vector<uint64_t>({1, 2, 3}), + nostd::get<std::vector<uint64_t>>(cur_span_data2->GetAttributes().at("attr6"))); + ASSERT_EQ(std::vector<double>({1.1, 2.1, 3.1}), + nostd::get<std::vector<double>>(cur_span_data2->GetAttributes().at("attr7"))); + ASSERT_EQ(std::vector<bool>({true, false}), + nostd::get<std::vector<bool>>(cur_span_data2->GetAttributes().at("attr8"))); + ASSERT_EQ(std::vector<std::string>({"a", "b"}), + nostd::get<std::vector<std::string>>(cur_span_data2->GetAttributes().at("attr9"))); +} + +TEST(Tracer, StartSpanWithAttributesCopy) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + { + std::unique_ptr<std::vector<int64_t>> numbers(new std::vector<int64_t>); + numbers->push_back(1); + numbers->push_back(2); + numbers->push_back(3); + + std::unique_ptr<std::vector<nostd::string_view>> strings(new std::vector<nostd::string_view>); + std::string s1("a"); + std::string s2("b"); + std::string s3("c"); + strings->push_back(s1); + strings->push_back(s2); + strings->push_back(s3); + tracer + ->StartSpan("span 1", + {{"attr1", *numbers}, {"attr2", nostd::span<nostd::string_view>(*strings)}}) + ->End(); + } + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &cur_span_data = spans.at(0); + ASSERT_EQ(2, cur_span_data->GetAttributes().size()); + + auto numbers = nostd::get<std::vector<int64_t>>(cur_span_data->GetAttributes().at("attr1")); + ASSERT_EQ(3, numbers.size()); + ASSERT_EQ(1, numbers[0]); + ASSERT_EQ(2, numbers[1]); + ASSERT_EQ(3, numbers[2]); + + auto strings = nostd::get<std::vector<std::string>>(cur_span_data->GetAttributes().at("attr2")); + ASSERT_EQ(3, strings.size()); + ASSERT_EQ("a", strings[0]); + ASSERT_EQ("b", strings[1]); + ASSERT_EQ("c", strings[2]); +} + +TEST(Tracer, GetSampler) +{ + auto resource = Resource::Create({}); + // Create a Tracer with a default AlwaysOnSampler + auto tracer_on = initTracer(nullptr); + +#ifdef OPENTELEMETRY_RTTI_ENABLED + auto &t1 = std::dynamic_pointer_cast<Tracer>(tracer_on)->GetSampler(); +#else + auto &t1 = std::static_pointer_cast<Tracer>(tracer_on)->GetSampler(); +#endif + ASSERT_EQ("AlwaysOnSampler", t1.GetDescription()); + + // Create a Tracer with a AlwaysOffSampler + auto tracer_off = initTracer(nullptr, new AlwaysOffSampler()); + +#ifdef OPENTELEMETRY_RTTI_ENABLED + auto &t2 = std::dynamic_pointer_cast<Tracer>(tracer_off)->GetSampler(); +#else + auto &t2 = std::static_pointer_cast<Tracer>(tracer_off)->GetSampler(); +#endif + ASSERT_EQ("AlwaysOffSampler", t2.GetDescription()); +} + +TEST(Tracer, SpanSetAttribute) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + auto span = tracer->StartSpan("span 1"); + + span->SetAttribute("abc", 3.1); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + auto &cur_span_data = spans.at(0); + ASSERT_EQ(3.1, nostd::get<double>(cur_span_data->GetAttributes().at("abc"))); +} + +TEST(Tracer, TestAfterEnd) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + auto span = tracer->StartSpan("span 1"); + span->SetAttribute("abc", 3.1); + + span->End(); + + // test after end + span->SetAttribute("testing null recordable", 3.1); + span->AddEvent("event 1"); + span->AddEvent("event 2", std::chrono::system_clock::now()); + span->AddEvent("event 3", std::chrono::system_clock::now(), {{"attr1", 1}}); + std::string new_name{"new name"}; + span->UpdateName(new_name); + span->SetAttribute("attr1", 3.1); + std::string description{"description"}; + span->SetStatus(opentelemetry::trace::StatusCode::kError, description); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + auto &cur_span_data = spans.at(0); + ASSERT_EQ(3.1, nostd::get<double>(cur_span_data->GetAttributes().at("abc"))); +} + +TEST(Tracer, SpanSetEvents) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + auto span = tracer->StartSpan("span 1"); + span->AddEvent("event 1"); + span->AddEvent("event 2", std::chrono::system_clock::now()); + span->AddEvent("event 3", std::chrono::system_clock::now(), {{"attr1", 1}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_events = spans.at(0)->GetEvents(); + ASSERT_EQ(3, span_data_events.size()); + ASSERT_EQ("event 1", span_data_events[0].GetName()); + ASSERT_EQ("event 2", span_data_events[1].GetName()); + ASSERT_EQ("event 3", span_data_events[2].GetName()); + ASSERT_EQ(0, span_data_events[0].GetAttributes().size()); + ASSERT_EQ(0, span_data_events[1].GetAttributes().size()); + ASSERT_EQ(1, span_data_events[2].GetAttributes().size()); +} + +TEST(Tracer, SpanSetLinks) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + { + + // Single span link passed through Initialization list + tracer->StartSpan("efg", {{"attr1", 1}}, {{SpanContext(false, false), {{"attr2", 2}}}})->End(); + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(1, span_data_links.size()); + auto link = span_data_links.at(0); + ASSERT_EQ(nostd::get<int>(link.GetAttributes().at("attr2")), 2); + } + { + + // Multiple span links passed through Initialization list + tracer + ->StartSpan("efg", {{"attr1", 1}}, + {{SpanContext(false, false), {{"attr2", 2}}}, + {SpanContext(false, false), {{"attr3", 3}}}}) + ->End(); + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get<int>(link1.GetAttributes().at("attr2")), 2); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get<int>(link2.GetAttributes().at("attr3")), 3); + } + + { + + // Multiple links, each with multiple attributes passed through Initialization list + tracer + ->StartSpan("efg", {{"attr1", 1}}, + {{SpanContext(false, false), {{"attr2", 2}, {"attr3", 3}}}, + {SpanContext(false, false), {{"attr4", 4}}}}) + ->End(); + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get<int>(link1.GetAttributes().at("attr2")), 2); + ASSERT_EQ(nostd::get<int>(link1.GetAttributes().at("attr3")), 3); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get<int>(link2.GetAttributes().at("attr4")), 4); + } + + { + std::map<std::string, std::string> attrs1 = {{"attr1", "1"}, {"attr2", "2"}}; + std::map<std::string, std::string> attrs2 = {{"attr3", "3"}, {"attr4", "4"}}; + + std::vector<std::pair<SpanContext, std::map<std::string, std::string>>> links = { + {SpanContext(false, false), attrs1}, {SpanContext(false, false), attrs2}}; + tracer->StartSpan("efg", attrs1, links)->End(); + auto spans = span_data->GetSpans(); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get<std::string>(link1.GetAttributes().at("attr1")), "1"); + ASSERT_EQ(nostd::get<std::string>(link1.GetAttributes().at("attr2")), "2"); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get<std::string>(link2.GetAttributes().at("attr3")), "3"); + ASSERT_EQ(nostd::get<std::string>(link2.GetAttributes().at("attr4")), "4"); + } +} + +TEST(Tracer, TestAlwaysOnSampler) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer_on = initTracer(std::move(exporter)); + + // Testing AlwaysOn sampler. + // Create two spans for each tracer. Check the exported result. + auto span_on_1 = tracer_on->StartSpan("span 1"); + auto span_on_2 = tracer_on->StartSpan("span 2"); + span_on_2->End(); + span_on_1->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(2, spans.size()); + ASSERT_EQ("span 2", spans.at(0)->GetName()); // span 2 ends first. + ASSERT_EQ("span 1", spans.at(1)->GetName()); +} + +TEST(Tracer, TestAlwaysOffSampler) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer_off = initTracer(std::move(exporter), new AlwaysOffSampler()); + auto span_off_1 = tracer_off->StartSpan("span 1"); + auto span_off_2 = tracer_off->StartSpan("span 2"); + + span_off_1->SetAttribute("attr1", 3.1); // Not recorded. + + span_off_2->End(); + span_off_1->End(); + + // The tracer export nothing with an AlwaysOff sampler + ASSERT_EQ(0, span_data->GetSpans().size()); +} + +TEST(Tracer, TestParentBasedSampler) +{ + // Current ShouldSample always pass an empty ParentContext, + // so this sampler will work as an AlwaysOnSampler. + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data_parent_on = exporter->GetData(); + auto tracer_parent_on = + initTracer(std::move(exporter), new ParentBasedSampler(std::make_shared<AlwaysOnSampler>())); + + auto span_parent_on_1 = tracer_parent_on->StartSpan("span 1"); + auto span_parent_on_2 = tracer_parent_on->StartSpan("span 2"); + + span_parent_on_1->SetAttribute("attr1", 3.1); + + span_parent_on_2->End(); + span_parent_on_1->End(); + + auto spans = span_data_parent_on->GetSpans(); + ASSERT_EQ(2, spans.size()); + ASSERT_EQ("span 2", spans.at(0)->GetName()); + ASSERT_EQ("span 1", spans.at(1)->GetName()); + + // Current ShouldSample always pass an empty ParentContext, + // so this sampler will work as an AlwaysOnSampler. + std::unique_ptr<InMemorySpanExporter> exporter2(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data_parent_off = exporter2->GetData(); + auto tracer_parent_off = + initTracer(std::move(exporter2), + // Add this to avoid different results for old and new version of clang-format + new ParentBasedSampler(std::make_shared<AlwaysOffSampler>())); + + auto span_parent_off_1 = tracer_parent_off->StartSpan("span 1"); + auto span_parent_off_2 = tracer_parent_off->StartSpan("span 2"); + + span_parent_off_1->SetAttribute("attr1", 3.1); + + span_parent_off_1->End(); + span_parent_off_2->End(); + ASSERT_EQ(0, span_data_parent_off->GetSpans().size()); +} + +TEST(Tracer, WithActiveSpan) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + auto spans = span_data.get()->GetSpans(); + + ASSERT_EQ(0, spans.size()); + + { + auto span_first = tracer->StartSpan("span 1"); + auto scope_first = tracer->WithActiveSpan(span_first); + + { + auto span_second = tracer->StartSpan("span 2"); + auto scope_second = tracer->WithActiveSpan(span_second); + + spans = span_data->GetSpans(); + ASSERT_EQ(0, spans.size()); + + span_second->End(); + } + + spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + EXPECT_EQ("span 2", spans.at(0)->GetName()); + + span_first->End(); + } + + spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + EXPECT_EQ("span 1", spans.at(0)->GetName()); +} + +TEST(Tracer, ExpectParent) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + auto spans = span_data.get()->GetSpans(); + + ASSERT_EQ(0, spans.size()); + + auto span_first = tracer->StartSpan("span 1"); + + trace_api::StartSpanOptions options; + options.parent = span_first->GetContext(); + auto span_second = tracer->StartSpan("span 2", options); + + options.parent = span_second->GetContext(); + auto span_third = tracer->StartSpan("span 3", options); + + span_third->End(); + span_second->End(); + span_first->End(); + + spans = span_data->GetSpans(); + ASSERT_EQ(3, spans.size()); + auto spandata_first = std::move(spans.at(2)); + auto spandata_second = std::move(spans.at(1)); + auto spandata_third = std::move(spans.at(0)); + EXPECT_EQ("span 1", spandata_first->GetName()); + EXPECT_EQ("span 2", spandata_second->GetName()); + EXPECT_EQ("span 3", spandata_third->GetName()); + + EXPECT_EQ(spandata_first->GetSpanId(), spandata_second->GetParentSpanId()); + EXPECT_EQ(spandata_second->GetSpanId(), spandata_third->GetParentSpanId()); +} + +TEST(Tracer, ExpectParentAsContext) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + auto spans = span_data.get()->GetSpans(); + + ASSERT_EQ(0, spans.size()); + + auto span_first = tracer->StartSpan("span 1"); + + opentelemetry::context::Context c1; + auto c2 = trace_api::SetSpan(c1, span_first); + trace_api::StartSpanOptions options; + options.parent = c2; + auto span_second = tracer->StartSpan("span 2", options); + + auto c3 = trace_api::SetSpan(c2, span_second); + options.parent = c3; + auto span_third = tracer->StartSpan("span 3", options); + + span_third->End(); + span_second->End(); + span_first->End(); + + spans = span_data->GetSpans(); + ASSERT_EQ(3, spans.size()); + auto spandata_first = std::move(spans.at(2)); + auto spandata_second = std::move(spans.at(1)); + auto spandata_third = std::move(spans.at(0)); + EXPECT_EQ("span 1", spandata_first->GetName()); + EXPECT_EQ("span 2", spandata_second->GetName()); + EXPECT_EQ("span 3", spandata_third->GetName()); + + EXPECT_EQ(spandata_first->GetSpanId(), spandata_second->GetParentSpanId()); + EXPECT_EQ(spandata_second->GetSpanId(), spandata_third->GetParentSpanId()); +} + +TEST(Tracer, ValidTraceIdToSampler) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter), new MockSampler()); + + auto span = tracer->StartSpan("span 1"); + // sampler was fed with valid trace_id, so span shouldn't be NoOp Span. + EXPECT_TRUE(span->IsRecording()); + EXPECT_TRUE(span->GetContext().IsValid()); +} + +TEST(Tracer, SpanCleanupWithScope) +{ + std::unique_ptr<InMemorySpanExporter> exporter(new InMemorySpanExporter()); + std::shared_ptr<InMemorySpanData> span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + { + auto span0 = tracer->StartSpan("Span0"); + auto span1 = tracer->StartSpan("span1"); + { + trace_api::Scope scope(span1); + auto span2 = tracer->StartSpan("span2"); + { + trace_api::Scope scope(span2); + auto span3 = tracer->StartSpan("span3"); + } + } + } + EXPECT_EQ(4, span_data->GetSpans().size()); +} |