diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index c8d4c5b546..95a969c64d 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", @@ -155,6 +157,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 = [ @@ -257,6 +283,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 a57ec80b24..a7871ab56f 100755 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -233,5 +233,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_http_client.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h index 3e1fc2639f..2a600b9890 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h @@ -150,6 +150,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 @@ -171,6 +176,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 { @@ -224,11 +235,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_metric_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h index 90c4b45041..311f68494e 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter.h @@ -82,6 +82,12 @@ class OtlpHttpMetricExporter final : public opentelemetry::sdk::metrics::MetricE 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; 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..1616ea3aec 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -67,6 +67,11 @@ std::unique_ptr OtlpHttpExporter::MakeRec opentelemetry::sdk::common::ExportResult OtlpHttpExporter::Export( const nostd::span> &spans) noexcept { + if (http_client_->IsShutdown()) + { + 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..11141344c2 100644 --- a/exporters/otlp/src/otlp_http_log_exporter.cc +++ b/exporters/otlp/src/otlp_http_log_exporter.cc @@ -69,6 +69,11 @@ std::unique_ptr OtlpHttpLogExporter::MakeR opentelemetry::sdk::common::ExportResult OtlpHttpLogExporter::Export( const nostd::span> &logs) noexcept { + if (http_client_->IsShutdown()) + { + 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 index 94250de933..e91a37dff8 100644 --- a/exporters/otlp/src/otlp_http_metric_exporter.cc +++ b/exporters/otlp/src/otlp_http_metric_exporter.cc @@ -4,7 +4,7 @@ #ifndef ENABLE_METRICS_PREVIEW # include "opentelemetry/exporters/otlp/otlp_http_metric_exporter.h" -# include "opentelemetry/exporters/otlp/otlp_metrics_utils.h" +# include "opentelemetry/exporters/otlp/otlp_metric_utils.h" # include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" @@ -64,12 +64,17 @@ OtlpHttpMetricExporter::OtlpHttpMetricExporter(std::unique_ptr h opentelemetry::sdk::common::ExportResult OtlpHttpMetricExporter::Export( const opentelemetry::sdk::metrics::ResourceMetrics &data) noexcept { + if (http_client_->IsShutdown()) + { + 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; - OtlpMetricsUtils::PopulateRequest(data, &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]( @@ -103,6 +108,11 @@ opentelemetry::sdk::common::ExportResult OtlpHttpMetricExporter::Export( # 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); 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..dbadb7b78e --- /dev/null +++ b/exporters/otlp/test/otlp_http_metric_exporter_test.cc @@ -0,0 +1,451 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# 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 nostd::span MakeSpan(T (&array)[N]) +{ + return nostd::span(array); +} + +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, &data, &resource]( + 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, &data, &resource]( + 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(); + } +}; + +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); +} +# else +TEST_F(OtlpHttpMetricExporterTestPeer, ExportJsonIntegrationTestSumPointData) +{ + ExportJsonIntegrationTestExportSumPointData(); +} +TEST_F(OtlpHttpMetricExporterTestPeer, ExportBinaryIntegrationTestSumPointData) +{ + ExportBinaryIntegrationTestExportSumPointData(); +} +# 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