From bf61a77f0bb249a0a8db73bf939741ce7279544d Mon Sep 17 00:00:00 2001 From: Erin McNulty Date: Mon, 15 Dec 2025 17:57:08 -0500 Subject: [PATCH] SERVER-115315 Set up MockExporter for unit testing the Metrics Service (#45192) GitOrigin-RevId: ed27957a906158f6e382e6e94c4ca56ce4db7419 --- bazel/mongo_src_rules.bzl | 1 + src/mongo/otel/metrics/BUILD.bazel | 88 ++++++---- .../metrics/metrics_initialization_test.cpp | 65 ++++--- src/mongo/otel/metrics/metrics_service.cpp | 14 -- src/mongo/otel/metrics/metrics_service.h | 13 +- .../otel/metrics/metrics_service_test.cpp | 50 ++---- src/mongo/otel/metrics/metrics_test_util.h | 136 +++++++++++++++ .../opentelemetry-cpp/exporters/memory/BUILD | 107 ++++++++++++ .../exporters/memory/in_memory_data.h | 62 +++++++ .../exporters/memory/in_memory_metric_data.h | 72 ++++++++ .../in_memory_metric_exporter_factory.h | 44 +++++ .../exporters/memory/in_memory_span_data.h | 33 ++++ .../memory/in_memory_span_exporter.h | 101 +++++++++++ .../memory/in_memory_span_exporter_factory.h | 46 +++++ .../memory/src/in_memory_metric_data.cc | 54 ++++++ .../src/in_memory_metric_exporter_factory.cc | 93 ++++++++++ .../src/in_memory_span_exporter_factory.cc | 33 ++++ .../memory/test/in_memory_metric_data_test.cc | 55 ++++++ .../test/in_memory_metric_exporter_test.cc | 60 +++++++ .../memory/test/in_memory_span_data_test.cc | 27 +++ .../test/in_memory_span_exporter_test.cc | 29 ++++ ...SERVER-115315-vendor-memory-exporter.patch | 163 ++++++++++++++++++ .../opentelemetry-cpp/scripts/import.sh | 2 +- 23 files changed, 1211 insertions(+), 137 deletions(-) create mode 100644 src/mongo/otel/metrics/metrics_test_util.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/BUILD create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_data.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter_factory.h create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_data.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_exporter_factory.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_span_exporter_factory.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_data_test.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_exporter_test.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc create mode 100644 src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc create mode 100644 src/third_party/opentelemetry-cpp/patches/0004-SERVER-115315-vendor-memory-exporter.patch diff --git a/bazel/mongo_src_rules.bzl b/bazel/mongo_src_rules.bzl index 6df7b16eee5..8326f05ded4 100644 --- a/bazel/mongo_src_rules.bzl +++ b/bazel/mongo_src_rules.bzl @@ -513,6 +513,7 @@ def mongo_cc_library( visibility = visibility, deps = deps + cc_deps, exec_properties = exec_properties, + testonly = testonly, ) def _mongo_cc_binary_and_test( diff --git a/src/mongo/otel/metrics/BUILD.bazel b/src/mongo/otel/metrics/BUILD.bazel index 0a4cb61d2ea..9490ebead8b 100644 --- a/src/mongo/otel/metrics/BUILD.bazel +++ b/src/mongo/otel/metrics/BUILD.bazel @@ -18,38 +18,12 @@ idl_generator( src = "metrics_settings.idl", ) -mongo_cc_unit_test( - name = "metrics_counter_test", - srcs = [ - "metrics_counter_test.cpp", - ], - tags = ["mongo_unittest_third_group"], -) - -mongo_cc_library( - name = "metrics_service", - srcs = [ - "metrics_service.cpp", - ], - deps = [ - "//src/mongo/db:server_base", - ] + select( - { - "@//bazel/config:build_otel_enabled": [ - "//src/third_party/opentelemetry-cpp/api", - ], - "//conditions:default": [], - }, - ), -) - mongo_cc_library( name = "metrics_initialization", srcs = [ "metrics_initialization.cpp", "metrics_settings_gen", ], - no_undefined_ref_DO_NOT_USE = False, deps = [ "//src/mongo/db:server_base", ] + select( @@ -79,10 +53,28 @@ mongo_cc_unit_test( tags = ["mongo_unittest_second_group"], target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, deps = [ + ":in_memory_metrics_exporter", ":metrics_initialization", ], ) +mongo_cc_library( + name = "metrics_service", + srcs = [ + "metrics_service.cpp", + ], + deps = [ + "//src/mongo/db:server_base", + ] + select( + { + "@//bazel/config:build_otel_enabled": [ + "//src/third_party/opentelemetry-cpp/api", + ], + "//conditions:default": [], + }, + ), +) + mongo_cc_unit_test( name = "metrics_service_test", srcs = [ @@ -91,6 +83,7 @@ mongo_cc_unit_test( tags = ["mongo_unittest_first_group"], target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, deps = [ + ":in_memory_metrics_exporter", ":metrics_initialization", ":metrics_service", "//src/mongo/db:service_context_test_fixture", @@ -99,18 +92,6 @@ mongo_cc_unit_test( ], ) -mongo_cc_unit_test( - name = "metrics_histogram_test", - srcs = [ - "metrics_histogram_test.cpp", - ], - tags = ["mongo_unittest_sixth_group"], - target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, - deps = [ - "//src/third_party/opentelemetry-cpp/api", - ], -) - mongo_cc_library( name = "metric_units", srcs = ["metric_units.cpp"], @@ -123,3 +104,34 @@ mongo_cc_unit_test( tags = ["mongo_unittest_second_group"], deps = [":metric_units"], ) + +mongo_cc_unit_test( + name = "metrics_counter_test", + srcs = [ + "metrics_counter_test.cpp", + ], + tags = ["mongo_unittest_third_group"], +) + +mongo_cc_unit_test( + name = "metrics_histogram_test", + srcs = [ + "metrics_histogram_test.cpp", + ], + tags = ["mongo_unittest_sixth_group"], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + deps = [ + "//src/third_party/opentelemetry-cpp/api", + ], +) + +mongo_cc_library( + name = "in_memory_metrics_exporter", + testonly = True, + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + deps = [ + "//src/third_party/opentelemetry-cpp/exporters/memory:in_memory_data", + "//src/third_party/opentelemetry-cpp/exporters/memory:in_memory_metric_data", + "//src/third_party/opentelemetry-cpp/exporters/memory:in_memory_metric_exporter_factory", + ], +) diff --git a/src/mongo/otel/metrics/metrics_initialization_test.cpp b/src/mongo/otel/metrics/metrics_initialization_test.cpp index 6f41f1ad3ab..03f43c114b8 100644 --- a/src/mongo/otel/metrics/metrics_initialization_test.cpp +++ b/src/mongo/otel/metrics/metrics_initialization_test.cpp @@ -34,14 +34,14 @@ #include "mongo/idl/server_parameter_test_controller.h" #include "mongo/otel/metrics/metrics_initialization.h" #include "mongo/otel/metrics/metrics_settings_gen.h" +#include "mongo/otel/metrics/metrics_test_util.h" #include "mongo/unittest/temp_dir.h" #include "mongo/unittest/unittest.h" #include #include -namespace mongo { -namespace otel { +namespace mongo::otel::metrics { namespace { class OtelMetricsInitializationTest : public unittest::Test { @@ -64,44 +64,40 @@ private: RAIIServerParameterControllerForTest _featureFlagController{"featureFlagOtelMetrics", true}; }; -bool isNoop(opentelemetry::metrics::MeterProvider* provider) { - return !!dynamic_cast(provider); -} - TEST_F(OtelMetricsInitializationTest, NoMeterProvider) { - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, Shutdown) { RAIIServerParameterControllerForTest param{"openTelemetryMetricsDirectory", getMetricsPath()}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_FALSE(isNoop(provider.get())); + ASSERT_FALSE(isNoopMeterProvider(provider.get())); ASSERT_NOT_EQUALS(provider.get(), nullptr); - metrics::shutdown(); + shutdown(); provider = opentelemetry::metrics::Provider::GetMeterProvider(); ASSERT_EQ(provider.get(), nullptr); } TEST_F(OtelMetricsInitializationTest, FileMeterProvider) { RAIIServerParameterControllerForTest param{"openTelemetryMetricsDirectory", getMetricsPath()}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_FALSE(isNoop(provider.get())); + ASSERT_FALSE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, HttpMeterProvider) { RAIIServerParameterControllerForTest param{"openTelemetryMetricsHttpEndpoint", "http://localhost:4318/v1/traces"}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_FALSE(isNoop(provider.get())); + ASSERT_FALSE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, HttpAndDirectory) { @@ -109,37 +105,37 @@ TEST_F(OtelMetricsInitializationTest, HttpAndDirectory) { "http://localhost:4318/v1/traces"}; RAIIServerParameterControllerForTest directoryParam{"openTelemetryMetricsDirectory", getMetricsPath()}; - auto status = metrics::initialize(); + auto status = initialize(); ASSERT_FALSE(status.isOK()); ASSERT_EQ(status.codeString(), "InvalidOptions"); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, FeatureFlagDisabledNoParams) { RAIIServerParameterControllerForTest featureFlagController{"featureFlagOtelMetrics", false}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, FeatureFlagDisabledDirectorySet) { RAIIServerParameterControllerForTest featureFlagController{"featureFlagOtelMetrics", false}; RAIIServerParameterControllerForTest param{"openTelemetryMetricsDirectory", getMetricsPath()}; - ASSERT_EQ(metrics::initialize().code(), ErrorCodes::InvalidOptions); + ASSERT_EQ(initialize().code(), ErrorCodes::InvalidOptions); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, FeatureFlagDisabledHttpSet) { RAIIServerParameterControllerForTest featureFlagController{"featureFlagOtelMetrics", false}; RAIIServerParameterControllerForTest param{"openTelemetryMetricsHttpEndpoint", "http://localhost:4318/v1/traces"}; - ASSERT_EQ(metrics::initialize().code(), ErrorCodes::InvalidOptions); + ASSERT_EQ(initialize().code(), ErrorCodes::InvalidOptions); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } TEST_F(OtelMetricsInitializationTest, InvalidCompressionParam) { @@ -148,9 +144,9 @@ TEST_F(OtelMetricsInitializationTest, InvalidCompressionParam) { "http://localhost:4318/v1/traces"}; RAIIServerParameterControllerForTest compressionParam{"openTelemetryMetricsCompression", "foo"}; - ASSERT_EQ(metrics::initialize().code(), ErrorCodes::InvalidOptions); + ASSERT_EQ(initialize().code(), ErrorCodes::InvalidOptions); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } RAIIServerParameterControllerForTest directoryParam{"openTelemetryMetricsDirectory", @@ -158,9 +154,9 @@ TEST_F(OtelMetricsInitializationTest, InvalidCompressionParam) { for (const auto& value : {"gzip", "foo"}) { RAIIServerParameterControllerForTest compressionParam{"openTelemetryMetricsCompression", value}; - ASSERT_EQ(metrics::initialize().code(), ErrorCodes::InvalidOptions); + ASSERT_EQ(initialize().code(), ErrorCodes::InvalidOptions); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_TRUE(isNoop(provider.get())); + ASSERT_TRUE(isNoopMeterProvider(provider.get())); } } @@ -171,13 +167,13 @@ TEST_F(OtelMetricsInitializationTest, ValidCompressionParam) { for (const auto& value : {"gzip", "none"}) { RAIIServerParameterControllerForTest compressionParam{"openTelemetryMetricsCompression", value}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_FALSE(isNoop(provider.get())); + ASSERT_FALSE(isNoopMeterProvider(provider.get())); ASSERT_NOT_EQUALS(provider.get(), nullptr); - metrics::shutdown(); + shutdown(); } } @@ -185,16 +181,15 @@ TEST_F(OtelMetricsInitializationTest, ValidCompressionParam) { getMetricsPath()}; RAIIServerParameterControllerForTest compressionParam{"openTelemetryMetricsCompression", "none"}; - ASSERT_OK(metrics::initialize()); + ASSERT_OK(initialize()); auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - ASSERT_FALSE(isNoop(provider.get())); + ASSERT_FALSE(isNoopMeterProvider(provider.get())); ASSERT_NOT_EQUALS(provider.get(), nullptr); - metrics::shutdown(); + shutdown(); } } // namespace -} // namespace otel -} // namespace mongo +} // namespace mongo::otel::metrics #endif diff --git a/src/mongo/otel/metrics/metrics_service.cpp b/src/mongo/otel/metrics/metrics_service.cpp index 02203921bd5..7ef2e829bb9 100644 --- a/src/mongo/otel/metrics/metrics_service.cpp +++ b/src/mongo/otel/metrics/metrics_service.cpp @@ -30,26 +30,12 @@ #include "mongo/otel/metrics/metrics_service.h" -#ifdef MONGO_CONFIG_OTEL -#include -#endif - namespace mongo::otel::metrics { namespace { const auto& getMetricsService = ServiceContext::declareDecoration(); } // namespace -#ifdef MONGO_CONFIG_OTEL -MetricsService::MetricsService() { - auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); - invariant(provider, "Attempted to get the MeterProvider after shutdown() was called"); - _meter = provider->GetMeter(std::string{kMeterName}); -} -#else -MetricsService::MetricsService() {} -#endif - MetricsService& MetricsService::get(ServiceContext* serviceContext) { return getMetricsService(serviceContext); } diff --git a/src/mongo/otel/metrics/metrics_service.h b/src/mongo/otel/metrics/metrics_service.h index 4c9beadc5c1..4e0ad034787 100644 --- a/src/mongo/otel/metrics/metrics_service.h +++ b/src/mongo/otel/metrics/metrics_service.h @@ -36,7 +36,7 @@ #ifdef MONGO_CONFIG_OTEL #include - +#include namespace mongo::otel::metrics { @@ -51,21 +51,18 @@ public: static MetricsService& get(ServiceContext*); - MetricsService(); - // TODO SERVER-114945 Remove this method once we can validate meter construction succeeded via // the Instruments it produces opentelemetry::metrics::Meter* getMeter_forTest() const { - return _meter.get(); + auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); + invariant(provider, "Attempted to get the MeterProvider after shutdown() was called"); + return provider->GetMeter(std::string{kMeterName}).get(); } // TODO SERVER-114945 Add MetricsService::createUInt64Counter method // TODO SERVER-114954 Implement MetricsService::createUInt64Gauge // TODO SERVER-114955 Implement MetricsService::createDoubleGauge // TODO SERVER-115164 Implement MetricsService::createHistogram method - -private: - std::shared_ptr _meter{nullptr}; }; } // namespace mongo::otel::metrics #else @@ -75,8 +72,6 @@ public: static constexpr StringData kMeterName = "mongodb"; static MetricsService& get(ServiceContext*); - - MetricsService(); }; } // namespace mongo::otel::metrics #endif diff --git a/src/mongo/otel/metrics/metrics_service_test.cpp b/src/mongo/otel/metrics/metrics_service_test.cpp index b78a0df84ef..a892d2953c8 100644 --- a/src/mongo/otel/metrics/metrics_service_test.cpp +++ b/src/mongo/otel/metrics/metrics_service_test.cpp @@ -31,9 +31,8 @@ #include "mongo/config.h" #include "mongo/db/service_context_test_fixture.h" -#include "mongo/idl/server_parameter_test_controller.h" #include "mongo/otel/metrics/metrics_initialization.h" -#include "mongo/unittest/temp_dir.h" +#include "mongo/otel/metrics/metrics_test_util.h" #include "mongo/unittest/unittest.h" #include @@ -42,41 +41,19 @@ namespace mongo::otel::metrics { namespace { -/** - * Helper class that initializes OpenTelemetry metrics before ServiceContextTest - * creates the ServiceContext. This ensures MetricsService decoration gets a real - * SDK MeterProvider instead of a NoopMeterProvider. - */ -class MetricsInitializationHelper { -public: - MetricsInitializationHelper() - : _featureFlagController("featureFlagOtelMetrics", true), - _directoryController("openTelemetryMetricsDirectory", _tempMetricsDir.path()) { - // Initialize metrics before ServiceContext is created - auto status = metrics::initialize(); - invariant(status.isOK(), status.reason()); - } - ~MetricsInitializationHelper() { - metrics::shutdown(); - } - -private: - unittest::TempDir _tempMetricsDir{"otel_metrics_test"}; - RAIIServerParameterControllerForTest _featureFlagController; - RAIIServerParameterControllerForTest _directoryController; -}; - -// MetricsInitializationHelper must come before ServiceContextTest in inheritance -// so that metrics are initialized before ServiceContext is created. -class MetricsServiceTest : public MetricsInitializationHelper, public ServiceContextTest {}; +class MetricsServiceTest : public ServiceContextTest {}; +// Assert that when a valid MeterProvider in place, we create a working Meter implementation with +// the expected metadata. TEST_F(MetricsServiceTest, MeterIsInitialized) { + // Set up a valid MeterProvider. + OtelMetricsCapturer metricsCapturer; + const auto& metricsService = MetricsService::get(getServiceContext()); auto* meter = metricsService.getMeter_forTest(); ASSERT_TRUE(meter); - // Cast to SDK Meter to access GetInstrumentationScope auto* sdkMeter = dynamic_cast(meter); ASSERT_TRUE(sdkMeter); @@ -85,18 +62,11 @@ TEST_F(MetricsServiceTest, MeterIsInitialized) { ASSERT_EQ(scope->GetName(), std::string{MetricsService::kMeterName}); } -// Assert that we create a NoopMeter if the global MeterProvider hasn't been set before -// initialization. -class MetricsServiceBadInitializationTest : public ServiceContextTest {}; - -bool isNoop(opentelemetry::metrics::Meter* provider) { - return !!dynamic_cast(provider); -} - -TEST_F(MetricsServiceBadInitializationTest, ServiceContextInitBeforeMeterProvider) { +// Assert that we create a NoopMeter if the global MeterProvider hasn't been set. +TEST_F(MetricsServiceTest, ServiceContextInitBeforeMeterProvider) { const auto& metricsService = MetricsService::get(getServiceContext()); auto* meter = metricsService.getMeter_forTest(); - ASSERT_TRUE(isNoop(meter)); + ASSERT_TRUE(isNoopMeter(meter)); } } // namespace diff --git a/src/mongo/otel/metrics/metrics_test_util.h b/src/mongo/otel/metrics/metrics_test_util.h new file mode 100644 index 00000000000..77d265c70ae --- /dev/null +++ b/src/mongo/otel/metrics/metrics_test_util.h @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/idl/server_parameter_test_controller.h" +#include "mongo/util/modules.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mongo::otel::metrics { + +namespace { +/** + * The MetricReader is the OTel component that connects the exporter with each registered + * Instrument. This implementation allows callers to trigger collection on-demand so that unit tests + * can collect metrics in a predictable fashion. + */ +class OnDemandMetricReader : public opentelemetry::sdk::metrics::MetricReader { +public: + OnDemandMetricReader(std::unique_ptr exporter) + : _exporter{std::move(exporter)} {} + + opentelemetry::sdk::metrics::AggregationTemporality GetAggregationTemporality( + opentelemetry::sdk::metrics::InstrumentType instrument_type) const noexcept override { + return _exporter->GetAggregationTemporality(instrument_type); + } + + void triggerMetricExport() { + this->Collect([this](opentelemetry::sdk::metrics::ResourceMetrics& metric_data) { + this->_exporter->Export(metric_data); + return true; + }); + } + +private: + bool OnForceFlush(std::chrono::microseconds) noexcept override { + return true; + } + + bool OnShutDown(std::chrono::microseconds) noexcept override { + return true; + } + + std::unique_ptr _exporter; +}; +} // namespace + +bool isNoopMeter(opentelemetry::metrics::Meter* provider) { + return !!dynamic_cast(provider); +} + +bool isNoopMeterProvider(opentelemetry::metrics::MeterProvider* provider) { + return !!dynamic_cast(provider); +} + +/** + * Sets up a MetricProvider with an in-memory exporter so tests can create and inspect metrics. + * This must be constructed before creating any metrics in order to capture them. + */ +class MONGO_MOD_PUBLIC OtelMetricsCapturer { +public: + OtelMetricsCapturer() { + invariant(isNoopMeterProvider(opentelemetry::metrics::Provider::GetMeterProvider().get())); + + auto metrics = + std::make_shared(); + _metrics = metrics.get(); + + auto exporter = opentelemetry::exporter::memory::InMemoryMetricExporterFactory::Create( + std::move(metrics)); + + auto reader = std::make_shared(std::move(exporter)); + _reader = reader.get(); + + std::shared_ptr provider = + opentelemetry::sdk::metrics::MeterProviderFactory::Create(); + provider->AddMetricReader(std::move(reader)); + opentelemetry::metrics::Provider::SetMeterProvider(std::move(provider)); + } + + ~OtelMetricsCapturer() { + opentelemetry::metrics::Provider::SetMeterProvider( + opentelemetry::nostd::shared_ptr( + new opentelemetry::metrics::NoopMeterProvider())); + } + + // TODO SERVER-115538 When we actually have instrument reading to consume, implement a wrapper + // around SimpleAggregateInMemoryMetricData::Get that easily exposes the data we need. + +private: + RAIIServerParameterControllerForTest _featureFlagController{"featureFlagOtelMetrics", true}; + + // Stash the reader so that callers can trigger on-demand metric collection. + OnDemandMetricReader* _reader; + // This is the in-memory data structure that holds the collected metrics. The exporter writes to + // this DS, and the get() function will read from it. + opentelemetry::exporter::memory::SimpleAggregateInMemoryMetricData* _metrics; +}; + +} // namespace mongo::otel::metrics diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/BUILD b/src/third_party/opentelemetry-cpp/exporters/memory/BUILD new file mode 100644 index 00000000000..377d6025f77 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/BUILD @@ -0,0 +1,107 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +load("//bazel:mongo_src_rules.bzl", "mongo_cc_library") +load("//src/third_party/opentelemetry-cpp:otel_rules.bzl", "OTEL_COPTS", "OTEL_TARGET_COMPATIBLE_WITH") + +package(default_visibility = ["//visibility:public"]) + +mongo_cc_library( + name = "in_memory_metric_data", + testonly = True, + srcs = [ + "src/in_memory_metric_data.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_metric_data.h", + ], + strip_include_prefix = "include", + tags = [ + "memory", + "test", + ], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + copts = OTEL_COPTS, + deps = [ + ":in_memory_data", + "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], +) + +mongo_cc_library( + name = "in_memory_metric_exporter_factory", + testonly = True, + srcs = [ + "src/in_memory_metric_exporter_factory.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h", + ], + strip_include_prefix = "include", + tags = [ + "memory", + "test", + ], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + copts = OTEL_COPTS, + deps = [ + ":in_memory_metric_data", + "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], +) + +mongo_cc_library( + name = "in_memory_data", + testonly = True, + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + copts = OTEL_COPTS, + deps = [ + "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], +) + +mongo_cc_library( + name = "in_memory_span_data", + testonly = True, + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_span_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + copts = OTEL_COPTS, + deps = [ + ":in_memory_data", + "//src/third_party/opentelemetry-cpp/api", + "//src/third_party/opentelemetry-cpp/sdk/src/resource", + "//src/third_party/opentelemetry-cpp/sdk/src/trace", + ], +) + +mongo_cc_library( + name = "in_memory_span_exporter", + testonly = True, + srcs = [ + "src/in_memory_span_exporter_factory.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_span_exporter.h", + "include/opentelemetry/exporters/memory/in_memory_span_exporter_factory.h", + ], + strip_include_prefix = "include", + tags = [ + "memory", + "test", + ], + target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, + copts = OTEL_COPTS, + deps = [ + ":in_memory_span_data", + "//src/third_party/opentelemetry-cpp/sdk/src/trace", + ], +) diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_data.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_data.h new file mode 100644 index 00000000000..26acba4b63f --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_data.h @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/sdk/common/circular_buffer.h" + +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +/** + * A wrapper class holding in memory exporter data + */ +template +class InMemoryData +{ +public: + /** + * @param buffer_size a required value that sets the size of the CircularBuffer + */ + InMemoryData(size_t buffer_size) : data_(buffer_size) {} + + /** + * @param data a required unique pointer to the data to add to the CircularBuffer + */ + void Add(std::unique_ptr data) noexcept { data_.Add(data); } + + /** + * @return Returns a vector of unique pointers containing all the data in the + * CircularBuffer. This operation will empty the Buffer, which is why the data + * is returned as unique pointers + */ + std::vector> Get() noexcept + { + std::vector> res; + + // Pointer swap is required because the Consume function requires that the + // AtomicUniquePointer be set to null + data_.Consume( + data_.size(), [&](opentelemetry::sdk::common::CircularBufferRange< + opentelemetry::sdk::common::AtomicUniquePtr> range) noexcept { + range.ForEach([&](opentelemetry::sdk::common::AtomicUniquePtr &ptr) noexcept { + std::unique_ptr swap_ptr = nullptr; + ptr.Swap(swap_ptr); + res.push_back(std::move(swap_ptr)); + return true; + }); + }); + + return res; + } + +private: + opentelemetry::sdk::common::CircularBuffer data_; +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h new file mode 100644 index 00000000000..8d24c586b25 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_data.h @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_data.h" +#include "opentelemetry/sdk/metrics/data/metric_data.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ +struct ResourceMetrics; +} +} // namespace sdk +namespace exporter +{ +namespace memory +{ + +/// The abstract base class for types used to store in-memory data backing an +/// InMemoryMetricExporter. +class InMemoryMetricData +{ +public: + InMemoryMetricData() = default; + virtual ~InMemoryMetricData() = default; + + InMemoryMetricData(const InMemoryMetricData &) = delete; + InMemoryMetricData(InMemoryMetricData &&) = delete; + InMemoryMetricData &operator=(const InMemoryMetricData &) = delete; + InMemoryMetricData &operator=(InMemoryMetricData &&) = delete; + + virtual void Add(std::unique_ptr resource_metrics) = 0; +}; + +/// An implementation of InMemoryMetricData that stores full-fidelity data points in a circular +/// buffer. This allows tests to inspect every aspect of exported data, in exchange for a somewhat +/// cumbersome API. +class CircularBufferInMemoryMetricData final : public InMemoryMetricData, + public InMemoryData +{ +public: + explicit CircularBufferInMemoryMetricData(size_t buffer_size); + void Add(std::unique_ptr resource_metrics) override; +}; + +/// An implementation of InMemoryMetricData that stores only the most recent data point in each time +/// series, and allows convenient lookups of time series. This makes simple tests easier to write. +class SimpleAggregateInMemoryMetricData final : public InMemoryMetricData +{ +public: + using AttributeToPoint = std::map; + + void Add(std::unique_ptr resource_metrics) override; + const AttributeToPoint &Get(const std::string &scope, const std::string &metric); + void Clear(); + +private: + std::map, AttributeToPoint> data_; +}; + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h new file mode 100644 index 00000000000..f6934df7278 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ +class PushMetricExporter; +} // namespace metrics +} // namespace sdk +namespace exporter +{ +namespace memory +{ +class InMemoryMetricData; + +/// A factory for InMemoryMetricExporter +class InMemoryMetricExporterFactory +{ +public: + /// Create a InMemoryMetricExporter with a default buffer size and aggregation + /// temporality selector. + /// @param [out] data the InMemoryMetricData 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( + const std::shared_ptr &data, + const sdk::metrics::AggregationTemporalitySelector &temporality); + + static std::unique_ptr Create( + const std::shared_ptr &data); +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h new file mode 100644 index 00000000000..6eaae0663cc --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_data.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +class InMemorySpanData final : public exporter::memory::InMemoryData +{ +public: + /** + * @param buffer_size a required value that sets the size of the CircularBuffer + */ + explicit InMemorySpanData(size_t buffer_size) + : exporter::memory::InMemoryData(buffer_size) + {} + + std::vector> GetSpans() noexcept { return Get(); } +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h new file mode 100644 index 00000000000..6be83113ced --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/common/exporter_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +const size_t MAX_BUFFER_SIZE = 100; + +/** + * A in memory exporter that switches a flag once a valid recordable was received + * and keeps track of all received spans in memory. + */ +class InMemorySpanExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * @param buffer_size an optional value that sets the size of the InMemorySpanData + */ + InMemorySpanExporter(size_t buffer_size = MAX_BUFFER_SIZE) + : data_(new InMemorySpanData(buffer_size)) + {} + + /** + * @return Returns a unique pointer to an empty recordable object + */ + std::unique_ptr MakeRecordable() noexcept override + { + return std::unique_ptr(new sdk::trace::SpanData()); + } + + /** + * @param recordables a required span containing unique pointers to the data + * to add to the InMemorySpanData + * @return Returns the result of the operation + */ + sdk::common::ExportResult Export( + const nostd::span> &recordables) noexcept override + { + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[In Memory Span Exporter] Exporting " + << recordables.size() << " span(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + for (auto &recordable : recordables) + { + auto span = std::unique_ptr( + static_cast(recordable.release())); + if (span != nullptr) + { + data_->Add(std::move(span)); + } + } + + return sdk::common::ExportResult::kSuccess; + } + + /** + * @param timeout an optional value containing the timeout of the exporter + * note: passing custom timeout values is not currently supported for this exporter + * @return Returns the status of the operation + */ + bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override + { + is_shutdown_ = true; + return true; + } + + /** + * @return Returns a shared pointer to this exporters InMemorySpanData + */ + std::shared_ptr GetData() noexcept { return data_; } + +private: + std::shared_ptr data_; + std::atomic is_shutdown_{false}; + bool isShutdown() const noexcept { return is_shutdown_; } +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter_factory.h b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter_factory.h new file mode 100644 index 00000000000..549ff13c71d --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter_factory.h @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ + +/** + * Factory class for InMemorySpanExporter. + */ +class InMemorySpanExporterFactory +{ +public: + /** + * Create a InMemorySpanExporter with a default buffer size. + * @param [out] data the InMemorySpanData the exporter will write to, + * for the caller to inspect + */ + static std::unique_ptr Create( + std::shared_ptr &data); + + /** + * Create a InMemorySpanExporter with a default buffer size. + * @param [out] data the InMemorySpanData the exporter will write to, + * for the caller to inspect + * @param [in] buffer_size size of the underlying InMemorySpanData + */ + static std::unique_ptr Create( + std::shared_ptr &data, + size_t buffer_size); +}; + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_data.cc b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_data.cc new file mode 100644 index 00000000000..2a77e0b5a36 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_data.cc @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_data.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +using sdk::metrics::ResourceMetrics; + +CircularBufferInMemoryMetricData::CircularBufferInMemoryMetricData(size_t buffer_size) + : InMemoryData(buffer_size) +{} + +void CircularBufferInMemoryMetricData::Add(std::unique_ptr resource_metrics) +{ + InMemoryData::Add(std::move(resource_metrics)); +} + +void SimpleAggregateInMemoryMetricData::Add(std::unique_ptr resource_metrics) +{ + for (const auto &sm : resource_metrics->scope_metric_data_) + { + const auto &scope = sm.scope_->GetName(); + for (const auto &m : sm.metric_data_) + { + const auto &metric = m.instrument_descriptor.name_; + for (const auto &pda : m.point_data_attr_) + { + data_[{scope, metric}].insert({pda.attributes, pda.point_data}); + } + } + } +} + +const SimpleAggregateInMemoryMetricData::AttributeToPoint &SimpleAggregateInMemoryMetricData::Get( + const std::string &scope, + const std::string &metric) +{ + return data_[{scope, metric}]; +} + +void SimpleAggregateInMemoryMetricData::Clear() +{ + data_.clear(); +} + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_exporter_factory.cc b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_exporter_factory.cc new file mode 100644 index 00000000000..f2577c4e9b7 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_metric_exporter_factory.cc @@ -0,0 +1,93 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_exporter_factory.h" +#include "opentelemetry/exporters/memory/in_memory_metric_data.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.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; + +namespace +{ + +/// 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 +{ +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(const std::shared_ptr &data, + const sdk::metrics::AggregationTemporalitySelector &temporality) + : data_(data), temporality_(temporality) + {} + + ~InMemoryMetricExporter() override = default; + + InMemoryMetricExporter(const InMemoryMetricExporter &) = delete; + InMemoryMetricExporter(const InMemoryMetricExporter &&) = delete; + void operator=(const InMemoryMetricExporter &) = delete; + void operator=(const InMemoryMetricExporter &&) = delete; + + ExportResult Export(const ResourceMetrics &data) noexcept override + { + 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 GetAggregationTemporality( + InstrumentType instrument_type) const noexcept override + { + return temporality_(instrument_type); + } + + bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; } + + bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override + { + is_shutdown_ = true; + return true; + } + +private: + std::shared_ptr data_; + std::atomic is_shutdown_{false}; + sdk::metrics::AggregationTemporalitySelector temporality_; +}; + +} // namespace + +std::unique_ptr InMemoryMetricExporterFactory::Create( + const std::shared_ptr &data) +{ + return Create(data, [](auto) { return AggregationTemporality::kCumulative; }); +} + +std::unique_ptr InMemoryMetricExporterFactory::Create( + const std::shared_ptr &data, + const AggregationTemporalitySelector &temporality) +{ + return std::make_unique(data, temporality); +} + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_span_exporter_factory.cc b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_span_exporter_factory.cc new file mode 100644 index 00000000000..ff5bab92533 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/src/in_memory_span_exporter_factory.cc @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_exporter_factory.h" +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/sdk/trace/exporter.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ + +std::unique_ptr InMemorySpanExporterFactory::Create( + std::shared_ptr &data) +{ + return Create(data, MAX_BUFFER_SIZE); +} + +std::unique_ptr InMemorySpanExporterFactory::Create( + std::shared_ptr &data, + size_t buffer_size) +{ + InMemorySpanExporter *memory_exporter = new InMemorySpanExporter(buffer_size); + data = memory_exporter->GetData(); + std::unique_ptr exporter(memory_exporter); + return exporter; +} + +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_data_test.cc b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_data_test.cc new file mode 100644 index 00000000000..ffaba2cfb9c --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_data_test.cc @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_data.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/resource/resource.h" + +#include + +#include + +using opentelemetry::exporter::memory::CircularBufferInMemoryMetricData; +using opentelemetry::exporter::memory::SimpleAggregateInMemoryMetricData; +using opentelemetry::sdk::metrics::MetricData; +using opentelemetry::sdk::metrics::PointDataAttributes; +using opentelemetry::sdk::metrics::ResourceMetrics; +using opentelemetry::sdk::metrics::ScopeMetrics; +using opentelemetry::sdk::metrics::SumPointData; +using opentelemetry::sdk::resource::Resource; + +TEST(InMemoryMetricDataTest, CircularBuffer) +{ + CircularBufferInMemoryMetricData buf(10); + Resource resource = Resource::GetEmpty(); + buf.Add(std::make_unique( + &resource, std::vector{{nullptr, std::vector{}}})); + EXPECT_EQ((*buf.Get().begin())->resource_, &resource); +} + +TEST(InMemoryMetricDataTest, SimpleAggregate) +{ + SimpleAggregateInMemoryMetricData agg; + + Resource resource = Resource::GetEmpty(); + + auto scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create( + "my-scope", "1.0.0", "http://example.com"); + + SumPointData spd; + spd.value_ = 42.0; + PointDataAttributes pda{{{"hello", "world"}}, spd}; + + MetricData md; + md.instrument_descriptor.name_ = "my-metric"; + md.point_data_attr_.push_back(pda); + + agg.Add(std::make_unique( + &resource, std::vector{{scope.get(), std::vector{md}}})); + auto it = agg.Get("my-scope", "my-metric").begin(); + + auto saved_point = opentelemetry::nostd::get(it->second); + + EXPECT_EQ(opentelemetry::nostd::get(saved_point.value_), 42.0); +} diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_exporter_test.cc b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_exporter_test.cc new file mode 100644 index 00000000000..adaa322f719 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_metric_exporter_test.cc @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_metric_data.h" +#include "opentelemetry/exporters/memory/in_memory_metric_exporter_factory.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/sdk/resource/resource.h" + +#include + +using opentelemetry::exporter::memory::CircularBufferInMemoryMetricData; +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_ = + std::make_shared(10); + + 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); +} diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc new file mode 100644 index 00000000000..be013734ef6 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include + +using opentelemetry::exporter::memory::InMemorySpanData; +using opentelemetry::sdk::trace::SpanData; + +TEST(InMemorySpanData, AddRecordable) +{ + InMemorySpanData data(100); + + ASSERT_EQ(0, data.GetSpans().size()); + + std::unique_ptr spandata(new SpanData()); + + data.Add(std::move(spandata)); + + // Consumes all spans in exporter + ASSERT_EQ(1, data.GetSpans().size()); + + ASSERT_EQ(0, data.GetSpans().size()); +} diff --git a/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc new file mode 100644 index 00000000000..56a0ef77909 --- /dev/null +++ b/src/third_party/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include + +using opentelemetry::exporter::memory::InMemorySpanExporter; +using opentelemetry::sdk::trace::Recordable; +using opentelemetry::sdk::trace::SpanData; + +TEST(InMemorySpanExporter, ExportBatch) +{ + InMemorySpanExporter exporter; + + ASSERT_EQ(0, exporter.GetData().get()->GetSpans().size()); + + std::unique_ptr spandata(new SpanData()); + opentelemetry::nostd::span> batch(&spandata, 1); + + exporter.Export(batch); + + ASSERT_EQ(1, exporter.GetData().get()->GetSpans().size()); + + // Consumes all spans in exporter + ASSERT_EQ(0, exporter.GetData().get()->GetSpans().size()); +} diff --git a/src/third_party/opentelemetry-cpp/patches/0004-SERVER-115315-vendor-memory-exporter.patch b/src/third_party/opentelemetry-cpp/patches/0004-SERVER-115315-vendor-memory-exporter.patch new file mode 100644 index 00000000000..6b2f26496fa --- /dev/null +++ b/src/third_party/opentelemetry-cpp/patches/0004-SERVER-115315-vendor-memory-exporter.patch @@ -0,0 +1,163 @@ +diff --git a/src/third_party/opentelemetry-cpp/dist/exporters/memory/BUILD b/src/third_party/opentelemetry-cpp/dist/exporters/memory/BUILD +index 033c85d3698..377d6025f77 100644 +--- a/src/third_party/opentelemetry-cpp/dist/exporters/memory/BUILD ++++ b/src/third_party/opentelemetry-cpp/dist/exporters/memory/BUILD +@@ -1,10 +1,14 @@ + # Copyright The OpenTelemetry Authors + # SPDX-License-Identifier: Apache-2.0 + ++load("//bazel:mongo_src_rules.bzl", "mongo_cc_library") ++load("//src/third_party/opentelemetry-cpp:otel_rules.bzl", "OTEL_COPTS", "OTEL_TARGET_COMPATIBLE_WITH") ++ + package(default_visibility = ["//visibility:public"]) + +-cc_library( ++mongo_cc_library( + name = "in_memory_metric_data", ++ testonly = True, + srcs = [ + "src/in_memory_metric_data.cc", + ], +@@ -16,27 +20,17 @@ cc_library( + "memory", + "test", + ], ++ target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, ++ copts = OTEL_COPTS, + deps = [ + ":in_memory_data", +- "//sdk/src/metrics", +- ], +-) +- +-cc_test( +- name = "in_memory_metric_data_test", +- srcs = ["test/in_memory_metric_data_test.cc"], +- tags = [ +- "memory", +- "test", +- ], +- deps = [ +- ":in_memory_metric_data", +- "@com_google_googletest//:gtest_main", ++ "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], + ) + +-cc_library( ++mongo_cc_library( + name = "in_memory_metric_exporter_factory", ++ testonly = True, + srcs = [ + "src/in_memory_metric_exporter_factory.cc", + ], +@@ -48,67 +42,50 @@ cc_library( + "memory", + "test", + ], ++ target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, ++ copts = OTEL_COPTS, + deps = [ + ":in_memory_metric_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_factory", +- "@com_google_googletest//:gtest_main", ++ "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], + ) + +-cc_library( ++mongo_cc_library( + name = "in_memory_data", ++ testonly = True, + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], ++ target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, ++ copts = OTEL_COPTS, + deps = [ +- "//sdk:headers", ++ "//src/third_party/opentelemetry-cpp/sdk/src/metrics", + ], + ) + +-cc_library( ++mongo_cc_library( + name = "in_memory_span_data", ++ testonly = True, + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_span_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], ++ target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, ++ copts = OTEL_COPTS, + deps = [ + ":in_memory_data", +- "//api", +- "//sdk/src/resource", +- "//sdk/src/trace", ++ "//src/third_party/opentelemetry-cpp/api", ++ "//src/third_party/opentelemetry-cpp/sdk/src/resource", ++ "//src/third_party/opentelemetry-cpp/sdk/src/trace", + ], + ) + +-cc_test( +- name = "in_memory_span_data_test", +- srcs = ["test/in_memory_span_data_test.cc"], +- tags = [ +- "memory", +- "test", +- ], +- deps = [ +- ":in_memory_span_data", +- "@com_google_googletest//:gtest_main", +- ], +-) +- +-cc_library( ++mongo_cc_library( + name = "in_memory_span_exporter", ++ testonly = True, + srcs = [ + "src/in_memory_span_exporter_factory.cc", + ], +@@ -121,21 +98,10 @@ cc_library( + "memory", + "test", + ], ++ target_compatible_with = OTEL_TARGET_COMPATIBLE_WITH, ++ copts = OTEL_COPTS, + deps = [ + ":in_memory_span_data", +- "//sdk/src/trace", +- ], +-) +- +-cc_test( +- name = "in_memory_span_exporter_test", +- srcs = ["test/in_memory_span_exporter_test.cc"], +- tags = [ +- "memory", +- "test", +- ], +- deps = [ +- ":in_memory_span_exporter", +- "@com_google_googletest//:gtest_main", ++ "//src/third_party/opentelemetry-cpp/sdk/src/trace", + ], + ) diff --git a/src/third_party/opentelemetry-cpp/scripts/import.sh b/src/third_party/opentelemetry-cpp/scripts/import.sh index fb4e110d18e..d9d3487ddbc 100755 --- a/src/third_party/opentelemetry-cpp/scripts/import.sh +++ b/src/third_party/opentelemetry-cpp/scripts/import.sh @@ -54,7 +54,6 @@ rm -rf sdk/src/logs # Uneeded exporters rm -rf exporters/elasticsearch rm -rf exporters/etw -rm -rf exporters/memory rm -rf exporters/ostream rm -rf exporters/prometheus rm -rf exporters/zipkin @@ -72,6 +71,7 @@ git apply "${PATCHES_DIR}/0002-Build-system-changes-for-opentelemetry-cpp.patch" git apply "${PATCHES_DIR}/0003-Build-system-changes-for-opentelemetry-cpp.patch" git apply "${PATCHES_DIR}/0001-SERVER-100631-update-api-BUILD.patch" git apply "${PATCHES_DIR}/0001-SERVER-106258-vendor-OpenTelemetry-gRPC-exporter.patch" +git apply "${PATCHES_DIR}/0004-SERVER-115315-vendor-memory-exporter.patch" cp -R ${DIST}/* ${LIBDIR}/ echo ${DIST}