diff --git a/api/include/opentelemetry/context/context.h b/api/include/opentelemetry/context/context.h index 78e0134f31..422d9561c1 100644 --- a/api/include/opentelemetry/context/context.h +++ b/api/include/opentelemetry/context/context.h @@ -1,61 +1,219 @@ #pragma once + #include #include +#include + +#include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE namespace context { + std::mutex context_id_mutex; + + /*The context class provides a context identifier*/ + class Context{ + + private: + + /*The identifier itself*/ + std::map ctx_map_; + + /*Used to track that last ContextKey identifier and create the next one */ + static int last_key_identifier_; + + /* Context: A constructor that accepts a key/value map*/ + Context(std::map ctx_map){ + ctx_map_ = ctx_map; + } + + public: + + /*The ContextKey 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 ContextKey{ + private: + friend class Context; + + std::string key_name_; + + int identifier_; + + + /* GetIdentifier: returns the identifier*/ + int GetIdentifier(){ + return identifier_; + } + + /* ContextKey: constructs a new ContextKey with the + * passed in name and identifier. + */ + ContextKey(std::string key_name, int identifier){ + key_name_ = key_name; + identifier_ = identifier; + } + + public: + + /* ContextKey: Consructs a new ContextKey with the passed in name + * and increments the identifier then assigns it to be the key's + * identifier. + */ + ContextKey(std::string key_name){ + key_name_ = key_name; + + context_id_mutex.lock(); + + Context::last_key_identifier_++; + + identifier_ = Context::last_key_identifier_; + + context_id_mutex.unlock(); + } + + }; + + + /* Context: contructor, creates a context object with no key/value pairs + */ + Context(){ + ctx_map_ = std::map {}; + + } + + /* Context: contructor, creates a context object from a map + * of keys and identifiers + */ + Context(ContextKey key, int value){ + ctx_map_[key.GetIdentifier()] = value; + } + + + /* WriteValue: accepts a new key/value pair and then returns a new + * context that contains both the original pairs and the new pair. + */ + Context WriteValue(ContextKey key, int value){ + std::map temp_map = ctx_map_; + + temp_map[key.GetIdentifier()] = value; + + return Context(temp_map); + } + + + /* GetValue: Returns the value associated with the passed in key + */ + int GetValue(ContextKey key){ + return ctx_map_[key.GetIdentifier()]; + } + + /* CreateKey: Returns a ContextKey that has the passed in name and the + * next available identifier.*/ + ContextKey CreateKey(std::string key_name){ + int id; + + context_id_mutex.lock(); + + last_key_identifier_++; + + id = last_key_identifier_; + + context_id_mutex.unlock(); + + return ContextKey(key_name,id); + } + + + }; + + + + /* The token class provides an identifier that is used by + * the attach and detach methods to keep track of context + * objects.*/ + + class Token{ + private: + + Context ctx_; + + public: + + /* Token: A constructor that sets the token's Context object to the + * one that was passed in. + */ + Token(Context &ctx){ + ctx_ = ctx; + } + + /* GetContext: Returns the stored context object */ + Context GetContext(){ + return ctx_; + } + + }; + + + /* The RuntimeContext class provides a wrapper for + * propogating context through cpp*/ + class RuntimeContext { + private: + + static thread_local Context context_; + + public: + -/*The context class provides a context identifier*/ -class Context{ - private: - /*The identifier itself*/ - std::map ctx_; + RuntimeContext(){ + context_ = Context(); + } - public: - /* Context: contructor, creates a context object from a map - * of keys and identifiers - */ - Context(std::map ctx); -}; + /* RuntimeContext: A constructor that will set the context as + * the passed in context. + */ + RuntimeContext(Context &context){ + context_ = context; + } -/* The token class provides an identifier that is used by - * the RuntimeContext attach and detach methods to keep track of context - * objects.*/ -class Token{ - Token(); -}; + /* attach: Sets the current 'Context' object. Returns a token + * that can be used to reset to the previous Context. + */ + Token Attach(Context &context){ + Token old_context_token = Token(context_); -/* The RuntimeContext class provides a wrapper for - * propogating context through cpp*/ -class RuntimeContext { - public: + context_ = context; - /* RuntimeContext: A constructor that will set the current - * context to the threading local. - */ - RuntimeContext(); + return old_context_token; + } - /* attach: Sets the current 'Context' object. Returns a token - * that can be used to reset to the previous Context. - */ - static Token Attach(Context context); + /* GetCurrent: Return the current context. + */ + static Context GetCurrent(){ + Context context = context_; + return context_; + } - /* get_current: Return the current context. - */ - static Context GetCurrent(); - /* detach: Resets the context to a previous value. - */ - static void Detach(Token token); + /* Detach: Resets the context to a previous value stored in the + * passed in token. + */ + void Detach(Token &token){ + context_ = token.GetContext(); + } + }; + + thread_local Context RuntimeContext::context_ = Context(); + int Context::last_key_identifier_ = 0; -}; } +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..f69f02509c --- /dev/null +++ b/api/test/context/BUILD @@ -0,0 +1,25 @@ +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 = "runtimeContext_test", + srcs = [ + "runtimeContext_test.cc", + ], + copts = ["-Wall","-std=c++17"], + 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..a8903be443 --- /dev/null +++ b/api/test/context/context_test.cc @@ -0,0 +1,45 @@ +#include "opentelemetry/context/context.h" + +#include + +using namespace opentelemetry::context; + +/* Tests whether the original context objects changes + * when you write to it. + */ +TEST(Context_test, is_context_immutable) +{ + + Context test_context = Context(); + + Context::ContextKey test_key = test_context.CreateKey("test_key"); + + EXPECT_EQ(test_context.GetValue(test_key), NULL); + + Context new_test_context = test_context.WriteValue(test_key,7); + + EXPECT_EQ(new_test_context.GetValue(test_key), 7); + + EXPECT_EQ(test_context.GetValue(test_key), NULL); +} + +/* Tests whether the new Context Objects inherits the keys and values + * of the original context object + */ +TEST(Context_test, context_write_new_object) +{ + + + Context::ContextKey test_key = Context::ContextKey("test_key"); + + Context test_context = Context(test_key, 7); + + Context::ContextKey foo_key = test_context.CreateKey("foo_key"); + + Context new_test_context = test_context.WriteValue(foo_key,1); + + EXPECT_EQ(new_test_context.GetValue(test_key), 7); + + EXPECT_EQ(new_test_context.GetValue(foo_key), 1); +} + diff --git a/api/test/context/runtimeContext_test.cc b/api/test/context/runtimeContext_test.cc new file mode 100644 index 0000000000..682443cf48 --- /dev/null +++ b/api/test/context/runtimeContext_test.cc @@ -0,0 +1,59 @@ +#include "opentelemetry/context/context.h" + +#include + +using namespace opentelemetry::context; + + +/* Tests whether the runtimeContext object properly returns the current context + */ +TEST(runtimeContext_test, get_current_context) +{ + + Context::ContextKey test_key = Context::ContextKey("test_key"); + Context test_context = Context(test_key, 7); + + RuntimeContext test_runtime = RuntimeContext(test_context); + + EXPECT_EQ(test_runtime.GetCurrent().GetValue(test_key), + test_context.GetValue(test_key)); + +} + + + + +/* Tests whether the runtimeContext object properly attaches and detaches + * the context object. + */ +TEST(runtimeContext_test, attach_detach_context) +{ + + Context::ContextKey test_key = Context::ContextKey("test_key"); + Context test_context = Context(test_key, 7); + + RuntimeContext test_runtime = RuntimeContext(test_context); + + Context::ContextKey foo_key = Context::ContextKey("foo_key"); + Context foo_context = Context(foo_key, 5); + + EXPECT_EQ(test_runtime.GetCurrent().GetValue(test_key), + test_context.GetValue(test_key)); + EXPECT_NE(test_runtime.GetCurrent().GetValue(foo_key), + foo_context.GetValue(foo_key)); + + Token test_token = test_runtime.Attach(foo_context); + + EXPECT_NE(test_runtime.GetCurrent().GetValue(test_key), + test_context.GetValue(test_key)); + EXPECT_EQ(test_runtime.GetCurrent().GetValue(foo_key), + foo_context.GetValue(foo_key)); + + test_runtime.Detach(test_token); + + EXPECT_EQ(test_runtime.GetCurrent().GetValue(test_key), + test_context.GetValue(test_key)); + EXPECT_NE(test_runtime.GetCurrent().GetValue(foo_key), + foo_context.GetValue(foo_key)); +} +