diff --git a/api/include/opentelemetry/context/TBD b/api/include/opentelemetry/context/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/include/opentelemetry/context/context.h b/api/include/opentelemetry/context/context.h new file mode 100644 index 0000000000..30c650cdea --- /dev/null +++ b/api/include/opentelemetry/context/context.h @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/utility.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/key_value_iterable.h" +#include "opentelemetry/trace/key_value_iterable_view.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ + +/* The context class provides a context identifier */ +template +class Context +{ + +public: + /*The Key class is used to obscure access from the + * user to the context map. The identifier is used as a key + * to the context map. + */ + class Key + { + + public: + /*Returns the key's identifier*/ + nostd::string_view GetIdentifier() { return nostd::string_view(identifier_); } + /*Returns the key's name */ + nostd::string_view GetName() { return key_name_; } + + private: + friend class Context; + + /* Constructs a new Key with the passed in name. Sets the identifier as + * the address of this object. */ + Key(nostd::string_view key_name) : key_name_{key_name} + { + std::stringstream ss; + ss << (void *)this; + nostd::string_view temp_view; + temp_view = ss.str(); + + memcpy(identifier_, temp_view.data(), temp_view.size()); + } + + nostd::string_view key_name_; + + char identifier_[50]; + }; + + /* Creates a key with the passed in name and returns it. */ + Key CreateKey(nostd::string_view key_name) { return Key(key_name); } + + /* Contructor, creates a context object from a map of keys + * and identifiers. + */ + Context(const T &attributes) : key_vals_(attributes) + { + /* Currently only used as a check, to ensure T is of the right type. */ + trace::KeyValueIterableView iterable = trace::KeyValueIterableView(attributes); + } + + Context() {} + + /* Accepts a new iterable and then returns a new context that + * contains both the original pairs and the new pair. + */ + Context WriteValues(T &attributes) noexcept + { + /* Currently only used as a check, to ensure T is of the right type. */ + trace::KeyValueIterableView iterable = trace::KeyValueIterableView(attributes); + + std::insert_iterator back(attributes, std::begin(attributes)); + + for (auto iter = std::begin(key_vals_); iter != std::end(key_vals_); ++iter) + { + back = *iter; + } + + return Context(attributes); + } + + /* Returns the value associated with the passed in key */ + common::AttributeValue GetValue(Key key) + { + + for (auto iter = std::begin(key_vals_); iter != std::end(key_vals_); ++iter) + { + if (key.GetIdentifier() == iter->first) + { + return iter->second; + } + } + + return ""; + } + + /* Iterates through the current and comparing context objects + * to check for equality, */ + bool operator==(const Context &other) + { + + /*Check for case where either both or one object has no contents. */ + if (std::begin(other.key_vals_) == std::end(other.key_vals_)) + { + if (std::begin(key_vals_) == std::end(key_vals_)) + { + return true; + } + else + { + return false; + } + } + + if (std::begin(key_vals_) == std::end(key_vals_)) + { + return false; + } + + /*Compare the contexts*/ + for (auto iter = std::begin(key_vals_); iter != std::end(key_vals_); ++iter) + { + int found = 0; + + for (auto iter_other = std::begin(other.key_vals_); iter_other != std::end(other.key_vals_); + iter_other++) + { + if (iter->first == iter_other->first) + { + if (nostd::get(iter->second) == + nostd::get(iter_other->second)) + { + found = 1; + break; + } + } + } + + if (found == 0) + { + return false; + } + } + + return true; + } + + /* Copy Constructors. */ + Context(const Context &other) = default; + Context &operator=(const Context &other) = default; + +private: + T key_vals_; +}; + +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/runtime_context.h b/api/include/opentelemetry/context/runtime_context.h new file mode 100644 index 0000000000..b70f4ead1e --- /dev/null +++ b/api/include/opentelemetry/context/runtime_context.h @@ -0,0 +1,50 @@ +#pragma once + +#include "context.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ + +/* The RuntimeContext class provides a wrapper for + * propogating context through cpp. */ +template +class RuntimeContext +{ +public: + /* The token class provides an identifier that is used by + * the attach and detach methods to keep track of context + * objects. + */ + class Token + { + private: + friend class RuntimeContext; + + Context context_; + + /* A constructor that sets the token's Context object to the + * one that was passed in. + */ + Token(Context &context) { context_ = context; } + + /* Returns the stored context object. */ + Context GetContext() { return context_; } + }; + + /* Return the current context. */ + Context GetCurrent(); + + /* Sets the current 'Context' object. Returns a token + * that can be used to reset to the previous Context. + */ + + Token Attach(Context &context); + + /* Resets the context to a previous value stored in the + * passed in token. Returns zero if successful, -1 otherwise + */ + int Detach(Token &token); +}; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/threadlocal_context.h b/api/include/opentelemetry/context/threadlocal_context.h new file mode 100644 index 0000000000..7ecf2b5709 --- /dev/null +++ b/api/include/opentelemetry/context/threadlocal_context.h @@ -0,0 +1,73 @@ +#pragma once + +#include "opentelemetry/context/runtime_context.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ +/* The ThreadLocalContext class is a derived class from RuntimeContext and + * provides a wrapper for + * propogating context through cpp thread locally. */ +template +class ThreadLocalContext : public RuntimeContext +{ +public: + /* The token class provides an identifier that is used by + * the attach and detach methods to keep track of context + * objects. + */ + class Token + { + private: + friend class ThreadLocalContext; + + Context context_; + + /* A constructor that sets the token's Context object to the + * one that was passed in. + */ + Token(Context &context) { context_ = context; } + + /* Returns the stored context object. */ + Context GetContext() { return context_; } + }; + + /* Return the current context. */ + static Context GetCurrent() { return GetInstance(); } + + /* Resets the context to a previous value stored in the + * passed in token. Returns zero if successful, -1 otherwise + */ + static int Detach(Token &token) + { + + /* If the token context is the current context, return failure. */ + if (token.GetContext() == GetCurrent()) + { + return 1; + } + + GetInstance() = token.GetContext(); + return 0; + } + + /* Sets the current 'Context' object. Returns a token + * that can be used to reset to the previous Context. + */ + static Token Attach(Context &context) + { + Token old_context = Token(GetInstance()); + GetInstance() = context; + return old_context; + } + +private: + /* Provides storage and access to the thread_local context object */ + static Context &GetInstance() + { + static thread_local Context instance; + return instance; + } +}; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/test/CMakeLists.txt b/api/test/CMakeLists.txt index 21b3e9a350..5c5a8590e4 100644 --- a/api/test/CMakeLists.txt +++ b/api/test/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(core) +add_subdirectory(context) add_subdirectory(plugin) add_subdirectory(nostd) add_subdirectory(trace) diff --git a/api/test/context/BUILD b/api/test/context/BUILD new file mode 100644 index 0000000000..68363db1c9 --- /dev/null +++ b/api/test/context/BUILD @@ -0,0 +1,23 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "context_test", + srcs = [ + "context_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "thread_local_test", + srcs = [ + "thread_local_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/context/context_test.cc b/api/test/context/context_test.cc new file mode 100644 index 0000000000..50c8635553 --- /dev/null +++ b/api/test/context/context_test.cc @@ -0,0 +1,125 @@ +#include + +#include "opentelemetry/context/context.h" + +#include + +using namespace opentelemetry; + +/* Test ensurs that the context object doe not change when you write to it */ +TEST(ContextTest, ContextImmutability) +{ + using M = std::map; + context::Context test_context = context::Context(); + + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("test_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + + context::Context foo_context = test_context.WriteValues(m1); + + EXPECT_NE(nostd::get(test_context.GetValue(test_key)), "123"); + EXPECT_NE(nostd::get(test_context.GetValue(foo_key)), "456"); +} + +/* Tests whether the new Context Objects inherits the keys and values + * of the original context object. + */ +TEST(ContextTest, ContextInheritance) +{ + + using M = std::map; + context::Context test_context = context::Context(); + + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("foo_key"); + context::Context::Key other_key = test_context.CreateKey("other_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + M m2 = {{std::string(other_key.GetIdentifier()), "000"}}; + + context::Context foo_context = test_context.WriteValues(m1); + context::Context other_context = foo_context.WriteValues(m2); + + EXPECT_EQ(nostd::get(other_context.GetValue(test_key)), "123"); + EXPECT_EQ(nostd::get(other_context.GetValue(foo_key)), "456"); +} + +/* Tests that when you add a key value pair where the key is already in + * existance, they key is overwritten. + */ +TEST(ContextTest, ContextKeyOverwrite) +{ + + using M = std::map; + context::Context test_context = context::Context(); + + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("foo_key"); + context::Context::Key other_key = test_context.CreateKey("other_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + M m2 = {{std::string(test_key.GetIdentifier()), "000"}}; + + context::Context foo_context = test_context.WriteValues(m1); + context::Context other_context = foo_context.WriteValues(m2); + + EXPECT_EQ(nostd::get(other_context.GetValue(test_key)), "000"); + EXPECT_NE(nostd::get(other_context.GetValue(test_key)), "123"); +} + +/* Tests that copying a context copies the key value pairs properly. */ +TEST(ContextTest, ContextCopy) +{ + + using M = std::map; + context::Context test_context = context::Context(); + + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("foo_key"); + context::Context::Key other_key = test_context.CreateKey("other_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + M m2 = {{std::string(other_key.GetIdentifier()), "000"}}; + + context::Context foo_context = test_context.WriteValues(m1); + context::Context other_context = foo_context.WriteValues(m2); + context::Context copied_context = other_context; + + EXPECT_EQ(nostd::get(copied_context.GetValue(test_key)), "123"); + EXPECT_EQ(nostd::get(copied_context.GetValue(foo_key)), "456"); +} + +/* Tests that the comparison compares properly. */ +TEST(ContextTest, ContextCompare) +{ + + using M = std::map; + context::Context test_context = context::Context(); + + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("foo_key"); + context::Context::Key other_key = test_context.CreateKey("other_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + M m2 = {{std::string(other_key.GetIdentifier()), "000"}}; + + context::Context foo_context = test_context.WriteValues(m1); + context::Context other_context = foo_context.WriteValues(m2); + context::Context copied_context = other_context; + + if (copied_context == other_context) + { + } + + if (copied_context == foo_context) + { + ADD_FAILURE(); + } +} diff --git a/api/test/context/thread_local_test.cc b/api/test/context/thread_local_test.cc new file mode 100644 index 0000000000..8f20b75f2a --- /dev/null +++ b/api/test/context/thread_local_test.cc @@ -0,0 +1,34 @@ + +#include "opentelemetry/context/threadlocal_context.h" + +#include + +using namespace opentelemetry; + +/* Tests whether the ThreadLocalContext attaches and detaches as expected */ +TEST(ThreadLocalContextTest, ThreadLocalContextAttachDetach) +{ + + using M = std::map; + + context::Context test_context = context::Context(); + context::Context::Key test_key = test_context.CreateKey("test_key"); + context::Context::Key foo_key = test_context.CreateKey("test_key"); + + M m1 = {{std::string(test_key.GetIdentifier()), "123"}, + {std::string(foo_key.GetIdentifier()), "456"}}; + + context::Context foo_context = test_context.WriteValues(m1); + + context::ThreadLocalContext test_thread_context; + + context::ThreadLocalContext::Token test_token = test_thread_context.Attach(foo_context); + + EXPECT_EQ(nostd::get(test_thread_context.GetCurrent().GetValue(test_key)), + "123"); + + EXPECT_EQ(test_thread_context.Detach(test_token), 0); + + EXPECT_NE(nostd::get(test_thread_context.GetCurrent().GetValue(test_key)), + "123"); +}