diff --git a/CHANGELOG.md b/CHANGELOG.md index 41bd27bdde..60ab34e4c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Increment the: * [TRACE SDK] Add trace sdk builders (#1393) [#1471](https://github.com/open-telemetry/opentelemetry-cpp/pull/1471) * [EXAMPLE] Fix memory ownership of InMemorySpanExporter (#1473) [#1471](https://github.com/open-telemetry/opentelemetry-cpp/pull/1471) +* [EXPORTER] Add metrics OTLP/HTTP exporter [#1487](https://github.com/open-telemetry/opentelemetry-cpp/pull/1487) * [EXPORTER] OTLP http exporter allow concurrency session ([#1209](https://github.com/open-telemetry/opentelemetry-cpp/pull/1209)) * [EXT] `curl::HttpClient` use `curl_multi_handle` instead of creating a thread for every request and it's able to reuse connections now. ([#1317](https://github.com/open-telemetry/opentelemetry-cpp/pull/1317)) diff --git a/cmake/opentelemetry-cpp-config.cmake.in b/cmake/opentelemetry-cpp-config.cmake.in index 34549d468f..0c1d898501 100644 --- a/cmake/opentelemetry-cpp-config.cmake.in +++ b/cmake/opentelemetry-cpp-config.cmake.in @@ -32,9 +32,11 @@ # opentelemetry-cpp::otlp_recordable - Imported target of opentelemetry-cpp::otlp_recordable # opentelemetry-cpp::otlp_grpc_exporter - Imported target of opentelemetry-cpp::otlp_grpc_exporter # opentelemetry-cpp::otlp_grpc_log_exporter - Imported target of opentelemetry-cpp::otlp_grpc_log_exporter +# opentelemetry-cpp::otlp_grpc_metrics_exporter - Imported target of opentelemetry-cpp::otlp_grpc_metrics_exporter # opentelemetry-cpp::otlp_http_client - Imported target of opentelemetry-cpp::otlp_http_client # opentelemetry-cpp::otlp_http_exporter - Imported target of opentelemetry-cpp::otlp_http_exporter # opentelemetry-cpp::otlp_http_log_exporter - Imported target of opentelemetry-cpp::otlp_http_log_exporter +# opentelemetry-cpp::otlp_http_metric_exporter - Imported target of opentelemetry-cpp::otlp_http_metric_exporter # opentelemetry-cpp::ostream_log_exporter - Imported target of opentelemetry-cpp::ostream_log_exporter # opentelemetry-cpp::ostream_metrics_exporter - Imported target of opentelemetry-cpp::ostream_metrics_exporter # opentelemetry-cpp::ostream_span_exporter - Imported target of opentelemetry-cpp::ostream_span_exporter @@ -84,9 +86,12 @@ set(_OPENTELEMETRY_CPP_LIBRARIES_TEST_TARGETS in_memory_span_exporter otlp_recordable otlp_grpc_exporter + otlp_grpc_log_exporter + otlp_grpc_metrics_exporter otlp_http_client otlp_http_exporter otlp_http_log_exporter + otlp_http_metric_exporter ostream_log_exporter ostream_metrics_exporter ostream_span_exporter diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 5af0ce6a25..1619a010ca 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -20,12 +20,14 @@ cc_library( name = "otlp_recordable", srcs = [ "src/otlp_log_recordable.cc", + "src/otlp_metric_utils.cc", "src/otlp_populate_attribute_utils.cc", "src/otlp_recordable.cc", "src/otlp_recordable_utils.cc", ], hdrs = [ "include/opentelemetry/exporters/otlp/otlp_log_recordable.h", + "include/opentelemetry/exporters/otlp/otlp_metric_utils.h", "include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h", "include/opentelemetry/exporters/otlp/otlp_recordable.h", "include/opentelemetry/exporters/otlp/otlp_recordable_utils.h", @@ -39,6 +41,7 @@ cc_library( "//sdk/src/resource", "//sdk/src/trace", "@com_github_opentelemetry_proto//:logs_service_proto_cc", + "@com_github_opentelemetry_proto//:metrics_service_proto_cc", "@com_github_opentelemetry_proto//:trace_service_proto_cc", ], ) @@ -161,6 +164,30 @@ cc_library( ], ) +cc_library( + name = "otlp_http_metric_exporter", + srcs = [ + "src/otlp_http_metric_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = [ + "otlp", + "otlp_http_metric", + ], + deps = [ + ":otlp_http_client", + ":otlp_recordable", + "//sdk/src/metrics", + "@com_github_opentelemetry_proto//:metrics_service_proto_cc", + ], +) + cc_library( name = "otlp_grpc_log_exporter", srcs = [ @@ -204,7 +231,10 @@ cc_test( cc_test( name = "otlp_log_recordable_test", - srcs = ["test/otlp_log_recordable_test.cc"], + srcs = [ + "test/otlp_log_recordable_test.cc", + "test/otlp_metrics_serialization_test.cc", + ], tags = [ "otlp", "test", @@ -294,6 +324,22 @@ cc_test( ], ) +cc_test( + name = "otlp_http_metric_exporter_test", + srcs = ["test/otlp_http_metric_exporter_test.cc"], + tags = [ + "otlp", + "otlp_http_metric", + "test", + ], + deps = [ + ":otlp_http_metric_exporter", + "//api", + "//ext/src/http/client/nosend:http_client_nosend", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "otlp_grpc_log_exporter_test", srcs = ["test/otlp_grpc_log_exporter_test.cc"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index a3e67650cd..a1f6546279 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -104,6 +104,21 @@ if(WITH_OTLP_HTTP) list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_http_log) endif() + if(NOT WITH_METRICS_PREVIEW) + add_library(opentelemetry_exporter_otlp_http_metric + src/otlp_http_metric_exporter.cc) + + set_target_properties(opentelemetry_exporter_otlp_http_metric + PROPERTIES EXPORT_NAME otlp_http_metric_exporter) + + target_link_libraries( + opentelemetry_exporter_otlp_http_metric + PUBLIC opentelemetry_otlp_recordable + opentelemetry_exporter_otlp_http_client) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS + opentelemetry_exporter_otlp_http_metric) + endif() endif() install( @@ -240,5 +255,22 @@ if(BUILD_TESTING) TEST_PREFIX exporter.otlp. TEST_LIST otlp_http_log_exporter_test) endif() + + if(NOT WITH_METRICS_PREVIEW) + add_executable(otlp_http_metric_exporter_test + test/otlp_http_metric_exporter_test.cc) + target_link_libraries( + otlp_http_metric_exporter_test + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} + opentelemetry_exporter_otlp_http_metric + opentelemetry_metrics + http_client_nosend) + gtest_add_tests( + TARGET otlp_http_metric_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_http_metric_exporter_test) + endif() endif() endif() # BUILD_TESTING diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h index 52bd111937..26f0fbf241 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h @@ -266,6 +266,53 @@ inline OtlpHeaders GetOtlpDefaultLogHeaders() return result; } + +inline const std::string GetOtlpDefaultHttpMetricEndpoint() +{ + constexpr char kOtlpMetricsEndpointEnv[] = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"; + constexpr char kOtlpEndpointEnv[] = "OTEL_EXPORTER_OTLP_ENDPOINT"; + constexpr char kOtlpEndpointDefault[] = "http://localhost:4318/v1/metrics"; + + auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpMetricsEndpointEnv); + if (endpoint.empty()) + { + endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpEndpointEnv); + if (!endpoint.empty()) + { + endpoint += "/v1/metrics"; + } + } + return endpoint.size() ? endpoint : kOtlpEndpointDefault; +} + +inline const std::chrono::system_clock::duration GetOtlpDefaultMetricTimeout() +{ + constexpr char kOtlpMetricsTimeoutEnv[] = "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT"; + constexpr char kOtlpTimeoutEnv[] = "OTEL_EXPORTER_OTLP_TIMEOUT"; + + auto timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpMetricsTimeoutEnv); + if (timeout.empty()) + { + timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTimeoutEnv); + } + return GetOtlpTimeoutFromString(timeout.c_str()); +} + +inline OtlpHeaders GetOtlpDefaultMetricHeaders() +{ + constexpr char kOtlpMetricsHeadersEnv[] = "OTEL_EXPORTER_OTLP_METRICS_HEADERS"; + constexpr char kOtlpHeadersEnv[] = "OTEL_EXPORTER_OTLP_HEADERS"; + + OtlpHeaders result; + std::unordered_set metric_remove_cache; + DumpOtlpHeaders(result, kOtlpHeadersEnv, metric_remove_cache); + + metric_remove_cache.clear(); + DumpOtlpHeaders(result, kOtlpMetricsHeadersEnv, metric_remove_cache); + + return result; +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h index 6f14b78a6d..70aa5ddbe6 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h @@ -138,6 +138,11 @@ class OtlpHttpClient std::function &&result_callback, std::size_t max_running_requests) noexcept; + /** + * Force flush the HTTP client. + */ + bool ForceFlush(std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept; + /** * Shut down the HTTP client. * @param timeout an optional timeout, the default timeout of 0 means that no @@ -159,6 +164,12 @@ class OtlpHttpClient */ inline const OtlpHttpClientOptions &GetOptions() const noexcept { return options_; } + /** + * Get if this OTLP http client is shutdown. + * @return return true after Shutdown is called. + */ + bool IsShutdown() const noexcept; + private: struct HttpSessionData { @@ -212,11 +223,11 @@ class OtlpHttpClient */ bool cleanupGCSessions() noexcept; - bool isShutdown() const noexcept; - // For testing friend class OtlpHttpExporterTestPeer; friend class OtlpHttpLogExporterTestPeer; + friend class OtlpHttpMetricExporterTestPeer; + /** * Create an OtlpHttpClient using the specified http client. * Only tests can call this constructor directly. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h index 3f48a9bc51..673e0d0bb3 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -59,7 +59,8 @@ class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter * timeout is applied. * @return return the status of this operation */ - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; private: // The configuration options associated with this exporter. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h index 28827b9de4..c6b2da30c3 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h @@ -97,7 +97,8 @@ class OtlpHttpLogExporter final : public opentelemetry::sdk::logs::LogExporter * Shutdown this exporter. * @param timeout The maximum time to wait for the shutdown method to return */ - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; private: // Configuration options for the exporter diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h new file mode 100644 index 0000000000..311f68494e --- /dev/null +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/sdk/metrics/metric_exporter.h" + +# include "opentelemetry/exporters/otlp/otlp_http_client.h" + +# include "opentelemetry/exporters/otlp/otlp_environment.h" + +# include +# include +# include +# include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * Struct to hold OTLP exporter options. + */ +struct OtlpHttpMetricExporterOptions +{ + // The endpoint to export to. By default + // @see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md + // @see https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver + std::string url = GetOtlpDefaultHttpMetricEndpoint(); + + // By default, post json data + HttpRequestContentType content_type = HttpRequestContentType::kJson; + + // If convert bytes into hex. By default, we will convert all bytes but id into base64 + // This option is ignored if content_type is not kJson + JsonBytesMappingKind json_bytes_mapping = JsonBytesMappingKind::kHexId; + + // If using the json name of protobuf field to set the key of json. By default, we will use the + // field name just like proto files. + bool use_json_name = false; + + // Whether to print the status of the exporter in the console + bool console_debug = false; + + // TODO: Enable/disable to verify SSL certificate + std::chrono::system_clock::duration timeout = GetOtlpDefaultMetricTimeout(); + + // Additional HTTP headers + OtlpHeaders http_headers = GetOtlpDefaultMetricHeaders(); + +# ifdef ENABLE_ASYNC_EXPORT + // Concurrent requests + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#otlpgrpc-concurrent-requests + std::size_t max_concurrent_requests = 64; + + // Requests per connections + std::size_t max_requests_per_connection = 8; +# endif +}; + +/** + * The OTLP exporter exports metrics data in OpenTelemetry Protocol (OTLP) format in HTTP. + */ +class OtlpHttpMetricExporter final : public opentelemetry::sdk::metrics::MetricExporter +{ +public: + /** + * Create an OtlpHttpMetricExporter with default exporter options. + */ + OtlpHttpMetricExporter(); + + /** + * Create an OtlpHttpMetricExporter with user specified options. + * @param options An object containing the user's configuration options. + */ + OtlpHttpMetricExporter(const OtlpHttpMetricExporterOptions &options); + + opentelemetry::sdk::common::ExportResult Export( + const opentelemetry::sdk::metrics::ResourceMetrics &data) noexcept override; + + /** + * Force flush the exporter. + */ + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + bool Shutdown( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + +private: + // Configuration options for the exporter + const OtlpHttpMetricExporterOptions options_; + + // Object that stores the HTTP sessions that have been created + std::unique_ptr http_client_; + // For testing + friend class OtlpHttpMetricExporterTestPeer; + + /** + * Create an OtlpHttpMetricExporter using the specified http client. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + OtlpHttpMetricExporter(std::unique_ptr http_client); +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h index c344613cb3..380c96d279 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h @@ -38,6 +38,9 @@ class OtlpMetricUtils static void ConvertHistogramMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, proto::metrics::v1::Histogram *const histogram) noexcept; + static void ConvertGaugeMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, + proto::metrics::v1::Gauge *const gauge) noexcept; + static void PopulateInstrumentationInfoMetric( const opentelemetry::sdk::metrics::MetricData &metric_data, proto::metrics::v1::Metric *metric) noexcept; diff --git a/exporters/otlp/src/otlp_http_client.cc b/exporters/otlp/src/otlp_http_client.cc index 27ed2967e4..40234be46b 100644 --- a/exporters/otlp/src/otlp_http_client.cc +++ b/exporters/otlp/src/otlp_http_client.cc @@ -651,7 +651,7 @@ OtlpHttpClient::OtlpHttpClient(OtlpHttpClientOptions &&options) OtlpHttpClient::~OtlpHttpClient() { - if (!isShutdown()) + if (!IsShutdown()) { Shutdown(); } @@ -760,17 +760,8 @@ sdk::common::ExportResult OtlpHttpClient::Export( return opentelemetry::sdk::common::ExportResult::kSuccess; } -bool OtlpHttpClient::Shutdown(std::chrono::microseconds timeout) noexcept +bool OtlpHttpClient::ForceFlush(std::chrono::microseconds timeout) noexcept { - { - std::lock_guard guard{session_manager_lock_}; - is_shutdown_ = true; - - // Shutdown the session manager - http_client_->CancelAllSessions(); - http_client_->FinishAllSessions(); - } - // ASAN will report chrono: runtime error: signed integer overflow: A + B cannot be represented // in type 'long int' here. So we reset timeout to meet signed long int limit here. timeout = opentelemetry::common::DurationUtil::AdjustWaitForTimeout( @@ -793,14 +784,29 @@ bool OtlpHttpClient::Shutdown(std::chrono::microseconds timeout) noexcept // checking and waiting, we should not wait forever. session_waker_.wait_for(lock, options_.timeout); } + return true; } else { - session_waker_.wait_for(lock, timeout, [this] { + return session_waker_.wait_for(lock, timeout, [this] { std::lock_guard guard{session_manager_lock_}; return running_sessions_.empty(); }); } +} + +bool OtlpHttpClient::Shutdown(std::chrono::microseconds timeout) noexcept +{ + { + std::lock_guard guard{session_manager_lock_}; + is_shutdown_ = true; + + // Shutdown the session manager + http_client_->CancelAllSessions(); + http_client_->FinishAllSessions(); + } + + ForceFlush(timeout); while (cleanupGCSessions()) ; @@ -907,7 +913,7 @@ OtlpHttpClient::createSession( // Send the request std::lock_guard guard{session_manager_lock_}; // Return failure if this exporter has been shutdown - if (isShutdown()) + if (IsShutdown()) { const char *error_message = "[OTLP HTTP Client] Export failed, exporter is shutdown"; if (options_.console_debug) @@ -976,7 +982,7 @@ bool OtlpHttpClient::cleanupGCSessions() noexcept return !gc_sessions_.empty(); } -bool OtlpHttpClient::isShutdown() const noexcept +bool OtlpHttpClient::IsShutdown() const noexcept { return is_shutdown_; } diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc index 70bdd8c90d..0fa1bf61e8 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -67,6 +67,14 @@ std::unique_ptr OtlpHttpExporter::MakeRec opentelemetry::sdk::common::ExportResult OtlpHttpExporter::Export( const nostd::span> &spans) noexcept { + if (http_client_->IsShutdown()) + { + std::size_t span_count = spans.size(); + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] ERROR: Export " + << span_count << " trace span(s) failed, exporter is shutdown"); + return opentelemetry::sdk::common::ExportResult::kFailure; + } + if (spans.empty()) { return opentelemetry::sdk::common::ExportResult::kSuccess; diff --git a/exporters/otlp/src/otlp_http_log_exporter.cc b/exporters/otlp/src/otlp_http_log_exporter.cc index 4eb992a61b..24a5b3d0fd 100644 --- a/exporters/otlp/src/otlp_http_log_exporter.cc +++ b/exporters/otlp/src/otlp_http_log_exporter.cc @@ -69,6 +69,14 @@ std::unique_ptr OtlpHttpLogExporter::MakeR opentelemetry::sdk::common::ExportResult OtlpHttpLogExporter::Export( const nostd::span> &logs) noexcept { + if (http_client_->IsShutdown()) + { + std::size_t log_count = logs.size(); + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] ERROR: Export " + << log_count << " log(s) failed, exporter is shutdown"); + return opentelemetry::sdk::common::ExportResult::kFailure; + } + if (logs.empty()) { return opentelemetry::sdk::common::ExportResult::kSuccess; diff --git a/exporters/otlp/src/otlp_http_metric_exporter.cc b/exporters/otlp/src/otlp_http_metric_exporter.cc new file mode 100644 index 0000000000..90fece02f0 --- /dev/null +++ b/exporters/otlp/src/otlp_http_metric_exporter.cc @@ -0,0 +1,128 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/otlp/otlp_http_metric_exporter.h" +# include "opentelemetry/exporters/otlp/otlp_metric_utils.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/sdk/common/global_log_handler.h" + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +OtlpHttpMetricExporter::OtlpHttpMetricExporter() + : OtlpHttpMetricExporter(OtlpHttpMetricExporterOptions()) +{} + +OtlpHttpMetricExporter::OtlpHttpMetricExporter(const OtlpHttpMetricExporterOptions &options) + : options_(options), + http_client_(new OtlpHttpClient(OtlpHttpClientOptions(options.url, + options.content_type, + options.json_bytes_mapping, + options.use_json_name, + options.console_debug, + options.timeout, + options.http_headers +# ifdef ENABLE_ASYNC_EXPORT + , + options.max_concurrent_requests, + options.max_requests_per_connection +# endif + ))) +{} + +OtlpHttpMetricExporter::OtlpHttpMetricExporter(std::unique_ptr http_client) + : options_(OtlpHttpMetricExporterOptions()), http_client_(std::move(http_client)) +{ + OtlpHttpMetricExporterOptions &options = const_cast(options_); + options.url = http_client_->GetOptions().url; + options.content_type = http_client_->GetOptions().content_type; + options.json_bytes_mapping = http_client_->GetOptions().json_bytes_mapping; + options.use_json_name = http_client_->GetOptions().use_json_name; + options.console_debug = http_client_->GetOptions().console_debug; + options.timeout = http_client_->GetOptions().timeout; + options.http_headers = http_client_->GetOptions().http_headers; +# ifdef ENABLE_ASYNC_EXPORT + options.max_concurrent_requests = http_client_->GetOptions().max_concurrent_requests; + options.max_requests_per_connection = http_client_->GetOptions().max_requests_per_connection; +# endif +} +// ----------------------------- Exporter methods ------------------------------ + +opentelemetry::sdk::common::ExportResult OtlpHttpMetricExporter::Export( + const opentelemetry::sdk::metrics::ResourceMetrics &data) noexcept +{ + if (http_client_->IsShutdown()) + { + std::size_t metric_count = data.instrumentation_info_metric_data_.size(); + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] ERROR: Export " + << metric_count << " metric(s) failed, exporter is shutdown"); + return opentelemetry::sdk::common::ExportResult::kFailure; + } + + if (data.instrumentation_info_metric_data_.empty()) + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + proto::collector::metrics::v1::ExportMetricsServiceRequest service_request; + OtlpMetricUtils::PopulateRequest(data, &service_request); + std::size_t metric_count = data.instrumentation_info_metric_data_.size(); +# ifdef ENABLE_ASYNC_EXPORT + http_client_->Export(service_request, [metric_count]( + opentelemetry::sdk::common::ExportResult result) { + if (result != opentelemetry::sdk::common::ExportResult::kSuccess) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] ERROR: Export " + << metric_count << " metric(s) error: " << static_cast(result)); + } + else + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] DEBUG: Export " << metric_count + << " metric(s) success"); + } + return true; + }); + return opentelemetry::sdk::common::ExportResult::kSuccess; +# else + opentelemetry::sdk::common::ExportResult result = http_client_->Export(service_request); + if (result != opentelemetry::sdk::common::ExportResult::kSuccess) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] ERROR: Export " + << metric_count << " metric(s) error: " << static_cast(result)); + } + else + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] DEBUG: Export " << metric_count + << " metric(s) success"); + } + return opentelemetry::sdk::common::ExportResult::kSuccess; +# endif +} + +bool OtlpHttpMetricExporter::ForceFlush(std::chrono::microseconds timeout) noexcept +{ + return http_client_->ForceFlush(timeout); +} + +bool OtlpHttpMetricExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + return http_client_->Shutdown(timeout); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/exporters/otlp/src/otlp_metric_utils.cc b/exporters/otlp/src/otlp_metric_utils.cc index ad9e2d117f..8b97cc1cf8 100644 --- a/exporters/otlp/src/otlp_metric_utils.cc +++ b/exporters/otlp/src/otlp_metric_utils.cc @@ -57,26 +57,25 @@ void OtlpMetricUtils::ConvertSumMetric(const metric_sdk::MetricData &metric_data auto ts = metric_data.end_ts.time_since_epoch().count(); for (auto &point_data_with_attributes : metric_data.point_data_attr_) { - proto::metrics::v1::NumberDataPoint proto_sum_point_data; - proto_sum_point_data.set_start_time_unix_nano(start_ts); - proto_sum_point_data.set_time_unix_nano(ts); + proto::metrics::v1::NumberDataPoint *proto_sum_point_data = sum->add_data_points(); + proto_sum_point_data->set_start_time_unix_nano(start_ts); + proto_sum_point_data->set_time_unix_nano(ts); auto sum_data = nostd::get(point_data_with_attributes.point_data); if ((nostd::holds_alternative(sum_data.value_))) { - proto_sum_point_data.set_as_int(nostd::get(sum_data.value_)); + proto_sum_point_data->set_as_int(nostd::get(sum_data.value_)); } else { - proto_sum_point_data.set_as_double(nostd::get(sum_data.value_)); + proto_sum_point_data->set_as_double(nostd::get(sum_data.value_)); } // set attributes for (auto &kv_attr : point_data_with_attributes.attributes) { - OtlpPopulateAttributeUtils::PopulateAttribute(proto_sum_point_data.add_attributes(), + OtlpPopulateAttributeUtils::PopulateAttribute(proto_sum_point_data->add_attributes(), kv_attr.first, kv_attr.second); } - *sum->add_data_points() = proto_sum_point_data; } } @@ -90,29 +89,30 @@ void OtlpMetricUtils::ConvertHistogramMetric( auto ts = metric_data.end_ts.time_since_epoch().count(); for (auto &point_data_with_attributes : metric_data.point_data_attr_) { - proto::metrics::v1::HistogramDataPoint proto_histogram_point_data; - proto_histogram_point_data.set_start_time_unix_nano(start_ts); - proto_histogram_point_data.set_time_unix_nano(ts); + proto::metrics::v1::HistogramDataPoint *proto_histogram_point_data = + histogram->add_data_points(); + proto_histogram_point_data->set_start_time_unix_nano(start_ts); + proto_histogram_point_data->set_time_unix_nano(ts); auto histogram_data = nostd::get(point_data_with_attributes.point_data); // sum if ((nostd::holds_alternative(histogram_data.sum_))) { - proto_histogram_point_data.set_sum(nostd::get(histogram_data.sum_)); + proto_histogram_point_data->set_sum(nostd::get(histogram_data.sum_)); } else { - proto_histogram_point_data.set_sum(nostd::get(histogram_data.sum_)); + proto_histogram_point_data->set_sum(nostd::get(histogram_data.sum_)); } // count - proto_histogram_point_data.set_count(histogram_data.count_); + proto_histogram_point_data->set_count(histogram_data.count_); // buckets if ((nostd::holds_alternative>(histogram_data.boundaries_))) { auto boundaries = nostd::get>(histogram_data.boundaries_); for (auto bound : boundaries) { - proto_histogram_point_data.add_explicit_bounds(bound); + proto_histogram_point_data->add_explicit_bounds(bound); } } else @@ -120,21 +120,50 @@ void OtlpMetricUtils::ConvertHistogramMetric( auto boundaries = nostd::get>(histogram_data.boundaries_); for (auto bound : boundaries) { - proto_histogram_point_data.add_explicit_bounds(bound); + proto_histogram_point_data->add_explicit_bounds(bound); } } // bucket counts for (auto bucket_value : histogram_data.counts_) { - proto_histogram_point_data.add_bucket_counts(bucket_value); + proto_histogram_point_data->add_bucket_counts(bucket_value); } // attributes for (auto &kv_attr : point_data_with_attributes.attributes) { - OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data.add_attributes(), + OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), + kv_attr.first, kv_attr.second); + } + } +} + +void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, + proto::metrics::v1::Gauge *const gauge) noexcept +{ + auto start_ts = metric_data.start_ts.time_since_epoch().count(); + auto ts = metric_data.end_ts.time_since_epoch().count(); + for (auto &point_data_with_attributes : metric_data.point_data_attr_) + { + proto::metrics::v1::NumberDataPoint *proto_gauge_point_data = gauge->add_data_points(); + proto_gauge_point_data->set_start_time_unix_nano(start_ts); + proto_gauge_point_data->set_time_unix_nano(ts); + auto gauge_data = + nostd::get(point_data_with_attributes.point_data); + + if ((nostd::holds_alternative(gauge_data.value_))) + { + proto_gauge_point_data->set_as_int(nostd::get(gauge_data.value_)); + } + else + { + proto_gauge_point_data->set_as_double(nostd::get(gauge_data.value_)); + } + // set attributes + for (auto &kv_attr : point_data_with_attributes.attributes) + { + OtlpPopulateAttributeUtils::PopulateAttribute(proto_gauge_point_data->add_attributes(), kv_attr.first, kv_attr.second); } - *histogram->add_data_points() = proto_histogram_point_data; } } @@ -146,17 +175,22 @@ void OtlpMetricUtils::PopulateInstrumentationInfoMetric( metric->set_description(metric_data.instrument_descriptor.description_); metric->set_unit(metric_data.instrument_descriptor.unit_); auto kind = GetAggregationType(metric_data.instrument_descriptor.type_); - if (kind == metric_sdk::AggregationType::kSum) - { - proto::metrics::v1::Sum sum; - ConvertSumMetric(metric_data, &sum); - *metric->mutable_sum() = sum; - } - else if (kind == metric_sdk::AggregationType::kHistogram) + switch (kind) { - proto::metrics::v1::Histogram histogram; - ConvertHistogramMetric(metric_data, &histogram); - *metric->mutable_histogram() = histogram; + case metric_sdk::AggregationType::kSum: { + ConvertSumMetric(metric_data, metric->mutable_sum()); + break; + } + case metric_sdk::AggregationType::kHistogram: { + ConvertHistogramMetric(metric_data, metric->mutable_histogram()); + break; + } + case metric_sdk::AggregationType::kLastValue: { + ConvertGaugeMetric(metric_data, metric->mutable_gauge()); + break; + } + default: + break; } } @@ -164,9 +198,8 @@ void OtlpMetricUtils::PopulateResourceMetrics( const opentelemetry::sdk::metrics::ResourceMetrics &data, proto::metrics::v1::ResourceMetrics *resource_metrics) noexcept { - proto::resource::v1::Resource proto; - OtlpPopulateAttributeUtils::PopulateAttribute(&proto, *(data.resource_)); - *resource_metrics->mutable_resource() = proto; + OtlpPopulateAttributeUtils::PopulateAttribute(resource_metrics->mutable_resource(), + *(data.resource_)); for (auto &instrumentation_metrics : data.instrumentation_info_metric_data_) { @@ -175,17 +208,15 @@ void OtlpMetricUtils::PopulateResourceMetrics( continue; } auto instrumentation_lib_metrics = resource_metrics->add_instrumentation_library_metrics(); - proto::common::v1::InstrumentationLibrary instrumentation_library; - instrumentation_library.set_name(instrumentation_metrics.instrumentation_library_->GetName()); - instrumentation_library.set_version( + proto::common::v1::InstrumentationLibrary *instrumentation_library = + instrumentation_lib_metrics->mutable_instrumentation_library(); + instrumentation_library->set_name(instrumentation_metrics.instrumentation_library_->GetName()); + instrumentation_library->set_version( instrumentation_metrics.instrumentation_library_->GetVersion()); - *instrumentation_lib_metrics->mutable_instrumentation_library() = instrumentation_library; for (auto &metric_data : instrumentation_metrics.metric_data_) { - proto::metrics::v1::Metric metric; - PopulateInstrumentationInfoMetric(metric_data, &metric); - *instrumentation_lib_metrics->add_metrics() = metric; + PopulateInstrumentationInfoMetric(metric_data, instrumentation_lib_metrics->add_metrics()); } } } diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index 6b7298013b..e3152ff3ab 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -431,6 +431,17 @@ class OtlpHttpExporterTestPeer : public ::testing::Test # endif }; +TEST(OtlpHttpExporterTest, Shutdown) +{ + auto exporter = std::unique_ptr(new OtlpHttpExporter()); + ASSERT_TRUE(exporter->Shutdown()); + + nostd::span> spans = {}; + + auto result = exporter->Export(spans); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTestSync) { diff --git a/exporters/otlp/test/otlp_http_log_exporter_test.cc b/exporters/otlp/test/otlp_http_log_exporter_test.cc index 498ae79111..24b0fd6593 100644 --- a/exporters/otlp/test/otlp_http_log_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_log_exporter_test.cc @@ -482,6 +482,17 @@ class OtlpHttpLogExporterTestPeer : public ::testing::Test # endif }; +TEST(OtlpHttpLogExporterTest, Shutdown) +{ + auto exporter = std::unique_ptr(new OtlpHttpLogExporter()); + ASSERT_TRUE(exporter->Shutdown()); + + nostd::span> logs = {}; + + auto result = exporter->Export(logs); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + // Create log records, let processor call Export() TEST_F(OtlpHttpLogExporterTestPeer, ExportJsonIntegrationTestSync) { diff --git a/exporters/otlp/test/otlp_http_metric_exporter_test.cc b/exporters/otlp/test/otlp_http_metric_exporter_test.cc new file mode 100644 index 0000000000..9c2101f23f --- /dev/null +++ b/exporters/otlp/test/otlp_http_metric_exporter_test.cc @@ -0,0 +1,1007 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include +# include +# include + +# include "opentelemetry/exporters/otlp/otlp_http_metric_exporter.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/ext/http/client/http_client_factory.h" +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" +# include "opentelemetry/ext/http/server/http_server.h" +# include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/resource/resource.h" + +# include +# include "gmock/gmock.h" + +# include "nlohmann/json.hpp" + +# if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +# endif + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +template +static IntegerType JsonToInteger(nlohmann::json value) +{ + if (value.is_string()) + { + return static_cast(strtol(value.get().c_str(), nullptr, 10)); + } + + return value.get(); +} + +OtlpHttpClientOptions MakeOtlpHttpClientOptions(HttpRequestContentType content_type, + bool async_mode) +{ + OtlpHttpMetricExporterOptions options; + options.content_type = content_type; + options.console_debug = true; + options.http_headers.insert( + std::make_pair("Custom-Header-Key", "Custom-Header-Value")); + OtlpHttpClientOptions otlp_http_client_options( + options.url, options.content_type, options.json_bytes_mapping, options.use_json_name, + options.console_debug, options.timeout, options.http_headers); + if (!async_mode) + { + otlp_http_client_options.max_concurrent_requests = 0; + } + return otlp_http_client_options; +} + +namespace http_client = opentelemetry::ext::http::client; + +class OtlpHttpMetricExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr GetExporter( + std::unique_ptr http_client) + { + return std::unique_ptr( + new OtlpHttpMetricExporter(std::move(http_client))); + } + + // Get the options associated with the given exporter. + const OtlpHttpMetricExporterOptions &GetOptions(std::unique_ptr &exporter) + { + return exporter->options_; + } + static std::pair> + GetMockOtlpHttpClient(HttpRequestContentType content_type, bool async_mode = false) + { + auto http_client = http_client::HttpClientFactory::CreateNoSend(); + return {new OtlpHttpClient(MakeOtlpHttpClientOptions(content_type, async_mode), http_client), + http_client}; + } + + void ExportJsonIntegrationTestExportSumPointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + opentelemetry::sdk::metrics::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + opentelemetry::sdk::metrics::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kCounter, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, sum_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, sum_point_data2}}}; + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + auto check_json = + nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + + auto resource_metrics = *check_json["resource_metrics"].begin(); + // auto scope_metrics = *resource_metrics["scope_metrics"].begin(); + // auto scope = scope_metrics["scope"]; + auto instrumentation_library_metrics = + *resource_metrics["instrumentation_library_metrics"].begin(); + auto scope = instrumentation_library_metrics["instrumentation_library"]; + EXPECT_EQ("library_name", scope["name"].get()); + EXPECT_EQ("1.5.0", scope["version"].get()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto metric = *instrumentation_library_metrics["metrics"].begin(); + EXPECT_EQ("metrics_library_name", metric["name"].get()); + EXPECT_EQ("metrics_description", metric["description"].get()); + EXPECT_EQ("metrics_unit", metric["unit"].get()); + + auto data_points = metric["sum"]["data_points"]; + EXPECT_EQ(10.0, data_points[0]["as_double"].get()); + EXPECT_EQ(20.0, data_points[1]["as_double"].get()); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } + + void ExportBinaryIntegrationTestExportSumPointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + opentelemetry::sdk::metrics::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + opentelemetry::sdk::metrics::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kCounter, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, sum_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, sum_point_data2}}}; + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + // auto &scope_metrics = request_body.resource_metrics(0).scope_metrics(0); + // auto &scope = instrumentation_library_metrics.scope(); + auto &instrumentation_library_metrics = + request_body.resource_metrics(0).instrumentation_library_metrics(0); + auto &scope = instrumentation_library_metrics.instrumentation_library(); + EXPECT_EQ("library_name", scope.name()); + EXPECT_EQ("1.5.0", scope.version()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto &metric = instrumentation_library_metrics.metrics(0); + EXPECT_EQ("metrics_library_name", metric.name()); + EXPECT_EQ("metrics_description", metric.description()); + EXPECT_EQ("metrics_unit", metric.unit()); + + auto &data_points = metric.sum().data_points(); + EXPECT_EQ(10.0, data_points.Get(0).as_double()); + bool has_attributes = false; + for (auto &kv : data_points.Get(0).attributes()) + { + if (kv.key() == "a1") + { + EXPECT_EQ("b1", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + EXPECT_EQ(20.0, data_points.Get(1).as_double()); + has_attributes = false; + for (auto &kv : data_points.Get(1).attributes()) + { + if (kv.key() == "a2") + { + EXPECT_EQ("b2", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } + + void ExportJsonIntegrationTestExportLastValuePointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + opentelemetry::sdk::metrics::LastValuePointData last_value_point_data{}; + last_value_point_data.value_ = 10.0; + last_value_point_data.is_lastvalue_valid_ = true; + last_value_point_data.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + opentelemetry::sdk::metrics::LastValuePointData last_value_point_data2{}; + last_value_point_data2.value_ = 20l; + last_value_point_data2.is_lastvalue_valid_ = true; + last_value_point_data2.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kObservableGauge, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, last_value_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, last_value_point_data2}}}; + + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + auto check_json = + nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + + auto resource_metrics = *check_json["resource_metrics"].begin(); + // auto scope_metrics = *resource_metrics["scope_metrics"].begin(); + // auto scope = scope_metrics["scope"]; + auto instrumentation_library_metrics = + *resource_metrics["instrumentation_library_metrics"].begin(); + auto scope = instrumentation_library_metrics["instrumentation_library"]; + EXPECT_EQ("library_name", scope["name"].get()); + EXPECT_EQ("1.5.0", scope["version"].get()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto metric = *instrumentation_library_metrics["metrics"].begin(); + EXPECT_EQ("metrics_library_name", metric["name"].get()); + EXPECT_EQ("metrics_description", metric["description"].get()); + EXPECT_EQ("metrics_unit", metric["unit"].get()); + + auto data_points = metric["gauge"]["data_points"]; + EXPECT_EQ(10.0, data_points[0]["as_double"].get()); + EXPECT_EQ(20l, JsonToInteger(data_points[1]["as_int"])); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } + + void ExportBinaryIntegrationTestExportLastValuePointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + opentelemetry::sdk::metrics::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + opentelemetry::sdk::metrics::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + opentelemetry::sdk::metrics::LastValuePointData last_value_point_data{}; + last_value_point_data.value_ = 10.0; + last_value_point_data.is_lastvalue_valid_ = true; + last_value_point_data.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + opentelemetry::sdk::metrics::LastValuePointData last_value_point_data2{}; + last_value_point_data2.value_ = 20l; + last_value_point_data2.is_lastvalue_valid_ = true; + last_value_point_data2.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kObservableGauge, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, last_value_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, last_value_point_data2}}}; + + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + // auto &scope_metrics = request_body.resource_metrics(0).scope_metrics(0); + // auto &scope = instrumentation_library_metrics.scope(); + auto &instrumentation_library_metrics = + request_body.resource_metrics(0).instrumentation_library_metrics(0); + auto &scope = instrumentation_library_metrics.instrumentation_library(); + EXPECT_EQ("library_name", scope.name()); + EXPECT_EQ("1.5.0", scope.version()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto &metric = instrumentation_library_metrics.metrics(0); + EXPECT_EQ("metrics_library_name", metric.name()); + EXPECT_EQ("metrics_description", metric.description()); + EXPECT_EQ("metrics_unit", metric.unit()); + + auto &data_points = metric.gauge().data_points(); + EXPECT_EQ(10.0, data_points.Get(0).as_double()); + bool has_attributes = false; + for (auto &kv : data_points.Get(0).attributes()) + { + if (kv.key() == "a1") + { + EXPECT_EQ("b1", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + EXPECT_EQ(20, data_points.Get(1).as_int()); + has_attributes = false; + for (auto &kv : data_points.Get(1).attributes()) + { + if (kv.key() == "a2") + { + EXPECT_EQ("b2", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } + + void ExportJsonIntegrationTestExportHistogramPointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + opentelemetry::sdk::metrics::HistogramPointData histogram_point_data{}; + histogram_point_data.boundaries_ = std::list{10.1, 20.2, 30.2}; + histogram_point_data.count_ = 3; + histogram_point_data.counts_ = {200, 300, 400, 500}; + histogram_point_data.sum_ = 900.5; + opentelemetry::sdk::metrics::HistogramPointData histogram_point_data2{}; + histogram_point_data2.boundaries_ = std::list{10, 20, 30}; + histogram_point_data2.count_ = 3; + histogram_point_data2.counts_ = {200, 300, 400, 500}; + histogram_point_data2.sum_ = 900l; + + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kHistogram, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, histogram_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, histogram_point_data2}}}; + + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + auto check_json = + nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + + auto resource_metrics = *check_json["resource_metrics"].begin(); + // auto scope_metrics = *resource_metrics["scope_metrics"].begin(); + // auto scope = scope_metrics["scope"]; + auto instrumentation_library_metrics = + *resource_metrics["instrumentation_library_metrics"].begin(); + auto scope = instrumentation_library_metrics["instrumentation_library"]; + EXPECT_EQ("library_name", scope["name"].get()); + EXPECT_EQ("1.5.0", scope["version"].get()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto metric = *instrumentation_library_metrics["metrics"].begin(); + EXPECT_EQ("metrics_library_name", metric["name"].get()); + EXPECT_EQ("metrics_description", metric["description"].get()); + EXPECT_EQ("metrics_unit", metric["unit"].get()); + + auto data_points = metric["histogram"]["data_points"]; + EXPECT_EQ(3, JsonToInteger(data_points[0]["count"])); + EXPECT_EQ(900.5, data_points[0]["sum"].get()); + EXPECT_EQ(4, data_points[0]["bucket_counts"].size()); + if (4 == data_points[0]["bucket_counts"].size()) + { + EXPECT_EQ(200, JsonToInteger(data_points[0]["bucket_counts"][0])); + EXPECT_EQ(300, JsonToInteger(data_points[0]["bucket_counts"][1])); + EXPECT_EQ(400, JsonToInteger(data_points[0]["bucket_counts"][2])); + EXPECT_EQ(500, JsonToInteger(data_points[0]["bucket_counts"][3])); + } + EXPECT_EQ(3, data_points[0]["explicit_bounds"].size()); + if (3 == data_points[0]["explicit_bounds"].size()) + { + EXPECT_EQ(10.1, data_points[0]["explicit_bounds"][0].get()); + EXPECT_EQ(20.2, data_points[0]["explicit_bounds"][1].get()); + EXPECT_EQ(30.2, data_points[0]["explicit_bounds"][2].get()); + } + + EXPECT_EQ(3, JsonToInteger(data_points[1]["count"])); + EXPECT_EQ(900.0, data_points[1]["sum"].get()); + EXPECT_EQ(4, data_points[1]["bucket_counts"].size()); + if (4 == data_points[1]["bucket_counts"].size()) + { + EXPECT_EQ(200, JsonToInteger(data_points[1]["bucket_counts"][0])); + EXPECT_EQ(300, JsonToInteger(data_points[1]["bucket_counts"][1])); + EXPECT_EQ(400, JsonToInteger(data_points[1]["bucket_counts"][2])); + EXPECT_EQ(500, JsonToInteger(data_points[1]["bucket_counts"][3])); + } + EXPECT_EQ(3, data_points[1]["explicit_bounds"].size()); + if (3 == data_points[1]["explicit_bounds"].size()) + { + EXPECT_EQ(10.0, data_points[1]["explicit_bounds"][0].get()); + EXPECT_EQ(20.0, data_points[1]["explicit_bounds"][1].get()); + EXPECT_EQ(30.0, data_points[1]["explicit_bounds"][2].get()); + } + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } + + void ExportBinaryIntegrationTestExportHistogramPointData( +# ifdef ENABLE_ASYNC_EXPORT + bool async_mode +# endif + ) + { + auto mock_otlp_client = + OtlpHttpMetricExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary +# ifdef ENABLE_ASYNC_EXPORT + , + async_mode +# endif + ); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + opentelemetry::sdk::metrics::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + opentelemetry::sdk::metrics::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + opentelemetry::sdk::metrics::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.5.0"); + + opentelemetry::sdk::metrics::HistogramPointData histogram_point_data{}; + histogram_point_data.boundaries_ = std::list{10.1, 20.2, 30.2}; + histogram_point_data.count_ = 3; + histogram_point_data.counts_ = {200, 300, 400, 500}; + histogram_point_data.sum_ = 900.5; + opentelemetry::sdk::metrics::HistogramPointData histogram_point_data2{}; + histogram_point_data2.boundaries_ = std::list{10, 20, 30}; + histogram_point_data2.count_ = 3; + histogram_point_data2.counts_ = {200, 300, 400, 500}; + histogram_point_data2.sum_ = 900l; + + opentelemetry::sdk::metrics::MetricData metric_data{ + opentelemetry::sdk::metrics::InstrumentDescriptor{ + "metrics_library_name", "metrics_description", "metrics_unit", + opentelemetry::sdk::metrics::InstrumentType::kHistogram, + opentelemetry::sdk::metrics::InstrumentValueType::kDouble}, + opentelemetry::sdk::metrics::AggregationTemporality::kDelta, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector{ + {opentelemetry::sdk::metrics::PointAttributes{{"a1", "b1"}}, histogram_point_data}, + {opentelemetry::sdk::metrics::PointAttributes{{"a2", "b2"}}, histogram_point_data2}}}; + + data.instrumentation_info_metric_data_ = + std::vector{ + {instrumentation_library.get(), + std::vector{metric_data}}}; + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session]( + std::shared_ptr callback) { + opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + // auto &scope_metrics = request_body.resource_metrics(0).scope_metrics(0); + // auto &scope = instrumentation_library_metrics.scope(); + auto &instrumentation_library_metrics = + request_body.resource_metrics(0).instrumentation_library_metrics(0); + auto &scope = instrumentation_library_metrics.instrumentation_library(); + EXPECT_EQ("library_name", scope.name()); + EXPECT_EQ("1.5.0", scope.version()); + + // auto metric = *scope_metrics["metrics"].begin(); + auto &metric = instrumentation_library_metrics.metrics(0); + EXPECT_EQ("metrics_library_name", metric.name()); + EXPECT_EQ("metrics_description", metric.description()); + EXPECT_EQ("metrics_unit", metric.unit()); + + auto &data_points = metric.histogram().data_points(); + EXPECT_EQ(3, data_points.Get(0).count()); + EXPECT_EQ(900.5, data_points.Get(0).sum()); + EXPECT_EQ(4, data_points.Get(0).bucket_counts_size()); + if (4 == data_points.Get(0).bucket_counts_size()) + { + EXPECT_EQ(200, data_points.Get(0).bucket_counts(0)); + EXPECT_EQ(300, data_points.Get(0).bucket_counts(1)); + EXPECT_EQ(400, data_points.Get(0).bucket_counts(2)); + EXPECT_EQ(500, data_points.Get(0).bucket_counts(3)); + } + EXPECT_EQ(3, data_points.Get(0).explicit_bounds_size()); + if (3 == data_points.Get(0).explicit_bounds_size()) + { + EXPECT_EQ(10.1, data_points.Get(0).explicit_bounds(0)); + EXPECT_EQ(20.2, data_points.Get(0).explicit_bounds(1)); + EXPECT_EQ(30.2, data_points.Get(0).explicit_bounds(2)); + } + + bool has_attributes = false; + for (auto &kv : data_points.Get(0).attributes()) + { + if (kv.key() == "a1") + { + EXPECT_EQ("b1", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + EXPECT_EQ(3, data_points.Get(1).count()); + EXPECT_EQ(900l, data_points.Get(1).sum()); + EXPECT_EQ(4, data_points.Get(1).bucket_counts_size()); + if (4 == data_points.Get(1).bucket_counts_size()) + { + EXPECT_EQ(200, data_points.Get(1).bucket_counts(0)); + EXPECT_EQ(300, data_points.Get(1).bucket_counts(1)); + EXPECT_EQ(400, data_points.Get(1).bucket_counts(2)); + EXPECT_EQ(500, data_points.Get(1).bucket_counts(3)); + } + EXPECT_EQ(3, data_points.Get(1).explicit_bounds_size()); + if (3 == data_points.Get(1).explicit_bounds_size()) + { + EXPECT_EQ(10, data_points.Get(1).explicit_bounds(0)); + EXPECT_EQ(20, data_points.Get(1).explicit_bounds(1)); + EXPECT_EQ(30, data_points.Get(1).explicit_bounds(2)); + } + has_attributes = false; + for (auto &kv : data_points.Get(1).attributes()) + { + if (kv.key() == "a2") + { + EXPECT_EQ("b2", kv.value().string_value()); + has_attributes = true; + } + } + EXPECT_TRUE(has_attributes); + + http_client::nosend::Response response; + response.Finish(*callback.get()); + }); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + exporter->ForceFlush(); + } +}; + +TEST(OtlpHttpMetricExporterTest, Shutdown) +{ + auto exporter = + std::unique_ptr(new OtlpHttpMetricExporter()); + ASSERT_TRUE(exporter->Shutdown()); + auto result = exporter->Export(opentelemetry::sdk::metrics::ResourceMetrics{}); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + +# ifdef ENABLE_ASYNC_EXPORT +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestSumPointDataAsync) +{ + ExportJsonIntegrationTestExportSumPointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestSumPointDataSync) +{ + ExportJsonIntegrationTestExportSumPointData(false); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestSumPointDataAsync) +{ + ExportBinaryIntegrationTestExportSumPointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestSumPointDataSync) +{ + ExportBinaryIntegrationTestExportSumPointData(false); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestLastValuePointDataAsync) +{ + ExportJsonIntegrationTestExportLastValuePointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestLastValuePointDataSync) +{ + ExportJsonIntegrationTestExportLastValuePointData(false); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestLastValuePointDataAsync) +{ + ExportBinaryIntegrationTestExportLastValuePointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestLastValuePointDataSync) +{ + ExportBinaryIntegrationTestExportLastValuePointData(false); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestHistogramPointDataAsync) +{ + ExportJsonIntegrationTestExportHistogramPointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestHistogramPointDataSync) +{ + ExportJsonIntegrationTestExportHistogramPointData(false); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestHistogramPointDataAsync) +{ + ExportBinaryIntegrationTestExportHistogramPointData(true); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestHistogramPointDataSync) +{ + ExportBinaryIntegrationTestExportHistogramPointData(false); +} + +# else +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestSumPointData) +{ + ExportJsonIntegrationTestExportSumPointData(); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestSumPointData) +{ + ExportBinaryIntegrationTestExportSumPointData(); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestLastValuePointData) +{ + ExportJsonIntegrationTestExportLastValuePointData(); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestLastValuePointData) +{ + ExportBinaryIntegrationTestExportLastValuePointData(); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestHistogramPointData) +{ + ExportJsonIntegrationTestExportHistogramPointData(); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestHistogramPointData) +{ + ExportBinaryIntegrationTestExportHistogramPointData(); +} +# endif + +// Test exporter configuration options +TEST_F(OtlpHttpMetricExporterTestPeer, ConfigTest) +{ + OtlpHttpMetricExporterOptions opts; + opts.url = "http://localhost:45456/v1/metrics"; + std::unique_ptr exporter(new OtlpHttpMetricExporter(opts)); + EXPECT_EQ(GetOptions(exporter).url, "http://localhost:45456/v1/metrics"); +} + +// Test exporter configuration options with use_json_name +TEST_F(OtlpHttpMetricExporterTestPeer, ConfigUseJsonNameTest) +{ + OtlpHttpMetricExporterOptions opts; + opts.use_json_name = true; + std::unique_ptr exporter(new OtlpHttpMetricExporter(opts)); + EXPECT_EQ(GetOptions(exporter).use_json_name, true); +} + +// Test exporter configuration options with json_bytes_mapping=JsonBytesMappingKind::kHex +TEST_F(OtlpHttpMetricExporterTestPeer, ConfigJsonBytesMappingTest) +{ + OtlpHttpMetricExporterOptions opts; + opts.json_bytes_mapping = JsonBytesMappingKind::kHex; + std::unique_ptr exporter(new OtlpHttpMetricExporter(opts)); + EXPECT_EQ(GetOptions(exporter).json_bytes_mapping, JsonBytesMappingKind::kHex); +} + +# ifndef NO_GETENV +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpHttpMetricExporterTestPeer, ConfigFromEnv) +{ + const std::string url = "http://localhost:9999/v1/metrics"; + setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:9999", 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_METRICS_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr exporter(new OtlpHttpMetricExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_METRICS_HEADERS"); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, ConfigFromMetricsEnv) +{ + const std::string url = "http://localhost:9999/v1/metrics"; + setenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", url.c_str(), 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_METRICS_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr exporter(new OtlpHttpMetricExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_METRICS_HEADERS"); +} + +TEST_F(OtlpHttpMetricExporterTestPeer, DefaultEndpoint) +{ + EXPECT_EQ("http://localhost:4318/v1/metrics", GetOtlpDefaultHttpMetricEndpoint()); +} + +# endif + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/exporters/otlp/test/otlp_metrics_serialization_test.cc b/exporters/otlp/test/otlp_metrics_serialization_test.cc index 30b176ee53..22e6a2173f 100644 --- a/exporters/otlp/test/otlp_metrics_serialization_test.cc +++ b/exporters/otlp/test/otlp_metrics_serialization_test.cc @@ -18,7 +18,7 @@ namespace proto = opentelemetry::proto; namespace metrics_sdk = opentelemetry::sdk::metrics; namespace otlp_exporter = opentelemetry::exporter::otlp; -metrics_sdk::MetricData CreateSumAggregationData() +static metrics_sdk::MetricData CreateSumAggregationData() { metrics_sdk::MetricData data; data.start_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); @@ -45,7 +45,7 @@ metrics_sdk::MetricData CreateSumAggregationData() return data; } -metrics_sdk::MetricData CreateHistogramAggregationData() +static metrics_sdk::MetricData CreateHistogramAggregationData() { metrics_sdk::MetricData data; data.start_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); @@ -78,6 +78,33 @@ metrics_sdk::MetricData CreateHistogramAggregationData() return data; } +static metrics_sdk::MetricData CreateObservableGaugeAggregationData() +{ + metrics_sdk::MetricData data; + data.start_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); + metrics_sdk::InstrumentDescriptor inst_desc = {"LastValue", "desc", "unit", + metrics_sdk::InstrumentType::kObservableGauge, + metrics_sdk::InstrumentValueType::kDouble}; + metrics_sdk::LastValuePointData s_data_1, s_data_2; + s_data_1.value_ = 30.2; + s_data_2.value_ = 50.2; + + data.aggregation_temporality = metrics_sdk::AggregationTemporality::kCumulative; + data.end_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); + data.instrument_descriptor = inst_desc; + metrics_sdk::PointDataAttributes point_data_attr_1, point_data_attr_2; + point_data_attr_1.attributes = {{"k1", "v1"}}; + point_data_attr_1.point_data = s_data_1; + + point_data_attr_2.attributes = {{"k2", "v2"}}; + point_data_attr_2.point_data = s_data_1; + std::vector point_data_attr; + point_data_attr.push_back(point_data_attr_1); + point_data_attr.push_back(point_data_attr_2); + data.point_data_attr_ = std::move(point_data_attr); + return data; +} + TEST(OtlpMetricSerializationTest, Counter) { metrics_sdk::MetricData data = CreateSumAggregationData(); @@ -111,6 +138,20 @@ TEST(OtlpMetricSerializationTest, Histogram) EXPECT_EQ(1, 1); } +TEST(OtlpMetricSerializationTest, ObservableGauge) +{ + metrics_sdk::MetricData data = CreateObservableGaugeAggregationData(); + opentelemetry::proto::metrics::v1::Gauge gauge; + otlp_exporter::OtlpMetricUtils::ConvertGaugeMetric(data, &gauge); + for (size_t i = 0; i < 1; i++) + { + auto proto_number_point = gauge.data_points(i); + EXPECT_EQ(proto_number_point.as_double(), i == 0 ? 30.2 : 50.2); + } + + EXPECT_EQ(1, 1); +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE