diff --git a/CMakeLists.txt b/CMakeLists.txt index 97375720b5..1e8aeed756 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,16 @@ option(WITH_ELASTICSEARCH "Whether to include the Elasticsearch Client in the SDK" OFF) option(BUILD_TESTING "Whether to enable tests" ON) +if(WIN32) + option(WITH_ETW "Whether to include the ETW Exporter in the SDK" ON) + if(WITH_ETW) + add_definitions(-DHAVE_MSGPACK) + # Option below will be removed once we donate the TraceLoggingDynamic.h to + # OSS + add_definitions(-DHAVE_NO_TLD) + endif(WITH_ETW) +endif(WIN32) + option(WITH_EXAMPLES "Whether to build examples" ON) find_package(Threads) diff --git a/exporters/CMakeLists.txt b/exporters/CMakeLists.txt index 68ea177c38..1f18637712 100644 --- a/exporters/CMakeLists.txt +++ b/exporters/CMakeLists.txt @@ -12,3 +12,7 @@ endif() if(WITH_ELASTICSEARCH) add_subdirectory(elasticsearch) endif() + +if(WITH_ETW) + add_subdirectory(etw) +endif() diff --git a/exporters/etw/BUILD b/exporters/etw/BUILD new file mode 100644 index 0000000000..1359906b94 --- /dev/null +++ b/exporters/etw/BUILD @@ -0,0 +1,55 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "etw_provider_exporter", + srcs = [ + "src/etw_provider_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/etw/etw_provider_exporter.h", + "include/opentelemetry/exporters/etw/utils.h", + "include/opentelemetry/exporters/etw/uuid.h", + ], + strip_include_prefix = "include", + deps = [ + "//api", + "//sdk/src/trace", + ], +) + +cc_test( + name = "etw_provider_test", + srcs = ["test/etw_provider_test.cc"], + deps = [ + ":etw_provider_exporter", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "etw_tracer_exporter", + srcs = [ + "src/etw_tracer_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/etw/etw_data.h", + "include/opentelemetry/exporters/etw/etw_tracer_exporter.h", + "include/opentelemetry/exporters/etw/utils.h", + "include/opentelemetry/exporters/etw/uuid.h", + ], + strip_include_prefix = "include", + deps = [ + ":etw_provider_exporter", + "//api", + "//sdk/src/trace", + ], +) + +cc_test( + name = "etw_tracer_test", + srcs = ["test/etw_tracer_test.cc"], + deps = [ + ":etw_tracer_exporter", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/exporters/etw/CMakeLists.txt b/exporters/etw/CMakeLists.txt new file mode 100644 index 0000000000..a3d02e6f35 --- /dev/null +++ b/exporters/etw/CMakeLists.txt @@ -0,0 +1,26 @@ +include_directories(include) + +add_library(opentelemetry_exporter_etw_provider src/etw_provider_exporter.cc) +add_library(opentelemetry_exporter_etw_tracer src/etw_tracer_exporter.cc) + +if(BUILD_TESTING) + add_executable(etw_provider_test test/etw_provider_test.cc) + add_executable(etw_tracer_test test/etw_tracer_test.cc) + + target_link_libraries( + etw_provider_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_etw_provider) + + target_link_libraries( + etw_tracer_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_etw_tracer) + + gtest_add_tests( + TARGET etw_provider_test + TEST_PREFIX exporter. + TEST_LIST etw_provider_test) + gtest_add_tests( + TARGET etw_tracer_test + TEST_PREFIX exporter. + TEST_LIST etw_tracer_test) +endif() # BUILD_TESTING diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_data.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_data.h new file mode 100644 index 0000000000..2cce6a6a90 --- /dev/null +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_data.h @@ -0,0 +1,534 @@ +#pragma once + +#include +#include +#include + +#include + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/etw/etw_provider_exporter.h" + +#include "opentelemetry/exporters/etw/utils.h" + +namespace core = opentelemetry::core; +namespace trace = opentelemetry::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace ETW +{ +class Span; + +/// +/// stream::Tracer class that allows to send spans to ETW +/// +class Tracer : public trace::Tracer +{ + /// + /// Parent provider of this Tracer + /// + trace::TracerProvider &parent; + + /// + /// Provider Id (Name or GUID) + /// + std::string provId; + + /// + /// Provider Handle + /// + ETWProvider::Handle provHandle; + + /// + /// Encoding (Manifest, MessagePack or XML) + /// + ETWProvider::EventFormat encoding; + + /// + /// ETWProvider is a singleton that aggregates all ETW writes. + /// + /// + static ETWProvider &etwProvider() + { + static ETWProvider instance; // C++11 magic static + return instance; + }; + +public: + /// + /// Tracer constructor + /// + /// Parent TraceProvider + /// providerId + /// Tracer instance + Tracer(trace::TracerProvider &parent, + nostd::string_view providerId = "", + ETWProvider::EventFormat encoding = ETWProvider::EventFormat::ETW_MANIFEST) + : trace::Tracer(), + parent(parent), + provId(providerId.data(), providerId.size()), + encoding(encoding) + { + provHandle = etwProvider().open(provId, encoding); + }; + + /// + /// Start Span + /// + /// Span name + /// Span options + /// Span + virtual nostd::shared_ptr StartSpan( + nostd::string_view name, + const common::KeyValueIterable &attributes, + const trace::SpanContextKeyValueIterable &links, + const trace::StartSpanOptions &options = {}) noexcept override + { + // TODO: support attributes + UNREFERENCED_PARAMETER(attributes); + UNREFERENCED_PARAMETER(links); + return trace::to_span_ptr(this, name, options); + }; + + /// + /// Force flush data to Tracer, spending up to given amount of microseconds to flush. + /// + /// Allow Tracer to drop data if timeout is reached + /// void + virtual void ForceFlushWithMicroseconds(uint64_t) noexcept override{}; + + /// + /// Close tracer, spending up to given amount of microseconds to flush and close. + /// + /// Allow Tracer to drop data if timeout is reached + /// + virtual void CloseWithMicroseconds(uint64_t) noexcept override + { + etwProvider().close(provHandle); + }; + + /// + /// Add event data to span associated with tracer + /// + /// + /// + /// + /// + /// + void AddEvent(Span &span, + nostd::string_view name, + core::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept + { + // TODO: associate events with span + (void)span; + // TODO: respect original timestamp + (void)timestamp; + + // Unfortunately we copy right now since we don't have the knowledge of original map object :-( + std::map m; + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + m[key] = value; + return true; + }); + m["name"] = name.data(); + +#ifdef HAVE_TLD + if (encoding == ETWProvider::ETW_MANIFEST) + { + etwProvider().writeTld(provHandle, m); + return; + } +#endif + +#ifdef HAVE_MSGPACK + if (encoding == ETWProvider::ETW_MSGPACK) + { + etwProvider().writeMsgPack(provHandle, m); + return; + } +#endif + }; + + /// + /// Add event data to span associated with tracer + /// + /// + /// + /// + /// + void AddEvent(Span &span, nostd::string_view name, core::SystemTimestamp timestamp) noexcept + { + AddEvent(span, name, timestamp, opentelemetry::sdk::GetEmptyAttributes()); + }; + + /// + /// Add event data to span associated with tracer + /// + /// + /// + void AddEvent(Span &span, nostd::string_view name) + { + AddEvent(span, name, std::chrono::system_clock::now(), + opentelemetry::sdk::GetEmptyAttributes()); + }; + + virtual ~Tracer() { CloseWithMicroseconds(0); }; +}; + +/// +/// stream::Span allows to send event data to stream +/// +class Span : public trace::Span +{ + +protected: + /// + /// Parent (Owner) Tracer of this Span + /// + Tracer &owner; + +public: + /// + /// Span constructor + /// + /// Owner Tracer + /// Span name + /// Span options + /// Span + Span(Tracer &owner, nostd::string_view name, const trace::StartSpanOptions &options) noexcept + : trace::Span(), owner(owner) + { + UNREFERENCED_PARAMETER(name); + UNREFERENCED_PARAMETER(options); + }; + + ~Span() { End(); } + + /// + /// Add named event with no attributes + /// + /// + /// + virtual void AddEvent(nostd::string_view name) noexcept override { owner.AddEvent(*this, name); } + + /// + /// Add named event with custom timestamp + /// + /// + /// + /// + virtual void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override + { + owner.AddEvent(*this, name, timestamp); + } + + /// + /// Add named event with custom timestamp and attributes + /// + /// + /// + /// + /// + void AddEvent(nostd::string_view name, + core::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept override + { + owner.AddEvent(*this, name, timestamp, attributes); + } + + /// + /// Set Span status + /// + /// + /// + /// + virtual void SetStatus(trace::CanonicalCode code, + nostd::string_view description) noexcept override + { + // TODO: not implemented + UNREFERENCED_PARAMETER(code); + UNREFERENCED_PARAMETER(description); + }; + + // Sets an attribute on the Span. If the Span previously contained a mapping for + // the key, the old value is replaced. + virtual void SetAttribute(nostd::string_view key, + const common::AttributeValue &value) noexcept override + { + // TODO: not implemented + UNREFERENCED_PARAMETER(key); + UNREFERENCED_PARAMETER(value); + }; + + /// + /// Update Span name + /// + /// + /// + virtual void UpdateName(nostd::string_view name) noexcept override + { + // TODO: not implemented + UNREFERENCED_PARAMETER(name); + } + + /// + /// End Span + /// + /// + virtual void End(const trace::EndSpanOptions & = {}) noexcept override + { + // TODO: signal this to owner + } + + virtual trace::SpanContext GetContext() const noexcept + { + // TODO: not implemented + static trace::SpanContext nullContext{trace::SpanContext::GetInvalid()}; + return nullContext; + } + + /// + /// Check if Span is recording data + /// + /// + virtual bool IsRecording() const noexcept override + { + // TODO: not implemented + return true; + } + + virtual void SetToken(nostd::unique_ptr &&token) noexcept + { + // TODO: not implemented + UNREFERENCED_PARAMETER(token); + } + + /// + /// Get Owner tracer of this Span + /// + /// + trace::Tracer &tracer() const noexcept { return this->owner; }; +}; + +/// +/// stream::TraceProvider +/// +class TracerProvider : public trace::TracerProvider +{ +public: + /// + /// Obtain a Tracer of given type (name) and supply extra argument arg2 to it. + /// + /// Tracer Type + /// Tracer arguments + /// + virtual nostd::shared_ptr GetTracer(nostd::string_view name, + nostd::string_view args = "") + { + // TODO: describe possible args. Currently supported: + // "MSGPACK" - use MessagePack + // "XML" - use XML + // "ETW" - use 'classic' Trace Logging Dynamic manifest ETW event + // +#if defined(HAVE_NO_TLD) && defined(HAVE_MSGPACK) + ETWProvider::EventFormat evtFmt = ETWProvider::EventFormat::ETW_MSGPACK; +#else + ETWProvider::EventFormat evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; +#endif + +#pragma warning(push) +#pragma warning(disable : 4307) /* Integral constant overflow - OK while computing hash */ + auto h = utils::hashCode(args.data()); + switch (h) + { + case CONST_HASHCODE(MSGPACK): + // nobrk + case CONST_HASHCODE(MsgPack): + // nobrk + case CONST_HASHCODE(MessagePack): + evtFmt = ETWProvider::EventFormat::ETW_MSGPACK; + break; + + case CONST_HASHCODE(XML): + // nobrk + case CONST_HASHCODE(xml): + evtFmt = ETWProvider::EventFormat::ETW_XML; + break; + + case CONST_HASHCODE(TLD): + // nobrk + case CONST_HASHCODE(tld): + // nobrk + evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; + break; + + default: + break; + } +#pragma warning(pop) + return nostd::shared_ptr{new (std::nothrow) Tracer(*this, name, evtFmt)}; + } +}; + +class ETWSpanData final : public sdk::trace::Recordable +{ +public: + ETWSpanData(std::string providerName) { InitTracerProvider(providerName); } + /** + * Get the trace id for this span + * @return the trace id for this span + */ + opentelemetry::trace::TraceId GetTraceId() const noexcept { return trace_id_; } + + /** + * Get the span id for this span + * @return the span id for this span + */ + opentelemetry::trace::SpanId GetSpanId() const noexcept { return span_id_; } + + /** + * Get the parent span id for this span + * @return the span id for this span's parent + */ + opentelemetry::trace::SpanId GetParentSpanId() const noexcept { return parent_span_id_; } + + /** + * Get the name for this span + * @return the name for this span + */ + opentelemetry::nostd::string_view GetName() const noexcept { return name_; } + + /** + * Get the status for this span + * @return the status for this span + */ + opentelemetry::trace::CanonicalCode GetStatus() const noexcept { return status_code_; } + + /** + * Get the status description for this span + * @return the description of the the status of this span + */ + opentelemetry::nostd::string_view GetDescription() const noexcept { return status_desc_; } + + /** + * Get the start time for this span + * @return the start time for this span + */ + opentelemetry::core::SystemTimestamp GetStartTime() const noexcept { return start_time_; } + + /** + * Get the duration for this span + * @return the duration for this span + */ + std::chrono::nanoseconds GetDuration() const noexcept { return duration_; } + + /** + * Get the attributes for this span + * @return the attributes for this span + */ + const std::unordered_map &GetAttributes() const + noexcept + { + return attribute_map_.GetAttributes(); + } + + void SetIds(opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::SpanId parent_span_id) noexcept override + { + trace_id_ = trace_id; + span_id_ = span_id; + parent_span_id_ = parent_span_id; + } + + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override + { + attribute_map_.SetAttribute(key, value); + } + + void AddEvent(nostd::string_view name, + core::SystemTimestamp timestamp, + const opentelemetry::common::KeyValueIterable &attributes) noexcept override + { + span_->AddEvent(name, timestamp, attributes); + } + + void AddLink(const opentelemetry::trace::SpanContext &span_context, + const opentelemetry::common::KeyValueIterable &attributes) noexcept override + { + // TODO: Link Implementation for the Span to be implemented + } + + void SetStatus(opentelemetry::trace::CanonicalCode code, + nostd::string_view description) noexcept override + { + status_code_ = code; + status_desc_ = std::string(description); + } + + void SetName(nostd::string_view name) noexcept override { name_ = std::string(name); } + + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override + { + span_kind_ = span_kind; + } + + void SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept override + { + start_time_ = start_time; + } + + void SetDuration(std::chrono::nanoseconds duration) noexcept override { duration_ = duration; } + + void InitTracerProvider(std::string providerName) + { + exporter::ETW::TracerProvider tracer_provider_; + + tracer_ = tracer_provider_.GetTracer(providerName); + span_ = tracer_->StartSpan(name_); + } + +private: + opentelemetry::trace::TraceId trace_id_; + opentelemetry::trace::SpanId span_id_; + opentelemetry::trace::SpanId parent_span_id_; + core::SystemTimestamp start_time_; + std::chrono::nanoseconds duration_{0}; + std::string name_; + opentelemetry::trace::CanonicalCode status_code_{opentelemetry::trace::CanonicalCode::OK}; + std::string status_desc_; + sdk::trace::AttributeMap attribute_map_; + opentelemetry::trace::SpanKind span_kind_{opentelemetry::trace::SpanKind::kInternal}; + nostd::shared_ptr tracer_; + nostd::shared_ptr span_; +}; + +} // namespace ETW +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_provider_exporter.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_provider_exporter.h new file mode 100644 index 0000000000..e69182b119 --- /dev/null +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_provider_exporter.h @@ -0,0 +1,658 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef _MSC_VER +// evntprov.h(838) : warning C4459 : declaration of 'Version' hides global declaration +# pragma warning(disable : 4459) +// needed for Unit Testing with krabs.hpp +# pragma warning(disable : 4018) +#endif + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#include "opentelemetry/exporters/etw/utils.h" + +#ifdef HAVE_MSGPACK +// This option requires INCLUDE_DIR=$(ProjectDir)\..\..\third_party\json\include;... +# include "nlohmann/json.hpp" +#endif + +#ifndef HAVE_NO_TLD +// Allow to opt-out from `TraceLoggingDynamic.h` header usage +# define HAVE_TLD +# include "TraceLoggingDynamic.h" +#endif + +#include +#include +#include +#include + +#ifdef HAVE_KRABS_TESTS +// krabs.hpp requires this definition of min macro from Windows.h +# ifndef min +# define min(a, b) (((a) < (b)) ? (a) : (b)) +# endif +#endif + +#define MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE 0x01000000 + +OPENTELEMETRY_BEGIN_NAMESPACE + +class ETWProvider +{ + +public: + const unsigned long STATUS_OK = 0; + const unsigned long STATUS_ERROR = ULONG_MAX; + const unsigned long STATUS_EFBIG = ULONG_MAX - 1; + + enum EventFormat + { + ETW_MANIFEST = 0, + ETW_MSGPACK = 1, + ETW_XML = 2 + }; + + /// + /// Entry that contains Provider Handle, Provider MetaData and Provider GUID + /// + struct Handle + { + REGHANDLE providerHandle; + std::vector providerMetaVector; + GUID providerGuid; + }; + + /// + /// Check if given provider is registered. + /// + /// + /// + bool is_registered(const std::string &providerId) + { + std::lock_guard lock(m_providerMapLock); + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + return true; + } + } + return false; + } + + /// + /// Get Provider by Name or string representation of GUID + /// + /// + /// + Handle &open(const std::string &providerId, EventFormat format = EventFormat::ETW_MANIFEST) + { + std::lock_guard lock(m_providerMapLock); + +#ifdef HAVE_NO_TLD + // Fallback to MessagePack-encoded ETW events + format = EventFormat::ETW_MSGPACK; +#endif + + // Check and return if provider is already registered + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + return it->second; + } + } + + // Register provider if necessary + auto &data = providers()[providerId]; + data.providerMetaVector.clear(); + + utils::UUID guid = (providerId.rfind("{", 0) == 0) ? utils::UUID(providerId.c_str()) + : // It's a ProviderGUID + utils::GetProviderGuid(providerId.c_str()); // It's a ProviderName + + data.providerGuid = guid.to_GUID(); + + // TODO: currently we do not allow to specify a custom group GUID + GUID providerGroupGuid = NULL_GUID; + + switch (format) + { +#ifdef HAVE_TLD + // Register with TraceLoggingDynamic facility - dynamic manifest ETW events. + case EventFormat::ETW_MANIFEST: + { + tld::ProviderMetadataBuilder> providerMetaBuilder( + data.providerMetaVector); + + // Use Tenant ID as provider Name + providerMetaBuilder.Begin(providerId.c_str()); + providerMetaBuilder.AddTrait(tld::ProviderTraitType::ProviderTraitGroupGuid, + (void *)&providerGroupGuid, sizeof(GUID)); + providerMetaBuilder.End(); + + REGHANDLE hProvider = 0; + if (0 != + tld::RegisterProvider(&hProvider, &data.providerGuid, data.providerMetaVector.data())) + { + // There was an error registering the ETW provider + data.providerHandle = INVALID_HANDLE; + } + else + { + data.providerHandle = hProvider; + }; + }; + break; +#endif + +#ifdef HAVE_MSGPACK + // Register for MsgPack payload ETW events. + case EventFormat::ETW_MSGPACK: + { + REGHANDLE hProvider = 0; + if (EventRegister(&data.providerGuid, NULL, NULL, &hProvider) != ERROR_SUCCESS) + { + // There was an error registering the ETW provider + data.providerHandle = INVALID_HANDLE; + } + else + { + data.providerHandle = hProvider; + } + }; + break; +#endif + + default: + // TODO: other protocols, e.g. XML events - not supported yet + break; + } + + // We always return an entry even if we failed to register. + // Caller should check whether the hProvider handle is valid. + return data; + } + + /// + /// Unregister Provider + /// + /// + /// + unsigned long close(Handle data) + { + std::lock_guard lock(m_providerMapLock); + + auto m = providers(); + auto it = m.begin(); + while (it != m.end()) + { + if (it->second.providerHandle == data.providerHandle) + { + auto result = EventUnregister(data.providerHandle); + m.erase(it); + return result; + } + }; + return STATUS_ERROR; + } + +#ifdef HAVE_MSGPACK + template + unsigned long writeMsgPack(Handle &providerData, T eventData) + { + + // Make sure you stop sending event before register unregistering providerData + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + }; + + const std::string EVENT_NAME = "name"; + std::string eventName = "NoName"; + auto nameField = eventData[EVENT_NAME]; + switch (nameField.index()) + { + case common::AttributeType::TYPE_STRING: + eventName = + (char *)(nostd::get(nameField).data()); // must be 0-terminated! + break; +# ifdef HAVE_CSTRING_TYPE + case common::AttributeType::TYPE_CSTRING: + eventName = (char *)(nostd::get(nameField)); + break; +# endif + default: + // This is user error. Invalid event name! + // We supply default 'NoName' event name in this case. + break; + } + + /* clang-format off */ + nlohmann::json jObj = + { + { "env_name", "Span" }, + { "env_ver", "4.0" }, + // + // TODO: compute time in MessagePack-friendly format + // TODO: should we consider uint64_t format with Unix timestamps for ELK stack? + { "env_time", + { + { "TypeCode", 255 }, + { "Body","0xFFFFFC60000000005F752C2C" } + } + }, + // + + // TODO: follow JSON implementation of OTLP or place protobuf for non-Microsoft flows + { "env_dt_traceId", "6dcdae7b9b0c7643967d74ee54056178" }, + { "env_dt_spanId", "5866c4322919e641" }, + // + { "name", eventName }, + { "kind", 0 }, + { "startTime", + { + // TODO: timestamp + { "TypeCode", 255 }, + { "Body", "0xFFFF87CC000000005F752C2C" } + } + } + }; + /* clang-format on */ + + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + // Don't include event name field in the payload + if (EVENT_NAME == name) + continue; + auto &value = kv.second; + switch (value.index()) + { + case common::AttributeType::TYPE_BOOL: + { + UINT8 temp = static_cast(nostd::get(value)); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_INT: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_INT64: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_UINT: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_UINT64: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_DOUBLE: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } + case common::AttributeType::TYPE_STRING: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } +# ifdef HAVE_CSTRING_TYPE + case common::AttributeType::TYPE_CSTRING: + { + auto temp = nostd::get(value); + jObj[name] = temp; + break; + } +# endif +# if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case common::AttributeType::TYPE_GUID: + { + auto temp = nostd::get(value); + // TODO: add transform from GUID type to string? + jObj[name] = temp; + break; + } +# endif + // TODO: arrays are not supported yet +# ifdef HAVE_SPAN_BYTE + case common::AttributeType::TYPE_SPAN_BYTE: +# endif + case common::AttributeType::TYPE_SPAN_BOOL: + case common::AttributeType::TYPE_SPAN_INT: + case common::AttributeType::TYPE_SPAN_INT64: + case common::AttributeType::TYPE_SPAN_UINT: + case common::AttributeType::TYPE_SPAN_UINT64: + case common::AttributeType::TYPE_SPAN_DOUBLE: + case common::AttributeType::TYPE_SPAN_STRING: + default: + // TODO: unsupported type + break; + } + }; + + // Layer 1 + nlohmann::json l1 = nlohmann::json::array(); + // Layer 2 + nlohmann::json l2 = nlohmann::json::array(); + // Layer 3 + nlohmann::json l3 = nlohmann::json::array(); + + l1.push_back("Span"); + + { + // TODO: clarify why this is needed + // TODO: fix time here + nlohmann::json j; + j["TypeCode"] = 255; + j["Body"] = "0xFFFFFC60000000005F752C2C"; + l3.push_back(j); + }; + + // Actual value object goes here + l3.push_back(jObj); + + l2.push_back(l3); + l1.push_back(l2); + + { + // Another time field again, but at the top + // TODO: fix time here + nlohmann::json j; + j["TypeCode"] = 255; + j["Body"] = "0xFFFFFC60000000005F752C2C"; + l1.push_back(j); + }; + + std::vector v = nlohmann::json::to_msgpack(l1); + + EVENT_DESCRIPTOR evtDescriptor; + EventDescCreate(&evtDescriptor, 0, 0x1, 0, 0, 0, 0, 0); + EVENT_DATA_DESCRIPTOR evtData[1]; + EventDataDescCreate(&evtData[0], v.data(), v.size()); + + auto writeResponse = EventWrite(providerData.providerHandle, &evtDescriptor, 1, evtData); + + switch (writeResponse) + { + case ERROR_INVALID_PARAMETER: + break; + case ERROR_INVALID_HANDLE: + break; + case ERROR_ARITHMETIC_OVERFLOW: + break; + case ERROR_MORE_DATA: + break; + case ERROR_NOT_ENOUGH_MEMORY: + break; + default: + break; + }; + + if (writeResponse == ERROR_ARITHMETIC_OVERFLOW) + { + return STATUS_EFBIG; + }; + return (unsigned long)(writeResponse); + } +#endif + + /// + /// Send event to Provider Id + /// + /// + /// + /// + template + unsigned long writeTld(Handle &providerData, T eventData) + { +#ifdef HAVE_TLD + // Make sure you stop sending event before register unregistering providerData + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + }; + + UINT32 eventTags = MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE; + + std::vector byteVector; + std::vector byteDataVector; + tld::EventMetadataBuilder> builder(byteVector); + tld::EventDataBuilder> dbuilder(byteDataVector); + + const std::string EVENT_NAME = "name"; + std::string eventName = "NoName"; + auto nameField = eventData[EVENT_NAME]; + switch (nameField.index()) + { + case common::AttributeType::TYPE_STRING: + eventName = + (char *)(nostd::get(nameField).data()); // must be 0-terminated! + break; +# ifdef HAVE_CSTRING_TYPE + case common::AttributeType::TYPE_CSTRING: + eventName = (char *)(nostd::get(nameField)); + break; +# endif + default: + // This is user error. Invalid event name! + // We supply default 'NoName' event name in this case. + break; + } + + builder.Begin(eventName.c_str(), eventTags); + + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + // Don't include event name field in the payload + if (EVENT_NAME == name) + continue; + auto &value = kv.second; + switch (value.index()) + { + case common::AttributeType::TYPE_BOOL: + { + builder.AddField(name, tld::TypeBool8); + UINT8 temp = static_cast(nostd::get(value)); + dbuilder.AddByte(temp); + break; + } + case common::AttributeType::TYPE_INT: + { + builder.AddField(name, tld::TypeInt32); + auto temp = nostd::get(value); + dbuilder.AddValue(temp); + break; + } + case common::AttributeType::TYPE_INT64: + { + builder.AddField(name, tld::TypeInt64); + auto temp = nostd::get(value); + dbuilder.AddValue(temp); + break; + } + case common::AttributeType::TYPE_UINT: + { + builder.AddField(name, tld::TypeUInt32); + auto temp = nostd::get(value); + dbuilder.AddValue(temp); + break; + } + case common::AttributeType::TYPE_UINT64: + { + builder.AddField(name, tld::TypeUInt64); + auto temp = nostd::get(value); + dbuilder.AddValue(temp); + break; + } + case common::AttributeType::TYPE_DOUBLE: + { + builder.AddField(name, tld::TypeDouble); + auto temp = nostd::get(value); + dbuilder.AddValue(temp); + break; + } + case common::AttributeType::TYPE_STRING: + { + builder.AddField(name, tld::TypeUtf8String); + auto temp = nostd::get(value); + dbuilder.AddString(temp.data()); + break; + } +# ifdef HAVE_CSTRING_TYPE + case common::AttributeType::TYPE_CSTRING: + { + builder.AddField(name, tld::TypeUtf8String); + auto temp = nostd::get(value); + dbuilder.AddString(temp); + break; + } +# endif +# if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case common::AttributeType::TYPE_GUID: + { + builder.AddField(name.c_str(), TypeGuid); + auto temp = nostd::get(value); + dbuilder.AddBytes(&temp, sizeof(GUID)); + break; + } +# endif + + // TODO: arrays are not supported +# ifdef HAVE_SPAN_BYTE + case common::AttributeType::TYPE_SPAN_BYTE: +# endif + case common::AttributeType::TYPE_SPAN_BOOL: + case common::AttributeType::TYPE_SPAN_INT: + case common::AttributeType::TYPE_SPAN_INT64: + case common::AttributeType::TYPE_SPAN_UINT: + case common::AttributeType::TYPE_SPAN_UINT64: + case common::AttributeType::TYPE_SPAN_DOUBLE: + case common::AttributeType::TYPE_SPAN_STRING: + default: + // TODO: unsupported type + break; + } + }; + + if (!builder.End()) // Returns false if the metadata is too large. + { + return STATUS_EFBIG; // if event is too big for UTC to handle + } + + tld::EventDescriptor eventDescriptor; + // eventDescriptor.Keyword = MICROSOFT_KEYWORD_CRITICAL_DATA; + // eventDescriptor.Keyword = MICROSOFT_KEYWORD_TELEMETRY; + // eventDescriptor.Keyword = MICROSOFT_KEYWORD_MEASURES; + + EVENT_DATA_DESCRIPTOR pDataDescriptors[3]; + + EventDataDescCreate(&pDataDescriptors[2], byteDataVector.data(), + static_cast(byteDataVector.size())); + + // Event size detection is needed + int64_t eventByteSize = byteDataVector.size() + byteVector.size(); + int64_t eventKBSize = (eventByteSize + 1024 - 1) / 1024; + // bool isLargeEvent = eventKBSize >= LargeEventSizeKB; + + // TODO: extract + // - GUID ActivityId + // - GUID RelatedActivityId + + HRESULT writeResponse = tld::WriteEvent(providerData.providerHandle, eventDescriptor, + providerData.providerMetaVector.data(), + byteVector.data(), 3, pDataDescriptors); + + if (writeResponse == HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)) + { + return STATUS_EFBIG; + }; + + return (unsigned long)(writeResponse); +#else + return STATUS_ERROR; +#endif + } + + template + unsigned long write(Handle &providerData, + T eventData, + ETWProvider::EventFormat format = ETWProvider::EventFormat::ETW_MANIFEST) + { + if (format == ETWProvider::EventFormat::ETW_MANIFEST) + { + return writeTld(providerData, eventData); + } + if (format == ETWProvider::EventFormat::ETW_MSGPACK) + { + return writeMsgPack(providerData, eventData); + } + if (format == ETWProvider::EventFormat::ETW_XML) + { + // TODO: not implemented + return STATUS_ERROR; + } + return STATUS_ERROR; + }; + + static const REGHANDLE INVALID_HANDLE = _UI64_MAX; + +protected: + const unsigned int LargeEventSizeKB = 62; + + const GUID NULL_GUID = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}; + + mutable std::mutex m_providerMapLock; + + using ProviderMap = std::map; + + ProviderMap &providers() + { + static std::map providers; + return providers; + }; +}; + +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h new file mode 100644 index 0000000000..762dd56b22 --- /dev/null +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h @@ -0,0 +1,103 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include + +#include + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/recordable.h" + +#include "opentelemetry/exporters/etw/etw_data.h" +#include "opentelemetry/exporters/etw/etw_provider_exporter.h" + +#include "opentelemetry/exporters/etw/utils.h" + +namespace core = opentelemetry::core; +namespace trace = opentelemetry::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace ETW +{ + +class ETWTracerExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * @param providerName + * @param eventName + */ + ETWTracerExporter(std::string providerName) : providerName_(providerName) {} + + /** + * @return Returns a unique pointer to an empty recordable object + */ + std::unique_ptr MakeRecordable() noexcept override + { + return std::unique_ptr(new ETWSpanData(providerName_)); + } + + /** + * @param recordables a required span containing unique pointers to the data + * to add to the ETWTracerExporter + * @return Returns the result of the operation + */ + sdk::trace::ExportResult Export( + const nostd::span> &recordables) noexcept override + { + for (auto &recordable : recordables) + { + auto span = std::unique_ptr(dynamic_cast(recordable.release())); + if (span != nullptr) + { + std::cout << span->GetName() << std::endl; + } + } + + return sdk::trace::ExportResult::kSuccess; + } + + /** + * @param timeout an optional value containing the timeout of the exporter + * note: passing custom timeout values is not currently supported for this exporter + */ + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + return true; + }; + +private: + std::string providerName_; +}; +} // namespace ETW +} // namespace exporter + +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/include/opentelemetry/exporters/etw/utils.h b/exporters/etw/include/opentelemetry/exporters/etw/utils.h new file mode 100644 index 0000000000..079d4e5592 --- /dev/null +++ b/exporters/etw/include/opentelemetry/exporters/etw/utils.h @@ -0,0 +1,210 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#ifdef _WIN32 +# include +# include +# include +# pragma comment(lib, "Advapi32.lib") +# pragma comment(lib, "Rpcrt4.lib") +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// +/// Compile-time constexpr djb2 hash function for strings +/// +static constexpr uint32_t hashCode(const char *str, uint32_t h = 0) +{ + return (uint32_t)(!str[h] ? 5381 : ((uint32_t)hashCode(str, h + 1) * (uint32_t)33) ^ str[h]); +} + +#define CONST_UINT32_T(x) std::integral_constant::value + +#define CONST_HASHCODE(name) CONST_UINT32_T(OPENTELEMETRY_NAMESPACE::utils::hashCode(#name)) + +#ifdef _WIN32 + +/// +/// Compute SHA-1 hash of input buffer and save to output +/// +/// Input buffer +/// Input buffer size +/// Output buffer +/// Output buffer size +/// +static inline bool sha1(const BYTE *pData, DWORD nData, BYTE *pHashedData, DWORD &nHashedData) +{ + bool bRet = false; + HCRYPTPROV hProv = NULL; + HCRYPTHASH hHash = NULL; + + if (!CryptAcquireContext(&hProv, // handle of the CSP + NULL, // key container name + NULL, // CSP name + PROV_RSA_FULL, // provider type + CRYPT_VERIFYCONTEXT)) // no key access is requested + { + bRet = false; + goto CleanUp; + } + + if (!CryptCreateHash(hProv, // handle of the CSP + CALG_SHA1, // hash algorithm to use + 0, // hash key + 0, // reserved + &hHash)) // + { + bRet = false; + goto CleanUp; + } + + if (!CryptHashData(hHash, // handle of the HMAC hash object + pData, // message to hash + nData, // number of bytes of data to add + 0)) // flags + { + bRet = false; + goto CleanUp; + } + + if (!CryptGetHashParam(hHash, // handle of the HMAC hash object + HP_HASHVAL, // query on the hash value + pHashedData, // filled on second call + &nHashedData, // length, in bytes,of the hash + 0)) + { + bRet = false; + goto CleanUp; + } + + bRet = true; + +CleanUp: + + if (hHash) + { + CryptDestroyHash(hHash); + } + + if (hProv) + { + CryptReleaseContext(hProv, 0); + } + return bRet; +} + +/// +/// Convert UTF-8 string to UTF-16 wide string. +/// +/// FIXME: this conversion is marked deprecated after C++17: +/// https://en.cppreference.com/w/cpp/locale/codecvt_utf8_utf16 +/// It works well with Visual C++, but may not work with clang. +/// Best long-term solution is to use Win32 API instead. +/// +/// +/// +/// +static inline std::wstring to_utf16_string(const std::string &in) +{ + std::wstring_convert, wchar_t> converter; + return converter.from_bytes(in); +} + +/// +/// Transform ETW provider name to provider GUID as described here: +/// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +/// +/// +/// +static inline GUID GetProviderGuid(const char *providerName) +{ + std::string name(providerName); + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return (char)::toupper(c); }); + + size_t len = name.length() * 2 + 0x10; + uint8_t *buffer = new uint8_t[len]; + uint32_t num = 0x482c2db2; + uint32_t num2 = 0xc39047c8; + uint32_t num3 = 0x87f81a15; + uint32_t num4 = 0xbfc130fb; + + for (int i = 3; i >= 0; i--) + { + buffer[i] = (uint8_t)num; + num = num >> 8; + buffer[i + 4] = (uint8_t)num2; + num2 = num2 >> 8; + buffer[i + 8] = (uint8_t)num3; + num3 = num3 >> 8; + buffer[i + 12] = (uint8_t)num4; + num4 = num4 >> 8; + } + + for (size_t j = 0; j < name.length(); j++) + { + buffer[((2 * j) + 0x10) + 1] = (uint8_t)name[j]; + buffer[(2 * j) + 0x10] = (uint8_t)(name[j] >> 8); + } + + const size_t sha1_hash_size = 21; + uint8_t *buffer2 = new uint8_t[sha1_hash_size]; + DWORD len2 = sha1_hash_size; + sha1((const BYTE *)buffer, (DWORD)len, (BYTE *)buffer2, len2); + + unsigned long a = (((((buffer2[3] << 8) + buffer2[2]) << 8) + buffer2[1]) << 8) + buffer2[0]; + unsigned short b = (unsigned short)((buffer2[5] << 8) + buffer2[4]); + unsigned short num9 = (unsigned short)((buffer2[7] << 8) + buffer2[6]); + + GUID guid; + guid.Data1 = a; + guid.Data2 = b; + guid.Data3 = (unsigned short)((num9 & 0xfff) | 0x5000); + guid.Data4[0] = buffer2[8]; + guid.Data4[1] = buffer2[9]; + guid.Data4[2] = buffer2[10]; + guid.Data4[3] = buffer2[11]; + guid.Data4[4] = buffer2[12]; + guid.Data4[5] = buffer2[13]; + guid.Data4[6] = buffer2[14]; + guid.Data4[7] = buffer2[15]; + + delete buffer; + delete buffer2; + + return guid; +} +#endif + +}; // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/include/opentelemetry/exporters/etw/uuid.h b/exporters/etw/include/opentelemetry/exporters/etw/uuid.h new file mode 100644 index 0000000000..4a9640a924 --- /dev/null +++ b/exporters/etw/include/opentelemetry/exporters/etw/uuid.h @@ -0,0 +1,423 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "opentelemetry/version.h" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include "Windows.h" +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// +/// The UUID structure represents the portable cross-platform implementation of a GUID (Globally +/// Unique ID). +/// +/// +/// UUIDs identify objects such as interfaces, manager entry-point vectors (EPVs), and class +/// objects. A UUID is a 128-bit value consisting of one group of eight hexadecimal digits, followed +/// by three groups of four hexadecimal digits, each followed by one group of 12 hexadecimal digits. +/// +#pragma pack(push) /* push current alignment to stack */ +#pragma pack(1) /* set alignment to 1 byte boundary */ +struct UUID +{ + /// + /// Specifies the first eight hexadecimal digits of the GUID. + /// + uint32_t Data1; + + /// + /// Specifies the first group of four hexadecimal digits. + /// + uint16_t Data2; + + /// + /// Specifies the second group of four hexadecimal digits. + /// + uint16_t Data3; + + /// + /// An array of eight bytes. + /// The first two bytes contain the third group of four hexadecimal digits. + /// The remaining six bytes contain the final 12 hexadecimal digits. + /// + uint8_t Data4[8]; + + /// + /// The default UUID constructor. + /// Creates a null instance of the UUID object (initialized to all zeros). + /// {00000000-0000-0000-0000-000000000000}. + /// + UUID() : Data1(0), Data2(0), Data3(0) + { + for (size_t i = 0; i < 8; i++) + { + Data4[i] = 0; + } + }; + + /// + /// A constructor that creates a UUID object from a hyphenated string as defined by + /// https://tools.ietf.org/html/rfc4122#page-4 + /// + /// A hyphenated string that contains the UUID (curly braces + /// optional). + UUID(const char *uuid_string) + { + const char *str = uuid_string; + // Skip curly brace + if (str[0] == '{') + { + str++; + } + // Convert to set of integer values + unsigned long p0; + unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + if ( + // Parse input with dashes + (11 == sscanf(str, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, + &p4, &p5, &p6, &p7, &p8, &p9, &p10)) || + // Parse input without dashes + (11 == sscanf(str, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, &p4, + &p5, &p6, &p7, &p8, &p9, &p10))) + { + Data1 = static_cast(p0); + Data2 = static_cast(p1); + Data3 = static_cast(p2); + Data4[0] = static_cast(p3); + Data4[1] = static_cast(p4); + Data4[2] = static_cast(p5); + Data4[3] = static_cast(p6); + Data4[4] = static_cast(p7); + Data4[5] = static_cast(p8); + Data4[6] = static_cast(p9); + Data4[7] = static_cast(p10); + } + else // Invalid input--use a safe default value + { + Data1 = 0; + Data2 = 0; + Data3 = 0; + Data4[0] = 0; + Data4[1] = 0; + Data4[2] = 0; + Data4[3] = 0; + Data4[4] = 0; + Data4[5] = 0; + Data4[6] = 0; + Data4[7] = 0; + } + } + + /// + /// A constructor that creates a UUID object from a byte array. + /// + /// A byte array. + /// + /// A boolean value that specifies the byte order.
+ /// A value of true specifies the more natural human-readable order.
+ /// A value of false (the default) specifies the same order as the .NET GUID constructor. + /// + UUID(const uint8_t guid_bytes[16], bool bigEndian = false) + { + if (bigEndian) + { + /* Use big endian - human-readable */ + // Part 1 + Data1 = guid_bytes[3]; + Data1 |= ((uint32_t)(guid_bytes[2])) << 8; + Data1 |= ((uint32_t)(guid_bytes[1])) << 16; + Data1 |= ((uint32_t)(guid_bytes[0])) << 24; + // Part 2 + Data2 = guid_bytes[5]; + Data2 |= ((uint16_t)(guid_bytes[4])) << 8; + // Part 3 + Data3 = guid_bytes[7]; + Data3 |= ((uint16_t)(guid_bytes[6])) << 8; + } + else + { + /* Use little endian - the same order as .NET C# Guid() class uses */ + // Part 1 + Data1 = guid_bytes[0]; + Data1 |= ((uint32_t)(guid_bytes[1])) << 8; + Data1 |= ((uint32_t)(guid_bytes[2])) << 16; + Data1 |= ((uint32_t)(guid_bytes[3])) << 24; + // Part 2 + Data2 = guid_bytes[4]; + Data2 |= ((uint16_t)(guid_bytes[5])) << 8; + // Part 3 + Data3 = guid_bytes[6]; + Data3 |= ((uint16_t)(guid_bytes[7])) << 8; + } + // Part 4 + for (size_t i = 0; i < 8; i++) + { + Data4[i] = guid_bytes[8 + i]; + } + } + + /// + /// A constructor that creates a UUID object from three integers and a byte array. + /// + /// An integer that specifies the first eight hexadecimal digits of the + /// UUID. An integer that specifies the first group of four hexadecimal + /// digits. An integer that specifies the second group of four + /// hexadecimal digits. A reference to an array of eight bytes. The first + /// two bytes contain the third group of four hexadecimal digits. The remaining six bytes contain + /// the final 12 hexadecimal digits. + UUID(int d1, int d2, int d3, const std::initializer_list &v) + : Data1((uint32_t)d1), Data2((uint16_t)d2), Data3((uint16_t)d3) + { + size_t i = 0; + for (auto val : v) + { + Data4[i] = val; + i++; + } + } + + /// + /// The UUID copy constructor. + /// + /// A UUID object. + UUID(const UUID &uuid) + { + this->Data1 = uuid.Data1; + this->Data2 = uuid.Data2; + this->Data3 = uuid.Data3; + memcpy(&(this->Data4[0]), &(uuid.Data4[0]), sizeof(uuid.Data4)); + } + +#ifdef _WIN32 + + /// + /// A constructor that creates a UUID object from a Windows GUID object. + /// + /// A Windows GUID object. + UUID(GUID guid) + { + this->Data1 = guid.Data1; + this->Data2 = guid.Data2; + this->Data3 = guid.Data3; + std::memcpy(&(this->Data4[0]), &(guid.Data4[0]), sizeof(guid.Data4)); + } + + /// + /// Converts a standard vector of bytes into a Windows GUID object. + /// + /// A standard vector of bytes. + /// A GUID. + static GUID to_GUID(std::vector const &bytes) + { + UUID temp_t = UUID(bytes.data()); + GUID temp; + temp.Data1 = temp_t.Data1; + temp.Data2 = temp_t.Data2; + temp.Data3 = temp_t.Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = temp_t.Data4[i]; + } + return temp; + } + + GUID to_GUID() + { + GUID temp; + temp.Data1 = Data1; + temp.Data2 = Data2; + temp.Data3 = Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = Data4[i]; + } + return temp; + } + +#endif + + /// + /// Converts this UUID to an array of bytes. + /// + /// A uint8_t array of 16 bytes. + void to_bytes(uint8_t (&guid_bytes)[16]) const + { + // Part 1 + guid_bytes[0] = (uint8_t)((Data1)&0xFF); + guid_bytes[1] = (uint8_t)((Data1 >> 8) & 0xFF); + guid_bytes[2] = (uint8_t)((Data1 >> 16) & 0xFF); + guid_bytes[3] = (uint8_t)((Data1 >> 24) & 0xFF); + // Part 2 + guid_bytes[4] = (uint8_t)((Data2)&0xFF); + guid_bytes[5] = (uint8_t)((Data2 >> 8) & 0xFF); + // Part 3 + guid_bytes[6] = (uint8_t)((Data3)&0xFF); + guid_bytes[7] = (uint8_t)((Data3 >> 8) & 0xFF); + // Part 4 + for (size_t i = 0; i < 8; i++) + { + guid_bytes[8 + i] = Data4[i]; + } + } + + /// + /// Convert this UUID object to a string. + /// + /// This UUID object in a string. + std::string to_string() const + { + static char inttoHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + const unsigned buffSize = 36 + 1; // 36 + null-terminator + char buf[buffSize] = {0}; + + int test = (Data1 >> 28 & 0x0000000F); + buf[0] = inttoHex[test]; + test = (int)(Data1 >> 24 & 0x0000000F); + buf[1] = inttoHex[test]; + test = (int)(Data1 >> 20 & 0x0000000F); + buf[2] = inttoHex[test]; + test = (int)(Data1 >> 16 & 0x0000000F); + buf[3] = inttoHex[test]; + test = (int)(Data1 >> 12 & 0x0000000F); + buf[4] = inttoHex[test]; + test = (int)(Data1 >> 8 & 0x0000000F); + buf[5] = inttoHex[test]; + test = (int)(Data1 >> 4 & 0x0000000F); + buf[6] = inttoHex[test]; + test = (int)(Data1 & 0x0000000F); + buf[7] = inttoHex[test]; + buf[8] = '-'; + test = (int)(Data2 >> 12 & 0x000F); + buf[9] = inttoHex[test]; + test = (int)(Data2 >> 8 & 0x000F); + buf[10] = inttoHex[test]; + test = (int)(Data2 >> 4 & 0x000F); + buf[11] = inttoHex[test]; + test = (int)(Data2 & 0x000F); + buf[12] = inttoHex[test]; + buf[13] = '-'; + test = (int)(Data3 >> 12 & 0x000F); + buf[14] = inttoHex[test]; + test = (int)(Data3 >> 8 & 0x000F); + buf[15] = inttoHex[test]; + test = (int)(Data3 >> 4 & 0x000F); + buf[16] = inttoHex[test]; + test = (int)(Data3 & 0x000F); + buf[17] = inttoHex[test]; + buf[18] = '-'; + test = (int)(Data4[0] >> 4 & 0x0F); + buf[19] = inttoHex[test]; + test = (int)(Data4[0] & 0x0F); + buf[20] = inttoHex[test]; + test = (int)(Data4[1] >> 4 & 0x0F); + buf[21] = inttoHex[test]; + test = (int)(Data4[1] & 0x0F); + buf[22] = inttoHex[test]; + buf[23] = '-'; + test = (int)(Data4[2] >> 4 & 0x0F); + buf[24] = inttoHex[test]; + test = (int)(Data4[2] & 0x0F); + buf[25] = inttoHex[test]; + test = (int)(Data4[3] >> 4 & 0x0F); + buf[26] = inttoHex[test]; + test = (int)(Data4[3] & 0x0F); + buf[27] = inttoHex[test]; + test = (int)(Data4[4] >> 4 & 0x0F); + buf[28] = inttoHex[test]; + test = (int)(Data4[4] & 0x0F); + buf[29] = inttoHex[test]; + test = (int)(Data4[5] >> 4 & 0x0F); + buf[30] = inttoHex[test]; + test = (int)(Data4[5] & 0x0F); + buf[31] = inttoHex[test]; + test = (int)(Data4[6] >> 4 & 0x0F); + buf[32] = inttoHex[test]; + test = (int)(Data4[6] & 0x0F); + buf[33] = inttoHex[test]; + test = (int)(Data4[7] >> 4 & 0x0F); + buf[34] = inttoHex[test]; + test = (int)(Data4[7] & 0x0F); + buf[35] = inttoHex[test]; + buf[36] = 0; + + return std::string(buf); + } + + /// + /// Calculates the size of this UUID object. + /// The output from this method is compatible with std::unordered_map. + /// + /// The size of the UUID object in bytes. + size_t Hash() const + { + // Compute individual hash values for Data1, Data2, Data3, and parts of Data4 + size_t res = 17; + res = res * 31 + Data1; + res = res * 31 + Data2; + res = res * 31 + Data3; + res = res * 31 + (Data4[0] << 24 | Data4[1] << 16 | Data4[6] << 8 | Data4[7]); + return res; + } + + /// + /// Tests to determine whether two UUID objects are equivalent (needed for maps). + /// + /// A boolean value that indicates success or failure. + bool operator==(UUID const &other) const + { + return Data1 == other.Data1 && Data2 == other.Data2 && Data3 == other.Data3 && + (0 == memcmp(Data4, other.Data4, sizeof(Data4))); + } + + /// + /// Tests to determine how to sort 2 UUID objects + /// + /// A boolean value that indicates success or failure. + bool operator<(UUID const &other) const + { + return Data1 < other.Data1 || Data2 < other.Data2 || Data3 == other.Data3 || + (memcmp(Data4, other.Data4, sizeof(Data4)) < 0); + } +}; +#pragma pack(pop) /* restore original alignment from stack */ + +/// +/// Declare UUIDComparer as the Comparer when using UUID as a key in a map or set +/// +struct UUIDComparer : std::less +{ + inline size_t operator()(UUID const &key) const { return key.Hash(); } + + inline bool operator()(UUID const &lhs, UUID const &rhs) const { return lhs.Hash() < rhs.Hash(); } +}; + +} // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/etw/src/etw_provider_exporter.cc b/exporters/etw/src/etw_provider_exporter.cc new file mode 100644 index 0000000000..3dd9396897 --- /dev/null +++ b/exporters/etw/src/etw_provider_exporter.cc @@ -0,0 +1,10 @@ +#ifdef _WIN32 + +/* TODO: this definition needs to be removed when TraceLoggingDynamic.h is OSS */ +# ifndef HAVE_NO_TLD +# define HAVE_NO_TLD +# endif + +# include "opentelemetry/exporters/etw/etw_provider_exporter.h" + +#endif diff --git a/exporters/etw/src/etw_tracer_exporter.cc b/exporters/etw/src/etw_tracer_exporter.cc new file mode 100644 index 0000000000..6842a83933 --- /dev/null +++ b/exporters/etw/src/etw_tracer_exporter.cc @@ -0,0 +1,10 @@ +#ifdef _WIN32 + +/* TODO: this definition needs to be removed when TraceLoggingDynamic.h is OSS */ +# ifndef HAVE_NO_TLD +# define HAVE_NO_TLD +# endif + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" + +#endif diff --git a/exporters/etw/test/etw_provider_test.cc b/exporters/etw/test/etw_provider_test.cc new file mode 100644 index 0000000000..1f797c91c8 --- /dev/null +++ b/exporters/etw/test/etw_provider_test.cc @@ -0,0 +1,78 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 + +# include +# include + +/* TODO: this definition needs to be removed when TraceLoggingDynamic.h is OSS */ +# ifndef HAVE_NO_TLD +# define HAVE_NO_TLD +# endif + +# include "opentelemetry/exporters/etw/etw_provider_exporter.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +TEST(ETWProvider, ProviderIsRegisteredSuccessfully) +{ + std::string providerName = "OpenTelemetry"; + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + bool registered = etw.is_registered(providerName); + ASSERT_TRUE(registered); +} + +TEST(ETWProvider, ProviderIsNotRegisteredSuccessfully) +{ + std::string providerName = "Telemetry"; + static ETWProvider etw; + + bool registered = etw.is_registered(providerName); + ASSERT_FALSE(registered); +} + +TEST(ETWProvider, CheckOpenGUIDDataSuccessfully) +{ + std::string providerName = "OpenTelemetry"; + + // get GUID from the handle returned + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + utils::UUID uuid_handle(handle.providerGuid); + auto guidStrHandle = uuid_handle.to_string(); + + // get GUID from the providerName + auto guid = utils::GetProviderGuid(providerName.c_str()); + utils::UUID uuid_name(guid); + auto guidStrName = uuid_name.to_string(); + + ASSERT_STREQ(guidStrHandle.c_str(), guidStrName.c_str()); +} + +TEST(ETWProvider, CheckCloseSuccess) +{ + std::string providerName = "OpenTelemetry"; + + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + auto result = etw.close(handle); + ASSERT_NE(result, etw.STATUS_ERROR); +} + +#endif diff --git a/exporters/etw/test/etw_tracer_test.cc b/exporters/etw/test/etw_tracer_test.cc new file mode 100644 index 0000000000..c07fa53cd8 --- /dev/null +++ b/exporters/etw/test/etw_tracer_test.cc @@ -0,0 +1,95 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 + +# include +# include + +/* TODO: this definition needs to be removed when TraceLoggingDynamic.h is OSS */ +# ifndef HAVE_NO_TLD +# define HAVE_NO_TLD +# endif + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using ETWEvent = std::map; + +TEST(ETWTracer, TracerCheck) +{ + std::string providerName = "OpenTelemetry"; + std::string eventName = "MyEvent"; + + exporter::ETW::TracerProvider tp; + auto tracer = tp.GetTracer(providerName); + auto span = tracer->StartSpan("MySpan"); + + ETWEvent event = { + {"uint32Key", (uint32_t)123456}, {"uint64Key", (uint64_t)123456}, {"strKey", "someValue"}}; + + EXPECT_NO_THROW(span->AddEvent(eventName, event)); + EXPECT_NO_THROW(span->End()); + EXPECT_NO_THROW(tracer->CloseWithMicroseconds(0)); +} + +TEST(ETWTracer, ETWTracerTest) +{ + std::string providerName = "OpenTelemetry"; + + auto exporter = std::unique_ptr( + new exporter::ETW::ETWTracerExporter(providerName)); + + auto processor = std::shared_ptr( + new sdk::trace::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + recordable->SetName("MySpan"); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + processor->OnEnd(std::move(recordable)); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = "MySpan\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(ETWTracer, ExportUnitTest) +{ + std::string providerName = "OpenTelemetry"; + + auto exporter = std::unique_ptr( + new exporter::ETW::ETWTracerExporter(providerName)); + + auto recordable = exporter->MakeRecordable(); + recordable->SetName("MySpan"); + + nostd::span> batch(&recordable, 1); + auto result = exporter->Export(batch); + EXPECT_EQ(sdk::trace::ExportResult::kSuccess, result); +} + +#endif diff --git a/sdk/include/opentelemetry/sdk/trace/processor.h b/sdk/include/opentelemetry/sdk/trace/processor.h index 060f4c7ebb..f1ef6ca6d0 100644 --- a/sdk/include/opentelemetry/sdk/trace/processor.h +++ b/sdk/include/opentelemetry/sdk/trace/processor.h @@ -47,7 +47,7 @@ class SpanProcessor * timeout is applied. */ virtual bool ForceFlush( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept = 0; + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept = 0; /** * Shut down the processor and do any cleanup required. Ended spans are @@ -58,7 +58,7 @@ class SpanProcessor * timeout is applied. */ virtual bool Shutdown( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept = 0; + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept = 0; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/simple_processor.h b/sdk/include/opentelemetry/sdk/trace/simple_processor.h index 7ad5848c07..739c541866 100644 --- a/sdk/include/opentelemetry/sdk/trace/simple_processor.h +++ b/sdk/include/opentelemetry/sdk/trace/simple_processor.h @@ -51,13 +51,13 @@ class SimpleSpanProcessor : public SpanProcessor } bool ForceFlush( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override { return true; } bool Shutdown( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override { // We only call shutdown ONCE. if (exporter_ != nullptr && !shutdown_latch_.test_and_set(std::memory_order_acquire))