diff --git a/exporters/memory/BUILD b/exporters/memory/BUILD index daa0c662ff..c608a4acde 100644 --- a/exporters/memory/BUILD +++ b/exporters/memory/BUILD @@ -60,3 +60,33 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_library( + 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", + "//exporters/otlp:otlp_metric_utils", + "//sdk/src/metrics", + "@com_github_opentelemetry_proto//:metrics_proto_cc", + ], +) + +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", + ], +) diff --git a/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h b/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h new file mode 100644 index 0000000000..83e72ad679 --- /dev/null +++ b/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/exporters/memory/in_memory_data.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ + +class InMemoryMetricData final : public exporter::memory::InMemoryData +{ +public: + /** + * @param buffer_size a required value that sets the size of the CircularBuffer + */ + explicit InMemoryMetricData(size_t buffer_size) + : exporter::memory::InMemoryData(buffer_size) + {} + + std::vector> GetMetrics() noexcept { return Get(); } +}; +} // namespace memory +} // namespace exporter 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..449b633124 --- /dev/null +++ b/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter.h @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW + +# include +# include +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/exporters/memory/in_memory_metric_data.h" +# include "opentelemetry/sdk/common/circular_buffer.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +const size_t MAX_BUFFER_SIZE = 100; + +/** + * The in-memory metrics exporter exports metrics data to the memory. + * + */ +class InMemoryMetricExporter final : public opentelemetry::sdk::metrics::MetricExporter +{ +public: + /** + * Create an InMemoryMetricExporter. The constructor takes the buffer size as the input and + * initializes the ring buffer with the given size. + * + */ + explicit InMemoryMetricExporter(size_t buffer_size = MAX_BUFFER_SIZE) + : data_(new InMemoryMetricData(buffer_size)) + {} + + sdk::common::ExportResult Export(const sdk::metrics::ResourceMetrics &data) noexcept override; + + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + bool Shutdown( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + /** + * @return Returns a shared pointer to this exporters InMemoryMetricData. + */ + inline InMemoryMetricData &GetData() noexcept { return *data_; } + +private: + bool isShutdown() const noexcept; + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + + std::unique_ptr data_ = nullptr; +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif 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..353a6583f1 --- /dev/null +++ b/exporters/memory/src/in_memory_metric_exporter.cc @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/memory/in_memory_metric_exporter.h" +# include "opentelemetry/exporters/otlp/otlp_metric_utils.h" +# include "opentelemetry/sdk_config.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +sdk::common::ExportResult InMemoryMetricExporter::Export( + const sdk::metrics::ResourceMetrics &data) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[InMemory Metric] Exporting " + << data.instrumentation_info_metric_data_.size() + << " records(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + std::unique_ptr resource_metrics( + new proto::metrics::v1::ResourceMetrics); + otlp::OtlpMetricUtils::PopulateResourceMetrics(data, resource_metrics.get()); + data_->Add(std::move(resource_metrics)); + return sdk::common::ExportResult::kSuccess; +} + +bool InMemoryMetricExporter::ForceFlush(std::chrono::microseconds timeout) noexcept +{ + return true; +} + +bool InMemoryMetricExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard locked(lock_); + is_shutdown_ = true; + return true; +} + +bool InMemoryMetricExporter::isShutdown() const noexcept +{ + const std::lock_guard locked(lock_); + return is_shutdown_; +} +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif 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..ed39fd5da6 --- /dev/null +++ b/exporters/memory/test/in_memory_metric_exporter_test.cc @@ -0,0 +1,299 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/memory/in_memory_metric_exporter.h" +# include "google/protobuf/text_format.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" + +# include +# include + +# include + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +namespace memoryexporter = opentelemetry::exporter::memory; + +MATCHER_P(EqualsProto, pbtext, "") +{ + opentelemetry::proto::metrics::v1::ResourceMetrics p; + if (!::google::protobuf::TextFormat::ParseFromString(pbtext, &p)) + { + return false; + } + return p.DebugString() == arg->DebugString(); +} + +TEST(InMemoryMetricExporter, Shutdown) +{ + auto exporter = + std::unique_ptr(new memoryexporter::InMemoryMetricExporter); + + ASSERT_TRUE(exporter->Shutdown()); + auto result = exporter->Export(metric_sdk::ResourceMetrics{}); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + +TEST(InMemoryMetricExporter, ExportSumPointData) +{ + auto exporter = + std::unique_ptr(new memoryexporter::InMemoryMetricExporter); + + metric_sdk::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + metric_sdk::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + metric_sdk::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.2.0"); + auto attributes = metric_sdk::PointAttributes{{"a1", "b1"}}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{{attributes, sum_point_data}, + {attributes, sum_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + auto in_memory_exporter = static_cast(exporter.get()); + auto in_memory_data = in_memory_exporter->GetData().Get(); + EXPECT_EQ(in_memory_data.size(), 1); + EXPECT_THAT(in_memory_data[0], EqualsProto(R"pb( + resource { + attributes { + key: "service.name" + value { string_value: "unknown_service" } + } + attributes { + key: "telemetry.sdk.version" + value { string_value: "1.4.1" } + } + attributes { + key: "telemetry.sdk.name" + value { string_value: "opentelemetry" } + } + attributes { + key: "telemetry.sdk.language" + value { string_value: "cpp" } + } + } + instrumentation_library_metrics { + instrumentation_library { name: "library_name" version: "1.2.0" } + metrics { + name: "library_name" + description: "description" + unit: "unit" + sum { + data_points { + as_double: 10 + attributes { + key: "a1" + value { string_value: "b1" } + } + } + data_points { + as_double: 20 + attributes { + key: "a1" + value { string_value: "b1" } + } + } + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + is_monotonic: true + } + } + } + )pb")); +} + +TEST(InMemoryMetricExporter, ExportHistogramPointData) +{ + auto exporter = + std::unique_ptr(new memoryexporter::InMemoryMetricExporter); + + metric_sdk::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; + metric_sdk::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; + metric_sdk::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.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kHistogram, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}, {"a2", "b2"}}, histogram_point_data}, + {metric_sdk::PointAttributes{{"a1", "b1"}}, histogram_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + auto in_memory_exporter = static_cast(exporter.get()); + auto in_memory_data = in_memory_exporter->GetData().Get(); + EXPECT_EQ(in_memory_data.size(), 1); + EXPECT_THAT(in_memory_data[0], EqualsProto(R"pb( + resource { + attributes { + key: "service.name" + value { string_value: "unknown_service" } + } + attributes { + key: "telemetry.sdk.version" + value { string_value: "1.4.1" } + } + attributes { + key: "telemetry.sdk.name" + value { string_value: "opentelemetry" } + } + attributes { + key: "telemetry.sdk.language" + value { string_value: "cpp" } + } + } + instrumentation_library_metrics { + instrumentation_library { name: "library_name" version: "1.2.0" } + metrics { + name: "library_name" + description: "description" + unit: "unit" + histogram { + data_points { + count: 3 + sum: 900.5 + bucket_counts: 200 + bucket_counts: 300 + bucket_counts: 400 + bucket_counts: 500 + explicit_bounds: 10.1 + explicit_bounds: 20.2 + explicit_bounds: 30.2 + attributes { + key: "a1" + value { string_value: "b1" } + } + attributes { + key: "a2" + value { string_value: "b2" } + } + } + data_points { + count: 3 + sum: 900 + bucket_counts: 200 + bucket_counts: 300 + bucket_counts: 400 + bucket_counts: 500 + explicit_bounds: 10 + explicit_bounds: 20 + explicit_bounds: 30 + attributes { + key: "a1" + value { string_value: "b1" } + } + } + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + } + } + } + )pb")); +} + +TEST(InMemoryMetricExporter, ExportLastValuePointData) +{ + auto exporter = + std::unique_ptr(new memoryexporter::InMemoryMetricExporter); + + metric_sdk::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.2.0"); + metric_sdk::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{}; + metric_sdk::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{}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kObservableGauge, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{}, last_value_point_data}, + {metric_sdk::PointAttributes{}, last_value_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + + auto in_memory_exporter = static_cast(exporter.get()); + auto in_memory_data = in_memory_exporter->GetData().Get(); + EXPECT_EQ(in_memory_data.size(), 1); + EXPECT_THAT(in_memory_data[0], EqualsProto(R"pb( + resource { + attributes { + key: "service.name" + value { string_value: "unknown_service" } + } + attributes { + key: "telemetry.sdk.version" + value { string_value: "1.4.1" } + } + attributes { + key: "telemetry.sdk.name" + value { string_value: "opentelemetry" } + } + attributes { + key: "telemetry.sdk.language" + value { string_value: "cpp" } + } + } + instrumentation_library_metrics { + instrumentation_library { name: "library_name" version: "1.2.0" } + metrics { + name: "library_name" + description: "description" + unit: "unit" + gauge { + data_points { as_double: 10 } + data_points { as_int: 20 } + } + } + } + )pb")); +} + +#endif