diff options
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/sdk/test')
77 files changed, 8692 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/CMakeLists.txt new file mode 100644 index 000000000..dc8cc2b22 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_subdirectory(common) +add_subdirectory(trace) +if(WITH_METRICS_PREVIEW) + add_subdirectory(_metrics) +else() + add_subdirectory(metrics) +endif() +if(WITH_LOGS_PREVIEW) + add_subdirectory(logs) +endif() +add_subdirectory(resource) +add_subdirectory(instrumentationlibrary) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/BUILD new file mode 100644 index 000000000..44835d102 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/BUILD @@ -0,0 +1,165 @@ +cc_test( + name = "controller_test", + srcs = [ + "controller_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//exporters/ostream:ostream_metrics_exporter_deprecated", + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "gauge_aggregator_test", + srcs = [ + "gauge_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "min_max_sum_count_aggregator_test", + srcs = [ + "min_max_sum_count_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "meter_provider_sdk_test", + srcs = [ + "meter_provider_sdk_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "meter_test", + srcs = [ + "meter_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "counter_aggregator_test", + srcs = [ + "counter_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "exact_aggregator_test", + srcs = [ + "exact_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "histogram_aggregator_test", + srcs = [ + "histogram_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "metric_instrument_test", + srcs = [ + "metric_instrument_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "sketch_aggregator_test", + srcs = [ + "sketch_aggregator_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "ungrouped_processor_test", + srcs = [ + "ungrouped_processor_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/CMakeLists.txt new file mode 100644 index 000000000..5d950ffb3 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/CMakeLists.txt @@ -0,0 +1,21 @@ +foreach( + testname + meter_provider_sdk_test + gauge_aggregator_test + min_max_sum_count_aggregator_test + exact_aggregator_test + counter_aggregator_test + histogram_aggregator_test + ungrouped_processor_test + meter_test + metric_instrument_test + controller_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_metrics_deprecated) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX metrics. + TEST_LIST ${testname}) +endforeach() diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/controller_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/controller_test.cc new file mode 100644 index 000000000..aa92c8d04 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/controller_test.cc @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/_metrics/controller.h" +# include "opentelemetry/sdk/_metrics/meter.h" +# include "opentelemetry/sdk/_metrics/ungrouped_processor.h" + +# include <gtest/gtest.h> +# include <numeric> +# include <thread> +// #include <chrono> + +namespace metrics_api = opentelemetry::metrics; +using namespace opentelemetry::sdk::common; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ +class DummyExporter : public MetricsExporter +{ + ExportResult Export(const std::vector<Record> &records) noexcept + { + return ExportResult::kSuccess; + } +}; + +TEST(Controller, Constructor) +{ + + std::shared_ptr<metrics_api::Meter> meter = + std::shared_ptr<metrics_api::Meter>(new Meter("Test")); + PushController alpha(meter, std::unique_ptr<MetricsExporter>(new DummyExporter), + std::shared_ptr<MetricsProcessor>( + new opentelemetry::sdk::metrics::UngroupedMetricsProcessor(false)), + .05); + + auto instr = meter->NewIntCounter("test", "none", "none", true); + std::map<std::string, std::string> labels = {{"key", "value"}}; + auto labelkv = opentelemetry::common::KeyValueIterableView<decltype(labels)>{labels}; + + alpha.start(); + + for (int i = 0; i < 20; i++) + { + instr->add(i, labelkv); + } + alpha.stop(); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/counter_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/counter_aggregator_test.cc new file mode 100644 index 000000000..7ef452e77 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/counter_aggregator_test.cc @@ -0,0 +1,120 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" + +# include <gtest/gtest.h> +# include <numeric> +# include <thread> + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +TEST(CounterAggregator, NoUpdates) +{ + CounterAggregator<int> alpha(metrics_api::InstrumentKind::Counter); + + EXPECT_EQ(alpha.get_checkpoint().size(), 1); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint().size(), 1); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); +} + +TEST(CounterAggregator, Update) +{ + CounterAggregator<int> alpha(metrics_api::InstrumentKind::Counter); + CounterAggregator<int> beta(metrics_api::InstrumentKind::Counter); + + for (int i = 0; i < 123456; i++) + { + alpha.update(1); + } + + int sum = 0; + for (int i = 0; i < 100; i++) + { + int tmp = std::rand() % 128; + beta.update(tmp); + sum += tmp; + } + + EXPECT_EQ(alpha.get_checkpoint()[0], 0); // checkpoint shouldn't change even with updates + EXPECT_EQ(beta.get_checkpoint()[0], 0); + + alpha.checkpoint(); + beta.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], 123456); + EXPECT_EQ(beta.get_checkpoint()[0], sum); + + alpha.update(15); + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 15); // reset to 0 after first checkpoint call +} + +// callback update function used to test concurrent calls +void incrementingCallback(Aggregator<int> &agg) +{ + for (int i = 0; i < 2000000; i++) + { + agg.update(1); + } +} + +TEST(CounterAggregator, Concurrency) +{ + CounterAggregator<int> alpha(metrics_api::InstrumentKind::Counter); + + // spawn new threads that initiate the callback + std::thread first(incrementingCallback, std::ref(alpha)); + std::thread second(incrementingCallback, std::ref(alpha)); + + first.join(); + second.join(); + + alpha.checkpoint(); + + // race conditions result in values below 2*2000000 + EXPECT_EQ(alpha.get_checkpoint()[0], 2 * 2000000); +} + +TEST(CounterAggregator, Merge) +{ + CounterAggregator<int> alpha(metrics_api::InstrumentKind::Counter); + CounterAggregator<int> beta(metrics_api::InstrumentKind::Counter); + + alpha.merge(beta); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); // merge with no updates + + for (int i = 0; i < 500; i++) + { + alpha.update(1); + } + + for (int i = 0; i < 700; i++) + { + beta.update(1); + } + + alpha.merge(beta); + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 1200); + + // HistogramAggregator gamma(metrics_api::BoundInstrumentKind::BoundValueRecorder); + // ASSERT_THROW(alpha.merge(gamma), AggregatorMismatch); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/exact_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/exact_aggregator_test.cc new file mode 100644 index 000000000..443cb65ea --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/exact_aggregator_test.cc @@ -0,0 +1,229 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <gtest/gtest.h> + +#ifdef ENABLE_METRICS_PREVIEW +# include <thread> + +# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h" + +using namespace opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; + +TEST(ExactAggregatorOrdered, Update) +{ + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + std::vector<int> correct; + + ASSERT_EQ(agg.get_values(), correct); + + agg.update(1); + correct.push_back(1); + + ASSERT_EQ(agg.get_values(), std::vector<int>{1}); + + for (int i = 2; i <= 5; ++i) + { + correct.push_back(i); + agg.update(i); + } + ASSERT_EQ(agg.get_values(), correct); +} + +TEST(ExactAggregatorOrdered, Checkpoint) +{ + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + std::vector<int> correct; + + ASSERT_EQ(agg.get_checkpoint(), correct); + + agg.update(1); + correct.push_back(1); + agg.checkpoint(); + + ASSERT_EQ(agg.get_checkpoint(), correct); +} + +TEST(ExactAggregatorOrdered, Merge) +{ + ExactAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + ExactAggregator<int> agg2(metrics_api::InstrumentKind::Counter); + + agg1.update(1); + agg2.update(2); + agg1.merge(agg2); + + std::vector<int> correct{1, 2}; + + ASSERT_EQ(agg1.get_values(), correct); +} + +TEST(ExactAggregatorOrdered, BadMerge) +{ + // This verifies that we encounter and error when we try to merge + // two aggregators of different numeric types together. + ExactAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + ExactAggregator<int> agg2(metrics_api::InstrumentKind::ValueRecorder); + + agg1.update(1); + agg2.update(2); + + agg1.merge(agg2); + + // Verify that the aggregators did NOT merge + std::vector<int> correct{1}; + ASSERT_EQ(agg1.get_values(), correct); +} + +TEST(ExactAggregatorOrdered, Types) +{ + // This test verifies that we do not encounter any errors when + // using various numeric types. + ExactAggregator<int> agg_int(metrics_api::InstrumentKind::Counter); + ExactAggregator<long> agg_long(metrics_api::InstrumentKind::Counter); + ExactAggregator<float> agg_float(metrics_api::InstrumentKind::Counter); + ExactAggregator<double> agg_double(metrics_api::InstrumentKind::Counter); + + for (int i = 1; i <= 5; ++i) + { + agg_int.update(i); + agg_long.update(i); + } + + for (float i = 1.0; i <= 5.0; i += 1) + { + agg_float.update(i); + agg_double.update(i); + } + + std::vector<int> correct_int{1, 2, 3, 4, 5}; + std::vector<long> correct_long{1, 2, 3, 4, 5}; + std::vector<float> correct_float{1.0, 2.0, 3.0, 4.0, 5.0}; + std::vector<double> correct_double{1.0, 2.0, 3.0, 4.0, 5.0}; + + ASSERT_EQ(agg_int.get_values(), correct_int); + ASSERT_EQ(agg_long.get_values(), correct_long); + ASSERT_EQ(agg_float.get_values(), correct_float); + ASSERT_EQ(agg_double.get_values(), correct_double); +} + +TEST(ExactAggregatorQuant, Update) +{ + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter, true); + + std::vector<int> correct; + + ASSERT_EQ(agg.get_values(), correct); + + agg.update(1); + correct.push_back(1); + + ASSERT_EQ(agg.get_values(), std::vector<int>{1}); + + for (int i = 2; i <= 5; ++i) + { + correct.push_back(i); + agg.update(i); + } + ASSERT_EQ(agg.get_values(), correct); +} + +TEST(ExactAggregatorQuant, Checkpoint) +{ + // This test verifies that the aggregator updates correctly when + // quantile estimation is turned on. + + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter, true); + + std::vector<int> correct; + + ASSERT_EQ(agg.get_checkpoint(), correct); + + agg.update(1); + agg.update(0); + agg.update(-1); + + // The vector MUST be sorted when checkpointed + correct.push_back(-1); + correct.push_back(0); + correct.push_back(1); + agg.checkpoint(); + + ASSERT_EQ(agg.get_checkpoint(), correct); +} + +TEST(ExactAggregatorQuant, Quantile) +{ + // This test verifies that the quantile estimation function returns + // the correct values. + + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter, true); + + std::vector<int> tmp{3, 9, 42, 57, 163, 210, 272, 300}; + for (int i : tmp) + { + agg.update(i); + } + agg.checkpoint(); + ASSERT_EQ(agg.get_quantiles(.25), 42); + ASSERT_EQ(agg.get_quantiles(0.5), 163); + ASSERT_EQ(agg.get_quantiles(0.75), 272); +} + +TEST(ExactAggregatorInOrder, Quantile) +{ + // This test verifies that if the user has an exact aggregator in "in-order" mode + // an exception will be thrown if they call the quantile() function. + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + std::vector<int> tmp{3, 9, 42, 57, 163, 210, 272, 300}; + for (int i : tmp) + { + agg.update(i); + } + agg.checkpoint(); +# if __EXCEPTIONS + ASSERT_THROW(agg.get_quantiles(0.5), std::domain_error); +# else +# endif +} + +void callback(ExactAggregator<int> &agg) +{ + for (int i = 1; i <= 10000; ++i) + { + agg.update(i); + } +} + +TEST(ExactAggregatorQuant, Concurrency) +{ + // This test checks that the aggregator updates appropriately + // when called in a multi-threaded context. + ExactAggregator<int> agg(metrics_api::InstrumentKind::Counter, true); + + std::thread first(&callback, std::ref(agg)); + std::thread second(&callback, std::ref(agg)); + + first.join(); + second.join(); + + std::vector<int> correct; + for (int i = 1; i <= 10000; ++i) + { + correct.push_back(i); + correct.push_back(i); + } + agg.checkpoint(); + + ASSERT_EQ(agg.get_checkpoint(), correct); +} +#else +TEST(ExactAggregatorQuant, DummyTest) +{ + // empty +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/gauge_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/gauge_aggregator_test.cc new file mode 100644 index 000000000..9565e0629 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/gauge_aggregator_test.cc @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include <thread> + +# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h" + +using namespace opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; + +TEST(GaugeAggregator, Update) +{ + // This tests that the aggregator updates the maintained value correctly + // after a call to the update() function. + auto agg = new GaugeAggregator<int>(metrics_api::InstrumentKind::Counter); + + // Verify default value + ASSERT_EQ(agg->get_values()[0], 0); + + // Verify that the value updates correctly + agg->update(1); + ASSERT_EQ(agg->get_values()[0], 1); + + // Verify that the value continually updates correctly + for (int i = 0; i < 10; ++i) + { + agg->update(i); + } + ASSERT_EQ(agg->get_values()[0], 9); + delete agg; +} + +TEST(GaugeAggregator, Checkpoint) +{ + // This tests that the aggregator correctly updates the + // checkpoint_ value after a call to update() followed + // by a call to checkpoint(). + GaugeAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + // Verify default checkpoint, before updates + ASSERT_EQ(agg.get_checkpoint()[0], 0); + + agg.update(10); + agg.checkpoint(); + + // Verify that the new checkpoint contains the update value + ASSERT_EQ(agg.get_checkpoint()[0], 10); +} + +TEST(GaugeAggregator, Merge) +{ + // This tests that the values_ vector is updated correctly after + // two aggregators are merged together. + GaugeAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + GaugeAggregator<int> agg2(metrics_api::InstrumentKind::Counter); + + agg1.update(1); + agg2.update(2); + + agg1.merge(agg2); + + // Verify that the aggregators merged and the value was updated correctly + ASSERT_EQ(agg1.get_values()[0], 2); +} + +TEST(GaugeAggregator, BadMerge) +{ + // This verifies that we encounter and error when we try to merge + // two aggregators of different numeric types together. + GaugeAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + GaugeAggregator<int> agg2(metrics_api::InstrumentKind::ValueRecorder); + + agg1.update(1); + agg2.update(2); + agg1.merge(agg2); + + // Verify that the aggregators did NOT merge + std::vector<int> correct{1}; + ASSERT_EQ(agg1.get_values(), correct); +} + +TEST(GaugeAggregator, Types) +{ + // This test verifies that we do not encounter any errors when + // using various numeric types. + GaugeAggregator<int> agg_int(metrics_api::InstrumentKind::Counter); + GaugeAggregator<long> agg_long(metrics_api::InstrumentKind::Counter); + GaugeAggregator<float> agg_float(metrics_api::InstrumentKind::Counter); + GaugeAggregator<double> agg_double(metrics_api::InstrumentKind::Counter); + + for (int i = 1; i <= 10; ++i) + { + agg_int.update(i); + agg_long.update(i); + } + + for (float i = 1.0; i <= 10.0; i += 1) + { + agg_float.update(i); + agg_double.update(i); + } + + ASSERT_EQ(agg_int.get_values()[0], 10); + ASSERT_EQ(agg_long.get_values()[0], 10); + ASSERT_EQ(agg_float.get_values()[0], 10.0); + ASSERT_EQ(agg_double.get_values()[0], 10.0); +} + +static void callback(GaugeAggregator<int> &agg) +{ + for (int i = 1; i <= 10000; ++i) + { + agg.update(i); + } +} + +TEST(GaugeAggregator, Concurrency) +{ + // This test checks that the aggregator updates appropriately + // when called in a multi-threaded context. + GaugeAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + std::thread first(&callback, std::ref(agg)); + std::thread second(&callback, std::ref(agg)); + + first.join(); + second.join(); + + ASSERT_EQ(agg.get_values()[0], 10000); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/histogram_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/histogram_aggregator_test.cc new file mode 100644 index 000000000..daf920aa7 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/histogram_aggregator_test.cc @@ -0,0 +1,173 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h" + +# include <gtest/gtest.h> +# include <iostream> +# include <numeric> +# include <thread> + +// #include <chrono> + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +// Test updating with a uniform set of updates +TEST(Histogram, Uniform) +{ + std::vector<double> boundaries{10, 20, 30, 40, 50}; + HistogramAggregator<int> alpha(metrics_api::InstrumentKind::Counter, boundaries); + + EXPECT_EQ(alpha.get_aggregator_kind(), AggregatorKind::Histogram); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint().size(), 2); + EXPECT_EQ(alpha.get_counts().size(), 6); + + for (int i = 0; i < 60; i++) + { + alpha.update(i); + } + + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], 1770); + EXPECT_EQ(alpha.get_checkpoint()[1], 60); + + std::vector<int> correct = {10, 10, 10, 10, 10, 10}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +// Test updating with a normal distribution +TEST(Histogram, Normal) +{ + std::vector<double> boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator<int> alpha(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size()); + + std::vector<int> correct = {1, 2, 3, 4, 3, 2, 1}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +TEST(Histogram, Merge) +{ + std::vector<double> boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator<int> alpha(metrics_api::InstrumentKind::Counter, boundaries); + HistogramAggregator<int> beta(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + std::vector<int> otherVals{1, 1, 1, 1, 11, 11, 13, 13, 13, 15}; + for (int i : otherVals) + { + beta.update(i); + } + + alpha.merge(beta); + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0) + + std::accumulate(otherVals.begin(), otherVals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size() + otherVals.size()); + + std::vector<int> correct = {5, 2, 3, 4, 3, 4, 5}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +// Update callback used to validate multi-threaded performance +void histogramUpdateCallback(Aggregator<int> &agg, std::vector<int> vals) +{ + for (int i : vals) + { + agg.update(i); + } +} + +int randVal() +{ + return rand() % 15; +} + +TEST(Histogram, Concurrency) +{ + std::vector<double> boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator<int> alpha(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector<int> vals1(1000); + std::generate(vals1.begin(), vals1.end(), randVal); + + std::vector<int> vals2(1000); + std::generate(vals2.begin(), vals2.end(), randVal); + + std::thread first(histogramUpdateCallback, std::ref(alpha), vals1); + std::thread second(histogramUpdateCallback, std::ref(alpha), vals2); + + first.join(); + second.join(); + + HistogramAggregator<int> beta(metrics_api::InstrumentKind::Counter, boundaries); + + // Timing harness to compare linear and binary insertion + // auto start = std::chrono::system_clock::now(); + for (int i : vals1) + { + beta.update(i); + } + for (int i : vals2) + { + beta.update(i); + } + // auto end = std::chrono::system_clock::now(); + // auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start); + // std::cout <<"Update time: " <<elapsed.count() <<std::endl; + + alpha.checkpoint(); + beta.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint(), beta.get_checkpoint()); + EXPECT_EQ(alpha.get_counts(), beta.get_counts()); +} + +# if __EXCEPTIONS + +TEST(Histogram, Errors) +{ + std::vector<double> boundaries{2, 4, 6, 8, 10, 12}; + std::vector<double> boundaries2{1, 4, 6, 8, 10, 12}; + std::vector<double> unsortedBoundaries{10, 12, 4, 6, 8}; + EXPECT_ANY_THROW( + HistogramAggregator<int> alpha(metrics_api::InstrumentKind::Counter, unsortedBoundaries)); + + HistogramAggregator<int> beta(metrics_api::InstrumentKind::Counter, boundaries); + HistogramAggregator<int> gamma(metrics_api::InstrumentKind::Counter, boundaries2); + + EXPECT_ANY_THROW(beta.merge(gamma)); +} + +# endif + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_provider_sdk_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_provider_sdk_test.cc new file mode 100644 index 000000000..2181bb62a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_provider_sdk_test.cc @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> + +# include "opentelemetry/sdk/_metrics/meter.h" +# include "opentelemetry/sdk/_metrics/meter_provider.h" + +using namespace opentelemetry::sdk::metrics; + +TEST(MeterProvider, GetMeter) +{ + MeterProvider tf; + auto t1 = tf.GetMeter("test"); + auto t2 = tf.GetMeter("test"); + auto t3 = tf.GetMeter("different", "1.0.0"); + ASSERT_NE(nullptr, t1); + ASSERT_NE(nullptr, t2); + ASSERT_NE(nullptr, t3); + + // Should return the same instance each time. + ASSERT_EQ(t1, t2); + ASSERT_EQ(t1, t3); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_test.cc new file mode 100644 index 000000000..73ff1f9bd --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/meter_test.cc @@ -0,0 +1,297 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include <future> + +# include "opentelemetry/sdk/_metrics/meter.h" + +using namespace opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE + +TEST(Meter, CreateSyncInstruments) +{ + // Test that there are no errors creating synchronous instruments. + Meter m("Test"); + + m.NewShortCounter("Test-short-counter", "For testing", "Unitless", true); + m.NewIntCounter("Test-int-counter", "For testing", "Unitless", true); + m.NewFloatCounter("Test-float-counter", "For testing", "Unitless", true); + m.NewDoubleCounter("Test-double-counter", "For testing", "Unitless", true); + + m.NewShortUpDownCounter("Test-short-ud-counter", "For testing", "Unitless", true); + m.NewIntUpDownCounter("Test-int-ud-counter", "For testing", "Unitless", true); + m.NewFloatUpDownCounter("Test-float-ud-counter", "For testing", "Unitless", true); + m.NewDoubleUpDownCounter("Test-double-ud-counter", "For testing", "Unitless", true); + + m.NewShortValueRecorder("Test-short-recorder", "For testing", "Unitless", true); + m.NewIntValueRecorder("Test-int-recorder", "For testing", "Unitless", true); + m.NewFloatValueRecorder("Test-float-recorder", "For testing", "Unitless", true); + m.NewDoubleValueRecorder("Test-double-recorder", "For testing", "Unitless", true); +} + +// Dummy functions for asynchronous instrument constructors +void ShortCallback(metrics_api::ObserverResult<short>) {} +void IntCallback(metrics_api::ObserverResult<int>) {} +void FloatCallback(metrics_api::ObserverResult<float>) {} +void DoubleCallback(metrics_api::ObserverResult<double>) {} + +TEST(Meter, CreateAsyncInstruments) +{ + // Test that there are no errors when creating asynchronous instruments. + Meter m("Test"); + + m.NewShortSumObserver("Test-short-sum-obs", "For testing", "Unitless", true, &ShortCallback); + m.NewIntSumObserver("Test-int-sum-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatSumObserver("Test-float-sum-obs", "For testing", "Unitless", true, &FloatCallback); + m.NewDoubleSumObserver("Test-double-sum-obs", "For testing", "Unitless", true, &DoubleCallback); + + m.NewShortUpDownSumObserver("Test-short-ud-sum-obs", "For testing", "Unitless", true, + &ShortCallback); + m.NewIntUpDownSumObserver("Test-int-ud-sum-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatUpDownSumObserver("Test-float-ud-sum-obs", "For testing", "Unitless", true, + &FloatCallback); + m.NewDoubleUpDownSumObserver("Test-double-ud-sum-obs", "For testing", "Unitless", true, + &DoubleCallback); + + m.NewShortValueObserver("Test-short-val-obs", "For testing", "Unitless", true, &ShortCallback); + m.NewIntValueObserver("Test-int-val-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatValueObserver("Test-float-val-obs", "For testing", "Unitless", true, &FloatCallback); + m.NewDoubleValueObserver("Test-double-val-obs", "For testing", "Unitless", true, &DoubleCallback); +} + +TEST(Meter, CollectSyncInstruments) +{ + // Verify that the records returned on a call to Collect() are correct for synchronous + // instruments. + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + auto counter = m.NewShortCounter("Test-counter", "For testing", "Unitless", true); + + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + counter->add(1, labelkv); + + std::vector<Record> res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); + + // Now call add() and Collect() again to ensure that the value in the underlying + // aggregator was reset to the default. + + counter->add(10, labelkv); + + res = m.Collect(); + agg_var = res[0].GetAggregator(); + agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 10); +} + +TEST(Meter, CollectDeletedSync) +{ + // Verify that calling Collect() after creating a synchronous instrument and destroying + // the return pointer does not result in a segfault. + + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + { + auto counter = m.NewShortCounter("Test-counter", "For testing", "Unitless", true); + counter->add(1, labelkv); + } // counter shared_ptr deleted here + + std::vector<Record> res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); +} + +// Dummy function for asynchronous instrument constructors. +void Callback(metrics_api::ObserverResult<short> result) +{ + std::map<std::string, std::string> labels = {{"key", "value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + result.observe(1, labelkv); +} + +TEST(Meter, CollectAsyncInstruments) +{ + // Verify that the records returned on a call to Collect() are correct for asynchronous + // instruments. + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + auto sumobs = + m.NewShortSumObserver("Test-counter", "For testing", "Unitless", true, &ShortCallback); + + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + sumobs->observe(1, labelkv); + + std::vector<Record> res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); + + // Now call observe() and Collect() again to ensure that the value in the underlying + // aggregator was reset to the default. + + sumobs->observe(10, labelkv); + + res = m.Collect(); + agg_var = res[0].GetAggregator(); + agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 10); +} + +TEST(Meter, CollectDeletedAsync) +{ + // Verify that calling Collect() after creating an asynchronous instrument and destroying + // the return pointer does not result in a segfault. + + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + { + auto sumobs = m.NewShortSumObserver("Test-counter", "For testing", "Unitless", true, &Callback); + sumobs->observe(1, labelkv); + } // sumobs shared_ptr deleted here + + std::vector<Record> res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); +} + +TEST(Meter, RecordBatch) +{ + // This tests that RecordBatch appropriately updates the aggregators of the instruments + // passed to the function. Short, int, float, and double data types are tested. + Meter m("Test"); + + auto scounter = m.NewShortCounter("Test-scounter", "For testing", "Unitless", true); + auto icounter = m.NewIntCounter("Test-icounter", "For testing", "Unitless", true); + auto fcounter = m.NewFloatCounter("Test-fcounter", "For testing", "Unitless", true); + auto dcounter = m.NewDoubleCounter("Test-dcounter", "For testing", "Unitless", true); + + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + metrics_api::SynchronousInstrument<short> *sinstr_arr[] = {scounter.get()}; + short svalues_arr[] = {1}; + + nostd::span<metrics_api::SynchronousInstrument<short> *> sinstrs{sinstr_arr}; + nostd::span<const short, 1> svalues{svalues_arr}; + + m.RecordShortBatch(labelkv, sinstrs, svalues); + std::vector<Record> res = m.Collect(); + auto short_agg_var = res[0].GetAggregator(); + auto short_agg = nostd::get<0>(short_agg_var); + ASSERT_EQ(short_agg->get_checkpoint()[0], 1); + + metrics_api::SynchronousInstrument<int> *iinstr_arr[] = {icounter.get()}; + int ivalues_arr[] = {1}; + + nostd::span<metrics_api::SynchronousInstrument<int> *> iinstrs{iinstr_arr}; + nostd::span<const int, 1> ivalues{ivalues_arr}; + + m.RecordIntBatch(labelkv, iinstrs, ivalues); + res = m.Collect(); + auto int_agg_var = res[0].GetAggregator(); + auto int_agg = nostd::get<1>(int_agg_var); + ASSERT_EQ(int_agg->get_checkpoint()[0], 1); + + metrics_api::SynchronousInstrument<float> *finstr_arr[] = {fcounter.get()}; + float fvalues_arr[] = {1.0}; + + nostd::span<metrics_api::SynchronousInstrument<float> *> finstrs{finstr_arr}; + nostd::span<const float, 1> fvalues{fvalues_arr}; + + m.RecordFloatBatch(labelkv, finstrs, fvalues); + res = m.Collect(); + auto float_agg_var = res[0].GetAggregator(); + auto float_agg = nostd::get<2>(float_agg_var); + ASSERT_EQ(float_agg->get_checkpoint()[0], 1.0); + + metrics_api::SynchronousInstrument<double> *dinstr_arr[] = {dcounter.get()}; + double dvalues_arr[] = {1.0}; + + nostd::span<metrics_api::SynchronousInstrument<double> *> dinstrs{dinstr_arr}; + nostd::span<const double, 1> dvalues{dvalues_arr}; + + m.RecordDoubleBatch(labelkv, dinstrs, dvalues); + res = m.Collect(); + auto double_agg_var = res[0].GetAggregator(); + auto double_agg = nostd::get<3>(double_agg_var); + ASSERT_EQ(double_agg->get_checkpoint()[0], 1.0); +} + +TEST(Meter, DisableCollectSync) +{ + Meter m("Test"); + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto c = m.NewShortCounter("c", "", "", false); + c->add(1, labelkv); + ASSERT_EQ(m.Collect().size(), 0); +} + +TEST(Meter, DisableCollectAsync) +{ + Meter m("Test"); + std::map<std::string, std::string> labels = {{"Key", "Value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto c = m.NewShortValueObserver("c", "", "", false, &ShortCallback); + c->observe(1, labelkv); + ASSERT_EQ(m.Collect().size(), 0); +} + +TEST(MeterStringUtil, IsValid) +{ +# if __EXCEPTIONS + Meter m("Test"); + ASSERT_ANY_THROW(m.NewShortCounter("", "Empty name is invalid", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter("1a", "Can't begin with a number", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter(".a", "Can't begin with punctuation", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter(" a", "Can't begin with space", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter( + "te^ s=%t", "Only alphanumeric ., -, and _ characters are allowed", " ", true)); +# endif +} + +TEST(MeterStringUtil, AlreadyExists) +{ +# if __EXCEPTIONS + Meter m("Test"); + + m.NewShortCounter("a", "First instance of instrument named 'a'", "", true); + ASSERT_ANY_THROW(m.NewShortCounter("a", "Second (illegal) instrument named 'a'", "", true)); + ASSERT_ANY_THROW(m.NewShortSumObserver("a", "Still illegal even though it is not a short counter", + "", true, &ShortCallback)); +# endif +} +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/metric_instrument_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/metric_instrument_test.cc new file mode 100644 index 000000000..28df53fc3 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/metric_instrument_test.cc @@ -0,0 +1,487 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW + +# include <gtest/gtest.h> +# include <cstring> +# include <iostream> +# include <map> +# include <memory> +# include <string> +# include <thread> + +# include "opentelemetry/common/macros.h" +# include "opentelemetry/sdk/_metrics/async_instruments.h" +# include "opentelemetry/sdk/_metrics/sync_instruments.h" + +namespace metrics_api = opentelemetry::metrics; + +# ifdef OPENTELEMETRY_RTTI_ENABLED +# define METRICS_TEST_TYPE_CAST(TO, FROM) dynamic_cast<TO>(FROM) +# else +# define METRICS_TEST_TYPE_CAST(TO, FROM) static_cast<TO>(FROM) +# endif + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +void ObserverConstructorCallback(metrics_api::ObserverResult<int> result) +{ + std::map<std::string, std::string> labels = {{"key", "value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + result.observe(1, labelkv); +} + +TEST(ApiSdkConversion, async) +{ + nostd::shared_ptr<metrics_api::AsynchronousInstrument<int>> alpha = + nostd::shared_ptr<metrics_api::AsynchronousInstrument<int>>( + new ValueObserver<int>("ankit", "none", "unitles", true, &ObserverConstructorCallback)); + + std::map<std::string, std::string> labels = {{"key587", "value264"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + alpha->observe(123456, labelkv); + EXPECT_EQ(METRICS_TEST_TYPE_CAST(AsynchronousInstrument<int> *, alpha.get()) + ->GetRecords()[0] + .GetLabels(), + "{key587:value264}"); + + alpha->observe(123456, labelkv); + AggregatorVariant canCollect = METRICS_TEST_TYPE_CAST(AsynchronousInstrument<int> *, alpha.get()) + ->GetRecords()[0] + .GetAggregator(); + EXPECT_EQ(nostd::holds_alternative<std::shared_ptr<Aggregator<short>>>(canCollect), false); + EXPECT_EQ(nostd::holds_alternative<std::shared_ptr<Aggregator<int>>>(canCollect), true); + EXPECT_EQ(nostd::get<std::shared_ptr<Aggregator<int>>>(canCollect)->get_checkpoint()[0], 123456); +} + +TEST(IntValueObserver, InstrumentFunctions) +{ + ValueObserver<int> alpha("enabled", "no description", "unitless", true, + &ObserverConstructorCallback); + std::map<std::string, std::string> labels = {{"key", "value"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + EXPECT_EQ(alpha.GetName(), "enabled"); + EXPECT_EQ(alpha.GetDescription(), "no description"); + EXPECT_EQ(alpha.GetUnits(), "unitless"); + EXPECT_EQ(alpha.IsEnabled(), true); + EXPECT_EQ(alpha.GetKind(), metrics_api::InstrumentKind::ValueObserver); + + alpha.run(); + EXPECT_EQ(alpha.boundAggregators_[KvToString(labelkv)]->get_values()[0], 1); // min +} + +void ObserverCallback(std::shared_ptr<ValueObserver<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(i, labels); + } +} + +void NegObserverCallback(std::shared_ptr<ValueObserver<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(-i, labels); + } +} + +TEST(IntValueObserver, StressObserve) +{ + std::shared_ptr<ValueObserver<int>> alpha(new ValueObserver<int>( + "enabled", "no description", "unitless", true, &ObserverConstructorCallback)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(ObserverCallback, alpha, 25, + labelkv); // spawn new threads that call the callback + std::thread second(ObserverCallback, alpha, 25, labelkv); + std::thread third(ObserverCallback, alpha, 25, labelkv1); + std::thread fourth(NegObserverCallback, alpha, 25, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 0); // min + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[1], 24); // max + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[2], 600); // sum + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[3], 50); // count + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], -24); // min + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[1], 24); // max + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[2], 0); // sum + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[3], 50); // count +} + +void SumObserverCallback(std::shared_ptr<SumObserver<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(1, labels); + } +} + +TEST(IntSumObserver, StressObserve) +{ + std::shared_ptr<SumObserver<int>> alpha( + new SumObserver<int>("test", "none", "unitless", true, &ObserverConstructorCallback)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(SumObserverCallback, alpha, 100000, labelkv); + std::thread second(SumObserverCallback, alpha, 100000, labelkv); + std::thread third(SumObserverCallback, alpha, 300000, labelkv1); + + first.join(); + second.join(); + third.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 200000); + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], 300000); +} + +void UpDownSumObserverCallback(std::shared_ptr<UpDownSumObserver<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(1, labels); + } +} + +void NegUpDownSumObserverCallback(std::shared_ptr<UpDownSumObserver<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(-1, labels); + } +} + +TEST(IntUpDownObserver, StressAdd) +{ + std::shared_ptr<UpDownSumObserver<int>> alpha( + new UpDownSumObserver<int>("test", "none", "unitless", true, &ObserverConstructorCallback)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(UpDownSumObserverCallback, alpha, 12340, + labelkv); // spawn new threads that call the callback + std::thread second(UpDownSumObserverCallback, alpha, 12340, labelkv); + std::thread third(UpDownSumObserverCallback, alpha, 56780, labelkv1); + std::thread fourth(NegUpDownSumObserverCallback, alpha, 12340, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 12340 * 2); + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], 56780 - 12340); +} + +TEST(Counter, InstrumentFunctions) +{ + Counter<int> alpha("enabled", "no description", "unitless", true); + Counter<double> beta("not enabled", "some description", "units", false); + + EXPECT_EQ(static_cast<std::string>(alpha.GetName()), "enabled"); + EXPECT_EQ(static_cast<std::string>(alpha.GetDescription()), "no description"); + EXPECT_EQ(static_cast<std::string>(alpha.GetUnits()), "unitless"); + EXPECT_EQ(alpha.IsEnabled(), true); + + EXPECT_EQ(static_cast<std::string>(beta.GetName()), "not enabled"); + EXPECT_EQ(static_cast<std::string>(beta.GetDescription()), "some description"); + EXPECT_EQ(static_cast<std::string>(beta.GetUnits()), "units"); + EXPECT_EQ(beta.IsEnabled(), false); +} + +TEST(Counter, Binding) +{ + Counter<int> alpha("test", "none", "unitless", true); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + std::map<std::string, std::string> labels2 = {{"key2", "value2"}, {"key3", "value3"}}; + std::map<std::string, std::string> labels3 = {{"key3", "value3"}, {"key2", "value2"}}; + + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + auto labelkv2 = common::KeyValueIterableView<decltype(labels2)>{labels2}; + auto labelkv3 = common::KeyValueIterableView<decltype(labels3)>{labels3}; + + auto beta = alpha.bindCounter(labelkv); + auto gamma = alpha.bindCounter(labelkv1); + auto delta = alpha.bindCounter(labelkv1); + auto epsilon = alpha.bindCounter(labelkv1); + auto zeta = alpha.bindCounter(labelkv2); + auto eta = alpha.bindCounter(labelkv3); + + EXPECT_EQ(beta->get_ref(), 1); + EXPECT_EQ(gamma->get_ref(), 3); + EXPECT_EQ(eta->get_ref(), 2); + + delta->unbind(); + gamma->unbind(); + epsilon->unbind(); + + EXPECT_EQ(alpha.boundInstruments_[KvToString(labelkv1)]->get_ref(), 0); + EXPECT_EQ(alpha.boundInstruments_.size(), 3); +} + +TEST(Counter, getAggsandnewupdate) +{ + Counter<int> alpha("test", "none", "unitless", true); + + std::map<std::string, std::string> labels = {{"key3", "value3"}, {"key2", "value2"}}; + + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto beta = alpha.bindCounter(labelkv); + beta->add(1); + beta->unbind(); + + EXPECT_EQ(alpha.boundInstruments_[KvToString(labelkv)]->get_ref(), 0); + EXPECT_EQ(alpha.boundInstruments_.size(), 1); + + auto theta = alpha.GetRecords(); + EXPECT_EQ(theta.size(), 1); + EXPECT_EQ(theta[0].GetName(), "test"); + EXPECT_EQ(theta[0].GetDescription(), "none"); + EXPECT_EQ(theta[0].GetLabels(), "{key2:value2,key3:value3}"); +} + +void CounterCallback(std::shared_ptr<Counter<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(1, labels); + } +} + +TEST(Counter, StressAdd) +{ + std::shared_ptr<Counter<int>> alpha(new Counter<int>("test", "none", "unitless", true)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(CounterCallback, alpha, 1000, labelkv); + std::thread second(CounterCallback, alpha, 1000, labelkv); + std::thread third(CounterCallback, alpha, 3000, labelkv1); + + first.join(); + second.join(); + third.join(); + + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundCounter<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 2000); + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundCounter<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + 3000); +} + +void UpDownCounterCallback(std::shared_ptr<UpDownCounter<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(1, labels); + } +} + +void NegUpDownCounterCallback(std::shared_ptr<UpDownCounter<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(-1, labels); + } +} + +TEST(IntUpDownCounter, StressAdd) +{ + std::shared_ptr<UpDownCounter<int>> alpha( + new UpDownCounter<int>("test", "none", "unitless", true)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(UpDownCounterCallback, alpha, 12340, + labelkv); // spawn new threads that call the callback + std::thread second(UpDownCounterCallback, alpha, 12340, labelkv); + std::thread third(UpDownCounterCallback, alpha, 56780, labelkv1); + std::thread fourth(NegUpDownCounterCallback, alpha, 12340, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundUpDownCounter<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 12340 * 2); + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundUpDownCounter<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + 56780 - 12340); +} + +void RecorderCallback(std::shared_ptr<ValueRecorder<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->record(i, labels); + } +} + +void NegRecorderCallback(std::shared_ptr<ValueRecorder<int>> in, + int freq, + const common::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->record(-i, labels); + } +} + +TEST(IntValueRecorder, StressRecord) +{ + std::shared_ptr<ValueRecorder<int>> alpha( + new ValueRecorder<int>("test", "none", "unitless", true)); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + std::map<std::string, std::string> labels1 = {{"key1", "value1"}}; + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + auto labelkv1 = common::KeyValueIterableView<decltype(labels1)>{labels1}; + + std::thread first(RecorderCallback, alpha, 25, + labelkv); // spawn new threads that call the callback + std::thread second(RecorderCallback, alpha, 50, labelkv); + std::thread third(RecorderCallback, alpha, 25, labelkv1); + std::thread fourth(NegRecorderCallback, alpha, 100, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 0); // min + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[1], + 49); // max + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[2], + 1525); // sum + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[3], + 75); // count + + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + -99); // min + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[1], + 24); // max + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[2], + -4650); // sum + EXPECT_EQ(METRICS_TEST_TYPE_CAST(BoundValueRecorder<int> *, + alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[3], + 125); // count +} + +TEST(Instruments, NoUpdateNoRecord) +{ + // This test verifies that instruments that have received no updates + // in the last collection period are not made into records for export. + + Counter<int> alpha("alpha", "no description", "unitless", true); + + std::map<std::string, std::string> labels = {{"key", "value"}}; + + auto labelkv = common::KeyValueIterableView<decltype(labels)>{labels}; + + EXPECT_EQ(alpha.GetRecords().size(), 0); + alpha.add(1, labelkv); + EXPECT_EQ(alpha.GetRecords().size(), 1); + + UpDownCounter<int> beta("beta", "no description", "unitless", true); + + EXPECT_EQ(beta.GetRecords().size(), 0); + beta.add(1, labelkv); + EXPECT_EQ(beta.GetRecords().size(), 1); + + ValueRecorder<int> gamma("gamma", "no description", "unitless", true); + + EXPECT_EQ(gamma.GetRecords().size(), 0); + gamma.record(1, labelkv); + EXPECT_EQ(gamma.GetRecords().size(), 1); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/min_max_sum_count_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/min_max_sum_count_aggregator_test.cc new file mode 100644 index 000000000..3b657d955 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/min_max_sum_count_aggregator_test.cc @@ -0,0 +1,209 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include <thread> + +# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h" + +using namespace opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; + +TEST(MinMaxSumCountAggregator, Update) +{ + // This tests that the aggregator updates the maintained value correctly + // after a call to the update() function. + MinMaxSumCountAggregator<int> agg(metrics_api::InstrumentKind::Counter); + auto value_set = agg.get_values(); + ASSERT_EQ(value_set[0], 0); + ASSERT_EQ(value_set[1], 0); + ASSERT_EQ(value_set[2], 0); + ASSERT_EQ(value_set[3], 0); + + // 1 + 2 + 3 + ... + 10 = 55 + for (int i = 1; i <= 10; ++i) + { + agg.update(i); + } + + value_set = agg.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[1], 10); // max + ASSERT_EQ(value_set[2], 55); // sum + ASSERT_EQ(value_set[3], 10); // count +} + +TEST(MinMaxSumCountAggregator, FirstUpdate) +{ + // This tests that the aggregator appropriately maintains the min and + // max values after a single update call. + MinMaxSumCountAggregator<int> agg(metrics_api::InstrumentKind::Counter); + agg.update(1); + auto value_set = agg.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[1], 1); // max + ASSERT_EQ(value_set[2], 1); // sum + ASSERT_EQ(value_set[3], 1); // count +} + +TEST(MinMaxSumCountAggregator, Checkpoint) +{ + // This test verifies that the default checkpoint is set correctly + // and that the checkpoint values update correctly after a call + // to the checkpoint() function. + MinMaxSumCountAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + // Verify that the default checkpoint is set correctly. + auto checkpoint_set = agg.get_checkpoint(); + ASSERT_EQ(checkpoint_set[0], 0); // min + ASSERT_EQ(checkpoint_set[1], 0); // max + ASSERT_EQ(checkpoint_set[2], 0); // sum + ASSERT_EQ(checkpoint_set[3], 0); // count + + // 1 + 2 + 3 + ... + 10 = 55 + for (int i = 1; i <= 10; ++i) + { + agg.update(i); + } + + agg.checkpoint(); + + // Verify that the checkpoint values were updated. + checkpoint_set = agg.get_checkpoint(); + ASSERT_EQ(checkpoint_set[0], 1); // min + ASSERT_EQ(checkpoint_set[1], 10); // max + ASSERT_EQ(checkpoint_set[2], 55); // sum + ASSERT_EQ(checkpoint_set[3], 10); // count + + // Verify that the current values were reset to the default state. + auto value_set = agg.get_values(); + ASSERT_EQ(value_set[0], 0); // min + ASSERT_EQ(value_set[1], 0); // max + ASSERT_EQ(value_set[2], 0); // sum + ASSERT_EQ(value_set[3], 0); // count +} + +TEST(MinMaxSumCountAggregator, Merge) +{ + // This tests that the values_ vector is updated correctly after + // two aggregators are merged together. + MinMaxSumCountAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + MinMaxSumCountAggregator<int> agg2(metrics_api::InstrumentKind::Counter); + + // 1 + 2 + 3 + ... + 10 = 55 + for (int i = 1; i <= 10; ++i) + { + agg1.update(i); + } + + // 1 + 2 + 3 + ... + 20 = 210 + for (int i = 1; i <= 20; ++i) + { + agg2.update(i); + } + + agg1.merge(agg2); + + // Verify that the current values were changed by the merge. + auto value_set = agg1.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[1], 20); // max + ASSERT_EQ(value_set[2], 265); // sum + ASSERT_EQ(value_set[3], 30); // count +} + +TEST(MinMaxSumCountAggregator, BadMerge) +{ + // This verifies that we encounter and error when we try to merge + // two aggregators of different numeric types together. + MinMaxSumCountAggregator<int> agg1(metrics_api::InstrumentKind::Counter); + MinMaxSumCountAggregator<int> agg2(metrics_api::InstrumentKind::ValueRecorder); + + agg1.update(1); + agg2.update(2); + + agg1.merge(agg2); + + // Verify that the values did NOT merge + auto value_set = agg1.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[0], 1); // max + ASSERT_EQ(value_set[0], 1); // sum + ASSERT_EQ(value_set[0], 1); // count +} + +TEST(MinMaxSumCountAggregator, Types) +{ + // This test verifies that we do not encounter any errors when + // using various numeric types. + MinMaxSumCountAggregator<int> agg_int(metrics_api::InstrumentKind::Counter); + MinMaxSumCountAggregator<long> agg_long(metrics_api::InstrumentKind::Counter); + MinMaxSumCountAggregator<float> agg_float(metrics_api::InstrumentKind::Counter); + MinMaxSumCountAggregator<double> agg_double(metrics_api::InstrumentKind::Counter); + + for (int i = 1; i <= 10; ++i) + { + agg_int.update(i); + agg_long.update(i); + } + + for (float i = 1.0; i <= 10.0; i += 1) + { + agg_float.update(i); + agg_double.update(i); + } + + auto value_set = agg_int.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[1], 10); // max + ASSERT_EQ(value_set[2], 55); // sum + ASSERT_EQ(value_set[3], 10); // count + + auto value_set2 = agg_long.get_values(); + ASSERT_EQ(value_set[0], 1); // min + ASSERT_EQ(value_set[1], 10); // max + ASSERT_EQ(value_set[2], 55); // sum + ASSERT_EQ(value_set[3], 10); // count + + auto value_set3 = agg_float.get_values(); + ASSERT_EQ(value_set[0], 1.0); // min + ASSERT_EQ(value_set[1], 10.0); // max + ASSERT_EQ(value_set[2], 55.0); // sum + ASSERT_EQ(value_set[3], 10); // count + + auto value_set4 = agg_double.get_values(); + ASSERT_EQ(value_set[0], 1.0); // min + ASSERT_EQ(value_set[1], 10.0); // max + ASSERT_EQ(value_set[2], 55.0); // sum + ASSERT_EQ(value_set[3], 10); // count +} + +static void callback(MinMaxSumCountAggregator<int> &agg) +{ + // 1 + 2 + ... + 10000 = 50005000 + for (int i = 1; i <= 10000; ++i) + { + agg.update(i); + } +} + +TEST(MinMaxSumCountAggregator, Concurrency) +{ + // This test checks that the aggregator updates appropriately + // when called in a multi-threaded context. + MinMaxSumCountAggregator<int> agg(metrics_api::InstrumentKind::Counter); + + std::thread first(&callback, std::ref(agg)); + std::thread second(&callback, std::ref(agg)); + + first.join(); + second.join(); + + auto value_set = agg.get_values(); + ASSERT_EQ(value_set[0], 1); + ASSERT_EQ(value_set[1], 10000); + ASSERT_EQ(value_set[2], 2 * 50005000); + ASSERT_EQ(value_set[3], 2 * 10000); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/sketch_aggregator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/sketch_aggregator_test.cc new file mode 100644 index 000000000..6dc2fbef3 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/sketch_aggregator_test.cc @@ -0,0 +1,254 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h" + +# include <gtest/gtest.h> +# include <iostream> +# include <numeric> +# include <thread> + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +// Test updating with a uniform set of updates +TEST(Sketch, UniformValues) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .000005); + + EXPECT_EQ(alpha.get_aggregator_kind(), AggregatorKind::Sketch); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint().size(), 2); + EXPECT_EQ(alpha.get_boundaries().size(), 0); + EXPECT_EQ(alpha.get_counts().size(), 0); + + for (int i = 0; i < 60; i++) + { + alpha.update(i); + } + + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_boundaries().size(), 60); + EXPECT_EQ(alpha.get_counts().size(), 60); + + EXPECT_EQ(alpha.get_checkpoint()[0], 1770); + EXPECT_EQ(alpha.get_checkpoint()[1], 60); +} + +// Test updating with a normal distribution +TEST(Sketch, NormalValues) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .0005); + + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size()); + + std::vector<int> correct = {1, 2, 3, 4, 3, 2, 1}; + EXPECT_EQ(alpha.get_counts(), correct); + + std::vector<double> captured_bounds = alpha.get_boundaries(); + for (size_t i = 0; i < captured_bounds.size(); i++) + { + captured_bounds[i] = round(captured_bounds[i]); + } + + // It is not guaranteed that bounds are correct once the bucket sizes pass 1000 + std::vector<double> correct_bounds = {1, 3, 5, 7, 9, 11, 13}; + EXPECT_EQ(captured_bounds, correct_bounds); +} + +int randVal() +{ + return rand() % 100000; +} + +/** Note that in this case, "Large" refers to a number of distinct values which exceed the maximum + * number of allowed buckets. + */ +TEST(Sketch, QuantileSmall) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .00005); + + std::vector<int> vals1(2048); + std::generate(vals1.begin(), vals1.end(), randVal); + + std::vector<int> vals2(2048); + std::generate(vals1.begin(), vals1.end(), randVal); + + for (int i : vals1) + { + alpha.update(i); + } + alpha.checkpoint(); + std::sort(vals1.begin(), vals1.end()); + + EXPECT_TRUE(abs(alpha.get_quantiles(.25) - vals1[2048 * .25 - 1]) <= 10); + EXPECT_TRUE(abs(alpha.get_quantiles(.50) - vals1[2048 * .50 - 1]) <= 10); + EXPECT_TRUE(abs(alpha.get_quantiles(.75) - vals1[2048 * .75 - 1]) <= 10); +} + +TEST(Sketch, UpdateQuantileLarge) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .0005, 7); + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + // This addition should trigger the "1" and "3" buckets to merge + alpha.update(15); + alpha.checkpoint(); + + std::vector<int> correct = {3, 3, 4, 3, 2, 1, 1}; + EXPECT_EQ(alpha.get_counts(), correct); + + for (int i : vals) + { + alpha.update(i); + } + alpha.update(15); + alpha.update(17); + alpha.checkpoint(); + + correct = {6, 4, 3, 2, 1, 1, 1}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +TEST(Sketch, MergeSmall) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .0005); + SketchAggregator<int> beta(metrics_api::InstrumentKind::ValueRecorder, .0005); + + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + std::vector<int> otherVals{1, 1, 1, 1, 11, 11, 13, 13, 13, 15}; + for (int i : otherVals) + { + beta.update(i); + } + + alpha.merge(beta); + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0) + + std::accumulate(otherVals.begin(), otherVals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size() + otherVals.size()); + + std::vector<int> correct = {5, 2, 3, 4, 3, 4, 4, 1}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +TEST(Sketch, MergeLarge) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .0005, 7); + SketchAggregator<int> beta(metrics_api::InstrumentKind::ValueRecorder, .0005, 7); + + std::vector<int> vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + std::vector<int> otherVals{1, 1, 1, 1, 11, 11, 13, 13, 13, 15}; + for (int i : otherVals) + { + beta.update(i); + } + + alpha.merge(beta); + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0) + + std::accumulate(otherVals.begin(), otherVals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size() + otherVals.size()); + + std::vector<int> correct = {7, 3, 4, 3, 4, 4, 1}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +// Update callback used to validate multi-threaded performance +void sketchUpdateCallback(Aggregator<int> &agg, std::vector<int> vals) +{ + for (int i : vals) + { + agg.update(i); + } +} + +TEST(Sketch, Concurrency) +{ + SketchAggregator<int> alpha(metrics_api::InstrumentKind::ValueRecorder, .0005, 20); + + std::vector<int> vals1(1000); + std::generate(vals1.begin(), vals1.end(), randVal); + + std::vector<int> vals2(1000); + std::generate(vals2.begin(), vals2.end(), randVal); + + std::thread first(sketchUpdateCallback, std::ref(alpha), vals1); + std::thread second(sketchUpdateCallback, std::ref(alpha), vals2); + + first.join(); + second.join(); + + SketchAggregator<int> beta(metrics_api::InstrumentKind::ValueRecorder, .0005, 20); + + for (int i : vals1) + { + beta.update(i); + } + for (int i : vals2) + { + beta.update(i); + } + + alpha.checkpoint(); + beta.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint(), beta.get_checkpoint()); + EXPECT_EQ(alpha.get_counts(), beta.get_counts()); + EXPECT_EQ(alpha.get_boundaries(), beta.get_boundaries()); +} + +# if __EXCEPTIONS + +TEST(Sketch, Errors) +{ + + SketchAggregator<int> tol1(metrics_api::InstrumentKind::ValueRecorder, .000005); + SketchAggregator<int> tol2(metrics_api::InstrumentKind::ValueRecorder, .005); + SketchAggregator<int> sz1(metrics_api::InstrumentKind::ValueRecorder, .000005, 2938); + SketchAggregator<int> sz2(metrics_api::InstrumentKind::ValueRecorder, .000005); + + EXPECT_ANY_THROW(tol1.merge(tol2)); + EXPECT_ANY_THROW(sz1.merge(sz2)); + EXPECT_ANY_THROW(tol1.get_quantiles(-.000001)); + EXPECT_ANY_THROW(tol1.get_quantiles(1.000001)); +} + +# endif + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/ungrouped_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/ungrouped_processor_test.cc new file mode 100644 index 000000000..934bfe23e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/_metrics/ungrouped_processor_test.cc @@ -0,0 +1,601 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/_metrics/ungrouped_processor.h" +# include <gtest/gtest.h> +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +/* Test that CheckpointSelf() will return the amount of unique records in it, then + call FinishedCollection and see the map reset */ +TEST(UngroupedMetricsProcessor, UngroupedProcessorFinishedCollectionStateless) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(false)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5.5); + aggregator->checkpoint(); + + aggregator2->update(500.4); + aggregator2->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + // Must have different (name, description, label, instrument) to map to + metric_sdk::Record r2("name2", "description2", "labels2", aggregator2); + + processor->process(r); + processor->process(r2); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint.size(), 2); + + processor->FinishedCollection(); + + checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint.size(), 0); +} + +/* Test that CheckpointSelf() will return the amount of unique records in it, then + call FinishedCollection and see the map stay the same */ +TEST(UngroupedMetricsProcessor, UngroupedProcessorFinishedCollectionStateful) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5.5); + aggregator->checkpoint(); + + aggregator2->update(500.4); + aggregator2->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + // Must have different (name, description, label, instrument) to map to + metric_sdk::Record r2("name2", "description2", "labels2", aggregator2); + + processor->process(r); + processor->process(r2); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint.size(), 2); + + processor->FinishedCollection(); + + ASSERT_EQ(checkpoint.size(), 2); +} + +// Test to make sure we keep information from record(short) that goes through process() +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatelessShort) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(false)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::CounterAggregator<short>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(4); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(checkpoint[0].GetAggregator()), + aggregator); +} + +// Test to make sure we keep information from record(int) that goes through process() +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatelessInt) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(false)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::CounterAggregator<int>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()), + aggregator); +} + +// Test to make sure we keep information from record(float) that goes through process() +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatelessFloat) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(false)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<float>>( + new metric_sdk::CounterAggregator<float>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(8.5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(checkpoint[0].GetAggregator()), + aggregator); +} + +// Test to make sure we keep information from record(double) that goes through process() +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatelessDouble) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(false)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5.5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()), + aggregator); +} + +/** + * The following tests are for the Stateful version of the processor. These tests will make sure + * that when we send the same aggreagtor twice through process(), that the values will be merged. + * We can easily recreate this expected value by making a test aggregator that is updated through + * both process functions but only checkpointed at the end. + */ +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulShort) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::CounterAggregator<short>(metrics_api::InstrumentKind::Counter)); + + auto aggregator_test = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::CounterAggregator<short>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5); + aggregator_test->update(5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(4); + aggregator_test->update(4); + aggregator->checkpoint(); + aggregator_test->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint()[0], + aggregator_test->get_checkpoint()[0]); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulInt) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::CounterAggregator<int>(metrics_api::InstrumentKind::Counter)); + + auto aggregator_test = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::CounterAggregator<int>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5); + aggregator_test->update(5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(4); + aggregator_test->update(4); + aggregator->checkpoint(); + aggregator_test->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint()[0], + aggregator_test->get_checkpoint()[0]); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulFloat) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<float>>( + new metric_sdk::CounterAggregator<float>(metrics_api::InstrumentKind::Counter)); + + auto aggregator_test = std::shared_ptr<metric_sdk::Aggregator<float>>( + new metric_sdk::CounterAggregator<float>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5); + aggregator_test->update(5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(4); + aggregator_test->update(4); + aggregator->checkpoint(); + aggregator_test->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint()[0], + aggregator_test->get_checkpoint()[0]); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulDouble) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + auto aggregator_test = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5.5); + aggregator_test->update(5.5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(4.4); + aggregator_test->update(4.4); + aggregator->checkpoint(); + aggregator_test->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint()[0], + aggregator_test->get_checkpoint()[0]); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulMinMaxSumCount) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::MinMaxSumCountAggregator<double>(metrics_api::InstrumentKind::Counter)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::MinMaxSumCountAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(1.1); + aggregator->update(2.2); + aggregator2->update(1.1); + aggregator2->update(2.2); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(5.5); + aggregator2->update(5.5); + aggregator->checkpoint(); + aggregator2->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint2[0].GetAggregator()) + ->get_checkpoint(), + aggregator2->get_checkpoint()); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulGauge) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::GaugeAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(1.1); + aggregator->update(2.2); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(5.4); + aggregator->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint2[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulExact) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::ExactAggregator<double>(metrics_api::InstrumentKind::Counter, false)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::ExactAggregator<double>(metrics_api::InstrumentKind::Counter, false)); + + aggregator->update(1.1); + aggregator->update(2.2); + aggregator2->update(1.1); + aggregator2->update(2.2); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint[0].GetAggregator()) + ->get_checkpoint(), + aggregator->get_checkpoint()); + + aggregator->update(5.4); + aggregator2->update(5.4); + aggregator->checkpoint(); + aggregator2->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ( + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(checkpoint2[0].GetAggregator()) + ->get_checkpoint(), + aggregator2->get_checkpoint()); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulHistogram) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + std::vector<double> boundaries{10, 20, 30, 40, 50}; + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::HistogramAggregator<int>(metrics_api::InstrumentKind::Counter, boundaries)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::HistogramAggregator<int>(metrics_api::InstrumentKind::Counter, boundaries)); + + for (int i = 0; i < 60; i++) + { + aggregator->update(i); + aggregator2->update(i); + } + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_boundaries(), + aggregator->get_boundaries()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_counts(), + aggregator->get_counts()); + + for (int i = 0; i < 60; i++) + { + aggregator->update(i); + aggregator2->update(i); + } + aggregator->checkpoint(); + aggregator2->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + + ASSERT_EQ(checkpoint2.size(), 1); + ASSERT_EQ(checkpoint2[0].GetName(), "name"); + ASSERT_EQ(checkpoint2[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint2[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_boundaries(), + aggregator->get_boundaries()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_counts(), + aggregator2->get_counts()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_checkpoint(), + aggregator2->get_checkpoint()); +} + +TEST(UngroupedMetricsProcessor, UngroupedProcessorKeepsRecordInformationStatefulSketch) +{ + auto processor = std::unique_ptr<metric_sdk::MetricsProcessor>( + new metric_sdk::UngroupedMetricsProcessor(true)); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::SketchAggregator<int>(metrics_api::InstrumentKind::Counter, .00005)); + + auto test_aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::SketchAggregator<int>(metrics_api::InstrumentKind::Counter, .00005)); + + for (int i = 0; i < 60; i++) + { + aggregator->update(i); + test_aggregator->update(i); + } + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint[0].GetName(), "name"); + ASSERT_EQ(checkpoint[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_boundaries(), + aggregator->get_boundaries()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint[0].GetAggregator()) + ->get_counts(), + aggregator->get_counts()); + + for (int i = 0; i < 60; i++) + { + aggregator->update(i); + test_aggregator->update(i); + } + aggregator->checkpoint(); + test_aggregator->checkpoint(); + + processor->process(r); + + std::vector<metric_sdk::Record> checkpoint2 = processor->CheckpointSelf(); + ASSERT_EQ(checkpoint2[0].GetName(), "name"); + ASSERT_EQ(checkpoint2[0].GetLabels(), "labels"); + ASSERT_EQ(checkpoint2[0].GetDescription(), "description"); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_boundaries(), + test_aggregator->get_boundaries()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_counts(), + test_aggregator->get_counts()); + ASSERT_EQ(nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(checkpoint2[0].GetAggregator()) + ->get_checkpoint(), + test_aggregator->get_checkpoint()); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/BUILD new file mode 100644 index 000000000..91d56996f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/BUILD @@ -0,0 +1,153 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "random_test", + srcs = [ + "random_test.cc", + ], + tags = ["test"], + deps = [ + "//sdk/src/common:random", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "fast_random_number_generator_test", + srcs = [ + "fast_random_number_generator_test.cc", + ], + tags = ["test"], + deps = [ + "//sdk/src/common:random", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "random_benchmark", + srcs = ["random_benchmark.cc"], + tags = ["test"], + deps = ["//sdk/src/common:random"], +) + +cc_test( + name = "atomic_unique_ptr_test", + srcs = [ + "atomic_unique_ptr_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "circular_buffer_range_test", + srcs = [ + "circular_buffer_range_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "random_fork_test", + srcs = [ + "random_fork_test.cc", + ], + tags = ["test"], + deps = [ + "//sdk/src/common:random", + ], +) + +cc_library( + name = "baseline_circular_buffer", + hdrs = [ + "baseline_circular_buffer.h", + ], + include_prefix = "test/common", + deps = [ + "//api", + ], +) + +otel_cc_benchmark( + name = "circular_buffer_benchmark", + srcs = ["circular_buffer_benchmark.cc"], + tags = ["test"], + deps = [ + ":baseline_circular_buffer", + "//sdk:headers", + ], +) + +cc_test( + name = "empty_attributes_test", + srcs = [ + "empty_attributes_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "attribute_utils_test", + srcs = [ + "attribute_utils_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "global_log_handle_test", + srcs = [ + "global_log_handle_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "//sdk/src/common:global_log_handler", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "attributemap_hash_test", + srcs = [ + "attributemap_hash_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "attributemap_hash_benchmark", + srcs = ["attributemap_hash_benchmark.cc"], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/CMakeLists.txt new file mode 100644 index 000000000..0fefc86b1 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/CMakeLists.txt @@ -0,0 +1,37 @@ +foreach( + testname + random_test + fast_random_number_generator_test + atomic_unique_ptr_test + circular_buffer_range_test + circular_buffer_test + attribute_utils_test + attributemap_hash_test + global_log_handle_test) + + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_common opentelemetry_trace) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX trace. + TEST_LIST ${testname}) +endforeach() + +add_executable(random_fork_test random_fork_test.cc) +target_link_libraries(random_fork_test opentelemetry_common) +add_test(random_fork_test random_fork_test) + +add_executable(random_benchmark random_benchmark.cc) +target_link_libraries(random_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common) + +add_executable(circular_buffer_benchmark circular_buffer_benchmark.cc) +target_link_libraries(circular_buffer_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + +add_executable(attributemap_hash_benchmark attributemap_hash_benchmark.cc) +target_link_libraries(attributemap_hash_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/atomic_unique_ptr_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/atomic_unique_ptr_test.cc new file mode 100644 index 000000000..aa6d88a00 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/atomic_unique_ptr_test.cc @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/atomic_unique_ptr.h" + +#include <gtest/gtest.h> +using opentelemetry::sdk::common::AtomicUniquePtr; + +TEST(AtomicUniquePtrTest, SwapIfNullWithNull) +{ + AtomicUniquePtr<int> ptr; + EXPECT_TRUE(ptr.IsNull()); + + std::unique_ptr<int> x{new int{33}}; + EXPECT_TRUE(ptr.SwapIfNull(x)); + EXPECT_EQ(x, nullptr); +} + +TEST(AtomicUniquePtrTest, SwapIfNullWithNonNull) +{ + AtomicUniquePtr<int> ptr; + ptr.Reset(new int{11}); + std::unique_ptr<int> x{new int{33}}; + EXPECT_TRUE(!ptr.SwapIfNull(x)); + EXPECT_NE(x, nullptr); + EXPECT_EQ(*x, 33); + EXPECT_EQ(*ptr, 11); +} + +TEST(AtomicUniquePtrTest, Swap) +{ + AtomicUniquePtr<int> ptr; + EXPECT_TRUE(ptr.IsNull()); + + ptr.Reset(new int{11}); + std::unique_ptr<int> x{new int{33}}; + ptr.Swap(x); + EXPECT_FALSE(ptr.IsNull()); + EXPECT_NE(x, nullptr); + EXPECT_EQ(*x, 11); + EXPECT_EQ(*ptr, 33); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attribute_utils_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attribute_utils_test.cc new file mode 100644 index 000000000..b7ef17244 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attribute_utils_test.cc @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/attribute_utils.h" + +#include <gtest/gtest.h> + +TEST(AttributeMapTest, DefaultConstruction) +{ + + opentelemetry::sdk::common::AttributeMap attribute_map; + EXPECT_EQ(attribute_map.GetAttributes().size(), 0); +} + +TEST(OrderedAttributeMapTest, DefaultConstruction) +{ + opentelemetry::sdk::common::OrderedAttributeMap attribute_map; + EXPECT_EQ(attribute_map.GetAttributes().size(), 0); +} + +TEST(AttributeMapTest, AttributesConstruction) +{ + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {15, 24, 37}; + std::map<std::string, int> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>> iterable(attributes); + opentelemetry::sdk::common::AttributeMap attribute_map(iterable); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(opentelemetry::nostd::get<int>(attribute_map.GetAttributes().at(keys[i])), values[i]); + } +} + +TEST(OrderedAttributeMapTest, AttributesConstruction) +{ + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {15, 24, 37}; + std::map<std::string, int> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>> iterable(attributes); + opentelemetry::sdk::common::OrderedAttributeMap attribute_map(iterable); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(opentelemetry::nostd::get<int>(attribute_map.GetAttributes().at(keys[i])), values[i]); + } +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_benchmark.cc new file mode 100644 index 000000000..811ecb23d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_benchmark.cc @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <benchmark/benchmark.h> +#include "opentelemetry/sdk/common/attributemap_hash.h" + +using namespace opentelemetry::sdk::common; +namespace +{ +void BM_AttributeMapHash(benchmark::State &state) +{ + OrderedAttributeMap map1 = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}, {"k4", "v4"}, + {"k5", true}, {"k6", 12}, {"k7", 12.209}}; + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(GetHashForAttributeMap(map1)); + } +} +BENCHMARK(BM_AttributeMapHash); + +} // namespace +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_test.cc new file mode 100644 index 000000000..7d2748670 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/attributemap_hash_test.cc @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/attributemap_hash.h" +#include <gtest/gtest.h> + +using namespace opentelemetry::sdk::common; +TEST(AttributeMapHashTest, BasicTests) +{ + { + OrderedAttributeMap map1 = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; + OrderedAttributeMap map2 = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}, {"k4", "v4"}}; + OrderedAttributeMap map3 = {{"k3", "v3"}, {"k1", "v1"}, {"k2", "v2"}}; + + EXPECT_TRUE(GetHashForAttributeMap(map1) != 0); + EXPECT_TRUE(GetHashForAttributeMap(map1) == GetHashForAttributeMap(map1)); + EXPECT_TRUE(GetHashForAttributeMap(map1) != GetHashForAttributeMap(map2)); + EXPECT_TRUE(GetHashForAttributeMap(map1) == GetHashForAttributeMap(map3)); + } + + { + OrderedAttributeMap map1 = {{"k1", 10}, {"k2", true}, {"k3", 12.22}}; + OrderedAttributeMap map2 = {{"k3", 12.22}, {"k1", 10}, {"k2", true}}; + EXPECT_TRUE(GetHashForAttributeMap(map1) == GetHashForAttributeMap(map2)); + EXPECT_TRUE(GetHashForAttributeMap(map1) != 0); + } + + { + OrderedAttributeMap map1 = {}; + EXPECT_TRUE(GetHashForAttributeMap(map1) == 0); + } +}
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/baseline_circular_buffer.h b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/baseline_circular_buffer.h new file mode 100644 index 000000000..398a4d038 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/baseline_circular_buffer.h @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <cstdint> +#include <memory> +#include <mutex> +#include <vector> + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace testing +{ +/** + * A locking circular buffer. + * + * Used as a baseline in benchmarking. + */ +template <class T> +class BaselineCircularBuffer +{ +public: + explicit BaselineCircularBuffer(size_t max_size) : data_{max_size} {} + + /** + * Add an element to the circular buffer. + * @param element the element to add + * @return true if the element was added successfully + */ + bool Add(std::unique_ptr<T> &element) noexcept { return this->Add(std::move(element)); } + + bool Add(std::unique_ptr<T> &&element) noexcept + { + std::lock_guard<std::mutex> lock_guard{mutex_}; + if (tail_ + data_.size() == head_) + { + return false; + } + data_[head_ % data_.size()] = std::move(element); + head_ += 1; + return true; + } + + /** + * Consume elements in the circular buffer. + * @param f the callback to call for each element + */ + template <class F> + void Consume(F f) noexcept + { + std::lock_guard<std::mutex> lock_guard{mutex_}; + if (head_ == tail_) + { + return; + } + auto tail_index = tail_ % data_.size(); + auto head_index = head_ % data_.size(); + if (tail_index < head_index) + { + for (auto i = tail_index; i < head_index; ++i) + { + f(std::move(data_[i])); + } + } + else + { + for (auto i = tail_index; i < data_.size(); ++i) + { + f(std::move(data_[i])); + } + for (auto i = 0ull; i < head_index; ++i) + { + f(std::move(data_[i])); + } + } + tail_ = head_; + } + +private: + std::mutex mutex_; + uint64_t head_{0}; + uint64_t tail_{0}; + std::vector<std::unique_ptr<T>> data_; +}; +} // namespace testing +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_benchmark.cc new file mode 100644 index 000000000..1f2b9b42c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_benchmark.cc @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "benchmark/benchmark.h" + +#include <atomic> +#include <cstdint> +#include <iostream> +#include <memory> +#include <random> +#include <thread> +#include <vector> + +#include "opentelemetry/sdk/common/circular_buffer.h" +#include "test/common/baseline_circular_buffer.h" +using opentelemetry::sdk::common::AtomicUniquePtr; +using opentelemetry::sdk::common::CircularBuffer; +using opentelemetry::sdk::common::CircularBufferRange; +using opentelemetry::testing::BaselineCircularBuffer; + +const int N = 10000; + +static uint64_t ConsumeBufferNumbers(BaselineCircularBuffer<uint64_t> &buffer) noexcept +{ + uint64_t result = 0; + buffer.Consume([&](std::unique_ptr<uint64_t> &&x) { + result += *x; + x.reset(); + }); + return result; +} + +static uint64_t ConsumeBufferNumbers(CircularBuffer<uint64_t> &buffer) noexcept +{ + uint64_t result = 0; + buffer.Consume(buffer.size(), + [&](CircularBufferRange<AtomicUniquePtr<uint64_t>> &range) noexcept { + range.ForEach([&](AtomicUniquePtr<uint64_t> &ptr) noexcept { + result += *ptr; + ptr.Reset(); + return true; + }); + }); + return result; +} + +template <class Buffer> +static void GenerateNumbersForThread(Buffer &buffer, int n, std::atomic<uint64_t> &sum) noexcept +{ + thread_local std::mt19937_64 random_number_generator{std::random_device{}()}; + for (int i = 0; i < n; ++i) + { + auto x = random_number_generator(); + std::unique_ptr<uint64_t> element{new uint64_t{x}}; + if (buffer.Add(element)) + { + sum += x; + } + } +} + +template <class Buffer> +static uint64_t GenerateNumbers(Buffer &buffer, int num_threads, int n) noexcept +{ + std::atomic<uint64_t> sum{0}; + std::vector<std::thread> threads(num_threads); + for (auto &thread : threads) + { + thread = std::thread{GenerateNumbersForThread<Buffer>, std::ref(buffer), n, std::ref(sum)}; + } + for (auto &thread : threads) + { + thread.join(); + } + return sum; +} + +template <class Buffer> +static void ConsumeNumbers(Buffer &buffer, uint64_t &sum, std::atomic<bool> &finished) noexcept +{ + while (!finished) + { + sum += ConsumeBufferNumbers(buffer); + } + sum += ConsumeBufferNumbers(buffer); +} + +template <class Buffer> +static void RunSimulation(Buffer &buffer, int num_threads, int n) noexcept +{ + std::atomic<bool> finished{false}; + uint64_t consumer_sum{0}; + std::thread consumer_thread{ConsumeNumbers<Buffer>, std::ref(buffer), std::ref(consumer_sum), + std::ref(finished)}; + uint64_t producer_sum = GenerateNumbers(buffer, num_threads, n); + finished = true; + consumer_thread.join(); + if (consumer_sum != producer_sum) + { + std::cerr << "Sumulation failed: consumer_sum != producer_sum\n"; + std::terminate(); + } +} + +static void BM_BaselineBuffer(benchmark::State &state) +{ + const size_t max_elements = 500; + const int num_threads = static_cast<int>(state.range(0)); + const int n = static_cast<int>(N / num_threads); + BaselineCircularBuffer<uint64_t> buffer{max_elements}; + for (auto _ : state) + { + RunSimulation(buffer, num_threads, n); + } +} + +BENCHMARK(BM_BaselineBuffer)->Arg(1)->Arg(2)->Arg(4); + +static void BM_LockFreeBuffer(benchmark::State &state) +{ + const size_t max_elements = 500; + const int num_threads = static_cast<int>(state.range(0)); + const int n = static_cast<int>(N / num_threads); + CircularBuffer<uint64_t> buffer{max_elements}; + for (auto _ : state) + { + RunSimulation(buffer, num_threads, n); + } +} + +BENCHMARK(BM_LockFreeBuffer)->Arg(1)->Arg(2)->Arg(4); + +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_range_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_range_test.cc new file mode 100644 index 000000000..585270468 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_range_test.cc @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/circular_buffer_range.h" + +#include <iterator> + +#include <gtest/gtest.h> +using opentelemetry::sdk::common::CircularBufferRange; + +TEST(CircularBufferRangeTest, ForEach) +{ + int array1[] = {1, 2, 3, 4}; + int array2[] = {5, 6, 7}; + CircularBufferRange<int> range{array1, array2}; + + int x = 0; + range.ForEach([&](int y) { + EXPECT_EQ(++x, y); + return true; + }); + EXPECT_EQ(x, 7); +} + +TEST(CircularBufferRangeTest, ForEachWithExit) +{ + int array1[] = {1, 2, 3, 4}; + int array2[] = {5, 6, 7}; + CircularBufferRange<int> range{array1, array2}; + + int x = 0; + range.ForEach([&](int y) { + EXPECT_EQ(++x, y); + return false; + }); + EXPECT_EQ(x, 1); + + x = 0; + range.ForEach([&](int y) { + EXPECT_EQ(++x, y); + return y != 5; + }); + EXPECT_EQ(x, 5); +} + +TEST(CircularBufferRangeTest, Conversion) +{ + int array1[] = {1, 2, 3, 4}; + int array2[] = {5, 6, 7}; + CircularBufferRange<int> range{array1, array2}; + + CircularBufferRange<const int> range2{range}; + int x = 0; + range2.ForEach([&](int y) { + EXPECT_EQ(++x, y); + return true; + }); + EXPECT_EQ(x, 7); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_test.cc new file mode 100644 index 000000000..a20c3e42a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/circular_buffer_test.cc @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/circular_buffer.h" + +#include <algorithm> +#include <cassert> +#include <random> +#include <thread> + +#include <gtest/gtest.h> +using opentelemetry::sdk::common::AtomicUniquePtr; +using opentelemetry::sdk::common::CircularBuffer; +using opentelemetry::sdk::common::CircularBufferRange; + +static thread_local std::mt19937 RandomNumberGenerator{std::random_device{}()}; + +static void GenerateRandomNumbers(CircularBuffer<uint32_t> &buffer, + std::vector<uint32_t> &numbers, + int n) +{ + for (int i = 0; i < n; ++i) + { + auto value = static_cast<uint32_t>(RandomNumberGenerator()); + std::unique_ptr<uint32_t> x{new uint32_t{value}}; + if (buffer.Add(x)) + { + numbers.push_back(value); + } + } +} + +static void RunNumberProducers(CircularBuffer<uint32_t> &buffer, + std::vector<uint32_t> &numbers, + int num_threads, + int n) +{ + std::vector<std::vector<uint32_t>> thread_numbers(num_threads); + std::vector<std::thread> threads(num_threads); + for (int thread_index = 0; thread_index < num_threads; ++thread_index) + { + threads[thread_index] = std::thread{GenerateRandomNumbers, std::ref(buffer), + std::ref(thread_numbers[thread_index]), n}; + } + for (auto &thread : threads) + { + thread.join(); + } + for (int thread_index = 0; thread_index < num_threads; ++thread_index) + { + numbers.insert(numbers.end(), thread_numbers[thread_index].begin(), + thread_numbers[thread_index].end()); + } +} + +void RunNumberConsumer(CircularBuffer<uint32_t> &buffer, + std::atomic<bool> &exit, + std::vector<uint32_t> &numbers) +{ + while (true) + { + if (exit && buffer.Peek().empty()) + { + return; + } + auto n = std::uniform_int_distribution<size_t>{0, buffer.Peek().size()}(RandomNumberGenerator); + buffer.Consume(n, [&](CircularBufferRange<AtomicUniquePtr<uint32_t>> range) noexcept { + assert(range.size() == n); + range.ForEach([&](AtomicUniquePtr<uint32_t> &ptr) noexcept { + assert(!ptr.IsNull()); + numbers.push_back(*ptr); + ptr.Reset(); + return true; + }); + }); + } +} + +TEST(CircularBufferTest, Add) +{ + CircularBuffer<int> buffer{10}; + + std::unique_ptr<int> x{new int{11}}; + EXPECT_TRUE(buffer.Add(x)); + EXPECT_EQ(x, nullptr); + auto range = buffer.Peek(); + EXPECT_EQ(range.size(), 1); + range.ForEach([](const AtomicUniquePtr<int> &y) { + EXPECT_EQ(*y, 11); + return true; + }); +} + +TEST(CircularBufferTest, Clear) +{ + CircularBuffer<int> buffer{10}; + + std::unique_ptr<int> x{new int{11}}; + EXPECT_TRUE(buffer.Add(x)); + EXPECT_EQ(x, nullptr); + buffer.Clear(); + EXPECT_TRUE(buffer.empty()); +} + +TEST(CircularBufferTest, AddOnFull) +{ + CircularBuffer<int> buffer{10}; + for (int i = 0; i < static_cast<int>(buffer.max_size()); ++i) + { + std::unique_ptr<int> x{new int{i}}; + EXPECT_TRUE(buffer.Add(x)); + } + std::unique_ptr<int> x{new int{33}}; + EXPECT_FALSE(buffer.Add(x)); + EXPECT_NE(x, nullptr); + EXPECT_EQ(*x, 33); +} + +TEST(CircularBufferTest, Consume) +{ + CircularBuffer<int> buffer{10}; + for (int i = 0; i < static_cast<int>(buffer.max_size()); ++i) + { + std::unique_ptr<int> x{new int{i}}; + EXPECT_TRUE(buffer.Add(x)); + } + int count = 0; + buffer.Consume(5, [&](CircularBufferRange<AtomicUniquePtr<int>> range) noexcept { + range.ForEach([&](AtomicUniquePtr<int> &ptr) { + EXPECT_EQ(*ptr, count++); + ptr.Reset(); + return true; + }); + }); + EXPECT_EQ(count, 5); +} + +TEST(CircularBufferTest, Simulation) +{ + const int num_producer_threads = 4; + const int n = 25000; + for (size_t max_size : {1, 2, 10, 50, 100, 1000}) + { + CircularBuffer<uint32_t> buffer{max_size}; + std::vector<uint32_t> producer_numbers; + std::vector<uint32_t> consumer_numbers; + auto producers = std::thread{RunNumberProducers, std::ref(buffer), std::ref(producer_numbers), + num_producer_threads, n}; + std::atomic<bool> exit{false}; + auto consumer = std::thread{RunNumberConsumer, std::ref(buffer), std::ref(exit), + std::ref(consumer_numbers)}; + producers.join(); + exit = true; + consumer.join(); + std::sort(producer_numbers.begin(), producer_numbers.end()); + std::sort(consumer_numbers.begin(), consumer_numbers.end()); + + EXPECT_EQ(producer_numbers.size(), consumer_numbers.size()); + EXPECT_EQ(producer_numbers, consumer_numbers); + } +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/empty_attributes_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/empty_attributes_test.cc new file mode 100644 index 000000000..f37ea0a5c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/empty_attributes_test.cc @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/empty_attributes.h" + +#include <gtest/gtest.h> + +TEST(EmptyAttributesTest, TestSize) +{ + EXPECT_EQ(opentelemetry::sdk::GetEmptyAttributes().size(), 0); +} + +// Test that GetEmptyAttributes() always returns the same KeyValueIterableView +TEST(EmptyAttributesTest, TestMemory) +{ + auto attributes1 = opentelemetry::sdk::GetEmptyAttributes(); + auto attributes2 = opentelemetry::sdk::GetEmptyAttributes(); + EXPECT_EQ(memcmp(&attributes1, &attributes2, sizeof(attributes1)), 0); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/fast_random_number_generator_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/fast_random_number_generator_test.cc new file mode 100644 index 000000000..e9209fdb6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/fast_random_number_generator_test.cc @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "src/common/random.h" + +#include <random> + +#include <gtest/gtest.h> + +using opentelemetry::sdk::common::FastRandomNumberGenerator; + +TEST(FastRandomNumberGeneratorTest, GenerateUniqueNumbers) +{ + std::seed_seq seed_sequence{1, 2, 3}; + FastRandomNumberGenerator random_number_generator; + random_number_generator.seed(seed_sequence); + std::set<uint64_t> values; + for (int i = 0; i < 1000; ++i) + { + EXPECT_TRUE(values.insert(random_number_generator()).second); + } +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/global_log_handle_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/global_log_handle_test.cc new file mode 100644 index 000000000..a38bdc872 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/global_log_handle_test.cc @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/common/global_log_handler.h" + +#include <gtest/gtest.h> + +#include <cstring> + +class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler +{ +public: + void Handle(opentelemetry::sdk::common::internal_log::LogLevel level, + const char *, + int, + const char *msg, + const opentelemetry::sdk::common::AttributeMap &) noexcept override + { + if (level == opentelemetry::sdk::common::internal_log::LogLevel::Debug) + { + EXPECT_EQ(0, strncmp(msg, "Debug message", 13)); + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Error) + { + EXPECT_EQ(0, strncmp(msg, "Error message", 13)); + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Info) + { + EXPECT_EQ(0, strncmp(msg, "Info message", 12)); + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Warning) + { + EXPECT_EQ(0, strncmp(msg, "Warning message", 15)); + } + ++count; + } + + size_t count = 0; +}; + +TEST(GlobalLogHandleTest, CustomLogHandler) +{ + using opentelemetry::sdk::common::internal_log::LogHandler; + auto backup_log_handle = + opentelemetry::sdk::common::internal_log::GlobalLogHandler::GetLogHandler(); + auto backup_log_level = opentelemetry::sdk::common::internal_log::GlobalLogHandler::GetLogLevel(); + + auto custom_log_handler = opentelemetry::nostd::shared_ptr<LogHandler>(new CustomLogHandler{}); + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler(custom_log_handler); + auto before_count = static_cast<CustomLogHandler *>(custom_log_handler.get())->count; + opentelemetry::sdk::common::AttributeMap attributes = { + {"url", "https://opentelemetry.io/"}, {"content-length", 0}, {"content-type", "text/html"}}; + OTEL_INTERNAL_LOG_ERROR("Error message"); + OTEL_INTERNAL_LOG_DEBUG("Debug message. Headers:", attributes); + EXPECT_EQ(before_count + 1, static_cast<CustomLogHandler *>(custom_log_handler.get())->count); + + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel( + opentelemetry::sdk::common::internal_log::LogLevel::Debug); + OTEL_INTERNAL_LOG_ERROR("Error message"); + OTEL_INTERNAL_LOG_DEBUG("Debug message. Headers:", attributes); + OTEL_INTERNAL_LOG_INFO("Info message"); + OTEL_INTERNAL_LOG_WARN("Warning message"); + EXPECT_EQ(before_count + 5, static_cast<CustomLogHandler *>(custom_log_handler.get())->count); + + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler(backup_log_handle); + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel(backup_log_level); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_benchmark.cc new file mode 100644 index 000000000..df2ebf95e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_benchmark.cc @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "src/common/random.h" + +#include <cstdint> +#include <random> + +#include <benchmark/benchmark.h> + +namespace +{ +using opentelemetry::sdk::common::Random; + +void BM_RandomIdGeneration(benchmark::State &state) +{ + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(Random::GenerateRandom64()); + } +} +BENCHMARK(BM_RandomIdGeneration); + +void BM_RandomIdStdGeneration(benchmark::State &state) +{ + std::mt19937_64 generator{0}; + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(generator()); + } +} +BENCHMARK(BM_RandomIdStdGeneration); + +} // namespace +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_fork_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_fork_test.cc new file mode 100644 index 000000000..2db8b9fcd --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_fork_test.cc @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef __unix__ +// Verifies that IDs don't clash after forking the process. +// +// See https://github.com/opentracing-contrib/nginx-opentracing/issues/52 +# include "src/common/random.h" + +# include <sys/mman.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <unistd.h> +# include <cstdio> +# include <cstdlib> +# include <iostream> +using opentelemetry::sdk::common::Random; + +static uint64_t *child_id; + +int main() +{ + // Set up shared memory to communicate between parent and child processes. + // + // See https://stackoverflow.com/a/13274800/4447365 + child_id = static_cast<uint64_t *>( + mmap(nullptr, sizeof(*child_id), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + *child_id = 0; + if (fork() == 0) + { + *child_id = Random::GenerateRandom64(); + exit(EXIT_SUCCESS); + } + else + { + wait(nullptr); + auto parent_id = Random::GenerateRandom64(); + auto child_id_copy = *child_id; + munmap(static_cast<void *>(child_id), sizeof(*child_id)); + if (parent_id == child_id_copy) + { + std::cerr << "Child and parent ids are the same value " << parent_id << "\n"; + return -1; + } + } + return 0; +} +#else +int main() +{ + return 0; +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_test.cc new file mode 100644 index 000000000..35cfd4a1e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/common/random_test.cc @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "src/common/random.h" + +#include <algorithm> +#include <iterator> + +#include <gtest/gtest.h> +using opentelemetry::sdk::common::Random; + +TEST(RandomTest, GenerateRandom64) +{ + EXPECT_NE(Random::GenerateRandom64(), Random::GenerateRandom64()); +} + +TEST(RandomTest, GenerateRandomBuffer) +{ + uint8_t buf1[8] = {0}; + uint8_t buf2[8] = {0}; + Random::GenerateRandomBuffer(buf1); + Random::GenerateRandomBuffer(buf2); + EXPECT_FALSE(std::equal(std::begin(buf1), std::end(buf1), std::begin(buf2))); + + // Edge cases. + for (auto size : {7, 8, 9, 16, 17}) + { + std::vector<uint8_t> buf1(size); + std::vector<uint8_t> buf2(size); + + Random::GenerateRandomBuffer(buf1); + Random::GenerateRandomBuffer(buf2); + EXPECT_FALSE(std::equal(std::begin(buf1), std::end(buf1), std::begin(buf2))); + } +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/BUILD new file mode 100644 index 000000000..38cc25300 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/BUILD @@ -0,0 +1,14 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "instrumentationlibrary_test", + srcs = [ + "instrumentationlibrary_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/CMakeLists.txt new file mode 100644 index 000000000..512266dc8 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/CMakeLists.txt @@ -0,0 +1,11 @@ +include(GoogleTest) + +foreach(testname instrumentationlibrary_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX instrumentationlibrary. + TEST_LIST ${testname}) +endforeach() diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/instrumentationlibrary_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/instrumentationlibrary_test.cc new file mode 100644 index 000000000..a410ca99f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/instrumentationlibrary/instrumentationlibrary_test.cc @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" + +#include <gtest/gtest.h> +#include <string> +#include <vector> + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationlibrary; + +TEST(InstrumentationLibrary, CreateInstrumentationLibrary) +{ + + std::string library_name = "opentelemetry-cpp"; + std::string library_version = "0.1.0"; + std::string schema_url = "https://opentelemetry.io/schemas/1.2.0"; + auto instrumentation_library = + InstrumentationLibrary::Create(library_name, library_version, schema_url); + + EXPECT_EQ(instrumentation_library->GetName(), library_name); + EXPECT_EQ(instrumentation_library->GetVersion(), library_version); + EXPECT_EQ(instrumentation_library->GetSchemaURL(), schema_url); +} diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/BUILD new file mode 100644 index 000000000..c8f051070 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/BUILD @@ -0,0 +1,75 @@ +cc_test( + name = "logger_provider_sdk_test", + srcs = [ + "logger_provider_sdk_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//api", + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "logger_sdk_test", + srcs = [ + "logger_sdk_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "simple_log_processor_test", + srcs = [ + "simple_log_processor_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_record_test", + srcs = [ + "log_record_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "batch_log_processor_test", + srcs = [ + "batch_log_processor_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/CMakeLists.txt new file mode 100644 index 000000000..84b865d22 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/CMakeLists.txt @@ -0,0 +1,10 @@ +foreach(testname logger_provider_sdk_test logger_sdk_test log_record_test + simple_log_processor_test batch_log_processor_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_logs) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX logs. + TEST_LIST ${testname}) +endforeach() diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/batch_log_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/batch_log_processor_test.cc new file mode 100644 index 000000000..63e44676c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/batch_log_processor_test.cc @@ -0,0 +1,269 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/sdk/logs/batch_log_processor.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" + +# include <gtest/gtest.h> +# include <chrono> +# include <thread> + +using namespace opentelemetry::sdk::logs; +using namespace opentelemetry::sdk::common; + +/** + * A sample log exporter + * for testing the batch log processor + */ +class MockLogExporter final : public LogExporter +{ +public: + MockLogExporter(std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received, + std::shared_ptr<std::atomic<bool>> is_shutdown, + std::shared_ptr<std::atomic<bool>> is_export_completed, + const std::chrono::milliseconds export_delay = std::chrono::milliseconds(0)) + : logs_received_(logs_received), + is_shutdown_(is_shutdown), + is_export_completed_(is_export_completed), + export_delay_(export_delay) + {} + + std::unique_ptr<Recordable> MakeRecordable() noexcept + { + return std::unique_ptr<Recordable>(new LogRecord()); + } + + // Export method stores the logs received into a shared list of record names + ExportResult Export( + const opentelemetry::nostd::span<std::unique_ptr<Recordable>> &records) noexcept override + { + *is_export_completed_ = false; // Meant exclusively to test scheduled_delay_millis + + for (auto &record : records) + { + auto log = std::unique_ptr<LogRecord>(static_cast<LogRecord *>(record.release())); + if (log != nullptr) + { + logs_received_->push_back(std::move(log)); + } + } + + *is_export_completed_ = true; + return ExportResult::kSuccess; + } + + // toggles the boolean flag marking this exporter as shut down + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + { + *is_shutdown_ = true; + return true; + } + +private: + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received_; + std::shared_ptr<std::atomic<bool>> is_shutdown_; + std::shared_ptr<std::atomic<bool>> is_export_completed_; + const std::chrono::milliseconds export_delay_; +}; + +/** + * A fixture class for testing the BatchLogProcessor class that uses the TestExporter defined above. + */ +class BatchLogProcessorTest : public testing::Test // ::testing::Test +{ +public: + // returns a batch log processor that received a batch of log records, a shared pointer to a + // is_shutdown flag, and the processor configuration options (default if unspecified) + std::shared_ptr<LogProcessor> GetMockProcessor( + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_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), + const std::chrono::milliseconds scheduled_delay_millis = std::chrono::milliseconds(5000), + const size_t max_queue_size = 2048, + const size_t max_export_batch_size = 512) + { + return std::shared_ptr<LogProcessor>( + new BatchLogProcessor(std::unique_ptr<LogExporter>(new MockLogExporter( + logs_received, is_shutdown, is_export_completed, export_delay)), + max_queue_size, scheduled_delay_millis, max_export_batch_size)); + } +}; + +TEST_F(BatchLogProcessorTest, TestShutdown) +{ + // initialize a batch log processor with the test exporter + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + + auto batch_processor = GetMockProcessor(logs_received, is_shutdown); + + // Create a few test log records and send them to the processor + const int num_logs = 3; + + for (int i = 0; i < num_logs; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + + // Test that shutting down the processor will first wait for the + // current batch of logs to be sent to the log exporter + // by checking the number of logs sent and the names of the logs sent + EXPECT_EQ(true, batch_processor->Shutdown()); + // It's safe to shutdown again + EXPECT_TRUE(batch_processor->Shutdown()); + + EXPECT_EQ(num_logs, logs_received->size()); + + // Assume logs are received by exporter in same order as sent by processor + for (int i = 0; i < num_logs; ++i) + { + EXPECT_EQ("Log" + std::to_string(i), logs_received->at(i)->GetBody()); + } + + // Also check that the processor is shut down at the end + EXPECT_TRUE(is_shutdown->load()); +} + +TEST_F(BatchLogProcessorTest, TestForceFlush) +{ + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + + auto batch_processor = GetMockProcessor(logs_received, is_shutdown); + const int num_logs = 2048; + + for (int i = 0; i < num_logs; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_logs, logs_received->size()); + for (int i = 0; i < num_logs; ++i) + { + EXPECT_EQ("Log" + std::to_string(i), logs_received->at(i)->GetBody()); + } + + // Create some more logs to make sure that the processor still works + for (int i = 0; i < num_logs; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_logs * 2, logs_received->size()); + for (int i = 0; i < num_logs * 2; ++i) + { + EXPECT_EQ("Log" + std::to_string(i % num_logs), logs_received->at(i)->GetBody()); + } +} + +TEST_F(BatchLogProcessorTest, TestManyLogsLoss) +{ + /* Test that when exporting more than max_queue_size logs, 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<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + + const int max_queue_size = 4096; + + auto batch_processor = GetMockProcessor(logs_received, is_shutdown); + + // Create max_queue_size log records + for (int i = 0; i < max_queue_size; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + + EXPECT_TRUE(batch_processor->ForceFlush()); + + // Log should be exported by now + EXPECT_GE(max_queue_size, logs_received->size()); +} + +TEST_F(BatchLogProcessorTest, TestManyLogsLossLess) +{ + /* Test that no logs are lost when sending max_queue_size logs */ + + std::shared_ptr<std::atomic<bool>> is_shutdown(new std::atomic<bool>(false)); + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + auto batch_processor = GetMockProcessor(logs_received, is_shutdown); + + const int num_logs = 2048; + + for (int i = 0; i < num_logs; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + + EXPECT_TRUE(batch_processor->ForceFlush()); + + EXPECT_EQ(num_logs, logs_received->size()); + for (int i = 0; i < num_logs; ++i) + { + EXPECT_EQ("Log" + std::to_string(i), logs_received->at(i)->GetBody()); + } +} + +TEST_F(BatchLogProcessorTest, TestScheduledDelayMillis) +{ + /* Test that max_export_batch_size logs are exported every scheduled_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<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + + const std::chrono::milliseconds export_delay(0); + const std::chrono::milliseconds scheduled_delay_millis(2000); + const size_t max_export_batch_size = 512; + + auto batch_processor = GetMockProcessor(logs_received, is_shutdown, is_export_completed, + export_delay, scheduled_delay_millis); + + for (std::size_t i = 0; i < max_export_batch_size; ++i) + { + auto log = batch_processor->MakeRecordable(); + log->SetBody("Log" + std::to_string(i)); + batch_processor->OnReceive(std::move(log)); + } + // Sleep for scheduled_delay_millis milliseconds + std::this_thread::sleep_for(scheduled_delay_millis); + + // small delay to give time to export, which is being performed + // asynchronously by the worker thread (this thread will not + // forcibly join() the main thread unless processor's shutdown() is called). + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // Logs should be exported by now + EXPECT_TRUE(is_export_completed->load()); + EXPECT_EQ(max_export_batch_size, logs_received->size()); + for (size_t i = 0; i < max_export_batch_size; ++i) + { + EXPECT_EQ("Log" + std::to_string(i), logs_received->at(i)->GetBody()); + } +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/log_record_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/log_record_test.cc new file mode 100644 index 000000000..89b07473a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/log_record_test.cc @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/nostd/variant.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_id.h" + +# include <gtest/gtest.h> + +using opentelemetry::sdk::logs::LogRecord; +namespace trace_api = opentelemetry::trace; +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; + +// Test what a default LogRecord with no fields set holds +TEST(LogRecord, GetDefaultValues) +{ + trace_api::TraceId zero_trace_id; + trace_api::SpanId zero_span_id; + trace_api::TraceFlags zero_trace_flags; + LogRecord record; + + ASSERT_EQ(record.GetSeverity(), logs_api::Severity::kInvalid); + ASSERT_EQ(record.GetBody(), ""); + ASSERT_NE(record.GetResource().GetAttributes().size(), 0); + ASSERT_EQ(record.GetAttributes().size(), 0); + ASSERT_EQ(record.GetTraceId(), zero_trace_id); + ASSERT_EQ(record.GetSpanId(), zero_span_id); + ASSERT_EQ(record.GetTraceFlags(), zero_trace_flags); + ASSERT_EQ(record.GetTimestamp().time_since_epoch(), std::chrono::nanoseconds(0)); +} + +// Test LogRecord fields are properly set and get +TEST(LogRecord, SetAndGet) +{ + trace_api::TraceId trace_id; + trace_api::SpanId span_id; + trace_api::TraceFlags trace_flags; + opentelemetry::common::SystemTimestamp now(std::chrono::system_clock::now()); + + // Set all fields of the LogRecord + LogRecord record; + auto resource = opentelemetry::sdk::resource::Resource::Create({{"res1", true}}); + record.SetSeverity(logs_api::Severity::kInvalid); + record.SetBody("Message"); + record.SetResource(resource); + record.SetAttribute("attr1", (int64_t)314159); + record.SetTraceId(trace_id); + record.SetSpanId(span_id); + record.SetTraceFlags(trace_flags); + record.SetTimestamp(now); + + // Test that all fields match what was set + ASSERT_EQ(record.GetSeverity(), logs_api::Severity::kInvalid); + ASSERT_EQ(record.GetBody(), "Message"); + ASSERT_TRUE(nostd::get<bool>(record.GetResource().GetAttributes().at("res1"))); + ASSERT_EQ(nostd::get<int64_t>(record.GetAttributes().at("attr1")), 314159); + ASSERT_EQ(record.GetTraceId(), trace_id); + ASSERT_EQ(record.GetSpanId(), span_id); + ASSERT_EQ(record.GetTraceFlags(), trace_flags); + ASSERT_EQ(record.GetTimestamp().time_since_epoch(), now.time_since_epoch()); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_provider_sdk_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_provider_sdk_test.cc new file mode 100644 index 000000000..3e5e8dfb2 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_provider_sdk_test.cc @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include <array> +# include "opentelemetry/logs/provider.h" +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/string_view.h" +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/sdk/logs/logger.h" +# include "opentelemetry/sdk/logs/logger_provider.h" +# include "opentelemetry/sdk/logs/simple_log_processor.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::logs; +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; + +TEST(LoggerProviderSDK, PushToAPI) +{ + auto lp = + nostd::shared_ptr<logs_api::LoggerProvider>(new opentelemetry::sdk::logs::LoggerProvider()); + logs_api::Provider::SetLoggerProvider(lp); + + // Check that the loggerprovider was correctly pushed into the API + ASSERT_EQ(lp, logs_api::Provider::GetLoggerProvider()); +} + +TEST(LoggerProviderSDK, LoggerProviderGetLoggerSimple) +{ + auto lp = std::shared_ptr<logs_api::LoggerProvider>(new LoggerProvider()); + + nostd::string_view schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger1 = lp->GetLogger("logger1", "", "opentelelemtry_library", "", schema_url); + auto logger2 = lp->GetLogger("logger2", "", "", "", schema_url); + + // Check that the logger is not nullptr + ASSERT_NE(logger1, nullptr); + ASSERT_NE(logger2, nullptr); + + auto sdk_logger1 = static_cast<opentelemetry::sdk::logs::Logger *>(logger1.get()); + auto sdk_logger2 = static_cast<opentelemetry::sdk::logs::Logger *>(logger2.get()); + ASSERT_EQ(sdk_logger1->GetInstrumentationLibrary().GetName(), "opentelelemtry_library"); + ASSERT_EQ(sdk_logger1->GetInstrumentationLibrary().GetVersion(), ""); + ASSERT_EQ(sdk_logger1->GetInstrumentationLibrary().GetSchemaURL(), schema_url); + + ASSERT_EQ(sdk_logger2->GetInstrumentationLibrary().GetName(), "logger2"); + ASSERT_EQ(sdk_logger2->GetInstrumentationLibrary().GetVersion(), ""); + ASSERT_EQ(sdk_logger2->GetInstrumentationLibrary().GetSchemaURL(), schema_url); + + // Check that two loggers with different names aren't the same instance + ASSERT_NE(logger1, logger2); + + // Check that two loggers with the same name are the same instance + auto logger3 = lp->GetLogger("logger1", "", "opentelelemtry_library", "", schema_url); + ASSERT_EQ(logger1, logger3); + auto sdk_logger3 = static_cast<opentelemetry::sdk::logs::Logger *>(logger3.get()); + ASSERT_EQ(sdk_logger3->GetInstrumentationLibrary(), sdk_logger1->GetInstrumentationLibrary()); +} + +TEST(LoggerProviderSDK, LoggerProviderLoggerArguments) +{ + // Currently, arguments are not supported by the loggers. + // TODO: Once the logging spec defines what arguments are allowed, add more + // detail to this test + auto lp = std::shared_ptr<logs_api::LoggerProvider>(new LoggerProvider()); + + nostd::string_view schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger1 = lp->GetLogger("logger1", "", "opentelelemtry_library", "", schema_url); + + // Check GetLogger(logger_name, args) + std::array<nostd::string_view, 1> sv{"string"}; + nostd::span<nostd::string_view> args{sv}; + auto logger2 = lp->GetLogger("logger2", args, "opentelelemtry_library", "", schema_url); + auto sdk_logger1 = static_cast<opentelemetry::sdk::logs::Logger *>(logger1.get()); + auto sdk_logger2 = static_cast<opentelemetry::sdk::logs::Logger *>(logger2.get()); + ASSERT_EQ(sdk_logger2->GetInstrumentationLibrary(), sdk_logger1->GetInstrumentationLibrary()); +} + +class DummyProcessor : public LogProcessor +{ + std::unique_ptr<Recordable> MakeRecordable() noexcept + { + return std::unique_ptr<Recordable>(new LogRecord); + } + + void OnReceive(std::unique_ptr<Recordable> &&record) noexcept {} + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + { + return true; + } + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + { + return true; + } +}; + +TEST(LoggerProviderSDK, GetResource) +{ + // Create a LoggerProvider without a processor + auto resource = opentelemetry::sdk::resource::Resource::Create({{"key", "value"}}); + LoggerProvider lp{nullptr, resource}; + ASSERT_EQ(nostd::get<std::string>(lp.GetResource().GetAttributes().at("key")), "value"); +} + +TEST(LoggerProviderSDK, Shutdown) +{ + std::unique_ptr<SimpleLogProcessor> processor(new SimpleLogProcessor(nullptr)); + std::vector<std::unique_ptr<LogProcessor>> processors; + processors.push_back(std::move(processor)); + + LoggerProvider lp(std::make_shared<LoggerContext>(std::move(processors))); + + EXPECT_TRUE(lp.Shutdown()); + + // It's safe to shutdown again + EXPECT_TRUE(lp.Shutdown()); +} + +TEST(LoggerProviderSDK, ForceFlush) +{ + std::unique_ptr<SimpleLogProcessor> processor(new SimpleLogProcessor(nullptr)); + std::vector<std::unique_ptr<LogProcessor>> processors; + processors.push_back(std::move(processor)); + + LoggerProvider lp(std::make_shared<LoggerContext>(std::move(processors))); + + EXPECT_TRUE(lp.ForceFlush()); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_sdk_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_sdk_test.cc new file mode 100644 index 000000000..3747731f1 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/logger_sdk_test.cc @@ -0,0 +1,97 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/sdk/logs/logger.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::logs; +namespace logs_api = opentelemetry::logs; + +TEST(LoggerSDK, LogToNullProcessor) +{ + // Confirm Logger::Log() does not have undefined behavior + // even when there is no processor set + // since it calls Processor::OnReceive() + + auto lp = std::shared_ptr<logs_api::LoggerProvider>(new LoggerProvider()); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = lp->GetLogger("logger", "", "opentelelemtry_library", "", schema_url); + + auto sdk_logger = static_cast<opentelemetry::sdk::logs::Logger *>(logger.get()); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetName(), "opentelelemtry_library"); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetVersion(), ""); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetSchemaURL(), schema_url); + // Log a sample log record to a nullptr processor + logger->Debug("Test log"); +} + +class MockProcessor final : public LogProcessor +{ +private: + std::shared_ptr<LogRecord> record_received_; + +public: + // A processor used for testing that keeps a track of the recordable it received + explicit MockProcessor(std::shared_ptr<LogRecord> record_received) noexcept + : record_received_(record_received) + {} + + std::unique_ptr<Recordable> MakeRecordable() noexcept + { + return std::unique_ptr<Recordable>(new LogRecord); + } + // OnReceive stores the record it receives into the shared_ptr recordable passed into its + // constructor + void OnReceive(std::unique_ptr<Recordable> &&record) noexcept + { + // Cast the recordable received into a concrete LogRecord type + auto copy = std::shared_ptr<LogRecord>(static_cast<LogRecord *>(record.release())); + + // Copy over the received log record's severity, name, and body fields over to the recordable + // passed in the constructor + record_received_->SetSeverity(copy->GetSeverity()); + record_received_->SetBody(copy->GetBody()); + } + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + { + return true; + } + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + { + return true; + } +}; + +TEST(LoggerSDK, LogToAProcessor) +{ + // Create an API LoggerProvider and logger + auto api_lp = std::shared_ptr<logs_api::LoggerProvider>(new LoggerProvider()); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = api_lp->GetLogger("logger", "", "opentelelemtry_library", "", schema_url); + + // Cast the API LoggerProvider to an SDK Logger Provider and assert that it is still the same + // LoggerProvider by checking that getting a logger with the same name as the previously defined + // logger is the same instance + auto lp = static_cast<LoggerProvider *>(api_lp.get()); + auto logger2 = lp->GetLogger("logger", "", "opentelelemtry_library", "", schema_url); + ASSERT_EQ(logger, logger2); + + auto sdk_logger = static_cast<opentelemetry::sdk::logs::Logger *>(logger.get()); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetName(), "opentelelemtry_library"); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetVersion(), ""); + ASSERT_EQ(sdk_logger->GetInstrumentationLibrary().GetSchemaURL(), schema_url); + // Set a processor for the LoggerProvider + auto shared_recordable = std::shared_ptr<LogRecord>(new LogRecord()); + lp->AddProcessor(std::unique_ptr<LogProcessor>(new MockProcessor(shared_recordable))); + + // Check that the recordable created by the Log() statement is set properly + logger->Log(logs_api::Severity::kWarn, "Log Message"); + + ASSERT_EQ(shared_recordable->GetSeverity(), logs_api::Severity::kWarn); + ASSERT_EQ(shared_recordable->GetBody(), "Log Message"); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/simple_log_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/simple_log_processor_test.cc new file mode 100644 index 000000000..32c62a508 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/logs/simple_log_processor_test.cc @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/sdk/logs/simple_log_processor.h" +# include "opentelemetry/nostd/span.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" + +# include <gtest/gtest.h> + +# include <chrono> +# include <thread> + +using namespace opentelemetry::sdk::logs; +using namespace opentelemetry::sdk::common; +namespace nostd = opentelemetry::nostd; + +/* + * A test exporter that can return a vector of all the records it has received, + * and keep track of the number of times its Shutdown() function was called. + */ +class TestExporter final : public LogExporter +{ +public: + TestExporter(int *shutdown_counter, + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received, + size_t *batch_size_received) + : shutdown_counter_(shutdown_counter), + logs_received_(logs_received), + batch_size_received(batch_size_received) + {} + + std::unique_ptr<Recordable> MakeRecordable() noexcept override + { + return std::unique_ptr<Recordable>(new LogRecord()); + } + + // Stores the names of the log records this exporter receives to an internal list + ExportResult Export(const nostd::span<std::unique_ptr<Recordable>> &records) noexcept override + { + *batch_size_received = records.size(); + for (auto &record : records) + { + auto log_record = std::unique_ptr<LogRecord>(static_cast<LogRecord *>(record.release())); + + if (log_record != nullptr) + { + logs_received_->push_back(std::move(log_record)); + } + } + return ExportResult::kSuccess; + } + + // Increment the shutdown counter everytime this method is called + bool Shutdown(std::chrono::microseconds timeout) noexcept override + { + *shutdown_counter_ += 1; + return true; + } + +private: + int *shutdown_counter_; + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received_; + size_t *batch_size_received; +}; + +// Tests whether the simple processor successfully creates a batch of size 1 +// and whether the contents of the record is sent to the exporter correctly +TEST(SimpleLogProcessorTest, SendReceivedLogsToExporter) +{ + // Create a simple processor with a TestExporter attached + std::shared_ptr<std::vector<std::unique_ptr<LogRecord>>> logs_received( + new std::vector<std::unique_ptr<LogRecord>>); + size_t batch_size_received = 0; + + std::unique_ptr<TestExporter> exporter( + new TestExporter(nullptr, logs_received, &batch_size_received)); + + SimpleLogProcessor processor(std::move(exporter)); + + // Send some log records to the processor (which should then send to the TestExporter) + const int num_logs = 5; + for (int i = 0; i < num_logs; i++) + { + auto recordable = processor.MakeRecordable(); + recordable->SetBody("Log Body"); + processor.OnReceive(std::move(recordable)); + + // Verify that the batch of 1 log record sent by processor matches what exporter received + EXPECT_EQ(1, batch_size_received); + } + + // Test whether the processor's log sent matches the log record received by the exporter + EXPECT_EQ(logs_received->size(), num_logs); + for (int i = 0; i < num_logs; i++) + { + EXPECT_EQ("Log Body", logs_received->at(i)->GetBody()); + } +} + +// Tests behavior when calling the processor's ShutDown() multiple times +TEST(SimpleLogProcessorTest, ShutdownCalledOnce) +{ + // Create a TestExporter + int num_shutdowns = 0; + + std::unique_ptr<TestExporter> exporter(new TestExporter(&num_shutdowns, nullptr, nullptr)); + + // Create a processor with the previous test exporter + SimpleLogProcessor processor(std::move(exporter)); + + // The first time processor shutdown is called + EXPECT_EQ(0, num_shutdowns); + EXPECT_EQ(true, processor.Shutdown()); + EXPECT_EQ(1, num_shutdowns); + + EXPECT_EQ(true, processor.Shutdown()); + // Processor::ShutDown(), even if called more than once, should only shutdown exporter once + EXPECT_EQ(1, num_shutdowns); +} + +// A test exporter that always returns failure when shut down +class FailShutDownExporter final : public LogExporter +{ +public: + FailShutDownExporter() {} + + std::unique_ptr<Recordable> MakeRecordable() noexcept override + { + return std::unique_ptr<Recordable>(new LogRecord()); + } + + ExportResult Export(const nostd::span<std::unique_ptr<Recordable>> &records) noexcept override + { + return ExportResult::kSuccess; + } + + bool Shutdown(std::chrono::microseconds timeout) noexcept override { return false; } +}; + +// Tests for when when processor should fail to shutdown +TEST(SimpleLogProcessorTest, ShutDownFail) +{ + std::unique_ptr<FailShutDownExporter> exporter(new FailShutDownExporter()); + SimpleLogProcessor processor(std::move(exporter)); + + // Expect failure result when exporter fails to shutdown + EXPECT_EQ(false, processor.Shutdown()); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/BUILD new file mode 100644 index 000000000..819a8d225 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/BUILD @@ -0,0 +1,203 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "meter_provider_sdk_test", + srcs = [ + "meter_provider_sdk_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "metric_reader_test", + srcs = [ + "metric_reader_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "view_registry_test", + srcs = [ + "view_registry_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "aggregation_test", + srcs = [ + "aggregation_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "sync_metric_storage_test", + srcs = [ + "sync_metric_storage_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "sync_instruments_test", + srcs = [ + "sync_instruments_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "async_metric_storage_test", + srcs = [ + "async_metric_storage_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "observer_result_test", + srcs = [ + "observer_result_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "multi_metric_storage_test", + srcs = [ + "multi_metric_storage_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "attributes_processor_test", + srcs = [ + "attributes_processor_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "attributes_hashmap_test", + srcs = [ + "attributes_hashmap_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "attributes_processor_benchmark", + srcs = [ + "attributes_processor_benchmark.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + ], +) + +otel_cc_benchmark( + name = "attributes_hashmap_benchmark", + srcs = [ + "attributes_hashmap_benchmark.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/CMakeLists.txt new file mode 100644 index 000000000..faf2f2b49 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/CMakeLists.txt @@ -0,0 +1,34 @@ +foreach( + testname + meter_provider_sdk_test + view_registry_test + aggregation_test + attributes_processor_test + attributes_hashmap_test + sync_metric_storage_test + async_metric_storage_test + multi_metric_storage_test + observer_result_test + sync_instruments_test + async_instruments_test + metric_reader_test + periodic_exporting_metric_reader_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_resources opentelemetry_metrics) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX metrics. + TEST_LIST ${testname}) +endforeach() + +add_executable(attributes_processor_benchmark attributes_processor_benchmark.cc) +target_link_libraries(attributes_processor_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common) + +add_executable(attributes_hashmap_benchmark attributes_hashmap_benchmark.cc) +target_link_libraries(attributes_hashmap_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common) + +add_subdirectory(exemplar) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/aggregation_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/aggregation_test.cc new file mode 100644 index 000000000..f8051776d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/aggregation_test.cc @@ -0,0 +1,182 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/lastvalue_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/sum_aggregation.h" + +# include "opentelemetry/nostd/variant.h" + +using namespace opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +TEST(Aggregation, LongSumAggregation) +{ + LongSumAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<SumPointData>(data)); + auto sum_data = nostd::get<SumPointData>(data); + ASSERT_TRUE(nostd::holds_alternative<long>(sum_data.value_)); + EXPECT_EQ(nostd::get<long>(sum_data.value_), 0l); + EXPECT_NO_THROW(aggr.Aggregate(12l, {})); + EXPECT_NO_THROW(aggr.Aggregate(0l, {})); + sum_data = nostd::get<SumPointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<long>(sum_data.value_), 12l); +} + +TEST(Aggregation, DoubleSumAggregation) +{ + DoubleSumAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<SumPointData>(data)); + auto sum_data = nostd::get<SumPointData>(data); + ASSERT_TRUE(nostd::holds_alternative<double>(sum_data.value_)); + EXPECT_EQ(nostd::get<double>(sum_data.value_), 0); + EXPECT_NO_THROW(aggr.Aggregate(12.0, {})); + EXPECT_NO_THROW(aggr.Aggregate(1.0, {})); + sum_data = nostd::get<SumPointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<double>(sum_data.value_), 13.0); +} + +TEST(Aggregation, LongLastValueAggregation) +{ + LongLastValueAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<LastValuePointData>(data)); + auto lastvalue_data = nostd::get<LastValuePointData>(data); + ASSERT_TRUE(nostd::holds_alternative<long>(lastvalue_data.value_)); + EXPECT_EQ(lastvalue_data.is_lastvalue_valid_, false); + EXPECT_NO_THROW(aggr.Aggregate(12l, {})); + EXPECT_NO_THROW(aggr.Aggregate(1l, {})); + lastvalue_data = nostd::get<LastValuePointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<long>(lastvalue_data.value_), 1.0); +} + +TEST(Aggregation, DoubleLastValueAggregation) +{ + DoubleLastValueAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<LastValuePointData>(data)); + auto lastvalue_data = nostd::get<LastValuePointData>(data); + ASSERT_TRUE(nostd::holds_alternative<double>(lastvalue_data.value_)); + EXPECT_EQ(lastvalue_data.is_lastvalue_valid_, false); + EXPECT_NO_THROW(aggr.Aggregate(12.0, {})); + EXPECT_NO_THROW(aggr.Aggregate(1.0, {})); + lastvalue_data = nostd::get<LastValuePointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<double>(lastvalue_data.value_), 1.0); +} + +TEST(Aggregation, LongHistogramAggregation) +{ + LongHistogramAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<HistogramPointData>(data)); + auto histogram_data = nostd::get<HistogramPointData>(data); + ASSERT_TRUE(nostd::holds_alternative<long>(histogram_data.sum_)); + ASSERT_TRUE(nostd::holds_alternative<std::list<long>>(histogram_data.boundaries_)); + EXPECT_EQ(nostd::get<long>(histogram_data.sum_), 0); + EXPECT_EQ(histogram_data.count_, 0); + EXPECT_NO_THROW(aggr.Aggregate(12l, {})); // lies in fourth bucket + EXPECT_NO_THROW(aggr.Aggregate(100l, {})); // lies in eight bucket + histogram_data = nostd::get<HistogramPointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<long>(histogram_data.sum_), 112); + EXPECT_EQ(histogram_data.count_, 2); + EXPECT_EQ(histogram_data.counts_[3], 1); + EXPECT_EQ(histogram_data.counts_[7], 1); + EXPECT_NO_THROW(aggr.Aggregate(13l, {})); // lies in fourth bucket + EXPECT_NO_THROW(aggr.Aggregate(252l, {})); // lies in ninth bucket + histogram_data = nostd::get<HistogramPointData>(aggr.ToPoint()); + EXPECT_EQ(histogram_data.count_, 4); + EXPECT_EQ(histogram_data.counts_[3], 2); + EXPECT_EQ(histogram_data.counts_[8], 1); + + // Merge + LongHistogramAggregation aggr1; + aggr1.Aggregate(1l, {}); + aggr1.Aggregate(11l, {}); + aggr1.Aggregate(26l, {}); + + LongHistogramAggregation aggr2; + aggr2.Aggregate(2l, {}); + aggr2.Aggregate(3l, {}); + aggr2.Aggregate(13l, {}); + aggr2.Aggregate(28l, {}); + aggr2.Aggregate(105l, {}); + + auto aggr3 = aggr1.Merge(aggr2); + histogram_data = nostd::get<HistogramPointData>(aggr3->ToPoint()); + + EXPECT_EQ(histogram_data.count_, 8); // 3 each from aggr1 and aggr2 + EXPECT_EQ(histogram_data.counts_[1], 3); // 1, 2, 3 + EXPECT_EQ(histogram_data.counts_[3], 2); // 11, 13 + EXPECT_EQ(histogram_data.counts_[4], 2); // 25, 28 + EXPECT_EQ(histogram_data.counts_[7], 1); // 105 + + // Diff + auto aggr4 = aggr1.Diff(aggr2); + histogram_data = nostd::get<HistogramPointData>(aggr4->ToPoint()); + EXPECT_EQ(histogram_data.count_, 2); // aggr2:5 - aggr1:3 + EXPECT_EQ(histogram_data.counts_[1], 1); // aggr2(2, 3) - aggr1(1) + EXPECT_EQ(histogram_data.counts_[3], 0); // aggr2(13) - aggr1(11) + EXPECT_EQ(histogram_data.counts_[4], 0); // aggr2(28) - aggr1(25) + EXPECT_EQ(histogram_data.counts_[7], 1); // aggr2(105) - aggr1(0) +} + +TEST(Aggregation, DoubleHistogramAggregation) +{ + DoubleHistogramAggregation aggr; + auto data = aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative<HistogramPointData>(data)); + auto histogram_data = nostd::get<HistogramPointData>(data); + ASSERT_TRUE(nostd::holds_alternative<double>(histogram_data.sum_)); + ASSERT_TRUE(nostd::holds_alternative<std::list<double>>(histogram_data.boundaries_)); + EXPECT_EQ(nostd::get<double>(histogram_data.sum_), 0); + EXPECT_EQ(histogram_data.count_, 0); + EXPECT_NO_THROW(aggr.Aggregate(12.0, {})); // lies in fourth bucket + EXPECT_NO_THROW(aggr.Aggregate(100.0, {})); // lies in eight bucket + histogram_data = nostd::get<HistogramPointData>(aggr.ToPoint()); + EXPECT_EQ(nostd::get<double>(histogram_data.sum_), 112); + EXPECT_EQ(histogram_data.count_, 2); + EXPECT_EQ(histogram_data.counts_[3], 1); + EXPECT_EQ(histogram_data.counts_[7], 1); + EXPECT_NO_THROW(aggr.Aggregate(13.0, {})); // lies in fourth bucket + EXPECT_NO_THROW(aggr.Aggregate(252.0, {})); // lies in ninth bucket + histogram_data = nostd::get<HistogramPointData>(aggr.ToPoint()); + EXPECT_EQ(histogram_data.count_, 4); + EXPECT_EQ(histogram_data.counts_[3], 2); + EXPECT_EQ(histogram_data.counts_[8], 1); + EXPECT_EQ(nostd::get<double>(histogram_data.sum_), 377); + + // Merge + DoubleHistogramAggregation aggr1; + aggr1.Aggregate(1.0, {}); + aggr1.Aggregate(11.0, {}); + aggr1.Aggregate(25.1, {}); + + DoubleHistogramAggregation aggr2; + aggr2.Aggregate(2.0, {}); + aggr2.Aggregate(3.0, {}); + aggr2.Aggregate(13.0, {}); + aggr2.Aggregate(28.1, {}); + aggr2.Aggregate(105.0, {}); + + auto aggr3 = aggr1.Merge(aggr2); + histogram_data = nostd::get<HistogramPointData>(aggr3->ToPoint()); + + EXPECT_EQ(histogram_data.count_, 8); // 3 each from aggr1 and aggr2 + EXPECT_EQ(histogram_data.counts_[1], 3); // 1.0, 2.0, 3.0 + EXPECT_EQ(histogram_data.counts_[3], 2); // 11.0, 13.0 + EXPECT_EQ(histogram_data.counts_[4], 2); // 25.1, 28.1 + EXPECT_EQ(histogram_data.counts_[7], 1); // 105.0 + + // Diff + auto aggr4 = aggr1.Diff(aggr2); + histogram_data = nostd::get<HistogramPointData>(aggr4->ToPoint()); + EXPECT_EQ(histogram_data.count_, 2); // aggr2:5 - aggr1:3 + EXPECT_EQ(histogram_data.counts_[1], 1); // aggr2(2.0, 3.0) - aggr1(1.0) + EXPECT_EQ(histogram_data.counts_[3], 0); // aggr2(13.0) - aggr1(11.0) + EXPECT_EQ(histogram_data.counts_[4], 0); // aggr2(28.1) - aggr1(25.1) + EXPECT_EQ(histogram_data.counts_[7], 1); // aggr2(105.0) - aggr1(0) +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_instruments_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_instruments_test.cc new file mode 100644 index 000000000..ff9504f78 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_instruments_test.cc @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/async_instruments.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry; +using namespace opentelemetry::sdk::metrics; + +using M = std::map<std::string, std::string>; + +void asyc_generate_measurements_long(opentelemetry::metrics::ObserverResult<long> &observer) {} + +void asyc_generate_measurements_double(opentelemetry::metrics::ObserverResult<double> &observer) {} + +TEST(AsyncInstruments, LongObservableCounter) +{ + auto asyc_generate_meas_long = [](opentelemetry::metrics::ObserverResult<long> &observer) {}; + EXPECT_NO_THROW( + LongObservableCounter counter("long_counter", asyc_generate_meas_long, "description", "1")); +} + +TEST(AsyncInstruments, DoubleObservableCounter) +{ + auto asyc_generate_meas_double = [](opentelemetry::metrics::ObserverResult<double> &observer) {}; + EXPECT_NO_THROW(DoubleObservableCounter counter("long_counter", asyc_generate_meas_double, + "description", "1")); +} + +TEST(AsyncInstruments, LongObservableGauge) +{ + auto asyc_generate_meas_long = [](opentelemetry::metrics::ObserverResult<long> &observer) {}; + EXPECT_NO_THROW( + LongObservableGauge counter("long_counter", asyc_generate_meas_long, "description", "1")); +} + +TEST(AsyncInstruments, DoubleObservableGauge) +{ + auto asyc_generate_meas_double = [](opentelemetry::metrics::ObserverResult<double> &observer) {}; + EXPECT_NO_THROW( + DoubleObservableGauge counter("long_counter", asyc_generate_meas_double, "description", "1")); +} + +TEST(AsyncInstruments, LongObservableUpDownCounter) +{ + auto asyc_generate_meas_long = [](opentelemetry::metrics::ObserverResult<long> &observer) {}; + EXPECT_NO_THROW(LongObservableUpDownCounter counter("long_counter", asyc_generate_meas_long, + "description", "1")); +} + +TEST(AsyncInstruments, DoubleObservableUpDownCounter) +{ + auto asyc_generate_meas_double = [](opentelemetry::metrics::ObserverResult<double> &observer) {}; + EXPECT_NO_THROW(DoubleObservableUpDownCounter counter("long_counter", asyc_generate_meas_double, + "description", "1")); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_metric_storage_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_metric_storage_test.cc new file mode 100644 index 000000000..2be5332a8 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/async_metric_storage_test.cc @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/state/async_metric_storage.h" +# include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/meter_context.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/sdk/metrics/metric_reader.h" +# include "opentelemetry/sdk/metrics/observer_result.h" +# include "opentelemetry/sdk/metrics/state/metric_collector.h" + +# include <gtest/gtest.h> +# include <vector> + +using namespace opentelemetry::sdk::metrics; +using namespace opentelemetry::sdk::instrumentationlibrary; +using namespace opentelemetry::sdk::resource; + +using namespace opentelemetry::sdk::metrics; +using namespace opentelemetry::common; +using M = std::map<std::string, std::string>; + +class MockCollectorHandle : public CollectorHandle +{ +public: + MockCollectorHandle(AggregationTemporality temp) : temporality(temp) {} + + AggregationTemporality GetAggregationTemporality() noexcept override { return temporality; } + +private: + AggregationTemporality temporality; +}; + +class WritableMetricStorageTestFixture : public ::testing::TestWithParam<AggregationTemporality> +{}; + +class MeasurementFetcher +{ +public: + static void Fetcher(opentelemetry::metrics::ObserverResult<long> &observer_result, + void * /*state*/) + { + fetch_count++; + if (fetch_count == 1) + { + observer_result.Observe(20l, {{"RequestType", "GET"}}); + observer_result.Observe(10l, {{"RequestType", "PUT"}}); + number_of_get += 20l; + number_of_put += 10l; + } + else if (fetch_count == 2) + { + observer_result.Observe(40l, {{"RequestType", "GET"}}); + observer_result.Observe(20l, {{"RequestType", "PUT"}}); + number_of_get += 40l; + number_of_put += 20l; + } + } + + static void init_values() + { + fetch_count = 0; + number_of_get = 0; + number_of_put = 0; + } + + static size_t fetch_count; + static long number_of_get; + static long number_of_put; + static const size_t number_of_attributes = 2; // GET , PUT +}; + +size_t MeasurementFetcher::fetch_count; +long MeasurementFetcher::number_of_get; +long MeasurementFetcher::number_of_put; +const size_t MeasurementFetcher::number_of_attributes; + +TEST_P(WritableMetricStorageTestFixture, TestAggregation) +{ + MeasurementFetcher::init_values(); + AggregationTemporality temporality = GetParam(); + + InstrumentDescriptor instr_desc = {"name", "desc", "1unit", InstrumentType::kObservableCounter, + InstrumentValueType::kLong}; + + auto sdk_start_ts = std::chrono::system_clock::now(); + // Some computation here + auto collection_ts = std::chrono::system_clock::now() + std::chrono::seconds(5); + + std::shared_ptr<CollectorHandle> collector(new MockCollectorHandle(temporality)); + std::vector<std::shared_ptr<CollectorHandle>> collectors; + collectors.push_back(collector); + size_t count_attributes = 0; + long value = 0; + + MeasurementFetcher measurement_fetcher; + opentelemetry::sdk::metrics::AsyncMetricStorage<long> storage(instr_desc, AggregationType::kSum, + MeasurementFetcher::Fetcher, + new DefaultAttributesProcessor()); + + storage.Collect(collector.get(), collectors, sdk_start_ts, collection_ts, + [&](const MetricData data) { + for (auto data_attr : data.point_data_attr_) + { + auto data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data); + if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), + MeasurementFetcher::number_of_get); + } + else if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), + MeasurementFetcher::number_of_put); + } + count_attributes++; + } + return true; + }); + EXPECT_EQ(MeasurementFetcher::number_of_attributes, count_attributes); +} + +INSTANTIATE_TEST_SUITE_P(WritableMetricStorageTestLong, + WritableMetricStorageTestFixture, + ::testing::Values(AggregationTemporality::kCumulative, + AggregationTemporality::kDelta)); + +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_benchmark.cc new file mode 100644 index 000000000..38d515a7e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_benchmark.cc @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <benchmark/benchmark.h> +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/common/attributemap_hash.h" +# include "opentelemetry/sdk/metrics/aggregation/aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/drop_aggregation.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" + +# include <functional> +# include <vector> + +using namespace opentelemetry::sdk::metrics; +constexpr size_t MAX_THREADS = 500; +namespace +{ + +void BM_AttributseHashMap(benchmark::State &state) +{ + + AttributesHashMap hash_map; + std::vector<std::thread> workers; + std::vector<MetricAttributes> attributes = {{{"k1", "v1"}, {"k2", "v2"}}, + {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}}; + + std::function<std::unique_ptr<Aggregation>()> create_default_aggregation = + []() -> std::unique_ptr<Aggregation> { + return std::unique_ptr<Aggregation>(new DropAggregation); + }; + + while (state.KeepRunning()) + { + for (size_t i = 0; i < MAX_THREADS; i++) + { + workers.push_back(std::thread([&]() { + hash_map.GetOrSetDefault(attributes[i % 2], create_default_aggregation)->Aggregate(1l); + benchmark::DoNotOptimize(hash_map.Has(attributes[i % 2])); + })); + } + } + + for (auto &t : workers) + { + t.join(); + } +} + +BENCHMARK(BM_AttributseHashMap); +} // namespace +#endif +BENCHMARK_MAIN();
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_test.cc new file mode 100644 index 000000000..610744c8e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_hashmap_test.cc @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" +# include <gtest/gtest.h> +# include "opentelemetry/sdk/metrics/aggregation/drop_aggregation.h" +# include "opentelemetry/sdk/metrics/instruments.h" + +# include <functional> + +using namespace opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; + +TEST(AttributesHashMap, BasicTests) +{ + + // Empty map + AttributesHashMap hash_map; + EXPECT_EQ(hash_map.Size(), 0); + MetricAttributes m1 = {{"k1", "v1"}}; + EXPECT_EQ(hash_map.Get(m1), nullptr); + EXPECT_EQ(hash_map.Has(m1), false); + + // Set + std::unique_ptr<Aggregation> aggregation1( + new DropAggregation()); // = std::unique_ptr<Aggregation>(new DropAggregation); + hash_map.Set(m1, std::move(aggregation1)); + EXPECT_NO_THROW(hash_map.Get(m1)->Aggregate(1l)); + EXPECT_EQ(hash_map.Size(), 1); + EXPECT_EQ(hash_map.Has(m1), true); + + // Set same key again + auto aggregation2 = std::unique_ptr<Aggregation>(new DropAggregation()); + hash_map.Set(m1, std::move(aggregation2)); + EXPECT_NO_THROW(hash_map.Get(m1)->Aggregate(1l)); + EXPECT_EQ(hash_map.Size(), 1); + EXPECT_EQ(hash_map.Has(m1), true); + + // Set more enteria + auto aggregation3 = std::unique_ptr<Aggregation>(new DropAggregation()); + MetricAttributes m3 = {{"k1", "v1"}, {"k2", "v2"}}; + hash_map.Set(m3, std::move(aggregation3)); + EXPECT_EQ(hash_map.Has(m1), true); + EXPECT_EQ(hash_map.Has(m3), true); + EXPECT_NO_THROW(hash_map.Get(m3)->Aggregate(1l)); + EXPECT_EQ(hash_map.Size(), 2); + + // GetOrSetDefault + std::function<std::unique_ptr<Aggregation>()> create_default_aggregation = + []() -> std::unique_ptr<Aggregation> { + return std::unique_ptr<Aggregation>(new DropAggregation); + }; + MetricAttributes m4 = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; + EXPECT_NO_THROW(hash_map.GetOrSetDefault(m4, create_default_aggregation)->Aggregate(1l)); + EXPECT_EQ(hash_map.Size(), 3); + + // Set attributes with different order - shouldn't create a new entry. + MetricAttributes m5 = {{"k2", "v2"}, {"k1", "v1"}}; + EXPECT_EQ(hash_map.Has(m5), true); + + // GetAllEnteries + size_t count = 0; + hash_map.GetAllEnteries([&count](const MetricAttributes &attributes, Aggregation &aggregation) { + count++; + return true; + }); + EXPECT_EQ(count, hash_map.Size()); +} + +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_benchmark.cc new file mode 100644 index 000000000..d558a668f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_benchmark.cc @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <benchmark/benchmark.h> +#ifndef ENABLE_METRICS_PREVIEW +# include <map> +# include "opentelemetry/sdk/metrics/view/attributes_processor.h" +using namespace opentelemetry::sdk::metrics; +namespace +{ +void BM_AttributseProcessorFilter(benchmark::State &state) +{ + std::map<std::string, int> attributes = { + {"att1", 10}, {"attr1", 20}, {"attr3", 30}, {"attr4", 40}}; + FilteringAttributesProcessor attributes_processor( + {{"attr2", true}, {"attr4", true}, {"attr6", true}}); + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>> iterable(attributes); + while (state.KeepRunning()) + { + auto filtered_attributes = attributes_processor.process(iterable); + } +} + +BENCHMARK(BM_AttributseProcessorFilter); +} // namespace +#endif +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_test.cc new file mode 100644 index 000000000..d496cc7b0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/attributes_processor_test.cc @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/view/attributes_processor.h" +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::metrics; +using namespace opentelemetry::common; +using namespace opentelemetry::sdk::common; + +TEST(AttributesProcessor, FilteringAttributesProcessor) +{ + const int kNumFilterAttributes = 3; + std::unordered_map<std::string, bool> filter = { + {"attr2", true}, {"attr4", true}, {"attr6", true}}; + const int kNumAttributes = 6; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"}; + int values[kNumAttributes] = {10, 20, 30, 40, 50, 60}; + std::map<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]}, + {keys[2], values[2]}, {keys[3], values[3]}, + {keys[4], values[4]}, {keys[5], values[5]}}; + FilteringAttributesProcessor attributes_processor(filter); + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>> iterable(attributes); + auto filtered_attributes = attributes_processor.process(iterable); + for (auto &e : filtered_attributes) + { + EXPECT_FALSE(filter.find(e.first) == filter.end()); + } + EXPECT_EQ(filter.size(), kNumFilterAttributes); +} + +TEST(AttributesProcessor, FilteringAllAttributesProcessor) +{ + const int kNumFilterAttributes = 0; + std::unordered_map<std::string, bool> filter = {}; + const int kNumAttributes = 6; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"}; + int values[kNumAttributes] = {10, 20, 30, 40, 50, 60}; + std::map<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]}, + {keys[2], values[2]}, {keys[3], values[3]}, + {keys[4], values[4]}, {keys[5], values[5]}}; + FilteringAttributesProcessor attributes_processor(filter); + opentelemetry::common::KeyValueIterableView<std::map<std::string, int>> iterable(attributes); + auto filtered_attributes = attributes_processor.process(iterable); + EXPECT_EQ(filter.size(), kNumFilterAttributes); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/BUILD new file mode 100644 index 000000000..6481f679d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/BUILD @@ -0,0 +1,47 @@ +cc_test( + name = "no_exemplar_reservoir_test", + srcs = [ + "no_exemplar_reservoir_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "never_sample_filter_test", + srcs = [ + "never_sample_filter_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "always_sample_filter_test", + srcs = [ + "always_sample_filter_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//api", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/CMakeLists.txt new file mode 100644 index 000000000..303294761 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/CMakeLists.txt @@ -0,0 +1,10 @@ +foreach(testname no_exemplar_reservoir_test never_sample_filter_test + always_sample_filter_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_metrics) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX metrics. + TEST_LIST ${testname}) +endforeach() diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/always_sample_filter_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/always_sample_filter_test.cc new file mode 100644 index 000000000..cf4e44995 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/always_sample_filter_test.cc @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/exemplar/always_sample_filter.h" +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::metrics; + +TEST(AlwaysSampleFilter, SampleMeasurement) +{ + auto filter = opentelemetry::sdk::metrics::AlwaysSampleFilter::GetAlwaysSampleFilter(); + ASSERT_TRUE( + filter->ShouldSampleMeasurement(1.0, MetricAttributes{}, opentelemetry::context::Context{})); + ASSERT_TRUE( + filter->ShouldSampleMeasurement(1l, MetricAttributes{}, opentelemetry::context::Context{})); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/never_sample_filter_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/never_sample_filter_test.cc new file mode 100644 index 000000000..930c57220 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/never_sample_filter_test.cc @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/context/context.h" +#ifndef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include "opentelemetry/sdk/metrics/exemplar/never_sample_filter.h" + +using namespace opentelemetry::sdk::metrics; + +TEST(NeverSampleFilter, SampleMeasurement) +{ + auto filter = opentelemetry::sdk::metrics::NeverSampleFilter::GetNeverSampleFilter(); + ASSERT_FALSE( + filter->ShouldSampleMeasurement(1.0, MetricAttributes{}, opentelemetry::context::Context{})); + ASSERT_FALSE( + filter->ShouldSampleMeasurement(1l, MetricAttributes{}, opentelemetry::context::Context{})); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/no_exemplar_reservoir_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/no_exemplar_reservoir_test.cc new file mode 100644 index 000000000..3e16940ff --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/exemplar/no_exemplar_reservoir_test.cc @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/exemplar/no_exemplar_reservoir.h" +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::metrics; + +TEST(NoExemplarReservoir, OfferMeasurement) +{ + auto reservoir = opentelemetry::sdk::metrics::NoExemplarReservoir::GetNoExemplarReservoir(); + EXPECT_NO_THROW(reservoir->OfferMeasurement(1.0, MetricAttributes{}, + opentelemetry::context::Context{}, + std::chrono::system_clock::now())); + EXPECT_NO_THROW(reservoir->OfferMeasurement( + 1l, MetricAttributes{}, opentelemetry::context::Context{}, std::chrono::system_clock::now())); + auto exemplar_data = reservoir->CollectAndReset(MetricAttributes{}); + ASSERT_TRUE(exemplar_data.empty()); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/meter_provider_sdk_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/meter_provider_sdk_test.cc new file mode 100644 index 000000000..b0fabe50b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/meter_provider_sdk_test.cc @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include "opentelemetry/sdk/metrics/export/metric_producer.h" +# include "opentelemetry/sdk/metrics/meter.h" +# include "opentelemetry/sdk/metrics/meter_provider.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/sdk/metrics/metric_reader.h" +# include "opentelemetry/sdk/metrics/view/instrument_selector.h" +# include "opentelemetry/sdk/metrics/view/meter_selector.h" + +using namespace opentelemetry::sdk::metrics; + +class MockMetricExporter : public MetricExporter +{ + +public: + MockMetricExporter() = default; + opentelemetry::sdk::common::ExportResult Export(const ResourceMetrics &records) noexcept override + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override + { + return true; + } + + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + return true; + } +}; + +class MockMetricReader : public MetricReader +{ +public: + MockMetricReader(std::unique_ptr<MetricExporter> exporter) : exporter_(std::move(exporter)) {} + virtual bool OnForceFlush(std::chrono::microseconds timeout) noexcept override { return true; } + virtual bool OnShutDown(std::chrono::microseconds timeout) noexcept override { return true; } + virtual void OnInitialized() noexcept override {} + +private: + std::unique_ptr<MetricExporter> exporter_; +}; + +TEST(MeterProvider, GetMeter) +{ + + MeterProvider mp1; + // std::unique_ptr<View> view{std::unique_ptr<View>()}; + // MeterProvider mp1(std::move(exporters), std::move(readers), std::move(views); + auto m1 = mp1.GetMeter("test"); + auto m2 = mp1.GetMeter("test"); + auto m3 = mp1.GetMeter("different", "1.0.0"); + auto m4 = mp1.GetMeter(""); + auto m5 = mp1.GetMeter(opentelemetry::nostd::string_view{}); + auto m6 = mp1.GetMeter("different", "1.0.0", "https://opentelemetry.io/schemas/1.2.0"); + ASSERT_NE(nullptr, m1); + ASSERT_NE(nullptr, m2); + ASSERT_NE(nullptr, m3); + ASSERT_NE(nullptr, m6); + + // Should return the same instance each time. + ASSERT_EQ(m1, m2); + ASSERT_NE(m1, m3); + ASSERT_EQ(m4, m5); + ASSERT_NE(m3, m6); + + // Should be an sdk::trace::Tracer with the processor attached. +# ifdef OPENTELEMETRY_RTTI_ENABLED + auto sdkMeter1 = dynamic_cast<Meter *>(m1.get()); +# else + auto sdkMeter1 = static_cast<Meter *>(m1.get()); +# endif + ASSERT_NE(nullptr, sdkMeter1); + std::unique_ptr<MockMetricExporter> exporter(new MockMetricExporter()); + std::unique_ptr<MetricReader> reader{new MockMetricReader(std::move(exporter))}; + ASSERT_NO_THROW(mp1.AddMetricReader(std::move(reader))); + + std::unique_ptr<View> view{std::unique_ptr<View>()}; + std::unique_ptr<InstrumentSelector> instrument_selector{ + new InstrumentSelector(InstrumentType::kCounter, "instru1")}; + std::unique_ptr<MeterSelector> meter_selector{new MeterSelector("name1", "version1", "schema1")}; + ASSERT_NO_THROW( + mp1.AddView(std::move(instrument_selector), std::move(meter_selector), std::move(view))); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/metric_reader_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/metric_reader_test.cc new file mode 100644 index 000000000..c9c30853d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/metric_reader_test.cc @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/metric_reader.h" +# include <gtest/gtest.h> +# include "opentelemetry/sdk/metrics/meter_context.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationlibrary; +using namespace opentelemetry::sdk::metrics; + +class MockMetricReader : public MetricReader +{ +public: + MockMetricReader(AggregationTemporality aggr_temporality) : MetricReader(aggr_temporality) {} + + virtual bool OnForceFlush(std::chrono::microseconds timeout) noexcept override { return true; } + virtual bool OnShutDown(std::chrono::microseconds timeout) noexcept override { return true; } + virtual void OnInitialized() noexcept override {} +}; + +TEST(MetricReaderTest, BasicTests) +{ + AggregationTemporality aggr_temporality = AggregationTemporality::kDelta; + std::unique_ptr<MetricReader> metric_reader1(new MockMetricReader(aggr_temporality)); + EXPECT_EQ(metric_reader1->GetAggregationTemporality(), aggr_temporality); + + std::shared_ptr<MeterContext> meter_context1(new MeterContext()); + EXPECT_NO_THROW(meter_context1->AddMetricReader(std::move(metric_reader1))); + + std::unique_ptr<MetricReader> metric_reader2(new MockMetricReader(aggr_temporality)); + std::shared_ptr<MeterContext> meter_context2(new MeterContext()); + MetricProducer *metric_producer = + new MetricCollector(std::move(meter_context2), std::move(metric_reader2)); + EXPECT_NO_THROW(metric_producer->Collect([](ResourceMetrics &metric_data) { return true; })); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/multi_metric_storage_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/multi_metric_storage_test.cc new file mode 100644 index 000000000..d88946485 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/multi_metric_storage_test.cc @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/state/multi_metric_storage.h" +# include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/sdk/metrics/exemplar/no_exemplar_reservoir.h" +# include "opentelemetry/sdk/metrics/instruments.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationlibrary; +using namespace opentelemetry::sdk::metrics; + +class TestMetricStorage : public WritableMetricStorage +{ +public: + void RecordLong(long value, const opentelemetry::context::Context &context) noexcept override + { + num_calls_long++; + } + + void RecordLong(long value, + const opentelemetry::common::KeyValueIterable &attributes, + const opentelemetry::context::Context &context) noexcept override + { + num_calls_long++; + } + + void RecordDouble(double value, const opentelemetry::context::Context &context) noexcept override + { + num_calls_double++; + } + + void RecordDouble(double value, + const opentelemetry::common::KeyValueIterable &attributes, + const opentelemetry::context::Context &context) noexcept override + { + num_calls_double++; + } + + size_t num_calls_long; + size_t num_calls_double; +}; + +TEST(MultiMetricStorageTest, BasicTests) +{ + std::shared_ptr<opentelemetry::sdk::metrics::WritableMetricStorage> storage( + new TestMetricStorage()); + MultiMetricStorage storages{}; + storages.AddStorage(storage); + EXPECT_NO_THROW(storages.RecordLong(10l, opentelemetry::context::Context{})); + EXPECT_NO_THROW(storages.RecordLong(20l, opentelemetry::context::Context{})); + + EXPECT_NO_THROW(storages.RecordDouble(10.0, opentelemetry::context::Context{})); + EXPECT_NO_THROW(storages.RecordLong(30l, opentelemetry::context::Context{})); + + EXPECT_EQ(static_cast<TestMetricStorage *>(storage.get())->num_calls_long, 3); + EXPECT_EQ(static_cast<TestMetricStorage *>(storage.get())->num_calls_double, 1); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/observer_result_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/observer_result_test.cc new file mode 100644 index 000000000..a4cc28ae5 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/observer_result_test.cc @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/observer_result.h" +# include "opentelemetry/sdk/metrics/view/attributes_processor.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::metrics; +TEST(ObserverResult, BasicTests) +{ + const AttributesProcessor *attributes_processor = new DefaultAttributesProcessor(); + + ObserverResult<long> observer_result(attributes_processor); + + observer_result.Observe(10l); + observer_result.Observe(20l); + EXPECT_EQ(observer_result.GetMeasurements().size(), 1); + + std::map<std::string, int64_t> m1 = {{"k2", 12}}; + observer_result.Observe( + 30l, opentelemetry::common::KeyValueIterableView<std::map<std::string, int64_t>>(m1)); + EXPECT_EQ(observer_result.GetMeasurements().size(), 2); + + observer_result.Observe( + 40l, opentelemetry::common::KeyValueIterableView<std::map<std::string, int64_t>>(m1)); + EXPECT_EQ(observer_result.GetMeasurements().size(), 2); + + std::map<std::string, int64_t> m2 = {{"k2", 12}, {"k4", 12}}; + observer_result.Observe( + 40l, opentelemetry::common::KeyValueIterableView<std::map<std::string, int64_t>>(m2)); + EXPECT_EQ(observer_result.GetMeasurements().size(), 3); + + delete attributes_processor; +} + +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/periodic_exporting_metric_reader_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/periodic_exporting_metric_reader_test.cc new file mode 100644 index 000000000..5219f3110 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/periodic_exporting_metric_reader_test.cc @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader.h" +# include "opentelemetry/sdk/metrics/export/metric_producer.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationlibrary; +using namespace opentelemetry::sdk::metrics; + +class MockPushMetricExporter : public MetricExporter +{ +public: + opentelemetry::sdk::common::ExportResult Export(const ResourceMetrics &record) noexcept override + { + records_.push_back(record); + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override + { + return false; + } + + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + return true; + } + + size_t GetDataCount() { return records_.size(); } + +private: + std::vector<ResourceMetrics> records_; +}; + +class MockMetricProducer : public MetricProducer +{ +public: + MockMetricProducer(std::chrono::microseconds sleep_ms = std::chrono::microseconds::zero()) + : sleep_ms_{sleep_ms}, data_sent_size_(0) + {} + + bool Collect(nostd::function_ref<bool(ResourceMetrics &)> callback) noexcept override + { + std::this_thread::sleep_for(sleep_ms_); + data_sent_size_++; + ResourceMetrics data; + callback(data); + return true; + } + + size_t GetDataCount() { return data_sent_size_; } + +private: + std::chrono::microseconds sleep_ms_; + size_t data_sent_size_; +}; + +TEST(PeriodicExporingMetricReader, BasicTests) +{ + std::unique_ptr<MetricExporter> exporter(new MockPushMetricExporter()); + PeriodicExportingMetricReaderOptions options; + options.export_timeout_millis = std::chrono::milliseconds(200); + options.export_interval_millis = std::chrono::milliseconds(500); + auto exporter_ptr = exporter.get(); + PeriodicExportingMetricReader reader(std::move(exporter), options); + MockMetricProducer producer; + reader.SetMetricProducer(&producer); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + reader.Shutdown(); + EXPECT_EQ(static_cast<MockPushMetricExporter *>(exporter_ptr)->GetDataCount(), + static_cast<MockMetricProducer *>(&producer)->GetDataCount()); +} + +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_instruments_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_instruments_test.cc new file mode 100644 index 000000000..1a590d8d2 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_instruments_test.cc @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/sync_instruments.h" +# include "opentelemetry/context/context.h" +# include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" +# include "opentelemetry/sdk/metrics/exemplar/no_exemplar_reservoir.h" +# include "opentelemetry/sdk/metrics/state/multi_metric_storage.h" + +# include <gtest/gtest.h> +# include <cmath> +# include <limits> + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationlibrary; +using namespace opentelemetry::sdk::metrics; + +auto instrumentation_library = InstrumentationLibrary::Create("opentelemetry-cpp", "0.1.0"); + +using M = std::map<std::string, std::string>; + +TEST(SyncInstruments, LongCounter) +{ + InstrumentDescriptor instrument_descriptor = { + "long_counter", "description", "1", InstrumentType::kCounter, InstrumentValueType::kLong}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + LongCounter counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Add(10l)); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::context::Context{})); + + EXPECT_NO_THROW(counter.Add( + 10l, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}))); + EXPECT_NO_THROW(counter.Add( + 10l, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::common::KeyValueIterableView<M>({}))); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); +} + +TEST(SyncInstruments, DoubleCounter) +{ + InstrumentDescriptor instrument_descriptor = { + "double_counter", "description", "1", InstrumentType::kCounter, InstrumentValueType::kDouble}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + DoubleCounter counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Add(10.10)); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::context::Context{})); + + EXPECT_NO_THROW(counter.Add( + 10.10, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}))); + EXPECT_NO_THROW(counter.Add( + 10.10, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::common::KeyValueIterableView<M>({}))); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); +} + +TEST(SyncInstruments, LongUpDownCounter) +{ + InstrumentDescriptor instrument_descriptor = {"long_updowncounter", "description", "1", + InstrumentType::kUpDownCounter, + InstrumentValueType::kLong}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + LongUpDownCounter counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Add(10l)); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::context::Context{})); + + EXPECT_NO_THROW(counter.Add( + 10l, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}))); + EXPECT_NO_THROW(counter.Add( + 10l, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::common::KeyValueIterableView<M>({}))); + EXPECT_NO_THROW(counter.Add(10l, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); +} + +TEST(SyncInstruments, DoubleUpDownCounter) +{ + InstrumentDescriptor instrument_descriptor = {"double_updowncounter", "description", "1", + InstrumentType::kUpDownCounter, + InstrumentValueType::kDouble}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + DoubleUpDownCounter counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Add(10.10)); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::context::Context{})); + + EXPECT_NO_THROW(counter.Add( + 10.10, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Add( + 10.10, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}))); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Add(10.10, opentelemetry::common::KeyValueIterableView<M>({}))); +} + +TEST(SyncInstruments, LongHistogram) +{ + InstrumentDescriptor instrument_descriptor = { + "long_histogram", "description", "1", InstrumentType::kHistogram, InstrumentValueType::kLong}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + LongHistogram counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Record(10l, opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Record(-10l, opentelemetry::context::Context{})); // This is ignored + + EXPECT_NO_THROW(counter.Record( + 10l, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Record(10l, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); +} + +TEST(SyncInstruments, DoubleHistogram) +{ + InstrumentDescriptor instrument_descriptor = {"double_histogram", "description", "1", + InstrumentType::kHistogram, + InstrumentValueType::kDouble}; + std::unique_ptr<WritableMetricStorage> metric_storage(new MultiMetricStorage()); + DoubleHistogram counter(instrument_descriptor, std::move(metric_storage)); + EXPECT_NO_THROW(counter.Record(10.10, opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Record(-10.10, opentelemetry::context::Context{})); // This is ignored. + EXPECT_NO_THROW(counter.Record(std::numeric_limits<double>::quiet_NaN(), + opentelemetry::context::Context{})); // This is ignored too + EXPECT_NO_THROW(counter.Record(std::numeric_limits<double>::infinity(), + opentelemetry::context::Context{})); // This is ignored too + + EXPECT_NO_THROW(counter.Record( + 10.10, opentelemetry::common::KeyValueIterableView<M>({{"abc", "123"}, {"xyz", "456"}}), + opentelemetry::context::Context{})); + EXPECT_NO_THROW(counter.Record(10.10, opentelemetry::common::KeyValueIterableView<M>({}), + opentelemetry::context::Context{})); +} + +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_metric_storage_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_metric_storage_test.cc new file mode 100644 index 000000000..7dfc4f947 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/sync_metric_storage_test.cc @@ -0,0 +1,246 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/state/sync_metric_storage.h" +# include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/sdk/metrics/exemplar/no_exemplar_reservoir.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/view/attributes_processor.h" + +# include <gtest/gtest.h> +# include <map> + +using namespace opentelemetry::sdk::metrics; +using namespace opentelemetry::common; +using M = std::map<std::string, std::string>; + +class MockCollectorHandle : public CollectorHandle +{ +public: + MockCollectorHandle(AggregationTemporality temp) : temporality(temp) {} + + AggregationTemporality GetAggregationTemporality() noexcept override { return temporality; } + +private: + AggregationTemporality temporality; +}; + +class WritableMetricStorageTestFixture : public ::testing::TestWithParam<AggregationTemporality> +{}; + +TEST_P(WritableMetricStorageTestFixture, LongSumAggregation) +{ + AggregationTemporality temporality = GetParam(); + auto sdk_start_ts = std::chrono::system_clock::now(); + long expected_total_get_requests = 0; + long expected_total_put_requests = 0; + InstrumentDescriptor instr_desc = {"name", "desc", "1unit", InstrumentType::kCounter, + InstrumentValueType::kLong}; + std::map<std::string, std::string> attributes_get = {{"RequestType", "GET"}}; + std::map<std::string, std::string> attributes_put = {{"RequestType", "PUT"}}; + + opentelemetry::sdk::metrics::SyncMetricStorage storage( + instr_desc, AggregationType::kSum, new DefaultAttributesProcessor(), + NoExemplarReservoir::GetNoExemplarReservoir()); + + storage.RecordLong(10l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 10; + + EXPECT_NO_THROW(storage.RecordLong( + 30l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 30; + + storage.RecordLong(20l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 20; + + EXPECT_NO_THROW(storage.RecordLong( + 40l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 40; + + std::shared_ptr<CollectorHandle> collector(new MockCollectorHandle(temporality)); + std::vector<std::shared_ptr<CollectorHandle>> collectors; + collectors.push_back(collector); + + // Some computation here + auto collection_ts = std::chrono::system_clock::now(); + size_t count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData data) { + for (auto data_attr : data.point_data_attr_) + { + auto data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data); + if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), expected_total_get_requests); + count_attributes++; + } + else if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), expected_total_put_requests); + count_attributes++; + } + } + return true; + }); + + // In case of delta temporarily, subsequent collection would contain new data points, so resetting + // the counts + if (temporality == AggregationTemporality::kDelta) + { + expected_total_get_requests = 0; + expected_total_put_requests = 0; + } + + EXPECT_NO_THROW(storage.RecordLong( + 50l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{})); + expected_total_get_requests += 50; + EXPECT_NO_THROW(storage.RecordLong( + 40l, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 40; + + collection_ts = std::chrono::system_clock::now(); + count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData data) { + for (auto data_attr : data.point_data_attr_) + { + auto data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data); + if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), expected_total_get_requests); + count_attributes++; + } + else if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(opentelemetry::nostd::get<long>(data.value_), expected_total_put_requests); + count_attributes++; + } + } + return true; + }); +} +INSTANTIATE_TEST_SUITE_P(WritableMetricStorageTestLong, + WritableMetricStorageTestFixture, + ::testing::Values(AggregationTemporality::kCumulative, + AggregationTemporality::kDelta)); + +TEST_P(WritableMetricStorageTestFixture, DoubleSumAggregation) +{ + AggregationTemporality temporality = GetParam(); + auto sdk_start_ts = std::chrono::system_clock::now(); + double expected_total_get_requests = 0; + double expected_total_put_requests = 0; + InstrumentDescriptor instr_desc = {"name", "desc", "1unit", InstrumentType::kCounter, + InstrumentValueType::kDouble}; + std::map<std::string, std::string> attributes_get = {{"RequestType", "GET"}}; + std::map<std::string, std::string> attributes_put = {{"RequestType", "PUT"}}; + + opentelemetry::sdk::metrics::SyncMetricStorage storage( + instr_desc, AggregationType::kSum, new DefaultAttributesProcessor(), + NoExemplarReservoir::GetNoExemplarReservoir()); + + storage.RecordDouble(10.0, + KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 10; + + EXPECT_NO_THROW(storage.RecordDouble( + 30.0, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 30; + + storage.RecordDouble(20.0, + KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 20; + + EXPECT_NO_THROW(storage.RecordDouble( + 40.0, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 40; + + std::shared_ptr<CollectorHandle> collector(new MockCollectorHandle(temporality)); + std::vector<std::shared_ptr<CollectorHandle>> collectors; + collectors.push_back(collector); + + // Some computation here + auto collection_ts = std::chrono::system_clock::now(); + size_t count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData data) { + for (auto data_attr : data.point_data_attr_) + { + auto data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data); + if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(opentelemetry::nostd::get<double>(data.value_), expected_total_get_requests); + count_attributes++; + } + else if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(opentelemetry::nostd::get<double>(data.value_), expected_total_put_requests); + count_attributes++; + } + } + return true; + }); + + // In case of delta temporarily, subsequent collection would contain new data points, so resetting + // the counts + if (temporality == AggregationTemporality::kDelta) + { + expected_total_get_requests = 0; + expected_total_put_requests = 0; + } + + EXPECT_NO_THROW(storage.RecordDouble( + 50.0, KeyValueIterableView<std::map<std::string, std::string>>(attributes_get), + opentelemetry::context::Context{})); + expected_total_get_requests += 50; + EXPECT_NO_THROW(storage.RecordDouble( + 40.0, KeyValueIterableView<std::map<std::string, std::string>>(attributes_put), + opentelemetry::context::Context{})); + expected_total_put_requests += 40; + + collection_ts = std::chrono::system_clock::now(); + count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData data) { + for (auto data_attr : data.point_data_attr_) + { + auto data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data); + if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(opentelemetry::nostd::get<double>(data.value_), expected_total_get_requests); + count_attributes++; + } + else if (opentelemetry::nostd::get<std::string>( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(opentelemetry::nostd::get<double>(data.value_), expected_total_put_requests); + count_attributes++; + } + } + return true; + }); +} +INSTANTIATE_TEST_SUITE_P(WritableMetricStorageTestDouble, + WritableMetricStorageTestFixture, + ::testing::Values(AggregationTemporality::kCumulative, + AggregationTemporality::kDelta)); + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/view_registry_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/view_registry_test.cc new file mode 100644 index 000000000..8151d3754 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/metrics/view_registry_test.cc @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/sdk/metrics/view/view_registry.h" +# include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/view/predicate.h" + +# include <gtest/gtest.h> + +using namespace opentelemetry::sdk::metrics; +using namespace opentelemetry::sdk::instrumentationlibrary; + +TEST(ViewRegistry, FindViewsEmptyRegistry) +{ + InstrumentDescriptor default_instrument_descriptor = { + "test_name", // name + "test_descr", // description + "1", // unit + InstrumentType::kCounter, // instrument type + InstrumentValueType::kLong}; + + auto default_instrumentation_lib = + InstrumentationLibrary::Create("default", "1.0.0", "https://opentelemetry.io/schemas/1.7.0"); + int count = 0; + ViewRegistry registry; + auto status = + registry.FindViews(default_instrument_descriptor, *default_instrumentation_lib.get(), + [&count](const View &view) { + count++; + EXPECT_EQ(view.GetName(), "otel-default-view"); + EXPECT_EQ(view.GetDescription(), ""); + EXPECT_EQ(view.GetAggregationType(), AggregationType::kDefault); + return true; + }); + EXPECT_EQ(count, 1); + EXPECT_EQ(status, true); +} + +TEST(ViewRegistry, FindNonExistingView) +{ + // Add view + const std::string view_name = "test_view"; + const std::string view_description = "test description"; + const std::string instrumentation_name = "name1"; + const std::string instrumentation_version = "version1"; + const std::string instrumentation_schema = "schema1"; + const std::string instrument_name = "testname"; + const InstrumentType instrument_type = InstrumentType::kCounter; + + std::unique_ptr<InstrumentSelector> instrument_selector{ + new InstrumentSelector(instrument_type, instrument_name)}; + std::unique_ptr<MeterSelector> meter_selector{ + new MeterSelector(instrumentation_name, instrumentation_version, instrumentation_schema)}; + std::unique_ptr<View> view = std::unique_ptr<View>(new View(view_name, view_description)); + + ViewRegistry registry; + registry.AddView(std::move(instrument_selector), std::move(meter_selector), std::move(view)); + InstrumentDescriptor default_instrument_descriptor = {instrument_name, // name + "test_descr", // description + "1", // unit + instrument_type, // instrument type + InstrumentValueType::kLong}; + + auto default_instrumentation_lib = InstrumentationLibrary::Create( + instrumentation_name, instrumentation_version, instrumentation_schema); + int count = 0; + auto status = + registry.FindViews(default_instrument_descriptor, *default_instrumentation_lib.get(), + [&count, &view_name, &view_description](const View &view) { + count++; +# if HAVE_WORKING_REGEX + EXPECT_EQ(view.GetName(), view_name); + EXPECT_EQ(view.GetDescription(), view_description); +# endif + return true; + }); + EXPECT_EQ(count, 1); + EXPECT_EQ(status, true); +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/BUILD b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/BUILD new file mode 100644 index 000000000..e70f16ee7 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/BUILD @@ -0,0 +1,12 @@ +cc_test( + name = "resource_test", + srcs = [ + "resource_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/CMakeLists.txt new file mode 100644 index 000000000..514b98680 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/CMakeLists.txt @@ -0,0 +1,9 @@ +foreach(testname resource_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_resources) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX resources. + TEST_LIST ${testname}) +endforeach() diff --git a/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/resource_test.cc b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/resource_test.cc new file mode 100644 index 000000000..5f058eafb --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/sdk/test/resource/resource_test.cc @@ -0,0 +1,213 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/resource/experimental_semantic_conventions.h" +#include "opentelemetry/sdk/resource/resource_detector.h" + +#include <cstdlib> +#include <string> +#include <unordered_map> + +#include <gtest/gtest.h> + +#if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#endif + +using namespace opentelemetry::sdk::resource; +namespace nostd = opentelemetry::nostd; + +class TestResource : public Resource +{ +public: + TestResource(ResourceAttributes attributes = ResourceAttributes()) : Resource(attributes) {} +}; + +TEST(ResourceTest, create_without_servicename) +{ + ResourceAttributes expected_attributes = { + {"service", "backend"}, + {"version", (uint32_t)1}, + {"cost", 234.23}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkLanguage), "cpp"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkName), "opentelemetry"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkVersion), OPENTELEMETRY_SDK_VERSION}, + {OTEL_GET_RESOURCE_ATTR(AttrServiceName), "unknown_service"}}; + + ResourceAttributes attributes = { + {"service", "backend"}, {"version", (uint32_t)1}, {"cost", 234.23}}; + auto resource = Resource::Create(attributes); + auto received_attributes = resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + if (e.first == "version") + EXPECT_EQ(nostd::get<uint32_t>(expected_attributes.find(e.first)->second), + nostd::get<uint32_t>(e.second)); + else if (e.first == "cost") + EXPECT_EQ(nostd::get<double>(expected_attributes.find(e.first)->second), + nostd::get<double>(e.second)); + else + EXPECT_EQ(opentelemetry::nostd::get<std::string>(expected_attributes.find(e.first)->second), + opentelemetry::nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); // for missing service.name +} + +TEST(ResourceTest, create_with_servicename) +{ + ResourceAttributes expected_attributes = { + {"version", (uint32_t)1}, + {"cost", 234.23}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkLanguage), "cpp"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkName), "opentelemetry"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkVersion), OPENTELEMETRY_SDK_VERSION}, + {OTEL_GET_RESOURCE_ATTR(AttrServiceName), "backend"}, + }; + ResourceAttributes attributes = { + {"service.name", "backend"}, {"version", (uint32_t)1}, {"cost", 234.23}}; + auto resource = Resource::Create(attributes); + auto received_attributes = resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + if (e.first == "version") + EXPECT_EQ(nostd::get<uint32_t>(expected_attributes.find(e.first)->second), + nostd::get<uint32_t>(e.second)); + else if (e.first == "cost") + EXPECT_EQ(nostd::get<double>(expected_attributes.find(e.first)->second), + nostd::get<double>(e.second)); + else + EXPECT_EQ(nostd::get<std::string>(expected_attributes.find(e.first)->second), + nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); // for missing service.name +} + +TEST(ResourceTest, create_with_emptyatrributes) +{ + ResourceAttributes expected_attributes = { + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkLanguage), "cpp"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkName), "opentelemetry"}, + {OTEL_GET_RESOURCE_ATTR(AttrTelemetrySdkVersion), OPENTELEMETRY_SDK_VERSION}, + {OTEL_GET_RESOURCE_ATTR(AttrServiceName), "unknown_service"}, + }; + ResourceAttributes attributes = {}; + auto resource = Resource::Create(attributes); + auto received_attributes = resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + EXPECT_EQ(opentelemetry::nostd::get<std::string>(expected_attributes.find(e.first)->second), + opentelemetry::nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); // for missing service.name +} + +TEST(ResourceTest, create_with_schemaurl) +{ + const std::string schema_url = "https://opentelemetry.io/schemas/1.2.0"; + ResourceAttributes attributes = {}; + auto resource = Resource::Create(attributes, schema_url); + auto received_schema_url = resource.GetSchemaURL(); + + EXPECT_EQ(received_schema_url, schema_url); +} + +TEST(ResourceTest, Merge) +{ + TestResource resource1(ResourceAttributes({{"service", "backend"}})); + TestResource resource2(ResourceAttributes({{"host", "service-host"}})); + std::map<std::string, std::string> expected_attributes = {{"service", "backend"}, + {"host", "service-host"}}; + + auto merged_resource = resource1.Merge(resource2); + auto received_attributes = merged_resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + EXPECT_EQ(expected_attributes.find(e.first)->second, nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); +} + +TEST(ResourceTest, MergeEmptyString) +{ + TestResource resource1({{"service", "backend"}, {"host", "service-host"}}); + TestResource resource2({{"service", ""}, {"host", "another-service-host"}}); + std::map<std::string, std::string> expected_attributes = {{"service", ""}, + {"host", "another-service-host"}}; + + auto merged_resource = resource1.Merge(resource2); + auto received_attributes = merged_resource.GetAttributes(); + + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + EXPECT_EQ(expected_attributes.find(e.first)->second, nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); +} + +#ifndef NO_GETENV +TEST(ResourceTest, OtelResourceDetector) +{ + std::map<std::string, std::string> expected_attributes = {{"k", "v"}}; + + setenv("OTEL_RESOURCE_ATTRIBUTES", "k=v", 1); + + OTELResourceDetector detector; + auto resource = detector.Detect(); + auto received_attributes = resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + EXPECT_EQ(expected_attributes.find(e.first)->second, nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); + + unsetenv("OTEL_RESOURCE_ATTRIBUTES"); +} + +TEST(ResourceTest, OtelResourceDetectorEmptyEnv) +{ + std::map<std::string, std::string> expected_attributes = {}; + unsetenv("OTEL_RESOURCE_ATTRIBUTES"); + OTELResourceDetector detector; + auto resource = detector.Detect(); + auto received_attributes = resource.GetAttributes(); + for (auto &e : received_attributes) + { + EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); + if (expected_attributes.find(e.first) != expected_attributes.end()) + { + EXPECT_EQ(expected_attributes.find(e.first)->second, nostd::get<std::string>(e.second)); + } + } + EXPECT_EQ(received_attributes.size(), expected_attributes.size()); +} +#endif 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()); +} |