diff --git a/CHANGELOG.md b/CHANGELOG.md index bce84c84df..66d60f3513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Increment the: * [REMOVAL] Remove build option `WITH_DEPRECATED_SDK_FACTORY` [#2717](https://github.com/open-telemetry/opentelemetry-cpp/pull/2717) +* [EXPORTER] Add in-memory metric exporter + [#3043](https://github.com/open-telemetry/opentelemetry-cpp/pull/3043) + Breaking changes: * [REMOVAL] Remove build option `WITH_DEPRECATED_SDK_FACTORY` diff --git a/exporters/memory/BUILD b/exporters/memory/BUILD index a65066480c..1082fa163c 100644 --- a/exporters/memory/BUILD +++ b/exporters/memory/BUILD @@ -4,14 +4,58 @@ package(default_visibility = ["//visibility:public"]) cc_library( - name = "in_memory_span_data", + name = "in_memory_metric_exporter", + srcs = [ + "src/in_memory_metric_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_metric_exporter.h", + ], + strip_include_prefix = "include", + tags = [ + "memory", + "test", + ], + deps = [ + ":in_memory_data", + "//sdk/src/metrics", + ], +) + +cc_test( + name = "in_memory_metric_exporter_test", + srcs = ["test/in_memory_metric_exporter_test.cc"], + tags = [ + "memory", + "test", + ], + deps = [ + ":in_memory_metric_exporter", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "in_memory_data", hdrs = [ "include/opentelemetry/exporters/memory/in_memory_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], + deps = [ + "//sdk:headers", + ], +) + +cc_library( + name = "in_memory_span_data", + hdrs = [ "include/opentelemetry/exporters/memory/in_memory_span_data.h", ], strip_include_prefix = "include", tags = ["memory"], deps = [ + ":in_memory_data", "//api", "//sdk/src/resource", "//sdk/src/trace", diff --git a/exporters/memory/CMakeLists.txt b/exporters/memory/CMakeLists.txt index 3dff044018..3e6a0bcd9b 100644 --- a/exporters/memory/CMakeLists.txt +++ b/exporters/memory/CMakeLists.txt @@ -16,9 +16,25 @@ set_target_version(opentelemetry_exporter_in_memory) target_link_libraries(opentelemetry_exporter_in_memory PUBLIC opentelemetry_trace) +add_library(opentelemetry_exporter_in_memory_metric + src/in_memory_metric_exporter.cc) + +target_include_directories( + opentelemetry_exporter_in_memory_metric + PUBLIC "$" + "$") + +set_target_properties(opentelemetry_exporter_in_memory_metric + PROPERTIES EXPORT_NAME in_memory_metric_exporter) +set_target_version(opentelemetry_exporter_in_memory_metric) + +target_link_libraries(opentelemetry_exporter_in_memory_metric + PUBLIC opentelemetry_metrics) + if(OPENTELEMETRY_INSTALL) install( TARGETS opentelemetry_exporter_in_memory + opentelemetry_exporter_in_memory_metric EXPORT "${PROJECT_NAME}-target" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -35,6 +51,8 @@ if(BUILD_TESTING) add_executable(in_memory_span_data_test test/in_memory_span_data_test.cc) add_executable(in_memory_span_exporter_test test/in_memory_span_exporter_test.cc) + add_executable(in_memory_metric_exporter_test + test/in_memory_metric_exporter_test.cc) target_link_libraries( in_memory_span_data_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} @@ -45,6 +63,11 @@ if(BUILD_TESTING) ${CMAKE_THREAD_LIBS_INIT} opentelemetry_exporter_in_memory opentelemetry_resources) + target_link_libraries( + in_memory_metric_exporter_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_exporter_in_memory_metric + opentelemetry_resources) + gtest_add_tests( TARGET in_memory_span_data_test TEST_PREFIX exporter. @@ -53,4 +76,8 @@ if(BUILD_TESTING) TARGET in_memory_span_exporter_test TEST_PREFIX exporter. TEST_LIST in_memory_span_exporter_test) + gtest_add_tests( + TARGET in_memory_metric_exporter_test + TEST_PREFIX exporter. + TEST_LIST in_memory_metric_exporter_test) endif() diff --git a/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter.h b/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter.h new file mode 100644 index 0000000000..f426b33b3f --- /dev/null +++ b/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter.h @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_data.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ + +using InMemoryMetricData = InMemoryData; + +/// A Push Metric Exporter which accumulates metrics data in memory and allows it to be inspected. +/// It is not thread-safe. +class InMemoryMetricExporter final : public sdk::metrics::PushMetricExporter +{ + using AggregationTemporalityMap = + std::map; + +public: + /// @param buffer_size a required value that sets the size of the CircularBuffer + /// @param temporality Output temporality as a function of instrument kind. + InMemoryMetricExporter(size_t buffer_size, + const sdk::metrics::AggregationTemporalitySelector &temporality) + : data_(std::make_shared(buffer_size)), temporality_(temporality) + {} + + InMemoryMetricExporter(const InMemoryMetricExporter &) = delete; + InMemoryMetricExporter(const InMemoryMetricExporter &&) = delete; + void operator=(const InMemoryMetricExporter &) = delete; + void operator=(const InMemoryMetricExporter &&) = delete; + ~InMemoryMetricExporter() override = default; + + sdk::common::ExportResult Export(const sdk::metrics::ResourceMetrics &data) noexcept override; + sdk::metrics::AggregationTemporality GetAggregationTemporality( + sdk::metrics::InstrumentType instrument_type) const noexcept override; + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + + const std::shared_ptr &GetData() const; + +private: + std::shared_ptr data_; + std::atomic is_shutdown_{false}; + sdk::metrics::AggregationTemporalitySelector temporality_; +}; + +/// A factory for InMemoryMetricExporter +class InMemoryMetricExporterFactory +{ +public: + /// Create a InMemorySpanExporter with a default buffer size and aggregation + /// temporality selector. + /// @param [out] data the InMemorySpanData the exporter will write to, + /// for the caller to inspect + /// @param [in] buffer_size number of entries to save in the circular buffer + /// @param [in] temporality output temporality as a function of instrument kind + static std::unique_ptr Create( + std::shared_ptr &data, + size_t buffer_size = 100, + const sdk::metrics::AggregationTemporalitySelector &temporality = [](auto) { + return sdk::metrics::AggregationTemporality::kCumulative; + }); +}; + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/memory/src/in_memory_metric_exporter.cc b/exporters/memory/src/in_memory_metric_exporter.cc new file mode 100644 index 0000000000..eeb2d78ae6 --- /dev/null +++ b/exporters/memory/src/in_memory_metric_exporter.cc @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_exporter.h" +#include "opentelemetry/sdk/common/global_log_handler.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ + +using opentelemetry::sdk::metrics::PushMetricExporter; +using sdk::common::ExportResult; +using sdk::metrics::AggregationTemporality; +using sdk::metrics::AggregationTemporalitySelector; +using sdk::metrics::InstrumentType; +using sdk::metrics::ResourceMetrics; + +ExportResult InMemoryMetricExporter::Export(const ResourceMetrics &data) noexcept +{ + if (is_shutdown_) + { + OTEL_INTERNAL_LOG_ERROR("[In Memory Metric Exporter] Exporting failed, exporter is shutdown"); + return ExportResult::kFailure; + } + data_->Add(std::make_unique(data)); + return ExportResult::kSuccess; +} + +AggregationTemporality InMemoryMetricExporter::GetAggregationTemporality( + InstrumentType instrument_type) const noexcept +{ + return temporality_(instrument_type); +} + +bool InMemoryMetricExporter::ForceFlush(std::chrono::microseconds /* timeout */) noexcept +{ + return true; +} + +bool InMemoryMetricExporter::Shutdown(std::chrono::microseconds /* timeout */) noexcept +{ + is_shutdown_ = true; + return true; +} + +const std::shared_ptr &InMemoryMetricExporter::GetData() const +{ + return data_; +} + +std::unique_ptr InMemoryMetricExporterFactory::Create( + std::shared_ptr &data, + size_t buffer_size, + const AggregationTemporalitySelector &temporality) +{ + auto result = std::make_unique(buffer_size, temporality); + data = result->GetData(); + return result; +} + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/memory/test/in_memory_metric_exporter_test.cc b/exporters/memory/test/in_memory_metric_exporter_test.cc new file mode 100644 index 0000000000..3a9bafa62f --- /dev/null +++ b/exporters/memory/test/in_memory_metric_exporter_test.cc @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_exporter.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/resource/resource.h" + +#include + +using opentelemetry::exporter::memory::InMemoryMetricData; +using opentelemetry::exporter::memory::InMemoryMetricExporterFactory; +using opentelemetry::sdk::common::ExportResult; +using opentelemetry::sdk::metrics::AggregationTemporality; +using opentelemetry::sdk::metrics::InstrumentType; +using opentelemetry::sdk::metrics::PushMetricExporter; +using opentelemetry::sdk::metrics::ResourceMetrics; +using opentelemetry::sdk::metrics::ScopeMetrics; +using opentelemetry::sdk::resource::Resource; + +class InMemoryMetricExporterTest : public ::testing::Test +{ +protected: + InMemoryMetricExporterTest() { exporter_ = InMemoryMetricExporterFactory::Create(data_); } + + std::unique_ptr exporter_; + std::shared_ptr data_; + + Resource resource_ = Resource::GetEmpty(); + ResourceMetrics resource_metrics_{&resource_, std::vector{}}; +}; + +TEST_F(InMemoryMetricExporterTest, Export) +{ + EXPECT_EQ(exporter_->Export(resource_metrics_), ExportResult::kSuccess); + + auto data = data_->Get(); + EXPECT_EQ(data.size(), 1); + EXPECT_EQ((*data.begin())->resource_, &resource_); +} + +TEST_F(InMemoryMetricExporterTest, ForceFlush) +{ + EXPECT_TRUE(exporter_->ForceFlush()); +} + +TEST_F(InMemoryMetricExporterTest, Shutdown) +{ + EXPECT_TRUE(exporter_->Shutdown()); + EXPECT_EQ(exporter_->Export(resource_metrics_), ExportResult::kFailure); +} + +TEST_F(InMemoryMetricExporterTest, TemporalitySelector) +{ + EXPECT_EQ(exporter_->GetAggregationTemporality(InstrumentType::kCounter), + AggregationTemporality::kCumulative); +}