From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- .../opentelemetry-cpp/api/test/common/BUILD | 41 ++++ .../api/test/common/CMakeLists.txt | 15 ++ .../api/test/common/kv_properties_test.cc | 235 +++++++++++++++++++++ .../api/test/common/spinlock_benchmark.cc | 152 +++++++++++++ .../api/test/common/string_util_test.cc | 47 +++++ 5 files changed, 490 insertions(+) create mode 100644 src/jaegertracing/opentelemetry-cpp/api/test/common/BUILD create mode 100644 src/jaegertracing/opentelemetry-cpp/api/test/common/CMakeLists.txt create mode 100644 src/jaegertracing/opentelemetry-cpp/api/test/common/kv_properties_test.cc create mode 100644 src/jaegertracing/opentelemetry-cpp/api/test/common/spinlock_benchmark.cc create mode 100644 src/jaegertracing/opentelemetry-cpp/api/test/common/string_util_test.cc (limited to 'src/jaegertracing/opentelemetry-cpp/api/test/common') diff --git a/src/jaegertracing/opentelemetry-cpp/api/test/common/BUILD b/src/jaegertracing/opentelemetry-cpp/api/test/common/BUILD new file mode 100644 index 000000000..5bfc1115d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/api/test/common/BUILD @@ -0,0 +1,41 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +otel_cc_benchmark( + name = "spinlock_benchmark", + srcs = ["spinlock_benchmark.cc"], + tags = [ + "api", + "test", + ], + deps = ["//api"], +) + +cc_test( + name = "kv_properties_test", + srcs = [ + "kv_properties_test.cc", + ], + tags = [ + "api", + "test", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "string_util_test", + srcs = [ + "string_util_test.cc", + ], + tags = [ + "api", + "test", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/api/test/common/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/api/test/common/CMakeLists.txt new file mode 100644 index 000000000..5816b64b1 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/api/test/common/CMakeLists.txt @@ -0,0 +1,15 @@ +include(GoogleTest) + +foreach(testname kv_properties_test string_util_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 common. + TEST_LIST ${testname}) +endforeach() + +add_executable(spinlock_benchmark spinlock_benchmark.cc) +target_link_libraries(spinlock_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) diff --git a/src/jaegertracing/opentelemetry-cpp/api/test/common/kv_properties_test.cc b/src/jaegertracing/opentelemetry-cpp/api/test/common/kv_properties_test.cc new file mode 100644 index 000000000..e5d9a2439 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/api/test/common/kv_properties_test.cc @@ -0,0 +1,235 @@ + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include + +// ------------------------- Entry class tests --------------------------------- + +using namespace opentelemetry; +using opentelemetry::common::KeyValueProperties; +// Test constructor that takes a key-value pair +TEST(EntryTest, KeyValueConstruction) +{ + opentelemetry::nostd::string_view key = "test_key"; + opentelemetry::nostd::string_view val = "test_value"; + KeyValueProperties::Entry e(key, val); + + EXPECT_EQ(key.size(), e.GetKey().size()); + EXPECT_EQ(key, e.GetKey()); + + EXPECT_EQ(val.size(), e.GetValue().size()); + EXPECT_EQ(val, e.GetValue()); +} + +// Test copy constructor +TEST(EntryTest, Copy) +{ + KeyValueProperties::Entry e("test_key", "test_value"); + KeyValueProperties::Entry copy(e); + EXPECT_EQ(copy.GetKey(), e.GetKey()); + EXPECT_EQ(copy.GetValue(), e.GetValue()); +} + +// Test assignment operator +TEST(EntryTest, Assignment) +{ + KeyValueProperties::Entry e("test_key", "test_value"); + KeyValueProperties::Entry empty; + empty = e; + EXPECT_EQ(empty.GetKey(), e.GetKey()); + EXPECT_EQ(empty.GetValue(), e.GetValue()); +} + +TEST(EntryTest, SetValue) +{ + KeyValueProperties::Entry e("test_key", "test_value"); + opentelemetry::nostd::string_view new_val = "new_value"; + e.SetValue(new_val); + + EXPECT_EQ(new_val.size(), e.GetValue().size()); + EXPECT_EQ(new_val, e.GetValue()); +} + +// ------------------------- KeyValueStringTokenizer tests --------------------------------- + +using opentelemetry::common::KeyValueStringTokenizer; +using opentelemetry::common::KeyValueStringTokenizerOptions; + +TEST(KVStringTokenizer, SinglePair) +{ + bool valid_kv; + nostd::string_view key, value; + opentelemetry::nostd::string_view str = "k1=v1"; + KeyValueStringTokenizerOptions opts; + KeyValueStringTokenizer tk(str, opts); + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k1"); + EXPECT_EQ(value, "v1"); + EXPECT_FALSE(tk.next(valid_kv, key, value)); +} + +TEST(KVStringTokenizer, AcceptEmptyEntries) +{ + bool valid_kv; + nostd::string_view key, value; + opentelemetry::nostd::string_view str = ":k1=v1::k2=v2: "; + KeyValueStringTokenizerOptions opts; + opts.member_separator = ':'; + opts.ignore_empty_members = false; + + KeyValueStringTokenizer tk(str, opts); + EXPECT_TRUE(tk.next(valid_kv, key, value)); // empty pair + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k1"); + EXPECT_EQ(value, "v1"); + EXPECT_TRUE(tk.next(valid_kv, key, value)); // empty pair + EXPECT_EQ(key, ""); + EXPECT_EQ(value, ""); + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(tk.next(valid_kv, key, value)); // empty pair + EXPECT_FALSE(tk.next(valid_kv, key, value)); +} + +TEST(KVStringTokenizer, ValidPairsWithEmptyEntries) +{ + opentelemetry::nostd::string_view str = "k1:v1===k2:v2=="; + bool valid_kv; + nostd::string_view key, value; + KeyValueStringTokenizerOptions opts; + opts.member_separator = '='; + opts.key_value_separator = ':'; + + KeyValueStringTokenizer tk(str, opts); + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k1"); + EXPECT_EQ(value, "v1"); + + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k2"); + EXPECT_EQ(value, "v2"); + + EXPECT_FALSE(tk.next(valid_kv, key, value)); +} + +TEST(KVStringTokenizer, InvalidPairs) +{ + opentelemetry::nostd::string_view str = "k1=v1,invalid ,, k2=v2 ,invalid"; + KeyValueStringTokenizer tk(str); + bool valid_kv; + nostd::string_view key, value; + EXPECT_TRUE(tk.next(valid_kv, key, value)); + + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k1"); + EXPECT_EQ(value, "v1"); + + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_FALSE(valid_kv); + + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_TRUE(valid_kv); + EXPECT_EQ(key, "k2"); + EXPECT_EQ(value, "v2"); + + EXPECT_TRUE(tk.next(valid_kv, key, value)); + EXPECT_FALSE(valid_kv); + + EXPECT_FALSE(tk.next(valid_kv, key, value)); +} + +TEST(KVStringTokenizer, NumTokens) +{ + struct + { + const char *input; + size_t expected; + } testcases[] = {{"k1=v1", 1}, + {" ", 1}, + {"k1=v1,k2=v2,k3=v3", 3}, + {"k1=v1,", 1}, + {"k1=v1,k2=v2,invalidmember", 3}, + {"", 0}}; + for (auto &testcase : testcases) + { + KeyValueStringTokenizer tk(testcase.input); + EXPECT_EQ(tk.NumTokens(), testcase.expected); + } +} + +//------------------------- KeyValueProperties tests --------------------------------- + +TEST(KeyValueProperties, PopulateKVIterableContainer) +{ + std::vector> kv_pairs = {{"k1", "v1"}, {"k2", "v2"}}; + + auto kv_properties = KeyValueProperties(kv_pairs); + EXPECT_EQ(kv_properties.Size(), 2); + + std::string value; + bool present = kv_properties.GetValue("k1", value); + EXPECT_TRUE(present); + EXPECT_EQ(value, "v1"); + + present = kv_properties.GetValue("k2", value); + EXPECT_TRUE(present); + EXPECT_EQ(value, "v2"); +} + +TEST(KeyValueProperties, AddEntry) +{ + auto kv_properties = KeyValueProperties(1); + kv_properties.AddEntry("k1", "v1"); + std::string value; + bool present = kv_properties.GetValue("k1", value); + EXPECT_TRUE(present); + EXPECT_EQ(value, "v1"); + + kv_properties.AddEntry("k2", "v2"); // entry will not be added as max size reached. + EXPECT_EQ(kv_properties.Size(), 1); + present = kv_properties.GetValue("k2", value); + EXPECT_FALSE(present); +} + +TEST(KeyValueProperties, GetValue) +{ + auto kv_properties = KeyValueProperties(1); + kv_properties.AddEntry("k1", "v1"); + std::string value; + bool present = kv_properties.GetValue("k1", value); + EXPECT_TRUE(present); + EXPECT_EQ(value, "v1"); + + present = kv_properties.GetValue("k3", value); + EXPECT_FALSE(present); +} + +TEST(KeyValueProperties, GetAllEntries) +{ + std::vector> kv_pairs = { + {"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; + const size_t kNumPairs = 3; + opentelemetry::nostd::string_view keys[kNumPairs] = {"k1", "k2", "k3"}; + opentelemetry::nostd::string_view values[kNumPairs] = {"v1", "v2", "v3"}; + auto kv_properties = KeyValueProperties(kv_pairs); + + size_t index = 0; + kv_properties.GetAllEntries( + [&keys, &values, &index](nostd::string_view key, nostd::string_view value) { + EXPECT_EQ(key, keys[index]); + EXPECT_EQ(value, values[index]); + index++; + return true; + }); + + EXPECT_EQ(index, kNumPairs); +} diff --git a/src/jaegertracing/opentelemetry-cpp/api/test/common/spinlock_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/api/test/common/spinlock_benchmark.cc new file mode 100644 index 000000000..07579579c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/api/test/common/spinlock_benchmark.cc @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/common/spin_lock_mutex.h" + +#include +#include + +namespace +{ +using opentelemetry::common::SpinLockMutex; + +constexpr int TightLoopLocks = 10000; + +// Runs a thrash-test where we spin up N threads, each of which will +// attempt to lock-mutate-unlock a total of `TightLoopLocks` times. +// +// lock: A lambda denoting how to lock. Accepts a reference to `SpinLockType`. +// unlock: A lambda denoting how to unlock. Accepts a reference to `SpinLockType`. +template +inline void SpinThrash(benchmark::State &s, SpinLockType &spinlock, LockF lock, UnlockF unlock) +{ + auto num_threads = s.range(0); + // Value we will increment, fighting over a spinlock. + // The contention is meant to be brief, as close to our expected + // use cases of "updating pointers" or "pushing an event onto a buffer". + std::int64_t value = 0; + + std::vector threads; + threads.reserve(num_threads); + + // Timing loop + for (auto _ : s) + { + for (auto i = 0; i < num_threads; i++) + { + threads.emplace_back([&] { + // Increment value once each time the lock is acquired. Spin a few times + // to ensure maximum thread contention. + for (int i = 0; i < TightLoopLocks; i++) + { + lock(spinlock); + value++; + unlock(spinlock); + } + }); + } + // Join threads + for (auto &thread : threads) + thread.join(); + threads.clear(); + } +} + +// Benchmark of full spin-lock implementation. +static void BM_SpinLockThrashing(benchmark::State &s) +{ + SpinLockMutex spinlock; + SpinThrash( + s, spinlock, [](SpinLockMutex &m) { m.lock(); }, [](SpinLockMutex &m) { m.unlock(); }); +} + +// Naive `while(try_lock()) {}` implementation of lock. +static void BM_NaiveSpinLockThrashing(benchmark::State &s) +{ + SpinLockMutex spinlock; + SpinThrash( + s, spinlock, + [](SpinLockMutex &m) { + while (!m.try_lock()) + { + // Left this comment to keep the same format on old and new versions of clang-format + } + }, + [](SpinLockMutex &m) { m.unlock(); }); +} + +// Simple `while(try_lock()) { yield-processor }` +static void BM_ProcYieldSpinLockThrashing(benchmark::State &s) +{ + SpinLockMutex spinlock; + SpinThrash( + s, spinlock, + [](SpinLockMutex &m) { + while (!m.try_lock()) + { +#if defined(_MSC_VER) + YieldProcessor(); +#elif defined(__i386__) || defined(__x86_64__) +# if defined(__clang__) + _mm_pause(); +# else + __builtin_ia32_pause(); +# endif +#elif defined(__arm__) + __asm__ volatile("yield" ::: "memory"); +#endif + } + }, + [](SpinLockMutex &m) { m.unlock(); }); +} + +// SpinLock thrashing with thread::yield(). +static void BM_ThreadYieldSpinLockThrashing(benchmark::State &s) +{ + std::atomic_flag mutex = ATOMIC_FLAG_INIT; + SpinThrash( + s, mutex, + [](std::atomic_flag &l) { + uint32_t try_count = 0; + while (l.test_and_set(std::memory_order_acq_rel)) + { + ++try_count; + if (try_count % 32) + { + std::this_thread::yield(); + } + } + std::this_thread::yield(); + }, + [](std::atomic_flag &l) { l.clear(std::memory_order_release); }); +} + +// Run the benchmarks at 2x thread/core and measure the amount of time to thrash around. +BENCHMARK(BM_SpinLockThrashing) + ->RangeMultiplier(2) + ->Range(1, std::thread::hardware_concurrency()) + ->MeasureProcessCPUTime() + ->UseRealTime() + ->Unit(benchmark::kMillisecond); +BENCHMARK(BM_ProcYieldSpinLockThrashing) + ->RangeMultiplier(2) + ->Range(1, std::thread::hardware_concurrency()) + ->MeasureProcessCPUTime() + ->UseRealTime() + ->Unit(benchmark::kMillisecond); +BENCHMARK(BM_NaiveSpinLockThrashing) + ->RangeMultiplier(2) + ->Range(1, std::thread::hardware_concurrency()) + ->MeasureProcessCPUTime() + ->UseRealTime() + ->Unit(benchmark::kMillisecond); +BENCHMARK(BM_ThreadYieldSpinLockThrashing) + ->RangeMultiplier(2) + ->Range(1, std::thread::hardware_concurrency()) + ->MeasureProcessCPUTime() + ->UseRealTime() + ->Unit(benchmark::kMillisecond); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/api/test/common/string_util_test.cc b/src/jaegertracing/opentelemetry-cpp/api/test/common/string_util_test.cc new file mode 100644 index 000000000..da2e63511 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/api/test/common/string_util_test.cc @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include + +// ------------------------- StringUtil class tests --------------------------------- + +using opentelemetry::common::StringUtil; + +TEST(StringUtilTest, TrimStringWithIndex) +{ + struct + { + const char *input; + const char *expected; + } testcases[] = {{"k1=v1", "k1=v1"}, {"k1=v1,k2=v2, k3=v3", "k1=v1,k2=v2, k3=v3"}, + {" k1=v1", "k1=v1"}, {"k1=v1 ", "k1=v1"}, + {" k1=v1 ", "k1=v1"}, {" ", ""}}; + for (auto &testcase : testcases) + { + EXPECT_EQ(StringUtil::Trim(testcase.input, 0, strlen(testcase.input) - 1), testcase.expected); + } +} + +TEST(StringUtilTest, TrimString) +{ + struct + { + const char *input; + const char *expected; + } testcases[] = {{"k1=v1", "k1=v1"}, + {"k1=v1,k2=v2, k3=v3", "k1=v1,k2=v2, k3=v3"}, + {" k1=v1", "k1=v1"}, + {"k1=v1 ", "k1=v1"}, + {" k1=v1 ", "k1=v1"}, + {" ", ""}, + {"", ""}}; + for (auto &testcase : testcases) + { + EXPECT_EQ(StringUtil::Trim(testcase.input), testcase.expected); + } +} -- cgit v1.2.3