diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..baf8fe8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,46 @@ +--- +Language : Cpp +BasedOnStyle : Google +AccessModifierOffset: -2 +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignOperands: true +AllowShortIfStatementsOnASingleLine: false +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakAfterDefinitionReturnType: true +AlwaysBreakAfterReturnType: None +BinPackArguments: false +BinPackParameters: false +BreakConstructorInitializersBeforeComma: true +BraceWrapping: + AfterClass: false + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBraces: Custom + +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 0 +IndentCaseLabels: true +IndentWidth: 4 +MaxEmptyLinesToKeep: 1 +PointerAlignment: Left +SpaceAfterCStyleCast: true +SpaceBeforeParens: ControlStatements +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 4 +UseTab: Never +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba71fb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*.o +*.a +*.so +config.h +config.h.in +*.m4 +Makefile +Makefile.in +config.* +*/.deps/* +stamp-h1 +*/.dirstamp +INSTALL +autom4te.cache/* +compile +configure +depcomp +install-sh +libtool +ltmain.sh +missing +CMakeFiles +*.cmake +bin/ +build/ +test/unittest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b20a243 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/googletest"] + path = thirdparty/googletest + url = https://github.com/google/googletest diff --git a/AUTHORS b/AUTHORS index d9b736a..6db9cfc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,7 @@ AUTHORS ------- - - OpenTracing Developers +Dmitrios Kouzis-Loukas : dkouzislouka AT bloomberg DOT net +James Wells : jwells31 AT bloomberg DOT net +Jim Quinn : jquinn47 AT bloomberg DOT net diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8662e46 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required (VERSION 2.8) +set(PROJECT_NAME "opentracing") +project (${PROJECT_NAME}) + +set(lib_major_version "2") +set(lib_minor_version "0") +set(lib_patch_version "0") +set(opentracing_version "${lib_major_version}.${lib_minor_version}.${lib_patch_version}") +set(opentracing_url "https://github.com/opentracing/opentracing-cpp") + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +option(enable_noop "Build noop tracer." OFF) +option(enable_tests "Build unit tests." OFF) + +add_subdirectory(opentracing) + +if(enable_tests) + enable_testing() + add_subdirectory(thirdparty/googletest/googletest) + add_subdirectory(test) +endif() diff --git a/ChangeLog b/ChangeLog index 7a1533c..89f6a9e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,46 @@ - -opentracing-cpp ChangeLog - -* initial release +f18e949 (HEAD -> master, jq/cmake-fun) Remove default contructors and handle side effects +29e1285 Remove cmake makefile +a83c752 Merge branch 'master' of https://bbgithub.dev.bloomberg.com/jquinn1/opentracing-cpp +963d016 Cmake, removing unused template functionality, ditching config.h for now +1120bb0 (jq/master) Merge pull request #3 from jquinn1/noop-shift +bde3545 Clean up build system, ability to turn off no-op, integrate tests +ef885ee (jq/noop-shift) Update reqs for tracerimps +88e1e8b Merge branch 'master' of https://bbgithub.dev.bloomberg.com/jquinn1/opentracing-cpp +a8b9017 Moving noop into its own package, updated tests +5e3885a Merge pull request #2 from jquinn1/changes +5ff6015 (jq/changes) Review feedback, removing guards, updated docs +eeb6fa3 typo +05f3bf3 Example compiler problem +b5e5486 Update examples +9f1daca formatting never ends +8f1025e formatting never ends +59e565f formatting +1f77d05 formatting +63ff455 Updating docs +9656c0b Adding iterator behaviors +3b86ccf Adding log/tags/baggage usage +d35931b Update usage +47a9047 Adding constants.h +c8d422c Adding guards, updating documentation and tests +d42770a Updating documentation +419580e Updating tests, adding spanoptions, consistently documented +1ee01dc Minor formatting +94227b4 Adding No-op implementation +b45a540 Adding noop, Baggage/BaggageWide, StringRefWide, and docs +2f37155 Solving the wide baggage problem +06ff151 Some documentation, StringRefWide +70d1545 Remove bad example +3381ca1 Testing everything, at least a little +ded4c0c StringRef, BaggageRef, BaggageIterator drafted +efadeda (origin/master, origin/HEAD) keep gcc specific flags specific to a gcc build (#5) +012743c Update configure.ac +3933120 Update configure.ac +6623183 Update ChangeLog +e1c9ea6 Update AUTHORS +7fecd71 a clarification (#4) +e0ea9eb Converting TextMapReader callback to functor (#2) +8b1efd1 Initial opentracing C++ version +368f2cd Merge pull request #1 from gitter-badger/gitter-badge +574ef91 Add Gitter badge +9ec9136 Update README.md +bfb0b74 Initial commit diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index abdab58..0000000 --- a/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -AUTOMAKE_OPTIONS = gnu - -lib_LTLIBRARIES = libopentracing.la - -libopentracing_la_SOURCES = opentracing/span.cc opentracing/tracer.cc - -nobase_include_HEADERS = opentracing/span.h opentracing/tracer.h - -libopentracing_la_LDFLAGS = -version-info 1:0:0 - - -ACLOCAL_AMFLAGS = -I m4 - -AM_CXXFLAGS= -fno-elide-constructors -pedantic-errors -ansi -std=c++98 - -if COMPILER_IS_GCC -AM_CXXFLAGS+= -Wall -Werror -Wextra -Winit-self -Wold-style-cast -AM_CXXFLAGS+= -Woverloaded-virtual -Wuninitialized -Wmissing-declarations -endif diff --git a/README.md b/README.md index fb6568e..f707458 100644 --- a/README.md +++ b/README.md @@ -3,35 +3,57 @@ C++ implementation of the OpenTracing API http://opentracing.io [![Join the chat at https://gitter.im/opentracing/opentracing-cpp](https://badges.gitter.im/opentracing/opentracing-cpp.svg)](https://gitter.im/opentracing/opentracing-cpp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + ## Required Reading -In order to understand the C++ platform API, one must first be familiar with the -[OpenTracing project](http://opentracing.io) and -[terminology](http://opentracing.io/spec/) more generally. This is a C++98 API that -is used as a "common denominator". Ît's up to implementors to choose the C++ level -they are going to use for their implementations: +In order to understand the C++ platform API, one must first be familiar with the [OpenTracing project](http://opentracing.io) and +[terminology](http://opentracing.io/spec/) more generally. This is a C++98 API that is used as a "common denominator". +Ît is up to implementors to choose the C++ level they are going to use for their implementations: ![stack of libraries](img/stack-of-libraries.png "Stack of Libraries") ## Compile and install +The default package is a header-only library. It can be installed as such + ``` -libtoolize # or glibtoolize -./autogen.sh -./configure -sudo make install +mkdir build +cd build +cmake .. +make install ``` -To test (requires gtest - see [here for OS X](http://stackoverflow.com/questions/20746232/how-to-properly-setup-googletest-on-os-x-aside-from-xcode), [here for ubuntu](http://www.eriksmistad.no/getting-started-with-google-test-on-ubuntu/) and [here for Red Hat](http://stackoverflow.com/questions/13513905/how-to-setup-googletest-as-a-shared-library-on-linux)/for Red Hat note also [this](http://stackoverflow.com/questions/4743233/is-usr-local-lib-searched-for-shared-libraries)): +If you would like to include the NoopTracer, a static library is created in addition to the headers. ``` -cd test +mkdir build +cd build +cmake -Denable_noop=ON .. +make install +``` + +#### Tests + +Testing requires the use of `gtest`. The `gtest` library is added as a submodule to this repository. +To build all of the tests, from the root repository: + +``` +git submodule init +git submodule update +mkdir build +cd build +cmake -Denable_tests=ON .. make -./test +./bin/unittest ``` +If you're using the `NoopTracer`, you would want to add `-Denable_noop=ON` as well. + ## API overview for those adding instrumentation -Everyday consumers of this `opentracing` package really only need to worry -about a couple of key abstractions: the `StartSpan` function, the `Span` -interface, and binding a `Tracer` at `main()`-time. +Clients of this `OpenTracing` only need to understand the key abstractions: + * Installing a Tracer + * Using the Global `Tracer` to create `Spans` and `SpanContexts` + * Adding tags, logs, or baggage to `Spans` + +See the detailed [usage](./docs/usage.md) documentation for details. diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 52a86ed..0000000 --- a/autogen.sh +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/sh - -aclocal \ -&& automake --add-missing \ -&& autoconf diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 9b89f54..0000000 --- a/configure.ac +++ /dev/null @@ -1,29 +0,0 @@ -AC_INIT([libopentracing], [1.0.0]) -#AC_CONFIG_SRCDIR(libopentracing.cc) -#AM_INIT_AUTOMAKE([-Wall -Werror foreign]) -AM_INIT_AUTOMAKE([subdir-objects]) - -AM_CONDITIONAL(COMPILER_IS_GCC, test "x$GCC" = "xyes") - -AC_CONFIG_MACRO_DIR([m4]) - -#AC_CONFIG_HEADERS([config.h]) - -AM_PROG_LIBTOOL - -AC_PROG_INSTALL - -AC_LANG_C -AC_PROG_CC -AC_PROG_CXX -AC_PROG_MAKE_SET - -AC_HEADER_STDC - -#AC_CHECK_HEADERS(unistd.h netdb.h netinet/in.h sys/types.h sys/socket.h,,AC_MSG_ERROR([required header file missing])) -#AC_CHECK_FUNCS(gethostbyname socket htons connect shutdown,,AC_MSG_ERROR([required standard library function missing])) - -AC_CONFIG_FILES([ - Makefile -]) -AC_OUTPUT diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..15336d9 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,330 @@ +# opentracing-cpp + +This file describes how clients can instrument their applications with the `opentracing-cpp` +API. It assumes you have finished the required reading outlined [here](../README.md). + +### Outline + +The `opentracing-cpp` interface uses the [Curiously Recurring Template Pattern](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +(CRTP) to define a set of compile time, polymorphic interfaces to `OpenTracing` implementations. + +In order to trace their systems, clients must: + + - Select their `opentracing-cpp` implementation + - Install the selected `Tracer` in their applications in main + - Use the API's Tracer interface to instrument their applications requests + + +### Select the implementation + +`opentracing-cpp` only defines the interfaces. In order to make any use of the +API, you will need to install a concrete `Tracer` implementation. To do so, it is +best practice to first create an reusable header you can share throughout your +organization that declares the implementation you will be using. + +This header should provide a few typedefs that make it easy to swap implementations +later. + +``` +// acme_tracing.h +#ifndef INCLUDED_ACME_TRACING +#define INCLUDED_ACME_TRACING + +// Include the interface templates +#include + +// Include the chosen implementation +#include + +namespace acme { + typedef opentracing::NoopTracer TracerImpl; + + typedef opentracing::GenericTracer< + opentracing::NoopTracer, + opentracing::NoopSpan, + opentracing::NoopOptions, + opentracing::NoopContext, + opentracing::NoopAdapter> Tracer; + // Tracer is required for O(1) implementation swaps + + typedef Tracer::SpanContext SpanContext; + typedef Tracer::SpanOptions SpanOptions; + typedef Tracer::Span Span; + // For convenience +} +#endif +``` + +By creating the `Tracer` typedef, we can now rely on the GenericTracer interface, +but avoid knowing any of the explicit implementation details in client code. + +The `Tracer` exposes a number of public typedefs which make it easier to create our +types: + + - `Tracer::Span` - Interface to Span objects + - `Tracer::SpanContext` - Interface to SpanContext objects + - `Tracer::SpanOptions` - Interface to SpanOptions objects + +Our theoretical "ACME Inc." organization can use these type names to instrument their applications. +The `Tracer` typedef is not standardized, but it is recommended. The rest of the documentation +will assume that `Tracer` was the chosen name for the Tracer type. + +If clients ever require that they swap `OpenTracing` implementations, they +will need to update only this header file and rebuild applications. +(Easier said then done for larger organizations...) + +### Installing the Tracer + +Once a implementation is chosen, your applications can rely on your global `opentracing-cpp` +configuration header to set up the `Tracer`. The `Tracer` implementation singleton is installed +explicitly in main. This must be done before any other parts of the API can be used: + +``` +// acmetask.m.cpp +#include + +using namespace acme; + +int main(int argc, const char * argv[]) +{ + TracerImpl tracerImpl; + Tracer::install(&tracerImpl); + + // ... + + return 0; +} + +``` + +### Creating Spans + +Once the `Tracer` is installed, we can begin to instrument the application. +Say we have a service that acts as a proxy to a number of other HTTP servers. +We may have a `getAccount` handler that reaches out to a user account service. + +To trace our application, we'll first need to start the span: + +``` +// requesthandler.m.cpp +#include + +using namespace acme; + +int getAccount(Repsonse * resp, const Request& req) +{ + Tracer* tracer = Tracer::instance(); + + Span* span(tracer->start("get_account")); + assert(span); + + // ... + + span->finish(); + tracer->cleanup(span); + + return 0; +} +``` + +### Defining carriers + +In order to instrument our entire network, we need a way to `inject` and `extract` +spans into and out of our RPC calls. Extending our example, when we make our request +to the backend HTTP service, we'll need a way to inject the details of the context +into our outgoing HTTP request. + +We'll define our HttpWriter inline with our request to do the `inject` part of this +process first. In practice, you'll want the Writers/Readers you use to be consistent +across your organization, so putting them into a library would be ideal. + +``` +#include + +using namespace opentracing; +using namespace acme; + +class HttpWriter : public GenericBinaryWriter +{ + public: + HttpWriter(HttpRequest * req) : m_req(req) + { + } + + int injectImp(const void* blob, const size_t len) + { + std::string header(static_cast(blob), len); + req->addHeader("x-acme-tracing-blob", header); + return 0; + } + + private: + HttpRequest* m_req; +}; + +int getAccount(Repsonse* resp, const Request& req) +{ + Tracer* tracer = Tracer::instance(); + + Span* span(tracer->start("get_account")); + assert(span); + + HttpResponse httpResponse; + HttpRequest httpRequest(req); + + HttpWriter writer(&httpRequest); + tracer->inject(&writer, span->context()); + + sendHttpRequest(&httpResponse, httpRequest); + + // ... + + span->finish(); + tracer->cleanup(span); + return 0; +} +``` + +On the server side of the HTTP request, we'll want to continue the original Trace by creating +a new SpanContext as a 'child of' the original: + +``` +#include + +using namespace opentracing; +using namespace acme; + +struct HttpReader: public GenericBinaryReader +{ + + public: + int extractImp(std::vector * const buf) const + { + std::string header = m_req.getHeader("x-acme-tracing-blob"); + + if (!header.empty()) + { + buf->assign(header.begin(), header.end()); + return 0; + } + else + { + return 1; + } + } + + private: + const HttpRequest& m_req; +}; + +int httpGetAccount(const HttpRequest& httpRequest) +{ + Tracer* tracer = Tracer::instance(); + + SpanContext* context(tracer->extract(HttpReader(httpRequest))); + + Span* span; + + if (!context) + { + span = tracer->start("get_account"); + } + else + { + SpanOptions* opts(tracer->makeSpanOptions()); + + opts->setReference(SpanReferenceType::e_ChildOf, *context); + opts->setOperation("get_account_server"); + + span = tracer->start(opts); + + tracer->cleanup(context); + tracer->cleanup(opts); + } + assert(span); + + // Send back our response... + + span->finish(); + tracer->cleanup(span); + + return 0; +} +``` + +### Tags, Logs, and Baggage + +The [OpenTracing specification](https://github.com/opentracing/specification/blob/master/specification.md) outlines how +users should be able tag their spans, log structured data for a span, or attach arbitrary baggage that propagates +through the entire system. + +##### Tags +The `opentracing-cpp` interface allows you to tag your spans with `key:value` pairs: + +``` +Span* span = tracer->start("get_bookmarks"); +span->tag("account", account_id); +span->tag("site", site); +``` + +The `key` must be a string, but the value can be any type that can be externalized via + +`std::ostream& operator<<(std::ostream&, const Type& t)`. + + +##### Logs +The `log` functions work similarly, but logs are also implicitly associated with the current wall-time as well. +Users can control the time-stamp behavior if they wish by providing it explicitly: + +``` +Span* span(tracer->start("get_bookmarks")); +span->log("db_access", account_id); // Use current wall-time +span->log("redis_access", site, 1484003943000); // Accessed redis on Jan 9, 2017 at 23:19:02 GMT +``` + +##### Baggage +Baggage is special. It is a set of text-only, key:value pairs that propagates with traces as they make their way +through a system. It can be prohibitively expensive if abused. It is also not a replacement for traditional message +schemas (e.g., protobuf). Care must be taken when adding any baggage. + +Baggage can be added directly to any Span you create through the underlying SpanContext. + +``` +Span* span = tracer->start("get_bookmarks"); +span->context().setBaggage("database", std::to_string(database_id)); +span->context().setBaggage("user", user); +``` + +Baggage can also be read back through the original Span or SpanContexts extracted from carriers. +Note that SpanContexts created through 'extract()' are read-only. + +``` +Span* span(tracer->start("get_bookmarks")); + +span->context().setBaggage("database", std::to_string(database_id)); +span->context().setBaggage("user", user); + +for(SpanContext::BaggageIterator it = span->context().baggageBegin(); + it != span->context().baggageEnd(); + ++it) +{ + std::cout << "baggage item: " << it->key() << " val: " << it->value() + << std::endl; +} +``` + +If you have access to C++11 features, we can use the range based for loop syntax too. + +``` +HttpReader reader(request); +SpanContext* context(tracer->extract(reader)); + +for(const auto& baggage : context->baggageRange()) +{ + std::cout << "baggage item: " << baggage.key() + << " val: " << baggage.value() << std::endl; +} +tracer->cleanup(context); +``` + +For details on the semantics of `BaggageIterators` and how they work, see [baggage.h](../opentracing/baggage.h). diff --git a/opentracing/CMakeLists.txt b/opentracing/CMakeLists.txt new file mode 100644 index 0000000..f566b3c --- /dev/null +++ b/opentracing/CMakeLists.txt @@ -0,0 +1,43 @@ +include_directories(${CMAKE_SOURCE_DIR}) + +set(headers + baggage.h + carriers.h + constants.h + spancontext.h + span.h + spanoptions.h + stringref.h + tracer.h) + +if(enable_noop) + # Used by pc file + set(opentracing_libs "-L${libdir} -lopentracing") + + # Updated set of included files + set(headers ${headers} noop.h) + + # Build our opentracing library + add_library(opentracing STATIC noop.cc) + + # Add the opentracing install target + install (TARGETS opentracing + DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} + COMPONENT dev) +endif() + +# Install the opentracing headers into /path/to/include/opentracing/ +install (FILES ${headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/opentracing + COMPONENT dev) + +# pkg-config +if("${UNIX}" OR "${CYGWIN}") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + @ONLY) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT pkgconfig) +endif() diff --git a/opentracing/Makefile.am b/opentracing/Makefile.am deleted file mode 100644 index e69de29..0000000 diff --git a/opentracing/baggage.h b/opentracing/baggage.h new file mode 100644 index 0000000..8d34a11 --- /dev/null +++ b/opentracing/baggage.h @@ -0,0 +1,359 @@ +#ifndef INCLUDED_OPENTRACING_BAGGAGE_H +#define INCLUDED_OPENTRACING_BAGGAGE_H + +// ========= +// baggage.h +// ========= +// class Baggage - Client wrapper for copies of key:value pairs +// class BaggageRef - Client wrapper for referencing key:value pairs +// +// class BaggageIteratorImp - Client interface to traverse span baggage +// class BaggageRangeImp - A range of BaggageIterators + +// ------------------- +// OpenTracing Baggage +// ------------------- +// Baggage items are key:value string pairs that apply to a given Span, its +// SpanContext, and all Spans which directly or transitively reference the local +// Span. That is, baggage items propagate in-band along with the trace itself. +// +// Baggage items enable powerful functionality given a full-stack OpenTracing +// integration (for example, arbitrary application data from a mobile +// application can make it, transparently, all the way into the depths of a +// storage system), and with it some powerful costs. +// +// Use this feature thoughtfully and with care. Every key and value is copied +// into every local and remote child of the associated Span, and that can add up +// to a lot of network and CPU overhead. +// +// See: https://github.com/opentracing/specification + +#include + +namespace opentracing { + +// ============= +// class Baggage +// ============= +// This class copies to the key:value pairs stored in SpanContexts. Once the +// value is returned, clients are free to move it around as they see fit. +// Baggage is the default value returned by iterators when they are +// dereferenced. + +class Baggage { + public: + Baggage(const StringRef& key, const StringRef& value); + // Construct a Baggage referencing 'key' and 'value' + + const std::string& key() const; + // Return the non-modifiable 'key' associated with this baggage. + + const std::string& value() const; + // Return the non-modifiable 'value' associated with this baggage. + + Baggage* operator->(); + const Baggage* operator->() const; + // Syntactic sugar to support dereferencing BaggageIterator's with + // the '->' operator: this method only returns 'this'. + + private: + std::string m_key; + std::string m_value; +}; + +// ================ +// class BaggageRef +// ================ +// This class wraps references to the key:value pairs stored in SpanContexts. +// The references themselves refer to data managed by SpanContext +// implementations, but make them available in a read-only fashion to avoid +// unnecessary copies. +// +// Since the implementations of SpanContexts is deferred, it is impossible to +// make strong guarantees on the lifetime of the references. At a minimum, the +// references should be valid until the iterator is destroyed, the SpanContext +// is destroyed, or until 'setBaggage' is called on the SpanContext. + +class BaggageRef { + public: + BaggageRef(const StringRef& key, const StringRef& value); + // Construct a BaggageRef referencing 'key' and 'value' + + const StringRef& key() const; + // Return the non-modifiable 'key' associated with this baggage. + + const StringRef& value() const; + // Return the non-modifiable 'value' associated with this baggage. + + const BaggageRef* operator->() const; + // Syntactic sugar to support dereferencing BaggageIterators with + // the '->' operator: this method only returns 'this'. + + private: + StringRef m_key; + StringRef m_value; +}; + +// ======================== +// class BaggageIteratorImp +// ======================== +// BaggageIteratorImp is used to obscure the data structure(s) used to store +// and traverse Span Baggage. Implementors should store the values as +// std::string types, but the underlying data structure decision is left to +// implementors. +// +// We do not want to expose the details of these data structure(s) to clients; +// If their code were to rely on any detail, it would make it difficult +// to change OpenTracing implementations. Instead, we define an Adapter class, +// to translate the implementation's iterators into baggage structures +// which clients can interact with. +// +// Adapter implementations are required to implement the following: +// +// struct AdapterClass { +// typedef ImplementationIterator iterator; +// typedef ImplementationIterator const_iterator; +// +// Baggage copy(const const_iterator& it) const; +// BaggageRef ref(const const_iterator& it) const; +// }; +// +// When clients create a BaggageIterator via 'SpanContext::baggageBegin()', +// the BaggageIterator should be created with an iterator to the underlying +// baggage implementation. The BaggageIterator caches the implementation +// iterator, incrementing the implementation iterator under the hood as clients +// move forward through the underlying sequence. +// +// When clients dereference the baggage iterator, the Adapter is invoked to +// convert the implementation iterator into Baggage/BaggageRefs. + +template +class BaggageIteratorImp { + public: + typedef typename ADAPTER::iterator iterator; + typedef typename ADAPTER::const_iterator const_iterator; + + BaggageIteratorImp(const iterator& iter); + BaggageIteratorImp(const const_iterator& iter); + // Construct a BaggageIteratorImp object which will be used to traverse the + // sequence pointed to by the input iterator 'iter'. + + BaggageRef operator*() const; + BaggageRef operator->() const; + // Returns a BaggageRef to return references to the key:value pair pointed + // to by this iterator. Undefined behavior if the iterator is equal to + // 'end()'. + + Baggage copy() const; + // Returns a copy of the key:value pairs pointed at by this iterator. + // Undefined behavior if the iterator is equal to 'end()'. + + BaggageRef ref() const; + // Returns a BaggageRef object, containing references to the underlying + // storage of the key:value pairs. References are invalided if the + // associated SpanContext is destroyed or modified. + + BaggageIteratorImp operator++(int); + // Return a copy of this iterator, then post-increment this iterator to + // point to the next key:value pair in the sequence. + + BaggageIteratorImp& operator++(); + // Increment this iterator, pointing to the next key:value pair in the + // sequence, then return a reference to this iterator. + + bool operator==(const BaggageIteratorImp& other) const; + // Returns true if this iterator points to the same key:value pair as + // 'other', and false otherwise. + + bool operator!=(const BaggageIteratorImp& other) const; + // Returns true if this iterator points at a different key:value pair + // than 'other', and false otherwise. + + private: + const_iterator d_iterator; // Traverses implementation storage + ADAPTER d_handler; // Converts d_iterator to Baggage wrappers +}; + +// ===================== +// class BaggageRangeImp +// ===================== +// If clients have access to C++11 features, BaggageRanges are a convenience +// class that allows an easy way to use range based for loops: +// +// `for(const auto& b: context.baggageRange()){ ... }` +// +// They define a 'begin' and 'end' method that allows the compiler to use +// this syntax. The class is otherwise redundant. + +template +class BaggageRangeImp { + public: + typedef BaggageIteratorImp BaggageIterator; + + BaggageRangeImp(const BaggageIterator& begin, const BaggageIterator& end); + + BaggageIterator begin() const; + BaggageIterator end() const; + + private: + const BaggageIterator m_begin; + const BaggageIterator m_end; +}; + +// ------------- +// class Baggage +// ------------- + +inline Baggage::Baggage(const StringRef& key, const StringRef& value) +: m_key(key.data(), key.length()), m_value(value.data(), value.length()) +{ +} + +inline const std::string& +Baggage::key() const +{ + return m_key; +} + +inline const std::string& +Baggage::value() const +{ + return m_value; +} + +inline Baggage* Baggage::operator->() +{ + return this; +} + +inline const Baggage* Baggage::operator->() const +{ + return this; +} + +// ---------------- +// class BaggageRef +// ---------------- + +inline BaggageRef::BaggageRef(const StringRef& key, const StringRef& value) +: m_key(key), m_value(value) +{ +} + +inline const StringRef& +BaggageRef::key() const +{ + return m_key; +} + +inline const StringRef& +BaggageRef::value() const +{ + return m_value; +} + +inline const BaggageRef* BaggageRef::operator->() const +{ + return this; +} + +// ------------------------ +// class BaggageIteratorImp +// ------------------------ + +template +BaggageIteratorImp::BaggageIteratorImp(const iterator& iter) +: d_iterator(iter), d_handler() +{ +} + +template +BaggageIteratorImp::BaggageIteratorImp(const const_iterator& iter) +: d_iterator(iter), d_handler() +{ +} + +template +BaggageRef BaggageIteratorImp::operator*() const +{ + return d_handler.ref(d_iterator); +} + +template +BaggageRef BaggageIteratorImp::operator->() const +{ + return d_handler.ref(d_iterator); +} + +template +Baggage +BaggageIteratorImp::copy() const +{ + return d_handler.copy(d_iterator); +} + +template +BaggageRef +BaggageIteratorImp::ref() const +{ + return d_handler.ref(d_iterator); +} + +template +BaggageIteratorImp BaggageIteratorImp::operator++(int) +{ + BaggageIteratorImp tmp(*this); + ++d_iterator; + return tmp; +} + +template +BaggageIteratorImp& BaggageIteratorImp::operator++() +{ + ++d_iterator; + return *this; +} + +template +bool +BaggageIteratorImp::operator==( + const BaggageIteratorImp& other) const +{ + return d_iterator == other.d_iterator; +} + +template +bool +BaggageIteratorImp::operator!=( + const BaggageIteratorImp& other) const +{ + return d_iterator != other.d_iterator; +} + +// --------------------- +// class BaggageRangeImp +// --------------------- + +template +BaggageRangeImp::BaggageRangeImp(const BaggageIterator& begin, + const BaggageIterator& end) +: m_begin(begin), m_end(end) +{ +} + +template +typename BaggageRangeImp::BaggageIterator +BaggageRangeImp::begin() const +{ + return m_begin; +} + +template +typename BaggageRangeImp::BaggageIterator +BaggageRangeImp::end() const +{ + return m_end; +} + +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_BAGGAGE_H diff --git a/opentracing/carriers.h b/opentracing/carriers.h new file mode 100644 index 0000000..fbcb1a8 --- /dev/null +++ b/opentracing/carriers.h @@ -0,0 +1,382 @@ +#ifndef INCLUDED_OPENTRACING_CARRIERS_H +#define INCLUDED_OPENTRACING_CARRIERS_H + +// ========== +// carriers.h +// ========== +// class TextMapPair - Struct of std::string name:value pairs +// +// class GenericTextWriter - CRTP interface for text map writers (inject) +// class GenericBinaryWriter - CRTP interface for binary writers (inject) +// class GenericWriter - CRTP interface for explicit writers (inject) +// +// class GenericTextReader - CRTP interface for text map readers (extract) +// class GenericBinaryReader - CRTP interface for binary readers (extract) +// class GenericReader - CRTP interface for explicit readers (extract) +// +// ======== +// Carriers +// ======== +// GenericTextWriter, GenericBinaryWriter, and GenericWriter are used by the +// Tracer's 'inject()' interface. They are used to embed SpanContexts into a +// carrier object. Those carrier objects are passed between process boundaries +// by applications as a part of their existing message passing infrastructure. +// +// When an RPC call is received, the SpanContext can be 'extract()'ed by +// the Tracer using the GenericTextReader, GenericBinaryReader, or GenericReader +// interfaces. If the readers are successful, an immutable SpanContext is +// returned to applications. That SpanContext can then be used to create +// new spans or accessing baggage. + +#include +#include +#include + +namespace opentracing { + +// ================= +// class TextMapPair +// ================= +// TextMapPair is a struct used to encapsulate name:value pairs going +// into (inject) and out of (extract) text map carriers. + +class TextMapPair { + public: + TextMapPair(); + TextMapPair(const StringRef& name, const StringRef& value); + + std::string m_key; + std::string m_value; +}; + +// ======================= +// class GenericTextWriter +// ======================= +// GenericTextWriters are used to 'inject' a list of TextMapPair strings +// into an arbitrary carrier message. +// +// It is templated on the implementation only, which must support: +// +// class TextWriter : GenericTextWriter{ +// public: +// int injectImp(const std::vector&); +// }; +// +// Implementations are responsible for translating the text map passed +// to them into a carrier object. When constructed, Writers should hold +// onto any references they may need to populate the outgoing RPC carrier +// object. + +template +class GenericTextWriter { + public: + int inject(const std::vector& textmap); + // Inject the supplied 'Textmap' into this carrier. + + protected: + GenericTextWriter(); + GenericTextWriter(const GenericTextWriter&); + // Protected to avoid direct construction +}; + +// ========================= +// class GenericBinaryWriter +// ========================= +// GenericBinaryWriters are used to 'inject' a binary blob of data into an +// arbitrary carrier message. +// +// It is templated on the implementation only, which must support: +// +// class BinaryWriter : GenericBinaryWriter{ +// public: +// int injectImp(const void* buf, const size_t len); +// }; +// +// Implementations are responsible for passing the blob along with a carrier +// object. When constructed, Writers should hold onto any references they may +// need to populate the outgoing RPC carrier object. + +template +class GenericBinaryWriter { + public: + int inject(const void* buf, const size_t len); + // Inject the binary representation of a span context into this carrier. + + protected: + GenericBinaryWriter(); + GenericBinaryWriter(const GenericBinaryWriter&); +}; + +// =================== +// class GenericWriter +// =================== +// GenericWriters are used to 'inject' an explicit implementation detail +// into a carrier message. +// +// If clients want to rely on the details of a particular implementation, +// the GenericWriter mechanism allows avoid access without working around +// the C++ type system. +// +// This removes flexibility, making it harder to change opentracing-cpp +// implementations, however, it is better to embed the dependency in the type +// system than rely on workarounds such as run time +// dynamic_cast/reinterpret_cast checks. +// +// For this to work, implementations must support: +// +// class ExplicitWriter: GenericWriter> +// { +// public: +// int injectImp(const ImplType&); +// }; +// +// Tracer implementations will be responsible for making sure 'ImplType' +// is passed to the carrier. Will fail to compile if the inject type +// is not compatible with the installed tracer implementation. + +template +class GenericWriter { + public: + template + int inject(const T& impl); + // Inject the supplied 'impl' object directly into this carrier. + + protected: + GenericWriter(); + GenericWriter(const GenericWriter&); +}; + +// ======================= +// class GenericTextReader +// ======================= +// GenericTextReaders are used to 'extract' a list of TextMapPair strings +// out of an arbitrary carrier message. +// +// It is templated on the implementation only, which must support: +// +// class TextReader : GenericTextReader{ +// public: +// int extractImp(std::vector*) const; +// }; +// +// Implementations are responsible for translating their carrier object +// into a text map, loading results into the vector they're passed. +// When constructed, they should hold onto any references they may need to +// populate the text map pair. The Tracer then uses that list to populate +// its SpanContexts appropriately. + +template +class GenericTextReader { + public: + int extract(std::vector* textmap) const; + // Extract the supplied 'Textmap' from this carrier. + + protected: + GenericTextReader(); + GenericTextReader(const GenericTextReader&); +}; + +// ========================= +// class GenericBinaryReader +// ========================= +// GenericBinaryReaders are used to 'extract' a blob out of an arbitrary carrier +// message. +// +// It is templated on the implementation only, which must support: +// +// class BinaryReader : GenericBinaryReader{ +// public: +// int extractImp(std::vector *buf) const; +// }; +// +// When constructed, they should hold onto any references they may need to +// retrieve the blob when 'extractImp()' is invoked. + +template +class GenericBinaryReader { + public: + int extract(std::vector* buffer) const; + // Load the binary representation of a span context into 'buffer'. + // Return 0 upon success and a non-zero value otherwise. + + protected: + GenericBinaryReader(); + GenericBinaryReader(const GenericBinaryReader&); +}; + +// =================== +// class GenericReader +// =================== +// GenericReaders are used to 'extract' an explicit implementation +// detail out of an arbitrary carrier message. +// +// This removes flexibility, making it harder to change opentracing-cpp +// implementations, however, it is better to embed the dependency in the type +// system than rely on workarounds such as run time +// dynamic_cast/reinterpret_cast checks. +// +// For this to work, implementations must support: +// +// class ExplicitReader: GenericReader +// { +// public: +// int extractImp(ImplType* ) const; +// }; + +template +class GenericReader { + public: + template + int extract(T* impl) const; + // Extract the 'impl' object directly from this carrier. + + protected: + GenericReader(); + GenericReader(const GenericReader&); +}; + +// ----------------- +// class TextMapPair +// ----------------- + +inline TextMapPair::TextMapPair() : m_key(), m_value() +{ +} + +inline TextMapPair::TextMapPair(const StringRef& name, const StringRef& value) +: m_key(name.data(), name.length()), m_value(value.data(), value.length()) +{ +} + +// ----------------------- +// class GenericTextWriter +// ----------------------- + +template +int +GenericTextWriter::inject(const std::vector& textmap) +{ + return static_cast(this)->injectImp(textmap); +} + +template +GenericTextWriter::GenericTextWriter() +{ +} + +template +GenericTextWriter::GenericTextWriter(const GenericTextWriter&) +{ +} + +// ------------------------- +// class GenericBinaryWriter +// ------------------------- + +template +int +GenericBinaryWriter::inject(const void* buf, const size_t len) +{ + return static_cast(this)->injectImp(buf, len); +} + +template +GenericBinaryWriter::GenericBinaryWriter() +{ +} + +template +GenericBinaryWriter::GenericBinaryWriter( + const GenericBinaryWriter&) +{ +} + +// ------------------- +// class GenericWriter +// ------------------- + +template +template +int +GenericWriter::inject(const T& impl) +{ + return static_cast(this)->injectImp(impl); +} + +template +GenericWriter::GenericWriter() +{ +} + +template +GenericWriter::GenericWriter(const GenericWriter&) +{ +} + +// ----------------------- +// class GenericTextReader +// ----------------------- + +template +int +GenericTextReader::extract(std::vector* textmap) const +{ + return static_cast(this)->extractImp(textmap); +} + +template +GenericTextReader::GenericTextReader() +{ +} + +template +GenericTextReader::GenericTextReader(const GenericTextReader&) +{ +} + +// ------------------------- +// class GenericBinaryReader +// ------------------------- + +template +int +GenericBinaryReader::extract(std::vector* buf) const +{ + return static_cast(this)->extractImp(buf); +} + +template +GenericBinaryReader::GenericBinaryReader() +{ +} + +template +GenericBinaryReader::GenericBinaryReader( + const GenericBinaryReader&) +{ +} + +// ------------------- +// class GenericReader +// ------------------- + +template +template +int +GenericReader::extract(T* impl) const +{ + return static_cast(this)->extractImp(impl); +} + +template +GenericReader::GenericReader() +{ +} + +template +GenericReader::GenericReader(const GenericReader&) +{ +} + +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_CARRIERS_H diff --git a/opentracing/constants.h b/opentracing/constants.h new file mode 100644 index 0000000..827f080 --- /dev/null +++ b/opentracing/constants.h @@ -0,0 +1,28 @@ +#ifndef INCLUDED_OPENTRACING_CONSTANTS_H +#define INCLUDED_OPENTRACING_CONSTANTS_H + +// =========== +// constants.h +// =========== +// This header provides a set of macros for the "standard" OpenTracing tag and +// log fields. For a description of each value, and how it should be used, see: +// https://github.com/opentracing/specification/blob/master/data_conventions.yaml + +#define OPENTRACING_TAG_ERROR "error" +#define OPENTRACING_TAG_COMPONENT "component" +#define OPENTRACING_TAG_SAMPLING_PRIORITY "sampling.priority " + +#define OPENTRACING_TAG_HTTP_URL "http.url" +#define OPENTRACING_TAG_HTTP_METHOD "http.method" +#define OPENTRACING_TAG_HTTP_STATUS "http.status" + +#define OPENTRACING_TAG_SPAN_KIND "span.kind" +#define OPENTRACING_TAG_PEER_HOSTNAME "peer.hostname" +#define OPENTRACING_TAG_PEER_IPV4 "peer.ipv4" +#define OPENTRACING_TAG_PEER_IPV6 "peer.ipv6" +#define OPENTRACING_TAG_PEER_PORT "peer.port" +#define OPENTRACING_TAG_PEER_SERVICE "peer.service" + +#define OPENTRACING_LOG_EVENT "event" + +#endif diff --git a/opentracing/noop.cc b/opentracing/noop.cc new file mode 100644 index 0000000..6752db6 --- /dev/null +++ b/opentracing/noop.cc @@ -0,0 +1,5 @@ +#include + +namespace opentracing { +NoopTracer* NoopTracer::s_tracer = 0; +} diff --git a/opentracing/noop.h b/opentracing/noop.h new file mode 100644 index 0000000..60128cc --- /dev/null +++ b/opentracing/noop.h @@ -0,0 +1,393 @@ +#ifndef INCLUDED_OPENTRACING_NOOP_H +#define INCLUDED_OPENTRACING_NOOP_H + +// ====== +// noop.h +// ====== +// class NoopAdapter - No-op adapter for NoopContext baggage +// class NoopContext - No-op span context implementation +// class NoopOptions - No-op span options implementation +// class NoopSpan - No-op span implementation +// class NoopTracer - No-op tracer implementation +// +// ----- +// No-Op +// ----- +// This header provides a No-Op implementation for the generic OpenTracing +// classes. It can be installed globally in your application to begin +// instrumenting or testing the interface; it has no-side effects. +// +// In order to have client code behave the same when a concrete implementation +// is used, clients should be calling finish() and cleanup() appropriately. +// +// See the specification for requirements of NoopTracers: +// https://github.com/opentracing/specification/blob/master/specification.md#nooptracer + +#include +#include +#include +#include +#include +#include +#include + +namespace opentracing { + +class NoopAdapter; +class NoopContext; +class NoopOptions; +class NoopSpan; +class NoopTracer; + +// ================= +// class NoopAdapter +// ================= +// The NoopAdapter has no container to iterator over. Regardless of what +// iterator is passed in, it will always return empty baggage objects or +// references. + +class NoopAdapter { + public: + typedef NoopContext* iterator; + typedef const NoopContext* const_iterator; + + BaggageRef ref(const const_iterator& it) const; + Baggage copy(const const_iterator& it) const; +}; + +// ================= +// class NoopContext +// ================= +// The NoopContext implements the GenericSpanContext interface. +// +// The baggage iterator interface is faked by always using the address of the +// encompassing NoopContext for both the baggageBeginImp() and baggageEndImp() +// calls. Since those pointers should always be equal for the same SpanContext, +// the iterator range will always be considered empty. +// +// If clients are using iterators correctly, the iterator should never +// be dereferenced, however, if they do dereference the BaggageIterator, +// they will be returned empty string references/copies. + +class NoopContext : public GenericSpanContext { + public: + BaggageIterator baggageBeginImp() const; + BaggageIterator baggageEndImp() const; + + int getBaggageImp(const StringRef&, std::string*) const; + int getBaggageImp(const StringRef&, std::vector*) const; +}; + +// ================= +// class NoopOptions +// ================= +// NoopOptions implements the GenericSpanOptions interface. It holds +// onto no references, nor does it manipulate the start time or operation +// names; it does not need to. + +class NoopOptions + : public GenericSpanOptions { + public: + int setOperationImp(const StringRef&); + int setStartTimeImp(const uint64_t); + int setReferenceImp(const SpanReferenceType::Value, const NoopContext&); + + template + int setTagImp(const StringRef&, const T&); +}; + +// ============== +// class NoopSpan +// ============== +// The NoopSpan implements the GenericSpan interface. It provides no-op +// overloads for all of the required Span behaviors. +// +// It owns a NoopContext to support the context() interface. Every method +// on the returned context() is also a no-op. + +class NoopSpan : public GenericSpan { + public: + const NoopContext* contextImp() const; + + int setOperationImp(const StringRef&); + int setBaggageImp(const StringRef&, const StringRef&); + + int getBaggageImp(const StringRef&, std::string*) const; + int getBaggageImp(const StringRef&, std::vector*) const; + + int finishImp(); + int finishImp(const uint64_t); + + template + int tagImp(const StringRef&, const T&); + + template + int logImp(const StringRef&, const T&); + + template + int logImp(const StringRef&, const T&, const uint64_t); + + NoopContext m_context; +}; + +// ================ +// class NoopTracer +// ================ +// The NoopTracer implements the GenericTracer interface. It provides no-op +// overloads for all of the required Tracer behaviors. +// +// Although clients can 'start()' spans or 'makeSpanOptions()', there are +// no allocations made. Clients should call 'finish()'/'cleanup()', however, +// this implementation has no side effects of them doing so. Inject/extract +// can be called, but there are no side effects; the noop tracer will +// never add itself to carrier objects, but will always return a valid +// pointer to a context. + +class NoopTracer : public GenericTracer { + public: + static void installImp(NoopTracer*); + static void uninstallImp(); + static NoopTracer* instanceImp(); + + NoopOptions* makeSpanOptionsImp(); + + NoopSpan* startImp(const StringRef&); + NoopSpan* startImp(const NoopOptions&); + + template + int injectImp(CARRIER* carrier, const T&) const; + + template + NoopContext* extractImp(const CARRIER& carrier); + + void cleanupImp(const NoopOptions*); + void cleanupImp(const Span*); + void cleanupImp(const NoopContext*); + + private: + static NoopTracer* s_tracer; + + NoopOptions m_opts; + NoopSpan m_span; +}; + +// ----------------- +// class NoopAdapter +// ----------------- + +inline BaggageRef +NoopAdapter::ref(const const_iterator&) const +{ + static const char empty[] = ""; + return BaggageRef(empty, empty); +} + +inline Baggage +NoopAdapter::copy(const const_iterator&) const +{ + return Baggage("", ""); +} + +// ----------------- +// class NoopContext +// ----------------- + +inline NoopContext::BaggageIterator +NoopContext::baggageBeginImp() const +{ + return baggageEndImp(); +} + +inline NoopContext::BaggageIterator +NoopContext::baggageEndImp() const +{ + return BaggageIterator(this); +} + +inline int +NoopContext::getBaggageImp(const StringRef&, std::string*) const +{ + return 1; +} + +inline int +NoopContext::getBaggageImp(const StringRef&, std::vector*) const +{ + return 1; +} + +// ----------------- +// class NoopOptions +// ----------------- + +inline int +NoopOptions::setOperationImp(const StringRef&) +{ + return 0; +} + +inline int +NoopOptions::setStartTimeImp(const uint64_t) +{ + return 0; +} + +inline int +NoopOptions::setReferenceImp(const SpanReferenceType::Value, const NoopContext&) +{ + return 0; +} + +template +int +NoopOptions::setTagImp(const StringRef&, const T&) +{ + return 0; +} + +// ------------- +// class NooSpan +// ------------- + +inline const NoopContext* +NoopSpan::contextImp() const +{ + return &m_context; +} + +// -------------- +// class NoopSpan +// -------------- + +inline int +NoopSpan::setOperationImp(const StringRef&) +{ + return 0; +} + +inline int +NoopSpan::setBaggageImp(const StringRef&, const StringRef&) +{ + return 0; +} + +inline int +NoopSpan::getBaggageImp(const StringRef&, std::string*) const +{ + return 1; +} + +inline int +NoopSpan::getBaggageImp(const StringRef&, std::vector*) const +{ + return 1; +} + +inline int +NoopSpan::finishImp() +{ + return 0; +} + +inline int +NoopSpan::finishImp(const uint64_t) +{ + return 0; +} + +template +int +NoopSpan::tagImp(const StringRef&, const T&) +{ + return 0; +} + +template +int +NoopSpan::logImp(const StringRef&, const T&) +{ + return 0; +} + +template +int +NoopSpan::logImp(const StringRef&, const T&, const uint64_t) +{ + return 0; +} + +// ---------------- +// class NoopTracer +// ---------------- + +inline void +NoopTracer::installImp(NoopTracer* tracer) +{ + s_tracer = tracer; +} + +inline NoopTracer* +NoopTracer::instanceImp() +{ + return s_tracer; +} + +inline void +NoopTracer::uninstallImp() +{ + s_tracer = 0; +} + +inline NoopOptions* +NoopTracer::makeSpanOptionsImp() +{ + return &m_opts; +} + +inline NoopSpan* +NoopTracer::startImp(const StringRef&) +{ + return &m_span; +} + +inline NoopSpan* +NoopTracer::startImp(const NoopOptions&) +{ + return &m_span; +} + +template +int +NoopTracer::injectImp(CARRIER*, const T&) const +{ + return 0; +} + +template +NoopContext* +NoopTracer::extractImp(const CARRIER&) +{ + return &m_span.m_context; +} + +inline void +NoopTracer::cleanupImp(const NoopOptions*) +{ +} + +inline void +NoopTracer::cleanupImp(const Span*) +{ +} + +inline void +NoopTracer::cleanupImp(const NoopContext*) +{ +} + +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_NOOP_H diff --git a/opentracing/opentracing.pc.in b/opentracing/opentracing.pc.in new file mode 100644 index 0000000..debaed1 --- /dev/null +++ b/opentracing/opentracing.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: C++ interface for the OpenTracing specification +URL: @opentracing_url@ +Version: @opentracing_version@ +Requires: +Requires.private: +Libs: @opentracing_libs@ +Libs.private: +Cflags: -I${includedir} diff --git a/opentracing/span.cc b/opentracing/span.cc deleted file mode 100644 index 49fb1ac..0000000 --- a/opentracing/span.cc +++ /dev/null @@ -1,111 +0,0 @@ -#include -#include - -namespace opentracing -{ - -Span::~Span() -{ -} - -Span * Span::setOperationName(const std::string & operationName) -{ - (void)operationName; - - return this; -} - -Span * Span::setTag(const std::string & key, const std::string & value) -{ - (void)key; - (void)value; - - return this; -} - -Span * Span::log(const LogData & data) -{ - (void)data; - - return this; -} - -Span * Span::log(const std::vector & logData) -{ - for (std::vector::const_iterator i = logData.begin(); i != logData.end(); ++i) - { - log(*i); - } - return this; -} - -Span * Span::setBaggageItem(const std::string & restrictedKey, const std::string & value) -{ - (void)restrictedKey; - (void)value; - - return this; -} - -bool Span::getBaggageItem(const std::string & restrictedKey, std::string * targetValue) const -{ - (void)restrictedKey; - (void)targetValue; - - return false; -} - -void Span::finish() -{ - finish(0); -} - -void Span::finish(const uint64_t & finishTime) -{ - (void)finishTime; -} - -LogData::LogData(const std::string & event_, const Payload & payload_) - : timestamp(0) - , event(event_) - , payload(payload_) -{ -} - -LogData::LogData(const uint64_t & timestamp_, const std::string & event_, const Payload & payload_) - : timestamp(timestamp_) - , event(event_) - , payload(payload_) -{ -} - -bool canonicalizeBaggageKey(std::string & key) -{ - if (key.empty()) - { - return false; - } - - for (std::string::iterator i = key.begin(); i != key.end(); ++i) - { - *i = tolower(*i); - } - - for (std::string::iterator i = key.begin(); i != key.end(); ++i) - { - char c(*i); - - if ((c>='a' && c<='z') || (c>='0' && c<='9') || (c=='-' && i != key.begin())) - { - // This is ok. - } - else - { - return false; - } - } - - return true; -} - -} diff --git a/opentracing/span.h b/opentracing/span.h index 3706a47..54acfb0 100644 --- a/opentracing/span.h +++ b/opentracing/span.h @@ -1,200 +1,225 @@ -#ifndef OPENTRACING_SPAN_H -#define OPENTRACING_SPAN_H +#ifndef INCLUDED_OPENTRACING_SPAN_H +#define INCLUDED_OPENTRACING_SPAN_H -#include -#include -#include +// ====== +// span.h +// ====== +// class GenericSpan - CRTP interface for Spans + +#include +#include +#include #include -namespace opentracing -{ +namespace opentracing { + +// ================= +// class GenericSpan +// ================= +// The GenericSpan has three template parameters: +// * SPAN - A Span implementation derived class +// * CONTEXT - A SpanContext implementation derived class +// * ADAPTER - A Baggage iterator adapter +// +// The CONTEXT and ADAPTER are used to alias the related GenericSpanContext +// class as the SpanContext. This is a convenience typedef to ensure what is +// exposed to clients is the same as the type used by the interface. +// +// CRTP SPAN implementations must support the following: +// +// class SpanImpl: GenericSpan +// { +// public: +// const ContextImpl * contextImp() const; +// +// int setOperationImp(const StringRef&); +// int setBaggageImp(const StringRef&, const StringRef&); +// +// int getBaggageImp(const StringRef&, std::string*) const; +// int getBaggageImp(const StringRef&, std::vector*) const; +// +// int finishImp(); +// int finishImp(const uint64_t); +// +// template +// int tagImp(const StringRef&, const T&); +// +// template +// int logImp(const StringRef&, const T&); +// +// template +// int logImp(const StringRef&, const T&, const uint64_t); +// }; +// +// The templated 'tagImp' and 'logImp' methods may assume that the following +// method is defined for any type 'T': +// `std::ostream& ::operator<<(std::ostream&, const T& val);` + +template +class GenericSpan { + public: + typedef GenericSpanContext SpanContext; + + const SpanContext* context() const; + // Return a copy of the SpanContext associated with this span. + // The context can be injected into carriers, or added as a reference to + // other causally related Spans. When finished with the SpanContext, it + // should be cleaned up with 'Tracer::cleanup()'. + + int setBaggage(const StringRef& key, const StringRef& value); + // Add the 'key:value' pair to this Span. Baggage is propagated with the + // Span + // in-band, throughout the lifetime of a Trace. Return 0 upon success + // and a non-zero value otherwise. + + int getBaggage(const StringRef& key, std::string* baggage) const; + // Load a single 'baggage' value associated with 'key'. Returns 0 if there + // is only one value associated with 'key' and that value was loaded + // successfully. Return a non-zero value otherwise. + + int getBaggage(const StringRef& key, + std::vector* baggage) const; + // Load the 'baggage' associated with 'key'. Returns 0 if the + // baggage is loaded successfully, and a non-zero value otherwise. + + int setOperation(const StringRef& operation); + // Modify the Span's operation. Return 0 upon success and a non-zero + // value otherwise. It is undefined behavior to call this method after + // 'finish()'. + + int finish(); + int finish(const uint64_t tsp); + // This should be the last method called on the Span. It marks the end of + // this Span (in microseconds since epoch) using the current wall-time, or + // an explicit timestamp 'tsp'. Returns 0 upon success and a non-zero + // value otherwise. Multiple calls to 'finish()' are redundant. The only + // other method that may be called on the span, after 'finish()' is to + // copy the 'context()'. -class Tracer; -class LogData; + template + int tag(const StringRef& key, const T& val); + // Tag this span with the supplied key:val pair. Returns 0 upon success and + // a non-zero value otherwise. It is undefined behavior to call this method + // after 'finish()'. + // + // Note: Any type passed must support being externalized: + // `std::ostream& operator<<(std::ostream& os, const T& val); + + template + int log(const StringRef& key, const T& val); + // Log the supplied key:val pair on this span with the current wall-time. + // Returns 0 upon success and a non-zero value otherwise. It is undefined + // behavior to call this method after 'finish()'. + // + // Note: Any type passed must support being externalized: + // `std::ostream& operator<<(std::ostream& os, const T& val); -/** - * A `Span` represents an active, un-finished span in the OpenTracing system. - * Spans are created by the `Tracer` interface. - */ -class Span -{ -public: - /** - * Destroys this Span. - */ - virtual ~Span(); - - /** - * Sets or changes the operation name. - */ - virtual Span * setOperationName(const std::string & operationName); - - /** - * Adds a tag with any value to the `Span`. If there is a - * existing tag set for `key`, it is overwritten. - */ template - Span * setTag(const std::string & key, const T & value); - - /** - * Adds a tag with a `std::string` value to the `Span`. If there is a - * existing tag set for `key`, it is overwritten. - */ - virtual Span * setTag(const std::string & key, const std::string & value); - - /** - * Log() records `data` to this Span. - * See LogData for semantic details. - */ - virtual Span * log(const LogData & data); - - /** - * Bulk, potentially more efficient version of log(). - */ - virtual Span * log(const std::vector & logData); - - /** - * `setBaggageItem()` sets a key:value pair on this `Span` that also - * propagates to future `Span` children. - * - * `setBaggageItem()` enables powerful functionality given a full-stack - * opentracing integration (e.g., arbitrary application data from a mobile - * app can make it, transparently, all the way into the depths of a storage - * system), and with it some powerful costs: use this feature with care. - * - * IMPORTANT NOTE #1: `setBaggageItem()` will only propagate trace - * baggage items to *future* children of the `Span`. - * - * IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and - * value is copied into every local *and remote* child of this `Span`, and - * that can add up to a lot of network and CPU overhead. - * - * IMPORTANT NOTE #3: Baggage item keys have a restricted format: - * implementations may wish to use them as HTTP header keys (or key - * suffixes), and of course HTTP headers are case insensitive. - * - * As such, `restrictedKey` MUST match the regular expression - * `(?i:[a-z0-9][-a-z0-9]*)` and is case-insensitive. That is, it must - * start with a letter or number, and the remaining characters must be - * letters, numbers, or hyphens. See `canonicalizeBaggageKey()`. If - * `restrictedKey` does not meet these criteria, `setBaggageItem()` - * results in undefined behavior. - * - * Returns a pointer to this `Span` for chaining, etc. - */ - virtual Span * setBaggageItem(const std::string & restrictedKey, const std::string & value); - - /* - * If `targetValue` is `NULL`, this method quickly returns true/false depending on - * if baggage is set for given key or not. If `targetValue` isn't `NULL` then the - * baggage value is assigned to `*targetValue` if it exists, otherwise `*targetValue` - * doesn't get modified. - * - * See the `setBaggageItem()` notes about `restrictedKey`. - */ - virtual bool getBaggageItem(const std::string & restrictedKey, std::string * targetValue = 0) const; - - /** - * Provides access to the `Tracer` that created this `Span`. Implementation should - * ensure the reference remains valid potentially in a multi-threaded context (might - * require some form of reference counting). - */ - virtual const Tracer & getTracer() const = 0; - - /** - * Stops this span. `finish()` should be the last call made to any span instance, - * and to do otherwise leads to undefined behavior. - */ - virtual void finish(); - - /** - * Same as `finish()` above but also sets explicitly the finishing time - * in microseconds since epoch. If set to the default value (the unix - * epoch), implementations should use the current time implicitly. - */ - virtual void finish(const uint64_t & finishTime); + int log(const StringRef& key, const T& val, const uint64_t tsp); + // Log the supplied key:val pair on this span with the supplied 'tsp' + // timestamp. Returns 0 upon success and a non-zero value otherwise. + // It is undefined behavior to call this method after 'finish()'. + // + // Note: Any type passed must support being externalized: + // `std::ostream& operator<<(std::ostream& os, const T& val); + + protected: + GenericSpan(); + GenericSpan(const GenericSpan&); + // Protected to avoid direct construction }; -/** - * LogData is data associated to a Span. Every LogData instance should specify - * at least one of Event and/or Payload. - */ -class LogData +// ----------------- +// class GenericSpan +// ----------------- + +template +GenericSpan::GenericSpan() { -public: - /** - * Payload should support arbitrary binary data. `std::string` can support - * this. - */ - typedef std::string Payload; - - /** - * Creates an empty `LogData` for current time and with optional parameters. - */ - explicit LogData(const std::string & event = "", const Payload & payload = Payload()); - - /** - * Creates an empty `LogData` for the given timestamp and with optional - * parameters. If set to the default value (the unix epoch), implementations - * should use the current time implicitly. - */ - explicit LogData(const uint64_t & timestamp, const std::string & event = "", const Payload & payload = Payload()); - - /** - * The timestamp of the log record; if set to the default value (the unix - * epoch), implementations should use the current time implicitly. - */ - uint64_t timestamp; - - /** - * Event (if non-empty) should be the stable name of some notable moment in - * the lifetime of a `Span`. For instance, a `Span` representing a browser page - * load might add an Event for each of the Performance.timing moments - * here: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming - * - * While it is not a formal requirement, Event strings will be most useful - * if they are *not* unique; rather, tracing systems should be able to use - * them to understand how two similar `Span`s relate from an internal timing - * perspective. - */ - std::string event; - - /** - * Payload is a free-form potentially structured object which Tracer - * implementations may retain and record all, none, or part of. - * - * If included, `payload` should be restricted to data derived from the - * instrumented application; in particular, it should not be used to pass - * semantic flags to a log() implementation. - * - * For example, an RPC system could log the wire contents in both - * directions, or a SQL library could log the query (with or without - * parameter bindings); tracing implementations may truncate or otherwise - * record only a snippet of these payloads (or may strip out PII, etc, - * etc). - */ - Payload payload; -}; +} + +template +GenericSpan::GenericSpan(const GenericSpan&) +{ +} + +template +const typename GenericSpan::SpanContext* +GenericSpan::context() const +{ + return static_cast(this)->contextImp(); +} + +template +int +GenericSpan::setOperation(const StringRef& operation) +{ + return static_cast(this)->setOperationImp(operation); +} + +template +int +GenericSpan::setBaggage(const StringRef& key, + const StringRef& value) +{ + return static_cast(this)->setBaggageImp(key, value); +} + +template +int +GenericSpan::getBaggage(const StringRef& key, + std::string* value) const +{ + return static_cast(this)->getBaggageImp(key, value); +} -/** - * `canonicalizeBaggageKey()` returns the canonicalized version of baggage item - * key `key`, and true if and only if the key was valid. Note: It might alter - * `key` irrespectively of if it is valid or not. - */ -bool canonicalizeBaggageKey(std::string & key); +template +int +GenericSpan::getBaggage( + const StringRef& key, std::vector* value) const +{ + return static_cast(this)->getBaggageImp(key, value); +} + +template +int +GenericSpan::finish() +{ + return static_cast(this)->finishImp(); +} -/** - * Default `setTag()` implementation. - */ +template +int +GenericSpan::finish(const uint64_t tsp) +{ + return static_cast(this)->finishImp(tsp); +} + +template template -Span * Span::setTag(const std::string & key, const T & value) +int +GenericSpan::tag(const StringRef& key, const T& val) { - std::ostringstream oss; - oss << value; - setTag(key, oss.str()); - return this; + return static_cast(this)->tagImp(key, val); } +template +template +int +GenericSpan::log(const StringRef& key, const T& val) +{ + return static_cast(this)->logImp(key, val); +} + +template +template +int +GenericSpan::log(const StringRef& key, + const T& val, + const uint64_t tsp) +{ + return static_cast(this)->logImp(key, val, tsp); } -#endif // #define OPENTRACING_SPAN_H +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_SPAN_H diff --git a/opentracing/spancontext.h b/opentracing/spancontext.h new file mode 100644 index 0000000..15e1b4e --- /dev/null +++ b/opentracing/spancontext.h @@ -0,0 +1,131 @@ +#ifndef INCLUDED_OPENTRACING_SPANCONTEXT_H +#define INCLUDED_OPENTRACING_SPANCONTEXT_H + +// ============= +// spancontext.h +// ============= +// class GenericSpanContext - CRTP interface for SpanContexts + +#include +#include +#include +#include + +namespace opentracing { + +// ======================== +// class GenericSpanContext +// ======================== +// The 'GenericSpanContext' has two template parameters: +// * CONTEXT - A SpanContext implementation derived class +// * ADAPTER - A BaggageIteratorImp adapter +// +// CONTEXT implementations are required to implement the following: +// +// class ContextImpl : GenericSpanContext +// { +// public: +// typename Adapter::const_iterator baggageBeginImp() const; +// typename Adapter::const_iterator baggageEndImp() const; +// +// int getBaggageImp(const StringRef&, std::string*) const; +// int getBaggageImp(const StringRef&, std::vector*) const; +// }; +// +// Implementations may choose how they implement storage of Baggage, but the +// baggageBeginImp/baggageEndImp iterators should interact with the installed +// BaggageIterator/Adapter correctly. Contexts should be immutable and +// thread-safe. + +template +class GenericSpanContext { + public: + typedef CONTEXT SpanContext; + typedef ADAPTER BaggageAdapter; + typedef BaggageIteratorImp BaggageIterator; + typedef BaggageRangeImp BaggageRange; + + BaggageIterator baggageBegin() const; + // Return a BaggageIterator to the beginning of the Baggage maintained + // by this SpanContext. + + BaggageIterator baggageEnd() const; + // Return a BaggageIterator to mark the end of the Baggage maintained + // by this SpanContext. + + BaggageRange baggageRange() const; + // Return a structure containing the range of iterators: + // [baggageBegin, baggageEnd). The object supports range-based for loops. + + int getBaggage(const StringRef &key, std::string *baggage) const; + // Load a single 'baggage' value associated with 'key'. Returns 0 if there + // is only one value associated with 'key' and that value was loaded + // successfully. Return a non-zero value otherwise. + + int getBaggage(const StringRef & key, + std::vector *baggage) const; + // Load the 'baggage' associated with 'key'. Returns 0 if the + // baggage is loaded successfully, and a non-zero value otherwise. + + protected: + GenericSpanContext(); + GenericSpanContext(const GenericSpanContext &); + // Protected to avoid direct construction +}; + +// ------------------------ +// class GenericSpanContext +// ------------------------ + +template +GenericSpanContext::GenericSpanContext() +{ +} + +template +GenericSpanContext::GenericSpanContext( + const GenericSpanContext &) +{ +} + +template +typename GenericSpanContext::BaggageIterator +GenericSpanContext::baggageBegin() const +{ + return BaggageIterator( + static_cast(this)->baggageBeginImp()); +} + +template +typename GenericSpanContext::BaggageIterator +GenericSpanContext::baggageEnd() const +{ + return BaggageIterator(static_cast(this)->baggageEndImp()); +} + +template +typename GenericSpanContext::BaggageRange +GenericSpanContext::baggageRange() const +{ + return BaggageRange(baggageBegin(), baggageEnd()); +} + +template +int +GenericSpanContext::getBaggage(const StringRef &key, + std::string *baggage) const + +{ + return static_cast(this)->getBaggageImp(key, baggage); +} + +template +int +GenericSpanContext::getBaggage( + const StringRef &key, std::vector *baggage) const +{ + return static_cast(this)->getBaggageImp(key, baggage); +} + +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_SPANCONTEXT_H diff --git a/opentracing/spanoptions.h b/opentracing/spanoptions.h new file mode 100644 index 0000000..fd8ada5 --- /dev/null +++ b/opentracing/spanoptions.h @@ -0,0 +1,112 @@ +#ifndef INCLUDED_OPENTRACING_SPANOPTIONS_H +#define INCLUDED_OPENTRACING_SPANOPTIONS_H + +// ============= +// spanoptions.h +// ============= +// class SpanReferenceType - Enumeration for Span relationship types +// class GenericSpanOptions - CRTP interface for SpanOptions + +#include +#include + +namespace opentracing { + +// ======================= +// class SpanReferenceType +// ======================= +// Spans may reference zero or more other SpanContexts that are causally +// related. +// OpenTracing presently defines two types of references: ChildOf and +// FollowsFrom. Both reference types specifically model direct causal +// relationships between a child Span and a parent Span. +// +// * e_ChildOf - A Span may be the ChildOf a parent Span. In a ChildOf +// reference, +// the parent Span depends on the child Span in some capacity. +// +// * e_FollowsFrom - Some parent Spans do not depend in any way on the result +// of +// their child Spans. In these cases, we say merely that the +// child Span FollowsFrom the parent Span in a causal sense. + +class SpanReferenceType { + public: + enum Value + { + e_ChildOf = 0, + e_FollowsFrom = 1 + }; +}; + +// ======================== +// class GenericSpanOptions +// ======================== +// This class defines the interface that clients can use to configure how a +// Span is created. These options are passed to the Tracer through the +// 'Tracer::start(const SpanOptions&)` method.. + +template +class GenericSpanOptions { + public: + typedef GenericSpanContext SpanContext; + + int setOperation(const StringRef&); + // Set the operation name to be used for any Span created with these + // options. Return 0 upon success and a non-zero value otherwise. + + int setStartTime(const uint64_t); + // Set the start time from this span. If start time is not supplied, the + // default is to use the current wall-time. Return 0 upon success and a + // non-zero value otherwise. + + int setReference(const SpanReferenceType::Value relationship, + const SpanContext& context); + // A new Span created with these options would have a 'relationship' + // referenced added for 'context'. Return 0 upon success and a non-zero + // value otherwise. + + template + int setTag(const StringRef& key, const T& value); + // A new Span created with these options would have this 'key:value' tag. + // Return 0 upon success and a non-zero value otherwise. +}; + +// ------------------------ +// class GenericSpanOptions +// ------------------------ + +template +int +GenericSpanOptions::setOperation(const StringRef& op) +{ + return static_cast(this)->setOperationImp(op); +} + +template +int +GenericSpanOptions::setStartTime(const uint64_t tsp) +{ + return static_cast(this)->setStartTimeImp(tsp); +} + +template +int +GenericSpanOptions::setReference( + const SpanReferenceType::Value rel, const SpanContext& context) +{ + const CONTEXT& contextImp = static_cast(context); + return static_cast(this)->setReferenceImp(rel, contextImp); +} + +template +template +int +GenericSpanOptions::setTag(const StringRef& key, + const T& value) +{ + return static_cast(this)->setTagImp(key, value); +} + +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_SPANOPTIONS_H diff --git a/opentracing/stringref.h b/opentracing/stringref.h new file mode 100644 index 0000000..1fe39ac --- /dev/null +++ b/opentracing/stringref.h @@ -0,0 +1,185 @@ +#ifndef INCLUDED_OPENTRACING_STRINGREF_H +#define INCLUDED_OPENTRACING_STRINGREF_H + +// =========== +// stringref.h +// =========== +// class StringRef - Constant reference to an external string +// +// ----------------- +// String References +// ----------------- +// This string references is a simplified version of the boost::string_ref. +// Its purpose is to avoid a number of efficiency problems that appear +// commonly when interacting with 'std::string' and c-strings. +// +// See the boost documentation for more background: +// http://www.boost.org/doc/libs/master/libs/utility/doc/html/string_ref.html + +#include +#include +#include + +namespace opentracing { + +// =============== +// class StringRef +// =============== +// Represent a constant reference to an external character array. The external +// array need not be null-terminated, if explicitly created with a known length. +// +// This class does not own the data. It is expected to be used in situations +// where the character data resides in some other buffer, whose lifetime extends +// past that of the StringRef. For this reason, it is not in general safe to +// store a StringRef. + +class StringRef { + public: + StringRef(); + // Construct an empty StringRef + + template + StringRef(const char (&str)[N]); + // Explicitly create string reference from a const character array + + explicit StringRef(const char* str); + // Explicitly create string reference from const character pointer + + StringRef(const std::basic_string& str); + // Create constant string reference from pointer and length + + StringRef(const char* str, size_t len); + // Create constant string reference from pointer and length + + operator const char*() const; + // Implicit conversion to plain char * + + template + void reset(const char (&str)[N]); + // Reset the string reference given a const character array + + void reset(const char* str); + // Reset this string ref to point at the supplied c-string + + void reset(const std::basic_string& str); + // Reset the string reference given a std::string + + void reset(const char* str, const size_t length); + // Reset this string ref to point at the supplied 'str' of 'length' bytes. + + const char* data() const; + // Return address of the referenced string + + size_t length() const; + // Return the length of the referenced string + + private: + template + StringRef(char (&str)[N]); + // Disallow construction from non-const array + + template + void reset(char (&str)[N]); + // Disallow reset from non-const array + + const char* m_data; // Pointer to external storage + size_t m_length; // Length of data pointed to by 'm_data' +}; + +// ----- +// Note: +// ----- +// Although we have the ability to use wide string refs, there are side +// effects in exposing an OpenTracing interface that works with narrow and wide +// strings at the same time. Storage on the implementation will have a 'native' +// format. +// +// Exposing references to that format avoid copies means clients would be +// dependent on that format. If they're dependent on that detail and then switch +// out the implementation to a different format, there would be lots of code +// that breaks if it was expecting wstring and starts receiving string all of a +// sudden. That design issue still needs to be addressed. + +// --------------- +// Class StringRef +// --------------- + +inline StringRef::StringRef() : m_data(0), m_length(0) +{ +} + +template +StringRef::StringRef(const char (&str)[N]) : m_data(str), m_length(N - 1) +{ +} + +inline StringRef::StringRef(const char* str) +: m_data(str), m_length(std::strlen(str)) +{ +} + +inline StringRef::StringRef(const std::basic_string& str) +: m_data(str.c_str()), m_length(str.length()) +{ +} + +inline StringRef::StringRef(const char* str, size_t len) +: m_data(str), m_length(len) +{ +} + +inline StringRef::operator const char*() const +{ + return m_data; +} + +inline void +StringRef::reset(const char* str, const size_t length) +{ + m_data = str; + m_length = length; +} + +template +void +StringRef::reset(const char (&str)[N]) +{ + m_data = str; + m_length = N; +} + +inline void +StringRef::reset(const char* str) +{ + m_data = str; + m_length = std::strlen(str); +} + +inline void +StringRef::reset(const std::basic_string& str) +{ + m_data = str.data(); + m_length = str.length(); +} + +inline const char* +StringRef::data() const +{ + return m_data; +} + +inline size_t +StringRef::length() const +{ + return m_length; +} + +} // namespace opentracing + +inline std::ostream& +operator<<(std::ostream& os, const opentracing::StringRef& ref) +{ + return os.write(ref.data(), ref.length()); +} + +#endif // INCLUDED_OPENTRACING_STRINGREF_H diff --git a/opentracing/tracer.cc b/opentracing/tracer.cc deleted file mode 100644 index 9411982..0000000 --- a/opentracing/tracer.cc +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include - -#include -#include - - -#if defined(OPENTRACING_USE_STD_ATOMIC) || defined(OPENTRACING_USE_BOOST_ATOMIC) - #ifdef OPENTRACING_USE_STD_ATOMIC - #include - #define TRACER_PTR_T std::atomic - #else - #include - #define TRACER_PTR_T boost::atomic - #endif - - #define SWAP_TRACER_PTR(ptr, value) ((value) = (ptr).exchange(value)) - #define RETRIEVE_TRACER_PTR(ptr) ((ptr).load()) -#else - #define TRACER_PTR_T Tracer * - #define SWAP_TRACER_PTR(ptr, value) do { TRACER_PTR_T tmp(ptr); (ptr) = (value); (value) = tmp; } while (0) - #define RETRIEVE_TRACER_PTR(ptr) (ptr) -#endif - - -namespace opentracing -{ - -StartSpanOptions::StartSpanOptions(const Span * parent_, const uint64_t & startTime_) - : parent(parent_) - , startTime(startTime_) -{ -} - -StartSpanOptions::StartSpanOptions(const uint64_t & startTime_) - : parent(0) - , startTime(startTime_) -{ -} - -Tracer::~Tracer() -{ -} - -Writer::~Writer() -{ -} - -TextMapWriter::~TextMapWriter() -{ -} - -Reader::~Reader() -{ -} - -TextMapReader::~TextMapReader() -{ -} - -TextMapReader::ReadCallback::~ReadCallback() -{ -} - -namespace { - -class NoopSpan : public virtual Span -{ -public: - - NoopSpan(const NoopTracer & noopTracer) - : m_noopTracer(noopTracer) - { - } - - virtual ~NoopSpan() - { - } - - virtual const Tracer & getTracer() const - { - return m_noopTracer; - } - -private: - - const NoopTracer & m_noopTracer; -}; - -} - -NoopTracer::~NoopTracer() -{ -} - -Span * NoopTracer::startSpan(const std::string & operationName, const StartSpanOptions & startSpanOptions) const -{ - (void)operationName; - (void)startSpanOptions; - return new NoopSpan(*this); -} - -Tracer::Result NoopTracer::inject(const Span & sp, const Writer & writer, std::string & error) const -{ - (void)sp; - (void)writer; - (void)error; - - return Success; -} - -Tracer::Result NoopTracer::join(Span ** sp, const std::string & operationName, const Reader & reader, std::string & error) const -{ - (void)sp; - (void)operationName; - (void)reader; - (void)error; - - return ErrTraceNotFound; -} - -namespace -{ - -NoopTracer defaultNoopTracer; -TRACER_PTR_T the_globalTracer(&defaultNoopTracer); - -} - -Tracer * globalTracer() -{ - return RETRIEVE_TRACER_PTR(the_globalTracer); -} - -Tracer * initGlobalTracer(Tracer * tracer) -{ - if (!tracer) - { - throw std::runtime_error("tracer is NULL"); - } - - SWAP_TRACER_PTR(the_globalTracer, tracer); - - return tracer == &defaultNoopTracer ? NULL : tracer; -} - -} diff --git a/opentracing/tracer.h b/opentracing/tracer.h index c1433a6..e514196 100644 --- a/opentracing/tracer.h +++ b/opentracing/tracer.h @@ -1,322 +1,335 @@ -#ifndef OPENTRACING_TRACER_H -#define OPENTRACING_TRACER_H +#ifndef INCLUDED_OPENTRACING_TRACER_H +#define INCLUDED_OPENTRACING_TRACER_H + +// ======== +// tracer.h +// ======== +// class GenericTracer - CRTP interface for the Tracer + +#include +#include +#include +#include + +namespace opentracing { + +// =================== +// class GenericTracer +// =================== +// The GenericTracer acts as a factory for the other OpenTracing types. It has +// the most functionality, and also requires the most details to be instantiated +// appropriately at compile time. As such, it has the most template parameters, +// giving it access to otherwise unknowable details, at compile time. +// +// The template parameters are: +// * TRACER - A Tracer implementation +// * SPAN - A Span implementation +// * OPTIONS - A SpanOptions implementation +// * CONTEXT - A SpanContext implementation +// * ADAPTER - A Baggage iterator adapter +// +// The Tracer also exposes a set of typedefs which obfuscate the details of +// the implementation. These typedefs make it possible for clients to +// rely on the installed implementation, without having to know about any of the +// underlying types. +// +// * Span - Aliasing GenericSpan +// * SpanContext - Aliasing GenericSpanContex +// * SpanOptions - Aliasing GenericSpanOptions +// +// CRTP TRACER implementations must support the following: +// +// class TracerImp : +// GenericTracer +// { +// public: +// static void installImp(TracerImp*); +// static void uninstallImp(); +// static TracerImp * instanceImp(); +// +// Options * makeSpanOptionsImp(); +// +// SpanImp * start(const StringRef& op); +// SpanImp * start(const OptionsImp& opts); +// +// template +// int injectImp(CARRIER*, const SpanImp& context) const; +// +// template +// int injectImp(CARRIER*, const ContextImp& context) const; +// +// template +// const ContextImp* extractImp(const CARRIER& carrier); +// +// void cleanupImp(const Options*); +// void cleanupImp(const SpanImp*); +// void cleanupImp(const ContextImp*); +// }; + +template +class GenericTracer { + public: + typedef GenericSpan Span; + typedef GenericSpanContext SpanContext; + typedef GenericSpanOptions SpanOptions; + // Public typedefs used by clients to use underlying implementation + // interfaces reliably. These must exist in order to support O(1) compile + // time changes of the Tracer implementations. + + static void install(GenericTracer* tracer); + // Install Tracer to be referenced globally with calls to 'instance()'. + // It is undefined behavior to call any other Tracer method if an + // implementation has not been installed. + // + // This method is not thread safe. It should be called once, in main, before + // any other thread or trace work is performed. Undefined behavior if called + // differently. + + static void uninstall(); + // Uninstall a previously installed Tracer. This method is not thread safe, + // nor is it required for production code. It is used to sanitize test + // environments when needed. + + static GenericTracer* instance(); + // Return an instance to the globally installed tracer. Returns NULL if a + // tracer has not been previously installed. It is undefined behavior + // to call this method without previously calling 'install()'. + + SpanOptions* makeSpanOptions(); + // This is a factory method to create SpanOptions. Options can be passed + // to 'start()' to control how Spans are created. Options should be + // subsequently cleaned up with a call to 'cleanup()' Returns NULL on + // failure. + + Span* start(const StringRef& op); + Span* start(const SpanOptions& opts); + // The 'start()' functions are factory methods to create a new Span. The + // only required parameter is the operation string 'op', which should + // describe the work being performed during the span. + // + // Optional arguments are encapsulated in the 'opts' argument. Those options + // can be used to modify the start time, set the operation name, and/or + // establish the causal relationship between other Spans. + // + // Returns NULL on error. + + template + int inject(CARRIER* carrier, const Span& span) const; + // Inject the supplied 'span' into the supplied 'carrier' writer. This + // method should expect one of the 'Writer' carriers defined in + // carriers.h. Returns 0 upon success and a non-zero value otherwise. + + template + int inject(CARRIER* carrier, const SpanContext& context) const; + // Inject the supplied 'span' into the supplied 'carrier' writer. This + // method should expect one of the 'Writer' carriers defined in + // carriers.h. Returns 0 upon success and a non-zero value otherwise. + + template + const SpanContext* extract(const CARRIER& carrier); + // Extract the read-only supplied 'context' from the text map reader + // 'carrier' upon success, and NULL otherwise. Should expect one + // of the 'Reader' carriers defined in carriers.h. + + void cleanup(const SpanOptions* opts); + // All SpanOptions pointers returned by the Tracer via 'makeSpanOptions()' + // must be passed back to the Tracer when client's are done with them via + // 'cleanup()'. + + void cleanup(const Span* sp); + // All Span pointers returned by the Tracer via 'start()' must be passed + // back to the Tracer when clients are done with them via 'cleanup()'. + + void cleanup(const SpanContext* sp); + // All SpanContext pointers created by the Tracer through 'extract()' should + // be passed back to the Tracer when client's are done with them via + // 'cleanup()'. + + protected: + GenericTracer(); + GenericTracer(const GenericTracer&); + // Protected to avoid direct construction +}; -#include -#include +// ------------------- +// class GenericTracer +// ------------------- + +template +void +GenericTracer::install( + GenericTracer* tracer) +{ + TRACER* tracerImp = static_cast(tracer); + TRACER::installImp(tracerImp); +} -namespace opentracing +template +void +GenericTracer::uninstall() { + return TRACER::uninstallImp(); +} -class Span; -class Writer; -class Reader; +template +GenericTracer* +GenericTracer::instance() +{ + return TRACER::instanceImp(); +} -/** - * Options used when you start a `Span`. - */ -struct StartSpanOptions +template +GenericTracer::GenericTracer() { - /** - * The parent span. `NULL` if there's no parent `Span`. - */ - const Span * parent; - - /** - * The `Span` start time. Set to 0 to use current time. - */ - const uint64_t startTime; - - /** - * Start a child `Span` with the given parent and `startTime`. - */ - StartSpanOptions(const Span * parent_, const uint64_t & startTime_ = 0); - - /** - * Start a child `Span` with the given `startTime`. - */ - explicit StartSpanOptions(const uint64_t & startTime_ = 0); -}; +} -/** - * Tracer is a simple, thin interface for `Span` creation. - */ -class Tracer +template +GenericTracer::GenericTracer( + const GenericTracer&) { -public: - /** - * Destroys the `Tracer`. - */ - virtual ~Tracer(); - - /** - * Return statuses for the `inject()` and `join()` methods. - */ - enum Result - { - /** - * Operation completed successfully. - */ - Success, - - /** - * `ErrUnsupportedFormat` occurs when the `format` passed to `Tracer::inject()` or - * `Tracer::join()` is not recognized by the Tracer implementation. - */ - ErrUnsupportedFormat, - - /** - * `ErrTraceNotFound` occurs when the `carrier` passed to `Tracer::join()` is - * valid and uncorrupted but has insufficient information to join or resume - * a trace. - */ - ErrTraceNotFound, - - /** - * `ErrInvalidSpan` errors occur when `Tracer::inject()` is asked to operate on - * a `Span` which it is not prepared to handle (for example, since it was - * created by a different tracer implementation). - */ - ErrInvalidSpan, - - /** - * `ErrInvalidCarrier` errors occur when `Tracer::inject()` or `Tracer::join()` - * implementations expect a different type of `carrier` than they are - * given. - */ - ErrInvalidCarrier, - - /** - * `ErrTraceCorrupted` occurs when the `carrier` passed to `Tracer::join()` is - * of the expected type but is corrupted. - */ - ErrTraceCorrupted - }; - - /** - * Create, start, and return a new Span with the given `operationName`, all - * without specifying a parent `Span` that can be used to incorporate the - * newly-returned `Span` into an existing trace. (I.e., the returned `Span` is - * the "root" of its trace). - * - * Examples: - * - * Span * sp = globalTracer()->startSpan("GetFeed()"); - * - * Span * sp = globalTracer()->startSpan("LoggedHTTPRequest()", StartSpanOptions(now, &parent)) - * ->setTag("user_agent", loggedReq.UserAgent); - * - * This method isn't expected to fail except of the usual out of memory errors. - */ - virtual Span * startSpan(const std::string & operationName, const StartSpanOptions & startSpanOptions = StartSpanOptions()) const = 0; - - /** - * `inject()` takes the `sp` `Span` instance and sends information topropagation - * carriers via the `Writer` interface. It returns `Result::Success` on success - * or some error otherwise. In the latter case, `error` will be set with a - * debug message. - * - * Example usage: - * - * CarrierT carrier; - * std::string error; - * - * Tracer::Result result = globalTracer()->inject(*span, - * MapTextAdapter(carrier), - * error); - * if (result != Tracer::Success) { ... - * - * NOTE: All opentracing.Tracer implementations MUST support `TextMapWriter` - * `Writer`s. - * - * Implementations may return `Result::ErrUnsupportedFormat` if `writer` - * is of unknown or unsupported type. - * - * Implementations may return `Result::ErrInvalidCarrier` or any other - * implementation-specific error if the format is supported but injection - * fails anyway. - * - * There's not specific reason for `Writer` being const. It's just to support - * the nice inline form described above. This might mean that you might have - * to use `mutable` in some cases. This `const` will be revised if there prove - * to be issues. - * - * See also `Tracer::join()`. - */ - virtual Result inject(const Span & sp, const Writer & writer, std::string & error) const = 0; - - /** - * `join()` returns a `Span` instance with operation name `operationName` given - * a carrier reader `reader`. - * - * `join()` is responsible for extracting and joining to the trace of a `Span` - * instance embedded in a format-specific "carrier" object. Typically the - * joining will take place on the server side of an RPC boundary, but - * message queues and other IPC mechanisms are also reasonable places to - * use `join()`. - * - * OpenTracing defines an extensible set of `Reader`s (see `Reader`), and - * each supports potentially many carrier types. - * - * - * - * Example usage: - * - * CarrierT carrier; - * std::string error; - * - * Span * span(0); - * Tracer::Result result = globalTracer()->join(&span, - * "operation()", - * MapTextAdapter(carrier), - * error); - * if (result != Tracer::Success) { ... - * - * NOTE: All `Tracer` implementations MUST support `TextMapReader` `Reader`s. - * - * Return values: - * - A successful `join()` will return `Tracer::Success` and will set `sp` to - * the value of an appropriately initialized `Span` pointer. - * - If there was simply no trace to join with in `carrier`, `join()` - * returns `Tracer::ErrTraceNotFound` and `sp` isn't modified. - * - If the `reader` is unsupported or unrecognized, `join()` returns - * `Tracer::ErrUnsupportedFormat` and `sp` isn't modified. - * - If there are more fundamental problems with the `reader` object, - * `join()` may return `Tracer::ErrInvalidCarrier` or - * `Tracer::ErrTraceCorrupted` and `sp` won't be modified. - * - * See also `Tracer::inject()`. - */ - virtual Result join(Span ** sp, const std::string & operationName, const Reader & reader, std::string & error) const = 0; -}; +} -/** - * This is the base interface for writing data to carriers. Not much - * functionality here but provides through `dynamic_cast<>`ing the means - * to check if an implementation is supported or not. - */ -class Writer +template +typename GenericTracer::SpanOptions* +GenericTracer::makeSpanOptions() { -public: - /** - * Destroys this Writer. - */ - virtual ~Writer(); -}; + return static_cast(this)->makeSpanOptionsImp(); +} -/** - * This is the base interface for reading data from carriers. Not much - * functionality here but provides through `dynamic_cast<>`ing the means - * to check if an implementation is supported or not. - */ -class Reader +template +typename GenericTracer::Span* +GenericTracer::start( + const StringRef& op) { -public: - /** - * Destroys this Reader. - */ - virtual ~Reader(); -}; + return static_cast(this)->startImp(op); +} -/** - * This is an implementation of `Writer` which should be supported - * by every tracing system. It allows the propagation of string key-value - * pairs through carriers. - */ -class TextMapWriter : public virtual Writer +template +typename GenericTracer::Span* +GenericTracer::start( + const SpanOptions& opts) { -public: - /** - * Destroys this TextMapWriter. - */ - virtual ~TextMapWriter(); - - /** - * Sets a key-value pair to the carrier. `isBaggage` will be true for baggage key-values. - */ - virtual void set(const std::string& key, const std::string& value, bool isBaggage) const = 0; -}; + const OPTIONS& optsImp = static_cast(opts); + return static_cast(this)->startImp(optsImp); +} -/** - * This is an implementation of `Reader` which should be supported - * by every tracing system. It allows joining Spans by using information - * on the form of string key-value pairs from the carriers. - */ -class TextMapReader : public virtual Reader +template +template +int +GenericTracer::inject( + CARRIER* carrier, const Span& span) const { -public: - /** - * Destroys this TextMapWriter. - */ - virtual ~TextMapReader(); - - /** - * Callback functor typically implemented from the tracer in order to retrieve - * key-value pairs from the `TextMapReader`. - */ - struct ReadCallback - { - /** - * Destroys this ReadCallback. - */ - virtual ~ReadCallback(); - - /** - * Will be called for each key-value pair. - */ - virtual void operator() (const std::string& key, const std::string& value, bool isBaggage) const = 0; - }; - - /** - * Reads from the carrier and calls the `callback()` for each key-value pair. - */ - virtual void forEachPair(const ReadCallback & callback) const = 0; -}; + const SPAN& spanImp = static_cast(span); + const TRACER* tracer = static_cast(this); + return tracer->injectImp(carrier, spanImp); +} -/** - * Default No-op implementation. Doesn't crash nor has any side-effects. - */ -struct NoopTracer : public Tracer +template +template +int +GenericTracer::inject( + CARRIER* carrier, const SpanContext& context) const { - /** - * Destroys this NoopTracer. - */ - virtual ~NoopTracer(); - - /** - * Starts a `Span` for a given operation name and options defined in - * `startSpanOptions`. - */ - virtual Span * startSpan(const std::string & operationName, const StartSpanOptions & startSpanOptions = StartSpanOptions()) const; - - /** - * The no-op Tracer's `inject()` method should always succeed - */ - virtual Result inject(const Span & sp, const Writer & writer, std::string & error) const; - - - /** - * The no-op Tracer's `join()` method should always return a "trace not found" - * status. - */ - virtual Result join(Span ** sp, const std::string & operationName, const Reader & reader, std::string & error) const; -}; + const CONTEXT& contextImp = static_cast(context); + const TRACER* tracer = static_cast(this); + return tracer->injectImp(carrier, contextImp); +} + +template +template +const typename GenericTracer:: + SpanContext* + GenericTracer::extract( + const CARRIER& carrier) +{ + return static_cast(this)->extractImp(carrier); +} -/** - * Returns the previous instance of the global tracer or `NULL` if - * the previous version was the default no-op `Tracer`. This method isn't - * thread safe by default. To make it thread-safe define - * `OPENTRACING_USE_STD_ATOMIC` to use `std::atomic<>` or - * `OPENTRACING_USE_BOOST_ATOMIC` to use `boost::atomic<>`. This function - * throws `std::runtime_error` if the `tracer` argument is `NULL`. - */ -Tracer * initGlobalTracer(Tracer * tracer); - -/** - * Returns the global tracer object. It's guaranteed to not be `NULL`. The - * same thread safety rules as for `initGlobalTracer()` apply. - */ -Tracer * globalTracer(); +template +void +GenericTracer::cleanup( + const SpanOptions* opts) +{ + const OPTIONS* optsImp = static_cast(opts); + return static_cast(this)->cleanupImp(optsImp); +} +template +void +GenericTracer::cleanup(const Span* sp) +{ + const SPAN* spanImp = static_cast(sp); + return static_cast(this)->cleanupImp(spanImp); +} + +template +void +GenericTracer::cleanup( + const SpanContext* spc) +{ + const CONTEXT* contextImp = static_cast(spc); + return static_cast(this)->cleanupImp(contextImp); } -#endif // #ifndef OPENTRACING_TRACER_H +} // namespace opentracing +#endif // INCLUDED_OPENTRACING_TRACER_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..49407ef --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,28 @@ +include(CheckCXXCompilerFlag) + +set(unittest_sources + baggage.t.cc + carriers.t.cc + span.t.cc + spancontext.t.cc + stringref.t.cc + tracer.t.cc + unittest.t.cc) + +option(enable_noop "Test noop tracer." OFF) + +if(enable_noop) + set(unittest_sources ${unittest_sources} noop.t.cc) + include_directories(${CMAKE_SOURCE_DIR}) + set(noop_lib "opentracing") +endif() + +include_directories(${CMAKE_SOURCE_DIR}) + +add_executable(unittest ${unittest_sources}) + +target_link_libraries(unittest ${noop_lib} gtest gtest_main) + +add_test(NAME unittest + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 23781c7..0000000 --- a/test/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -CC=g++ -CFLAGS=-Wall -fno-elide-constructors -pedantic-errors -Wextra -Wall -Winit-self -Wold-style-cast -Woverloaded-virtual -Winit-self -Wuninitialized -Wmissing-declarations -Werror -std=c++98 - -# Requires gtest. -# For example for mac: http://stackoverflow.com/questions/20746232/how-to-properly-setup-googletest-on-os-x-aside-from-xcode -# For ubuntu: http://www.eriksmistad.no/getting-started-with-google-test-on-ubuntu/ - -LDFLAGS= - -ODIR=. -LDIR =../lib - -LIBS=-lopentracing -lgtest -lgtest_main -lpthread - -_OBJ = opentracing.t.o -OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) - -$(ODIR)/%.o: %.cc - $(CC) -c -o $@ $< $(CFLAGS) - -test: $(OBJ) - $(CC) -o $@ $^ $(CFLAGS) $(LIBS) $(LDFLAGS) - -.PHONY: clean - -clean: - rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ diff --git a/test/baggage.t.cc b/test/baggage.t.cc new file mode 100644 index 0000000..f8ab44d --- /dev/null +++ b/test/baggage.t.cc @@ -0,0 +1,268 @@ +#include "unittest.h" + +#include // test include guard +#include + +#include +#include +#include + +TEST(Baggage, Constructor) +{ + Baggage b("", ""); + ASSERT_EQ("", b.key()); + ASSERT_EQ("", b.value()); +} + +TEST(Baggage, CopyConstructor) +{ + Baggage b("apple", "banana"); + Baggage cp(b); + + ASSERT_EQ("apple", cp.key()); + ASSERT_EQ("banana", cp.value()); +} + +TEST(Baggage, Assignment) +{ + Baggage b("apple", "banana"); + Baggage cp("", ""); + cp = b; + + ASSERT_EQ("apple", cp.key()); + ASSERT_EQ("banana", cp.value()); +} + +TEST(Baggage, ConstBehavior) +{ + const Baggage b("apple", "banana"); + ASSERT_EQ("apple", b.key()); + ASSERT_EQ("banana", b.value()); +} + +TEST(Baggage, ConstBehaviorThroughDeref) +{ + const Baggage b("apple", "banana"); + ASSERT_EQ("apple", b->key()); + ASSERT_EQ("banana", b->value()); +} + + +TEST(BaggageRef, Constructor) +{ + const char n[] = "hello"; + const char v[] = "world"; + + BaggageRef ref(n, v); + + ASSERT_EQ(n, ref.key().data()); + ASSERT_EQ(v, ref.value().data()); +} + +TEST(BaggageRef, CopyConstructor) +{ + const char n[] = "hello"; + const char v[] = "world"; + + BaggageRef ref(n, v); + BaggageRef cpy(ref); + + ASSERT_EQ(n, cpy.key().data()); + ASSERT_EQ(v, cpy.value().data()); +} + +TEST(BaggageRef, Assignment) +{ + const char n[] = "hello"; + const char v[] = "world"; + + const char n2[] = "hello"; + const char v2[] = "world"; + + BaggageRef ref(n, v); + BaggageRef ref2(n2, v2); + + ref = ref2; + + ASSERT_EQ(n2, ref.key().data()); + ASSERT_EQ(v2, ref.value().data()); +} + +// BaggageIterator adapter for map +class MapBaggage { + public: + typedef std::map Map; + typedef Map::iterator iterator; + typedef Map::const_iterator const_iterator; + + Baggage copy(const Map::const_iterator& iter) const + { + return Baggage(iter->first, iter->second); + } + BaggageRef ref(const Map::const_iterator& iter) const + { + return BaggageRef(iter->first, iter->second); + } +}; + +TEST(BaggageIterator, CopyAdapter) +{ + MapBaggage::Map m; + m["animal"] = "dog"; + m["fruit"] = "apple"; + m["veggie"] = "carrot"; + + BaggageIteratorImp it(m.begin()); + BaggageIteratorImp end(m.end()); + + ASSERT_FALSE(end == it); + Baggage copy = it.copy();; + + ASSERT_EQ("animal", copy.key()); + ASSERT_EQ("dog", copy.value()); + + BaggageIteratorImp prev = it++; + ASSERT_FALSE(prev == end); + ASSERT_FALSE(it == end); + + copy = prev.copy(); + + ASSERT_EQ("animal", copy.key()); + ASSERT_EQ("dog", copy.value()); + + copy = it.copy(); + + ASSERT_EQ("fruit", copy.key()); + ASSERT_EQ("apple", copy.value()); + + ++it; + ASSERT_FALSE(end == it); + + copy = it.copy(); + + ASSERT_EQ("veggie", copy.key()); + ASSERT_EQ("carrot", copy.value()); + + ++it; + + ASSERT_TRUE(end == it); +} + +TEST(BaggageIterator, RefAdapter) +{ + MapBaggage::Map m; + m["animal"] = "dog"; + m["fruit"] = "apple"; + m["veggie"] = "carrot"; + + BaggageIteratorImp it(m.begin()); + BaggageIteratorImp end(m.end()); + + ASSERT_FALSE(end == it); + BaggageRef ref = it.ref(); + + ASSERT_STREQ("animal", ref.key()); + ASSERT_STREQ("dog", ref.value()); + + BaggageIteratorImp prev = it++; + + ASSERT_FALSE(prev == end); + ASSERT_FALSE(it == end); + + ref = prev.ref(); + + ASSERT_STREQ("animal", ref.key()); + ASSERT_STREQ("dog", ref.value()); + + ref = it.ref(); + + ASSERT_STREQ("fruit", ref.key()); + ASSERT_STREQ("apple", ref.value()); + + ++it; + ASSERT_FALSE(end == it); + + ref = it.ref(); + + ASSERT_STREQ("veggie", ref.key()); + ASSERT_STREQ("carrot", ref.value()); + + ++it; + + ASSERT_TRUE(end == it); +} + +TEST(BaggageIterator, Implicit) +{ + MapBaggage::Map m; + m["animal"] = "dog"; + m["fruit"] = "apple"; + m["veggie"] = "carrot"; + + BaggageIteratorImp it(m.begin()); + BaggageIteratorImp end(m.end()); + + ASSERT_FALSE(end == it); + BaggageRef ref = *it; + + ASSERT_STREQ("animal", ref.key()); + ASSERT_STREQ("dog", ref.value()); + + BaggageIteratorImp prev = it++; + + ASSERT_FALSE(prev == end); + ASSERT_FALSE(it == end); + + ref = *prev; + + ASSERT_STREQ("animal", ref.key()); + ASSERT_STREQ("dog", ref.value()); + + ref = *it; + + ASSERT_STREQ("fruit", ref.key()); + ASSERT_STREQ("apple", ref.value()); + + ++it; + ASSERT_FALSE(end == it); + + ref = *it; + + ASSERT_STREQ("veggie", ref.key()); + ASSERT_STREQ("carrot", ref.value()); + + ++it; + + ASSERT_TRUE(end == it); +} + + +TEST(BaggageIterator, EmptyMapBaggage) +{ + MapBaggage::Map m; + + BaggageIteratorImp it(m.begin()); + BaggageIteratorImp end(m.end()); + + ASSERT_TRUE(end == it); +} + +TEST(BaggageIterator, ForLoopSyntax) +{ + MapBaggage::Map m; + m["animal"] = "dog"; + m["fruit"] = "apple"; + m["veggie"] = "carrot"; + + const char * expectedKeys[] = {"animal", "fruit", "veggie"}; + const char * expectedVals[] = {"dog", "apple", "carrot"}; + size_t index = 0; + + for (BaggageIteratorImp it = m.begin(), end = m.end(); it != end; + ++it, ++index) + { + // Test the -> syntax works + ASSERT_STREQ(expectedKeys[index], it->key()); + ASSERT_STREQ(expectedVals[index], it->value()); + } +} diff --git a/test/carriers.h b/test/carriers.h new file mode 100644 index 0000000..0136899 --- /dev/null +++ b/test/carriers.h @@ -0,0 +1,90 @@ +#ifndef INCLUDED_OPENTRACING_TEST_CARRIERS_H +#define INCLUDED_OPENTRACING_TEST_CARRIERS_H + +#include "unittest.h" + +#include // test include guard +#include + +#include "spancontext.h" + +struct TestTextWriter : public GenericTextWriter { + int + injectImp(const std::vector& textmap) + { + pairs = textmap; + return 0; + } + + std::vector pairs; +}; + +struct TestTextReader : public GenericTextReader { + int + extractImp(std::vector* textmap) const + { + *textmap = pairs; + return 0; + } + + std::vector pairs; +}; + +struct TestBinaryWriter : public GenericBinaryWriter { + int + injectImp(const void* buf, const size_t len) + { + if (len > sizeof(m_raw)) + { + return 1; + } + + std::memcpy(&m_raw, buf, len); + return 0; + } + + int32_t m_raw; +}; + +struct TestBinaryReader : public GenericBinaryReader { + int + extractImp(std::vector* buf) const + { + buf->resize(sizeof(m_raw)); + std::memcpy(&(*buf)[0], &m_raw, sizeof(m_raw)); + return 0; + } + + int32_t m_raw; +}; + +struct TestWriter : public GenericWriter { + int + injectImp(const TestContextImpl& context) + { + carrier = context.baggageMap(); + return 0; + } + + std::multimap carrier; +}; + +struct TestReader : public GenericReader { + int + extractImp(TestContextImpl* context) const + { + context->baggageMap() = carrier; + return 0; + } + + std::multimap carrier; +}; + +typedef GenericTextWriter TextWriter; +typedef GenericTextReader TextReader; +typedef GenericBinaryWriter BinaryWriter; +typedef GenericBinaryReader BinaryReader; +typedef GenericWriter ExplicitWriter; +typedef GenericReader ExplicitReader; + +#endif diff --git a/test/carriers.t.cc b/test/carriers.t.cc new file mode 100644 index 0000000..5d812bd --- /dev/null +++ b/test/carriers.t.cc @@ -0,0 +1,163 @@ +#include "carriers.h" +#include "unittest.h" + +TEST(Carriers, TextMapWriter) +{ + TestTextWriter imp; + TextWriter& t = imp; + + std::vector v; + v.push_back(TextMapPair("animal", "dog")); + v.push_back(TextMapPair("fruit", "apple")); + v.push_back(TextMapPair("veggie", "carrot")); + + int rc = t.inject(v); + + ASSERT_EQ(0, rc); + + ASSERT_EQ(3u, imp.pairs.size()); + + ASSERT_EQ(imp.pairs[0].m_key, "animal"); + ASSERT_EQ(imp.pairs[0].m_value, "dog"); + + ASSERT_EQ(imp.pairs[1].m_key, "fruit"); + ASSERT_EQ(imp.pairs[1].m_value, "apple"); + + ASSERT_EQ(imp.pairs[2].m_key, "veggie"); + ASSERT_EQ(imp.pairs[2].m_value, "carrot"); +} + +TEST(Carriers, TextMapReader) +{ + TestTextReader imp; + + std::vector& v = imp.pairs; + v.push_back(TextMapPair("animal", "dog")); + v.push_back(TextMapPair("fruit", "apple")); + v.push_back(TextMapPair("veggie", "carrot")); + + TextReader& t = imp; + + std::vector o; + + int rc = t.extract(&o); + + ASSERT_EQ(0, rc); + + ASSERT_EQ(3u, o.size()); + + ASSERT_EQ(o[0].m_key, "animal"); + ASSERT_EQ(o[0].m_value, "dog"); + + ASSERT_EQ(o[1].m_key, "fruit"); + ASSERT_EQ(o[1].m_value, "apple"); + + ASSERT_EQ(o[2].m_key, "veggie"); + ASSERT_EQ(o[2].m_value, "carrot"); +} + +TEST(Carriers, BinaryReader) +{ + TestBinaryReader imp; + imp.m_raw = 0xdeadbeef; + + BinaryReader& t = imp; + + std::vector buf; + + int rc = t.extract(&buf); + ASSERT_EQ(0, rc); + ASSERT_EQ(buf.size(), sizeof(imp.m_raw)); + + int32_t out = 0; + std::memcpy(&out, &buf[0], buf.size()); + + ASSERT_EQ(out, imp.m_raw); +} + +TEST(Carriers, BinaryWriter) +{ + TestBinaryWriter imp; + BinaryWriter& t = imp; + + const int64_t tooBig= 0x00000000deadbeef; + + std::vector buf; + buf.resize(sizeof(tooBig)); + std::memcpy(&buf[0], &tooBig, sizeof(tooBig)); + + int rc = t.inject(buf.data(), buf.size()); + ASSERT_NE(0, rc); + + const int32_t expected = 0xdeadbeef; + + buf.resize(sizeof(expected)); + std::memcpy(&buf[0], &expected, sizeof(expected)); + + rc = t.inject(buf.data(), buf.size()); + ASSERT_EQ(0, rc); + ASSERT_EQ(expected, imp.m_raw); +} + +TEST(Carriers, ExplicitWriter) +{ + TestContextImpl context; + ASSERT_EQ(0, context.setBaggage("animal", "dog")); + ASSERT_EQ(0, context.setBaggage("fruit", "apple")); + ASSERT_EQ(0, context.setBaggage("veggie", "carrot")); + + TestWriter imp; + ExplicitWriter& t = imp; + + int rc = t.inject(context); + ASSERT_EQ(0, rc); + + ASSERT_EQ(3u, imp.carrier.size()); + + TestBaggageContainer::const_iterator it = + imp.carrier.find("animal"); + + ASSERT_NE(imp.carrier.end(), it); + ASSERT_EQ("dog", it->second); + + it = imp.carrier.find("fruit"); + ASSERT_NE(imp.carrier.end(), it); + ASSERT_EQ("apple", it->second); + + it = imp.carrier.find("veggie"); + ASSERT_NE(imp.carrier.end(), it); + ASSERT_EQ("carrot", it->second); +} + +TEST(Carriers, ExplicitReader) +{ + typedef TestBaggageContainer Map; + + TestReader imp; + imp.carrier.insert(Map::value_type("animal", "dog")); + imp.carrier.insert(Map::value_type("fruit", "apple")); + imp.carrier.insert(Map::value_type("veggie", "carrot")); + + ExplicitReader& t = imp; + + TestContextImpl context; + int rc = t.extract(&context); + ASSERT_EQ(0, rc); + + TestBaggageContainer& m = context.baggageMap(); + + ASSERT_EQ(3u, m.size()); + + TestBaggageContainer::const_iterator it = m.find("animal"); + + ASSERT_NE(m.end(), it); + ASSERT_EQ(it->second, "dog"); + + it = m.find("fruit"); + ASSERT_NE(m.end(), it); + ASSERT_EQ(it->second, "apple"); + + it = m.find("veggie"); + ASSERT_NE(m.end(), it); + ASSERT_EQ(it->second, "carrot"); +} diff --git a/test/noop.t.cc b/test/noop.t.cc new file mode 100644 index 0000000..f1502e5 --- /dev/null +++ b/test/noop.t.cc @@ -0,0 +1,297 @@ +#include "unittest.h" + +#include "carriers.h" + +#include +#include // test include guard + +// These run the same tests as the tracer.t.cc. The interface should be the +// same, however, there may be some differences in return codes due +// to the differing implementations. + +typedef GenericTracer + GlobalTracer; + +class NoopTracerEnv : public ::testing::Test { + public: + virtual void + SetUp() + { + GlobalTracer::install(&imp); + tracer = &imp; + }; + + virtual void + TearDown() + { + GlobalTracer::uninstall(); + }; + + NoopTracer imp; + GlobalTracer * tracer; +}; + +TEST_F(NoopTracerEnv, Intantiation){ + NoopTracer noop; + + GlobalTracer::install(&noop); + ASSERT_EQ(GlobalTracer::instance(), &noop); + + GlobalTracer::uninstall(); + ASSERT_FALSE(GlobalTracer::instance()); +} + +TEST_F(NoopTracerEnv, StartWithOp) +{ + GlobalTracer::Span* span(tracer->start("hello")); + EXPECT_TRUE(span); + + int rc = 0; + + rc = span->log("server", "blahblahblah"); + ASSERT_EQ(0, rc); + + rc = span->setBaggage("hello", "world"); + ASSERT_EQ(0, rc); + + rc = span->setBaggage("apple", "banana"); + ASSERT_EQ(0, rc); + + std::string val; + rc = span->getBaggage("apple", &val); + ASSERT_NE(0, rc); + + std::vector vals; + rc = span->getBaggage("apple", &vals); + ASSERT_NE(0, rc); + + tracer->cleanup(span); +} + +TEST_F(NoopTracerEnv, StartWithOpAndParent) +{ + // We create spans, which have contexts or create contexts directly + // through the 'extract' interface. Either way, clients only deal + // with the 'GlobalContext' type. Rather than make this simple start + // test dependent on those other interfaces, I'm going to just pull + // a context out of thin air to test the interface. + + GlobalTracer::Span* guard(tracer->start("hello")); + EXPECT_TRUE(guard); +} + +TEST_F(NoopTracerEnv, InjectSpan) +{ + TestTextWriter writer; + + GlobalTracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + span->setBaggage("animal", "tiger"); + span->setBaggage("animal", "cat"); + + int rc = tracer->inject(&writer, *span); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); +} + +TEST_F(NoopTracerEnv, InjectText) +{ + TestTextWriter writer; + + GlobalTracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + + span->setBaggage("animal", "tiger"); + span->setBaggage("animal", "cat"); + + const GlobalTracer::SpanContext * context = span->context(); + + int rc = tracer->inject(&writer, *context); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); + tracer->cleanup(context); +} + + + +TEST_F(NoopTracerEnv, ExtractText) +{ + TestTextReader reader; + reader.pairs.push_back(TextMapPair("animal", "tiger")); + reader.pairs.push_back(TextMapPair("fruit", "apple")); + reader.pairs.push_back(TextMapPair("veggie", "carrot")); + + const GlobalTracer::SpanContext* context(tracer->extract(reader)); + ASSERT_TRUE(context); + + size_t index = 0; + + const char * names[] = {"animal", "fruit", "veggie"}; + const char * values[] = {"tiger", "apple", "carrot"}; + + for (GlobalTracer::SpanContext::BaggageIterator it = context->baggageBegin(); + it != context->baggageEnd(); + ++it) + { + ASSERT_STREQ(names[index], it.ref().key()); + ASSERT_STREQ(values[index], it.ref().value()); + ++index; + } + + tracer->cleanup(context); +} + +TEST_F(NoopTracerEnv, InjectBinary) +{ + TestBinaryWriter writer; + GlobalTracer::Span* span = tracer->start("op"); + ASSERT_TRUE(span); + + const GlobalTracer::SpanContext * context = span->context(); + + int rc = tracer->inject(&writer, *context); + ASSERT_EQ(0, rc); + tracer->cleanup(span); + tracer->cleanup(context); +} + +TEST_F(NoopTracerEnv, ExtractBinary) +{ + TestBinaryReader reader; + reader.m_raw = 0xdeadbeef; + const GlobalTracer::SpanContext* context(tracer->extract(reader)); + ASSERT_TRUE(context); + tracer->cleanup(context); +} + +struct NoopWriter: public GenericWriter +{ + // won't ever be called +}; + +TEST_F(NoopTracerEnv, InjectExplicit) +{ + NoopWriter w; + GlobalTracer::Span* span(tracer->start("span")); + ASSERT_TRUE(span); + + const GlobalTracer::SpanContext * context = span->context(); + + int rc = tracer->inject(&w, *context); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); + tracer->cleanup(context); +} + +struct NoopReader : public GenericReader +{ + // won't ever be called +}; + +TEST_F(NoopTracerEnv, ExtractExplicit) +{ + NoopReader r; + const GlobalTracer::SpanContext* context(tracer->extract(r)); + ASSERT_TRUE(context); + tracer->cleanup(context); +} + +TEST_F(NoopTracerEnv, StartWithOpts) +{ + NoopReader r; + const GlobalTracer::SpanContext* otherContext(tracer->extract(r)); + ASSERT_TRUE(otherContext); + + GlobalTracer::SpanOptions* opts(tracer->makeSpanOptions()); + + opts->setOperation("hello"); + opts->setStartTime(1251251); + opts->setReference(SpanReferenceType::e_FollowsFrom, *otherContext); + opts->setTag("hello", 125); + + GlobalTracer::Span* span(tracer->start("hello")); + EXPECT_TRUE(span); + + tracer->cleanup(span); + tracer->cleanup(otherContext); + tracer->cleanup(opts); +} + +template +class NoopSpanTypeTests : public ::testing::Test { + public: + virtual void + SetUp() + { + GlobalTracer::install(&imp); + }; + + virtual void + TearDown() + { + GlobalTracer::uninstall(); + }; + + NoopTracer imp; +}; + +typedef ::testing::Types + OverloadedTypes; + +TYPED_TEST_CASE(NoopSpanTypeTests, OverloadedTypes); + +TYPED_TEST(NoopSpanTypeTests, TagInterface) +{ + NoopTracer noop; + GlobalTracer* tracer = &noop; + + GlobalTracer::Span* span(tracer->start("hello world")); + int rc = span->tag("key", TypeParam()); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); +} + +TYPED_TEST(NoopSpanTypeTests, LogInterface) +{ + NoopTracer noop; + GlobalTracer* tracer = &noop; + GlobalTracer::Span* span(tracer->start("hello world")); + int rc = span->log("key", TypeParam()); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); +} + +TYPED_TEST(NoopSpanTypeTests, LogTspInterface) +{ + NoopTracer noop; + GlobalTracer* tracer = &noop; + + GlobalTracer::Span* span(tracer->start("hello world")); + int rc = span->log("key", TypeParam(), 0); + ASSERT_EQ(0, rc); + + tracer->cleanup(span); +} diff --git a/test/opentracing.t.cc b/test/opentracing.t.cc deleted file mode 100644 index f5d42a0..0000000 --- a/test/opentracing.t.cc +++ /dev/null @@ -1,212 +0,0 @@ -#include -#include - -#include - -#include - -#include - -using namespace opentracing; - -TEST(opentracing, init) -{ - // Get the default tracer. - Tracer * tracer = globalTracer(); - - // The default tracer is non-NULL. - ASSERT_TRUE(tracer); - - // We aren't allowed to set the value to NULL. - ASSERT_THROW(initGlobalTracer(NULL), std::runtime_error); - - // Create a local noop Tracer. - NoopTracer localTracer; - - // It's previous value should be the default tracer - // thus null is returned. We assign to a random value - ASSERT_FALSE(initGlobalTracer(&localTracer)); - - // Note - at this point the globalTracer() points to a completely - // random address. Any operation (apart from retrieval) would be - // expected to fail. - - // This should return the value we just set. - ASSERT_EQ(&localTracer, globalTracer()); - - // Restore tracer (to the default tracer). The value returned should - // be the one we set last. Here the application might be required to - // perform finalizations and/or `delete` the old `tracer` object. - ASSERT_EQ(&localTracer, initGlobalTracer(tracer)); - - // Once again, this should return the value we just set. - ASSERT_EQ(tracer, globalTracer()); -} - -TEST(opentracing, creation) -{ - // Accidentaly forget to assign it anywhere. Doesn't matter. Auto-deletes. - globalTracer()->startSpan("test()"); - - // You can create it and store it. - Span * parent(globalTracer()->startSpan("test()")); - - ASSERT_TRUE(parent); - ASSERT_EQ(&parent->getTracer(), globalTracer()); - - // Or you can create it and use the builder to populate it. - Span * child(globalTracer()->startSpan("test()", StartSpanOptions(parent, 100)) - ->setOperationName("update()") - ->setTag("stringValue", "test") - ->setTag("boolValue", true) - ->setTag("doubleValue", 3.14) - ->setTag("intValue", 12)); - - ASSERT_TRUE(child); - ASSERT_EQ(&child->getTracer(), globalTracer()); - - delete child; - delete parent; -} - -TEST(opentracing, span_ops) -{ - // Create a Span - Span * span = globalTracer()->startSpan("test()"); - - ASSERT_TRUE(span); - - // Set the operation name - span->setOperationName("newName()"); - - // Exercise various setTag() methods - span->setTag("stringValue", "test"); - span->setTag("boolValue", true); - span->setTag("doubleValue", 3.14); - span->setTag("intValue", 12); - - // Exercise different log() cases - LogData tmp; - span->log(tmp); - span->log(LogData("ready")); - span->log(LogData("ready", "{\"j\":\"son\"}")); - - uint64_t now(100); - span->log(LogData(now)); - span->log(LogData(now, "ready")); - span->log(LogData(now, "ready", "{\"j\":\"son\"}")); - - // Exercise bulk log() case - std::vector bulk; - bulk.push_back(tmp); - bulk.push_back(tmp); - span->log(bulk); - - // Exercise baggage functions - span->setBaggageItem("key1", "value1") - ->setBaggageItem("key2", "value2") - ->setBaggageItem("key3", "value3"); - - // No-op implementation shouldn't incur any baggage overhead i.e. - // it should ignore the values despite the fact they're set with - // the previous statement - std::string target; - ASSERT_FALSE(span->getBaggageItem("key1")); - ASSERT_FALSE(span->getBaggageItem("key2")); - ASSERT_FALSE(span->getBaggageItem("key3", &target)); - - // Exercise getTracer() - ASSERT_EQ(&span->getTracer(), globalTracer()); - - // Exercise finish() - span->finish(); - - delete span; -} - -TEST(opentracing, canonicalize_baggage_key) -{ - std::string badKey("some-weird-sign!#"); - ASSERT_FALSE(canonicalizeBaggageKey(badKey)); - - std::string badKey2("-another-sign"); - ASSERT_FALSE(canonicalizeBaggageKey(badKey2)); - - std::string goodKey("000-Capitalized-9"); - ASSERT_TRUE(canonicalizeBaggageKey(goodKey)); - ASSERT_EQ("000-capitalized-9", goodKey); -} - -namespace -{ - -template -class MapTextAdapter : public TextMapWriter, public TextMapReader -{ -public: - MapTextAdapter(T & carrier) - : m_carrier(carrier) - { - } - - virtual ~MapTextAdapter() - { - } - - virtual void set(const std::string& key, const std::string& value, bool isBaggage) const - { - m_carrier.insert(typename T::value_type(key, std::make_pair(value, isBaggage))); - } - - virtual void forEachPair(const ReadCallback & callback) const - { - for (typename T::const_iterator i = m_carrier.begin(); i != m_carrier.end(); ++i) - { - callback(i->first, i->second.first, i->second.second); - } - } - -private: - T & m_carrier; -}; - -} - -typedef std::map > CarrierT; - -TEST(opentracing, propagation_inject) -{ - Span * span = globalTracer()->startSpan("test()"); - - ASSERT_TRUE(span); - - CarrierT carrier; - std::string error; - - Tracer::Result result = globalTracer()->inject( - *span, - MapTextAdapter(carrier), - error); - - ASSERT_EQ(Tracer::Success, result); - - span->finish(); - - delete span; -} - -TEST(opentracing, propagation_join) -{ - CarrierT carrier; - std::string error; - - Span * span(0); - Tracer::Result result = globalTracer()->join( - &span, - "operation()", - MapTextAdapter(carrier), - error); - - ASSERT_EQ(Tracer::ErrTraceNotFound, result); - ASSERT_EQ(0, span); -} diff --git a/test/span.h b/test/span.h new file mode 100644 index 0000000..ea52ce1 --- /dev/null +++ b/test/span.h @@ -0,0 +1,121 @@ +#ifndef INCLUDED_OPENTRACING_TEST_SPAN_H +#define INCLUDED_OPENTRACING_TEST_SPAN_H + +#include "spancontext.h" +#include "unittest.h" + +#include // test include guard +#include + +class TestSpanImpl : public GenericSpan { + public: + const TestContextImpl * + contextImp() const + { + TestContextImpl * context = new TestContextImpl(); + context->baggageMap() = m_context.baggageMap(); + } + + int + setOperationImp(const StringRef&) + { + return 0; + } + + int + getBaggageImp(const StringRef& key, std::string* baggage) const + { + std::vector out; + + getBaggageImp(key, &out); + + if (1u == out.size()) + { + *baggage = out[0]; + return 0; + } + else + { + return 1; + } + } + + int + getBaggageImp(const StringRef& key, std::vector* baggage) const + { + baggage->clear(); + + const std::string mkey(key.data(), key.length()); + + const std::pair + range = m_context.baggageMap().equal_range(mkey); + + for (TestBaggageContainer::const_iterator it = range.first, + end = range.second; + it != end; + ++it) + { + baggage->push_back(it->second); + } + + return baggage->empty(); + } + int + setBaggageImp(const StringRef& key, const StringRef& baggage) + { + m_context.baggageMap().insert(TestBaggageContainer::value_type( + std::string(key.data(), key.length()), + std::string(baggage.data(), baggage.length()))); + + return 0; + } + + int + addReferenceImp(const TestContextImpl&, const SpanReferenceType::Value) + { + return 0; + } + + int + finishImp() + { + return 0; + } + + int + finishImp(const uint64_t) + { + return 0; + } + + template + int + tagImp(const StringRef&, const T&) + { + return 0; + } + + template + int + logImp(const StringRef&, const T&) + { + return 0; + } + + template + int + logImp(const StringRef&, const T&, const uint64_t) + { + return 0; + } + + TestContextImpl m_context; +}; + +typedef GenericSpan + TestSpan; + +#endif diff --git a/test/span.t.cc b/test/span.t.cc new file mode 100644 index 0000000..6622367 --- /dev/null +++ b/test/span.t.cc @@ -0,0 +1,58 @@ +#include "unittest.h" +#include "span.h" + + +TEST(SpanTests, setOperation) +{ + TestSpanImpl impl; + TestSpan& t = impl; + const int rc = t.setOperation("hello world"); + ASSERT_EQ(0, rc); +} + +template +class SpanTypeTests : public ::testing::Test { +}; + +typedef ::testing::Types + OverloadedTypes; + +TYPED_TEST_CASE(SpanTypeTests, OverloadedTypes); + +TYPED_TEST(SpanTypeTests, TagInterface) +{ + TestSpanImpl impl; + TestSpan& t = impl; + int rc = t.tag("key", TypeParam()); + ASSERT_EQ(0, rc); +} + +TYPED_TEST(SpanTypeTests, LogInterface) +{ + TestSpanImpl impl; + TestSpan& t = impl; + int rc = t.log("key", TypeParam()); + ASSERT_EQ(0, rc); +} + +TYPED_TEST(SpanTypeTests, LogTspInterface) +{ + TestSpanImpl impl; + TestSpan& t = impl; + int rc = t.log("key", TypeParam(), 0); + ASSERT_EQ(0, rc); +} diff --git a/test/spancontext.h b/test/spancontext.h new file mode 100644 index 0000000..aa7afea --- /dev/null +++ b/test/spancontext.h @@ -0,0 +1,115 @@ +#ifndef INCLUDED_OPENTRACING_TEST_SPANCONTEXT_H +#define INCLUDED_OPENTRACING_TEST_SPANCONTEXT_H + +#include "unittest.h" + +#include +#include // test include guard + +#include +#include + +typedef std::multimap TestBaggageContainer; + +struct TestContextBaggageAdapter { + typedef TestBaggageContainer::iterator iterator; + typedef TestBaggageContainer::const_iterator const_iterator; + + Baggage + copy(const const_iterator& it) const + { + return Baggage(it->first, it->second); + } + BaggageRef + ref(const const_iterator& it) const + { + return BaggageRef(it->first, it->second); + } +}; + +class TestContextImpl + : public GenericSpanContext { + public: + // For tests only, not part of the interface + int + setBaggage(const StringRef& key, const StringRef& baggage) + { + m_baggage.insert(TestBaggageContainer::value_type( + std::string(key.data(), key.length()), + std::string(baggage.data(), baggage.length()))); + + return 0; + } + + int + getBaggageImp(const StringRef& key, std::string* baggage) const + { + std::vector out; + + getBaggageImp(key, &out); + + if (1u == out.size()) + { + *baggage = out[0]; + return 0; + } + else + { + return 1; + } + } + + int + getBaggageImp(const StringRef& key, std::vector* baggage) const + { + baggage->clear(); + + const std::string mkey(key.data(), key.length()); + + const std::pair + range = m_baggage.equal_range(mkey); + + for (TestBaggageContainer::const_iterator it = range.first, + end = range.second; + it != end; + ++it) + { + baggage->push_back(it->second); + } + + return baggage->empty(); + } + + BaggageIterator + baggageBeginImp() const + { + return BaggageIterator(m_baggage.begin()); + } + + BaggageIterator + baggageEndImp() const + { + return BaggageIterator(m_baggage.end()); + } + + TestBaggageContainer& + baggageMap() + { + return m_baggage; + } + + const TestBaggageContainer& + baggageMap() const + { + return m_baggage; + } + + private: + TestBaggageContainer m_baggage; +}; + +typedef GenericSpanContext + TestContext; + +#endif // INCLUDED_OPENTRACING_TEST_SPANCONTEXT_H diff --git a/test/spancontext.t.cc b/test/spancontext.t.cc new file mode 100644 index 0000000..ae19211 --- /dev/null +++ b/test/spancontext.t.cc @@ -0,0 +1,58 @@ +#include "unittest.h" +#include "spancontext.h" + +#include // test include guard +#include + +TEST(GenericSpanContext, BaggageCopies) +{ + TestContextImpl imp; + TestContext& t = imp; + + int rc = imp.setBaggage("hello", "world"); + ASSERT_EQ(0, rc); + + std::vector vals; + rc = t.getBaggage("hello", &vals); + + ASSERT_EQ(0, rc); + ASSERT_EQ(1u, vals.size()); + ASSERT_EQ("world", vals[0]); + + rc = t.getBaggage("unknown", &vals); + ASSERT_NE(0, rc); + + ASSERT_TRUE(t.baggageBegin() != t.baggageEnd()); + + TestContext::BaggageIterator it = t.baggageBegin(); + + Baggage b = it.copy(); + ASSERT_EQ("hello", b.key()); + ASSERT_EQ("world", b.value()); + + ++it; + + ASSERT_TRUE(it == t.baggageEnd()); + + ASSERT_EQ("hello", b.key()); + ASSERT_EQ("world", b.value()); + +} + +TEST(GenericSpanContext, CopyConstructor) +{ + TestContextImpl impl; + TestContext& t = impl; + + int rc = impl.setBaggage("hello", "world"); + ASSERT_EQ(0, rc); + + TestContextImpl implCopy = impl; + TestContext& tc = implCopy; + + std::string val; + rc = tc.getBaggage("hello", &val); + + ASSERT_EQ(0, rc); + ASSERT_EQ("world", val); +} diff --git a/test/stringref.t.cc b/test/stringref.t.cc new file mode 100644 index 0000000..a79f81e --- /dev/null +++ b/test/stringref.t.cc @@ -0,0 +1,99 @@ +#include "unittest.h" + +#include // test include guard +#include + +TEST(StringRef, Empty) +{ + StringRef ref; + ASSERT_EQ(0, ref.data()); + ASSERT_EQ(0, ref.length()); +} + +TEST(StringRef, CString) +{ + const char * val = "hello world"; + + StringRef ref(val); + + ASSERT_EQ(val, ref.data()); + ASSERT_EQ(std::strlen(val), ref.length()); +} + +TEST(StringRef, CStringArray) +{ + const char val[] = "hello world"; + + StringRef ref(val); + + ASSERT_EQ(val, ref.data()); + ASSERT_EQ(std::strlen(val), ref.length()); +} + +TEST(StringRef, StdString) +{ + const std::string val = "hello world"; + + StringRef ref(val); + + ASSERT_EQ(val, ref.data()); + ASSERT_EQ(val.length(), ref.length()); +} + +TEST(StringRef, Copy) +{ + const std::string val = "hello world"; + + StringRef ref(val); + StringRef cpy(ref); + + ASSERT_EQ(val, cpy.data()); + ASSERT_EQ(val.length(), cpy.length()); +} + +TEST(StringRef, Reset) +{ + StringRef ref; + ASSERT_EQ(0, ref.data()); + ASSERT_EQ(0, ref.length()); + + ref.reset("hello world"); + + ASSERT_STREQ("hello world", ref); + ASSERT_STREQ("hello world", ref.data()); + ASSERT_EQ(std::strlen("hello world"), ref.length()); + + const char arr[] = "hello world 1"; + + ref.reset(arr); + ASSERT_EQ(arr, ref); + ASSERT_EQ(arr, ref.data()); + ASSERT_EQ(std::strlen(arr), ref.length()); + ASSERT_STREQ(arr, ref); + ASSERT_STREQ(arr, ref.data()); + + const char * p = "hello world 2"; + + ref.reset(p); + ASSERT_EQ(p, ref); + ASSERT_EQ(p, ref.data()); + ASSERT_EQ(std::strlen(p), ref.length()); + ASSERT_STREQ(p, ref); + ASSERT_STREQ(p, ref.data()); + + const std::string s = "hello world 3"; + + ref.reset(s); + ASSERT_EQ(s.data(), ref); + ASSERT_EQ(s.data(), ref.data()); + ASSERT_EQ(s.length(), ref.length()); + ASSERT_STREQ(s.c_str(), ref); + ASSERT_STREQ(s.c_str(), ref.data()); + + ref.reset(p, std::strlen(p)); + ASSERT_EQ(p, ref); + ASSERT_EQ(p, ref.data()); + ASSERT_EQ(std::strlen(p), ref.length()); + ASSERT_STREQ(p, ref); + ASSERT_STREQ(p, ref.data()); +} diff --git a/test/tracer.h b/test/tracer.h new file mode 100644 index 0000000..5ba799f --- /dev/null +++ b/test/tracer.h @@ -0,0 +1,228 @@ +#ifndef INCLUDED_OPENTRACING_TEST_TRACER_H +#define INCLUDED_OPENTRACING_TEST_TRACER_H + +#include "unittest.h" + +#include // test include guard +#include + +#include "span.h" +#include "spancontext.h" + +class TestOptionsImpl : public GenericSpanOptions { + public: + int + setOperationImp(const StringRef&) + { + return 0; + } + + int + setStartTimeImp(const uint64_t) + { + return 0; + } + + int + setReferenceImp(const SpanReferenceType::Value, const TestContextImpl&) + { + return 0; + } + + template + int + setTagImp(const StringRef&, const T&) + { + return 0; + } +}; + +class TestTracerImpl : public GenericTracer { + public: + static TestTracerImpl* s_tracer; + + static void + installImp(TestTracerImpl* inst) + { + s_tracer = inst; + } + + static TestTracerImpl* + instanceImp() + { + return s_tracer; + } + + static void + uninstallImp() + { + s_tracer = 0; + } + + TestOptionsImpl* + makeSpanOptionsImp() + { + return new TestOptionsImpl(); + } + + + TestSpanImpl* + startImp(const StringRef&) + { + return new TestSpanImpl(); + } + + TestSpanImpl* + startImp(const TestOptionsImpl&) + { + return new TestSpanImpl(); + } + + template + int + injectImp(CARRIER_T* carrier, const TestSpanImpl& imp) const + { + return injectImp(carrier, imp.m_context); + } + + template + int + injectImp(GenericTextWriter* carrier, + const TestContextImpl& imp) const + { + std::vector pairs; + pairs.reserve(imp.baggageMap().size()); + + for (TestBaggageContainer::const_iterator it = imp.baggageMap().begin(); + it != imp.baggageMap().end(); + ++it) + { + pairs.push_back(TextMapPair(it->first, it->second)); + } + + return carrier->inject(pairs); + } + + template + int + injectImp(GenericBinaryWriter* carrier, const TestContext&) const + { + // Context unused for test. Implementations should encode + // the context for a wire protocol here, most likely + const int deadbeef = 0xdeadbeef; + std::vector buf(sizeof(deadbeef)); + buf.resize(sizeof(deadbeef)); + std::memcpy(&buf[0], &deadbeef, sizeof(deadbeef)); + + return carrier->inject(buf.data(), buf.size()); + } + + template + int + injectImp(GenericWriter* carrier, + const TestContextImpl& context) const + { + return carrier->inject(context); + } + + template + TestContextImpl* + extractImp(const GenericTextReader& carrier) const + { + std::vector pairs; + + if (int rc = carrier.extract(&pairs)) + { + return NULL; + } + + TestContextImpl* imp = new TestContextImpl(); + + for (std::vector::const_iterator it = pairs.begin(); + it != pairs.end(); + ++it) + { + imp->baggageMap().insert( + TestBaggageContainer::value_type(it->m_key, it->m_value)); + } + return imp; + } + + template + TestContextImpl* + extractImp(const GenericBinaryReader& carrier) + { + const int expected = 0xdeadbeef; + + std::vector buf; + + if (int rc = carrier.extract(&buf)) + { + return NULL; + } + + if (buf.size() == sizeof(expected) && + expected == *(reinterpret_cast(&buf[0]))) + { + return new TestContextImpl(); + } + else + { + return NULL; + } + } + + template + TestContextImpl* + extractImp(const GenericReader& carrier) + { + TestContextImpl* imp = new TestContextImpl; + if (int rc = carrier.extract(imp)) + { + delete imp; + return NULL; + } + else + { + return imp; + } + } + + void + cleanupImp(const TestOptionsImpl* opts) + { + delete opts; + } + + void + cleanupImp(const TestSpanImpl* sp) + { + delete sp; + } + + void + cleanupImp(const TestContextImpl* spc) + { + delete spc; + } +}; + +typedef GenericSpanOptions + SpanOptions; + +typedef GenericTracer + Tracer; + +#endif diff --git a/test/tracer.t.cc b/test/tracer.t.cc new file mode 100644 index 0000000..a0cedd8 --- /dev/null +++ b/test/tracer.t.cc @@ -0,0 +1,271 @@ +#include "unittest.h" +#include "tracer.h" + +#include "carriers.h" + +TestTracerImpl* TestTracerImpl::s_tracer = 0; + +class TracerEnv : public ::testing::Test { + public: + virtual void + SetUp() + { + Tracer::install(&imp); + tracer = &imp; + }; + + virtual void + TearDown() + { + Tracer::uninstall(); + }; + + TestTracerImpl imp; + Tracer * tracer; +}; + +TEST_F(TracerEnv, SingletonTests) +{ + ASSERT_EQ(&imp, Tracer::instance()); + Tracer::uninstall(); + ASSERT_EQ(0, Tracer::instance()); +} + +TEST_F(TracerEnv, StartWithOp) +{ + Tracer::Span * span = tracer->start("hello"); + ASSERT_TRUE(span); + + int rc = 0; + + rc = span->log("server", "blahblahblah"); + ASSERT_EQ(0, rc); + + rc = span->setBaggage("hello", "world"); + ASSERT_EQ(0, rc); + + rc = span->setBaggage("apple", "banana"); + ASSERT_EQ(0, rc); + + std::vector vals; + rc = span->getBaggage("apple", &vals); + + ASSERT_EQ(0, rc); + ASSERT_EQ(1u, vals.size()); + ASSERT_EQ("banana", vals[0]); + + std::string val; + rc = span->getBaggage("apple", &val); + + ASSERT_EQ(0, rc); + ASSERT_EQ("banana", val); + + tracer->cleanup(span); +} + +TEST_F(TracerEnv, InjectText) +{ + TestTextWriter writer; + + Tracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + + span->setBaggage("animal", "tiger"); + span->setBaggage("animal", "cat"); + + const Tracer::SpanContext * context = span->context(); + + int rc = tracer->inject(&writer, *context); + ASSERT_EQ(0, rc); + ASSERT_EQ(2u, writer.pairs.size()); + + ASSERT_EQ(writer.pairs[0].m_key, "animal"); + ASSERT_EQ(writer.pairs[1].m_key, "animal"); + + ASSERT_TRUE(writer.pairs[0].m_value == "cat" || writer.pairs[0].m_value == "tiger"); + ASSERT_TRUE(writer.pairs[1].m_value == "cat" || writer.pairs[1].m_value == "tiger"); + + tracer->cleanup(span); + tracer->cleanup(context); +} + +TEST_F(TracerEnv, SpanInjectText) +{ + TestTextWriter writer; + + Tracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + + span->setBaggage("animal", "tiger"); + span->setBaggage("animal", "cat"); + + int rc = tracer->inject(&writer, *span); + ASSERT_EQ(0, rc); + ASSERT_EQ(2u, writer.pairs.size()); + + ASSERT_EQ(writer.pairs[0].m_key, "animal"); + ASSERT_EQ(writer.pairs[1].m_key, "animal"); + + ASSERT_TRUE(writer.pairs[0].m_value == "cat" || writer.pairs[0].m_value == "tiger"); + ASSERT_TRUE(writer.pairs[1].m_value == "cat" || writer.pairs[1].m_value == "tiger"); + + tracer->cleanup(span); +} + +TEST_F(TracerEnv, ExtractText) +{ + TestTextReader reader; + reader.pairs.push_back(TextMapPair("animal", "tiger")); + reader.pairs.push_back(TextMapPair("fruit", "apple")); + reader.pairs.push_back(TextMapPair("veggie", "carrot")); + + const Tracer::SpanContext* context(tracer->extract(reader)); + ASSERT_TRUE(context); + + size_t index = 0; + + const char * names[] = {"animal", "fruit", "veggie"}; + const char * values[] = {"tiger", "apple", "carrot"}; + + for (Tracer::SpanContext::BaggageIterator it = context->baggageBegin(); + it != context->baggageEnd(); + ++it) + { + ASSERT_STREQ(names[index], it.ref().key()); + ASSERT_STREQ(values[index], it.ref().value()); + ++index; + } + + tracer->cleanup(context); +} + +TEST_F(TracerEnv, InjectBinary) +{ + TestBinaryWriter writer; + + Tracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + + const Tracer::SpanContext * context = span->context(); + + int rc = tracer->inject(&writer, *context); + ASSERT_EQ(0, rc); + ASSERT_EQ(0xdeadbeef, writer.m_raw); + + tracer->cleanup(context); + tracer->cleanup(span); +} + +TEST_F(TracerEnv, SpanInjectBinary) +{ + TestBinaryWriter writer; + + Tracer::Span* span(tracer->start("op")); + ASSERT_TRUE(span); + + int rc = tracer->inject(&writer, *span); + ASSERT_EQ(0, rc); + ASSERT_EQ(0xdeadbeef, writer.m_raw); + + tracer->cleanup(span); +} + +TEST_F(TracerEnv, ExtractBinary) +{ + TestBinaryReader reader; + reader.m_raw = 0xdeadbeef; + + const Tracer::SpanContext* context(tracer->extract(reader)); + ASSERT_TRUE(context); + tracer->cleanup(context); +} + + +TEST_F(TracerEnv, InjectExplicit) +{ + TestWriter w; + + Tracer::Span* span(tracer->start("span")); + ASSERT_TRUE(span); + + span->setBaggage("animal", "tiger"); + span->setBaggage("fruit", "apple"); + + const Tracer::SpanContext * const context = span->context(); + + int rc = tracer->inject(&w, *context); + ASSERT_EQ(0, rc); + ASSERT_EQ(2u, w.carrier.size()); + + TestBaggageContainer::const_iterator cit = w.carrier.find("animal"); + + ASSERT_NE(cit, w.carrier.end()); + ASSERT_EQ("tiger", cit->second); + + cit = w.carrier.find("fruit"); + ASSERT_NE(cit, w.carrier.end()); + ASSERT_EQ("apple", cit->second); + + tracer->cleanup(span); + tracer->cleanup(context); +} + +TEST_F(TracerEnv, SpanInjectExplicit) +{ + TestWriter w; + + Tracer::Span* span(tracer->start("span")); + ASSERT_TRUE(span); + + span->setBaggage("animal", "tiger"); + span->setBaggage("fruit", "apple"); + + int rc = tracer->inject(&w, *span); + ASSERT_EQ(0, rc); + ASSERT_EQ(2u, w.carrier.size()); + + TestBaggageContainer::const_iterator cit = w.carrier.find("animal"); + + ASSERT_NE(cit, w.carrier.end()); + ASSERT_EQ("tiger", cit->second); + + cit = w.carrier.find("fruit"); + ASSERT_NE(cit, w.carrier.end()); + ASSERT_EQ("apple", cit->second); + + tracer->cleanup(span); +} + +TEST_F(TracerEnv, ExtractExplicit) +{ + TestReader reader; + reader.carrier.insert(TestBaggageContainer::value_type("animal", "tiger")); + reader.carrier.insert(TestBaggageContainer::value_type("fruit", "apple")); + const Tracer::SpanContext* context(tracer->extract(reader)); + ASSERT_TRUE(context); + tracer->cleanup(context); +} + +TEST_F(TracerEnv, StartWithOptions) +{ + TestReader reader; + reader.carrier.insert(TestBaggageContainer::value_type("animal", "tiger")); + reader.carrier.insert(TestBaggageContainer::value_type("fruit", "apple")); + const Tracer::SpanContext* otherContext(tracer->extract(reader)); + ASSERT_TRUE(otherContext); + + Tracer::SpanOptions* opts(tracer->makeSpanOptions()); + ASSERT_TRUE(opts); + + opts->setOperation("test"); + opts->setStartTime(12414); + opts->setReference(SpanReferenceType::e_ChildOf, *otherContext); + opts->setTag("hello", "world"); + + Tracer::Span* span(tracer->start(*opts)); + ASSERT_TRUE(span); + + tracer->cleanup(otherContext); + tracer->cleanup(opts); + tracer->cleanup(span); +} diff --git a/test/unittest.h b/test/unittest.h new file mode 100644 index 0000000..5af8640 --- /dev/null +++ b/test/unittest.h @@ -0,0 +1,15 @@ +#ifndef INCLUDED_OPENTRACING_UNITTEST_H +#define INCLUDED_OPENTRACING_UNITTEST_H + +#include + +#include +#include +#include + +namespace opentracing{ +} + +using namespace opentracing; + +#endif diff --git a/test/unittest.t.cc b/test/unittest.t.cc new file mode 100644 index 0000000..f0eec43 --- /dev/null +++ b/test/unittest.t.cc @@ -0,0 +1,8 @@ +#include "unittest.h" + +int +main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/thirdparty/googletest b/thirdparty/googletest new file mode 160000 index 0000000..5e7fd50 --- /dev/null +++ b/thirdparty/googletest @@ -0,0 +1 @@ +Subproject commit 5e7fd50e17b6edf1cadff973d0ec68966cf3265e