Skip to content

Commit

Permalink
Add plugin support (open-telemetry#36)
Browse files Browse the repository at this point in the history
* Add code for dynamic loading

* Add a mock hook

* Fix CopyErrorMessage

* Set up loading example

* Add plugin example

* Add plugin example

* Make a noisy plugin

* Remove mock from sdk

* Add ci for testing plugin

* Reformat

* Remove noexcept from alias

* Add windows stub

* Add missing includes

* Document

* Reorganize

* Fix formatting

* Setup plugin test for windows

* Fix typo

* Fix windows script

* Fix windows script

* Fix typo

* Fix windows path

* Fill out windows testing

* Fix windows script

* Fix windows script

* Fix windows script

* Fix windows script

* Fix windows script

* Fill in windows loading code

* Use std::string for error message

* Fix example

* Use string for error_message

* Fix namespace error

* Add windows error handling

* Fix windows preprocessor error

* Fix signature

* Add tests for plugins

* Fix formatting

* Refactor plugin test

* Drop unused private section

* Update for master
  • Loading branch information
rnburn authored Feb 25, 2020
1 parent 3cc2c69 commit 4dd7a6b
Show file tree
Hide file tree
Showing 35 changed files with 870 additions and 17 deletions.
26 changes: 26 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ jobs:
path: ~/build/Testing/Temporary/LastTest.log
destination: Test.log

plugin_test:
resource_class: xlarge
docker:
- image: ubuntu:18.04
steps:
- checkout
- run: ./ci/setup_ci_environment.sh
- run: ./ci/setup_cmake.sh
- run: ./ci/do_ci.sh cmake.test_example_plugin
- store_artifacts:
path: ~/build/Testing/Temporary/LastTest.log
destination: Test.log
- store_artifacts:
path: ~/plugin/libexample_plugin.so
destination: libexample_plugin_so

gcc_48_test:
resource_class: xlarge
docker:
Expand Down Expand Up @@ -87,15 +103,25 @@ jobs:
- run: ./ci/setup_windows_ci_environment.ps1
- run: ./ci/do_ci.ps1 cmake.test

windows_plugin_test:
executor: win/vs2019
steps:
- checkout
- run: ./ci/setup_windows_cmake.ps1
- run: ./ci/setup_windows_ci_environment.ps1
- run: ./ci/do_ci.ps1 cmake.test_example_plugin

workflows:
version: 2
build_and_test:
jobs:
- cmake_test
- plugin_test
- gcc_48_test
- bazel_test
- bazel_asan
- format
- benchmark
- osx_test
- windows
- windows_plugin_test
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ if(BUILD_TESTING)
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
endif()

include_directories(api/include)
add_subdirectory(api)
add_subdirectory(examples)
1 change: 0 additions & 1 deletion api/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
include_directories(include)
add_library(opentelemetry_api INTERFACE)
target_include_directories(
opentelemetry_api INTERFACE include
Expand Down
56 changes: 43 additions & 13 deletions api/include/opentelemetry/nostd/unique_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,48 @@ namespace opentelemetry
{
namespace nostd
{
namespace detail
{
template <class T>
struct unique_ptr_element_type
{
using type = T;
};

template <class T>
struct unique_ptr_element_type<T[]>
{
using type = T;
};
} // namespace detail

/**
* Provide a simplified port of std::unique_ptr that has ABI stability.
*
* Note: This implementation doesn't allow for a custom deleter or array specializations.
* Note: This implementation doesn't allow for a custom deleter.
*/
template <class T>
class unique_ptr
{
public:
using element_type = typename detail::unique_ptr_element_type<T>::type;
using pointer = element_type *;

unique_ptr() noexcept : ptr_{nullptr} {}

unique_ptr(std::nullptr_t) noexcept : ptr_{nullptr} {}

explicit unique_ptr(T *ptr) noexcept : ptr_{ptr} {}
explicit unique_ptr(pointer ptr) noexcept : ptr_{ptr} {}

unique_ptr(unique_ptr &&other) noexcept : ptr_{other.release()} {}

template <class U,
typename std::enable_if<std::is_convertible<U *, T *>::value>::type * = nullptr>
typename std::enable_if<std::is_convertible<U *, pointer>::value>::type * = nullptr>
unique_ptr(unique_ptr<U> &&other) noexcept : ptr_{other.release()}
{}

template <class U,
typename std::enable_if<std::is_convertible<U *, T *>::value>::type * = nullptr>
typename std::enable_if<std::is_convertible<U *, pointer>::value>::type * = nullptr>
unique_ptr(std::unique_ptr<U> &&other) noexcept : ptr_{other.release()}
{}

Expand All @@ -51,15 +69,15 @@ class unique_ptr
}

template <class U,
typename std::enable_if<std::is_convertible<U *, T *>::value>::type * = nullptr>
typename std::enable_if<std::is_convertible<U *, pointer>::value>::type * = nullptr>
unique_ptr &operator=(unique_ptr<U> &&other) noexcept
{
reset(other.release());
return *this;
}

template <class U,
typename std::enable_if<std::is_convertible<U *, T *>::value>::type * = nullptr>
typename std::enable_if<std::is_convertible<U *, pointer>::value>::type * = nullptr>
unique_ptr &operator=(std::unique_ptr<U> &&other) noexcept
{
reset(other.release());
Expand All @@ -70,22 +88,22 @@ class unique_ptr

operator bool() const noexcept { return ptr_ != nullptr; }

T &operator*() const noexcept { return *ptr_; }
element_type &operator*() const noexcept { return *ptr_; }

T *operator->() const noexcept { return get(); }
pointer operator->() const noexcept { return get(); }

T *get() const noexcept { return ptr_; }
pointer get() const noexcept { return ptr_; }

void reset(T *ptr = nullptr) noexcept
void reset(pointer ptr = nullptr) noexcept
{
if (ptr_ != nullptr)
{
delete ptr_;
this->delete_ptr();
}
ptr_ = ptr;
}

T *release() noexcept
pointer release() noexcept
{
auto result = ptr_;
ptr_ = nullptr;
Expand All @@ -95,7 +113,19 @@ class unique_ptr
void swap(unique_ptr &other) noexcept { std::swap(ptr_, other.ptr_); }

private:
T *ptr_;
pointer ptr_;

void delete_ptr() noexcept
{
if (std::is_array<T>::value)
{
delete[] ptr_;
}
else
{
delete ptr_;
}
}
};

template <class T1, class T2>
Expand Down
16 changes: 16 additions & 0 deletions api/include/opentelemetry/plugin/detail/dynamic_library_handle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

namespace opentelemetry
{
namespace plugin
{
/**
* Manage the ownership of a dynamically loaded library.
*/
class DynamicLibraryHandle
{
public:
virtual ~DynamicLibraryHandle() = default;
};
} // namespace plugin
} // namespace opentelemetry
70 changes: 70 additions & 0 deletions api/include/opentelemetry/plugin/detail/dynamic_load_unix.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#pragma once

#include <algorithm>
#include <memory>

#include <dlfcn.h>

#include "opentelemetry/plugin/detail/utility.h"
#include "opentelemetry/plugin/factory.h"
#include "opentelemetry/plugin/hook.h"
#include "opentelemetry/version.h"

namespace opentelemetry
{
namespace plugin
{
class DynamicLibraryHandleUnix final : public DynamicLibraryHandle
{
public:
explicit DynamicLibraryHandleUnix(void *handle) noexcept : handle_{handle} {}

~DynamicLibraryHandleUnix() override { ::dlclose(handle_); }

private:
void *handle_;
};

inline std::unique_ptr<Factory> LoadFactory(const char *plugin, std::string &error_message) noexcept
{
dlerror(); // Clear any existing error.

auto handle = ::dlopen(plugin, RTLD_NOW | RTLD_LOCAL);
if (handle == nullptr)
{
detail::CopyErrorMessage(dlerror(), error_message);
return nullptr;
}

std::shared_ptr<DynamicLibraryHandle> library_handle{new (std::nothrow)
DynamicLibraryHandleUnix{handle}};
if (library_handle == nullptr)
{
return nullptr;
}

auto make_factory_impl =
reinterpret_cast<OpenTelemetryHook *>(::dlsym(handle, "OpenTelemetryMakeFactoryImpl"));
if (make_factory_impl == nullptr)
{
detail::CopyErrorMessage(dlerror(), error_message);
return nullptr;
}
if (*make_factory_impl == nullptr)
{
detail::CopyErrorMessage("Invalid plugin hook", error_message);
return nullptr;
}
LoaderInfo loader_info;
nostd::unique_ptr<char[]> plugin_error_message;
auto factory_impl = (**make_factory_impl)(loader_info, plugin_error_message);
if (factory_impl == nullptr)
{
detail::CopyErrorMessage(plugin_error_message.get(), error_message);
return nullptr;
}
return std::unique_ptr<Factory>{new (std::nothrow)
Factory{std::move(library_handle), std::move(factory_impl)}};
}
} // namespace plugin
} // namespace opentelemetry
92 changes: 92 additions & 0 deletions api/include/opentelemetry/plugin/detail/dynamic_load_windows.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#include <memory>

#include "opentelemetry/plugin/detail/utility.h"
#include "opentelemetry/plugin/factory.h"
#include "opentelemetry/plugin/hook.h"
#include "opentelemetry/version.h"

#include <windows.h>

#include <errhandlingapi.h>
#include <winbase.h>

namespace opentelemetry
{
namespace plugin
{
namespace detail
{
inline void GetLastErrorMessage(std::string &error_message) noexcept
{
auto error_code = ::GetLastError();
// See https://stackoverflow.com/a/455533/4447365
LPTSTR error_text = nullptr;
auto size = ::FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&error_text), 0, nullptr);
if (size == 0)
{
return;
}
CopyErrorMessage(error_text, error_message);
::LocalFree(error_text);
}
} // namespace detail

class DynamicLibraryHandleWindows final : public DynamicLibraryHandle
{
public:
explicit DynamicLibraryHandleWindows(HINSTANCE handle) : handle_{handle} {}

~DynamicLibraryHandleWindows() override { ::FreeLibrary(handle_); }

private:
HINSTANCE handle_;
};

inline std::unique_ptr<Factory> LoadFactory(const char *plugin, std::string &error_message) noexcept
{
auto handle = ::LoadLibrary(plugin);
if (handle == nullptr)
{
detail::GetLastErrorMessage(error_message);
return nullptr;
}

std::shared_ptr<DynamicLibraryHandle> library_handle{new (std::nothrow)
DynamicLibraryHandleWindows{handle}};
if (library_handle == nullptr)
{
detail::CopyErrorMessage("Allocation failure", error_message);
return nullptr;
}

auto make_factory_impl = reinterpret_cast<OpenTelemetryHook *>(
::GetProcAddress(handle, "OpenTelemetryMakeFactoryImpl"));
if (make_factory_impl == nullptr)
{
detail::GetLastErrorMessage(error_message);
return nullptr;
}
if (*make_factory_impl == nullptr)
{
detail::CopyErrorMessage("Invalid plugin hook", error_message);
return nullptr;
}

LoaderInfo loader_info;
nostd::unique_ptr<char[]> plugin_error_message;
auto factory_impl = (**make_factory_impl)(loader_info, plugin_error_message);
if (factory_impl == nullptr)
{
detail::CopyErrorMessage(plugin_error_message.get(), error_message);
return nullptr;
}
return std::unique_ptr<Factory>{new (std::nothrow)
Factory{std::move(library_handle), std::move(factory_impl)}};
}
} // namespace plugin
} // namespace opentelemetry
22 changes: 22 additions & 0 deletions api/include/opentelemetry/plugin/detail/loader_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/version.h"

namespace opentelemetry
{
namespace plugin
{
/**
* LoaderInfo describes the versioning of the loader.
*
* Plugins can check against this information and properly error out if they were built against an
* incompatible OpenTelemetry API.
*/
struct LoaderInfo
{
nostd::string_view opentelemetry_version = OPENTELEMETRY_VERSION;
nostd::string_view opentelemetry_abi_version = OPENTELEMETRY_ABI_VERSION;
};
} // namespace plugin
} // namespace opentelemetry
Loading

0 comments on commit 4dd7a6b

Please sign in to comment.