diff --git a/CHANGELOG.md b/CHANGELOG.md index 838a5a096f..14296ffe3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ Increment the: ## [Unreleased] +* [SDK] Add `MeterProvider::UpdateMeterConfigurator()` and example. Instruments + observe the enabled state of the Meter that created them, so a disabled Meter + behaves as a no-op Meter without instruments having to be recreated + [#4256](https://github.com/open-telemetry/opentelemetry-cpp/issues/4256) + * [BUILD] Add missing opentelemetry_proto dependency to otlp_recordable pkg-config [#4316](https://github.com/open-telemetry/opentelemetry-cpp/pull/4316) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e1237e5308..8f4c12bbc6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(multi_processor) add_subdirectory(environment_carrier) add_subdirectory(tracer_configurator) add_subdirectory(logger_configurator) +add_subdirectory(meter_configurator) add_subdirectory(explicit_parent) if(WITH_EXAMPLES_HTTP) diff --git a/examples/meter_configurator/BUILD b/examples/meter_configurator/BUILD new file mode 100644 index 0000000000..1f9d8dc5e0 --- /dev/null +++ b/examples/meter_configurator/BUILD @@ -0,0 +1,20 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_test( + name = "example_meter_configurator", + srcs = [ + "main.cc", + ], + tags = [ + "ostream", + "tested_example", + ], + deps = [ + "//api", + "//exporters/ostream:ostream_metric_exporter", + "//sdk/src/metrics", + ], +) diff --git a/examples/meter_configurator/CMakeLists.txt b/examples/meter_configurator/CMakeLists.txt new file mode 100644 index 0000000000..1a63dbbe70 --- /dev/null +++ b/examples/meter_configurator/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +add_executable(example_meter_configurator main.cc) +target_link_libraries( + example_meter_configurator + PRIVATE opentelemetry-cpp::metrics + opentelemetry-cpp::ostream_metrics_exporter) + +if(BUILD_TESTING) + add_test(NAME examples.meter_configurator + COMMAND "$") +endif() diff --git a/examples/meter_configurator/README.md b/examples/meter_configurator/README.md new file mode 100644 index 0000000000..9f6bbd5700 --- /dev/null +++ b/examples/meter_configurator/README.md @@ -0,0 +1,252 @@ +# Meter Configurator Example + +This example demonstrates how to set a `MeterConfigurator` on construction +of the `MeterProvider` and to update it at runtime using +`MeterProvider::UpdateMeterConfigurator` to enable or disable specific +meters without restarting the application or recreating the meters. + +`MeterProvider::UpdateMeterConfigurator` recomputes and applies a new +`MeterConfig` to all existing meters, and the updated configurator is also +used for meters created afterwards. It is safe to call concurrently with +`MeterProvider::GetMeter` and with instrument creation and recording on +existing meters. + +Three meters with unique instrumentation scope names are used to simulate +a user application: + +- `my_application`: simulated user application +- `my_library`: simulated user library +- `external_library`: simulated external third-party library + +The example walks through a simulated cost management and debugging +workflow in four stages: + +- Stage 1: Startup. All meters are enabled and all three scopes report + metrics. +- Stage 2: Steady state. The noisy `external_library` metrics are not + needed, so its meter is disabled. +- Stage 3: A user reports unexpected behavior. Re-enable the + `external_library` meter to investigate. +- Stage 4: The investigation completes and the `external_library` meter is + disabled again. + +Instruments observe the enabled state of the meter that created them, so +they do not need to be recreated after a configurator update. An instrument +created while its meter is disabled starts recording once the meter is +enabled, and stops again when the meter is disabled. This matters for +third-party instrumentation, which an application cannot force to rebuild +its instruments. + +Disabling a meter stops collection and export for that scope, and +measurements recorded while it is disabled are dropped rather than buffered. +They are not revealed by a later export, which is why +`external_library.requests` reports `2` in stage 3: the stage 2 measurement +is not retained. + +## Build and run + +```sh +~/build/examples/meter_configurator/example_meter_configurator +``` + +**Expected output:** + +Metrics are exported to stdout via the `OStreamMetricExporter`. The example +calls `ForceFlush` at the end of each stage. The periodic reader also +collects once when it starts, so the exact number and ordering of exported +batches varies between runs; the transcript below shows one run and is not +an exact expected output. + +```sh +Stage 1: startup, all meters enabled + my_application, my_library and external_library report metrics +{ + scope name : my_application + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_application.work_items + description : Work items processed by the application + unit : {item} + type : SumPointData + value : 1 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : my_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_library.calls + description : Calls into the library + unit : {call} + type : SumPointData + value : 1 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : external_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : external_library.requests + description : Requests handled by the external library + unit : {request} + type : SumPointData + value : 1 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} + +Stage 2: steady state, external_library meter disabled + only my_application and my_library report metrics +{ + scope name : my_application + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_application.work_items + description : Work items processed by the application + unit : {item} + type : SumPointData + value : 2 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : my_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_library.calls + description : Calls into the library + unit : {call} + type : SumPointData + value : 2 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} + +Stage 3: investigating, external_library meter re-enabled + all three scopes report metrics again +{ + scope name : my_application + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_application.work_items + description : Work items processed by the application + unit : {item} + type : SumPointData + value : 3 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : my_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_library.calls + description : Calls into the library + unit : {call} + type : SumPointData + value : 3 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : external_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : external_library.requests + description : Requests handled by the external library + unit : {request} + type : SumPointData + value : 2 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} + +Stage 4: investigation complete, external_library meter disabled again + only my_application and my_library report metrics +{ + scope name : my_application + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_application.work_items + description : Work items processed by the application + unit : {item} + type : SumPointData + value : 4 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +{ + scope name : my_library + schema url : + version : + start time : Mon Jul 27 09:32:35 2026 + end time : Mon Jul 27 09:32:35 2026 + instrument name : my_library.calls + description : Calls into the library + unit : {call} + type : SumPointData + value : 4 + attributes : + resources : + service.name: meter_configurator_example + telemetry.sdk.language: cpp + telemetry.sdk.name: opentelemetry + telemetry.sdk.version: 1.29.0-dev +} +``` diff --git a/examples/meter_configurator/main.cc b/examples/meter_configurator/main.cc new file mode 100644 index 0000000000..b1fdbfbc0e --- /dev/null +++ b/examples/meter_configurator/main.cc @@ -0,0 +1,228 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// This example shows how to use MeterProvider::UpdateMeterConfigurator to enable and disable +// meters by instrumentation scope at runtime. Updating the MeterConfigurator affects all existing +// and future meters provided by the MeterProvider. It is safe to call concurrently with +// MeterProvider::GetMeter and with instrument creation and recording on existing instruments. +// +// Three instrumentation scopes are shown: +// 1. "my_application" (example instrumented user application code), +// 2. "my_library" (example instrumented user library code), +// 3. "external_library" (example instrumented third-party dependency). +// +// The example simulates a typical cost-management and debugging workflow: +// +// Stage 1: Startup. All meters are enabled and all three scopes report metrics. +// Stage 2: The noisy external_library metrics are not needed in steady state, so its meter is +// disabled. Its measurements are no longer collected or exported. +// Stage 3: A user reports unexpected behavior, so the external_library meter is re-enabled to +// investigate. Its metrics are collected and exported again. +// Stage 4: The investigation completes and the external_library meter is disabled again. +// +// Instruments observe their Meter's enabled state, so they need not be recreated after an update. +// Measurements recorded while a Meter is disabled are dropped, not buffered. + +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/ostream/metric_exporter_factory.h" +#include "opentelemetry/metrics/meter.h" +#include "opentelemetry/metrics/meter_provider.h" +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/metrics/sync_instruments.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h" +#include "opentelemetry/sdk/metrics/meter_config.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/metric_reader.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/metrics/view/view_registry.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/semconv/service_attributes.h" + +namespace metrics_api = opentelemetry::metrics; +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace metrics_exporters = opentelemetry::exporter::metrics; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace nostd = opentelemetry::nostd; + +namespace +{ + +// Simulated third-party dependency instrumented under its own scope. +namespace external_library +{ +class ExternalModule +{ +public: + ExternalModule() + : meter_(metrics_api::Provider::GetMeterProvider()->GetMeter("external_library")), + request_count_(meter_->CreateUInt64Counter("external_library.requests", + "Requests handled by the external library", + "{request}")) + {} + + bool Execute() + { + request_count_->Add(1); + return true; + } + +private: + nostd::shared_ptr meter_; + nostd::unique_ptr> request_count_; +}; +} // namespace external_library + +// Simulated user library instrumented under its own scope. +namespace my_library +{ +class MyModule +{ +public: + MyModule() + : meter_(metrics_api::Provider::GetMeterProvider()->GetMeter("my_library")), + call_count_( + meter_->CreateUInt64Counter("my_library.calls", "Calls into the library", "{call}")) + {} + + bool Execute() + { + call_count_->Add(1); + return external_module_.Execute(); + } + +private: + nostd::shared_ptr meter_; + nostd::unique_ptr> call_count_; + external_library::ExternalModule external_module_; +}; +} // namespace my_library + +// Simulated user application instrumented under its own scope. +class MyApplication +{ +public: + MyApplication() + : meter_(metrics_api::Provider::GetMeterProvider()->GetMeter("my_application")), + work_count_(meter_->CreateUInt64Counter("my_application.work_items", + "Work items processed by the application", + "{item}")) + {} + + void Execute() + { + work_count_->Add(1); + my_module_.Execute(); + } + +private: + nostd::shared_ptr meter_; + nostd::unique_ptr> work_count_; + my_library::MyModule my_module_; +}; + +// Builds a configurator that applies default_config to all scopes, with optional per-scope +// overrides. +std::unique_ptr> MakeMeterConfigurator( + metrics_sdk::MeterConfig default_config, + std::initializer_list> overrides = {}) +{ + scope_sdk::ScopeConfigurator::Builder builder(default_config); + for (const auto &kv : overrides) + { + const auto &name = kv.first; + const auto &config = kv.second; + builder.AddConditionNameEquals(name, config); + } + return std::make_unique>(builder.Build()); +} + +// Creates a MeterProvider with an OStreamMetricExporter and an initial ScopeConfigurator. +std::shared_ptr CreateMeterProvider( + std::unique_ptr> configurator) +{ + auto exporter = metrics_exporters::OStreamMetricExporterFactory::Create(); + + // Most exports come from the per-stage ForceFlush. The reader also collects once at startup, + // so the exact batch count and ordering is not deterministic. + metrics_sdk::PeriodicExportingMetricReaderOptions options; + options.export_interval_millis = std::chrono::milliseconds(10000); + options.export_timeout_millis = std::chrono::milliseconds(500); + auto reader = + metrics_sdk::PeriodicExportingMetricReaderFactory::Create(std::move(exporter), options); + + auto provider = std::make_shared( + std::unique_ptr(new metrics_sdk::ViewRegistry()), + opentelemetry::sdk::resource::Resource::Create( + {{opentelemetry::semconv::service::kServiceName, "meter_configurator_example"}}), + std::move(configurator)); + provider->AddMetricReader(std::move(reader)); + return provider; +} + +} // namespace + +int main() +{ + const metrics_sdk::MeterConfig enabled_config = metrics_sdk::MeterConfig::Enabled(); + const metrics_sdk::MeterConfig disabled_config = metrics_sdk::MeterConfig::Disabled(); + + // Stage 1: Startup, all meters enabled. + auto sdk_meter_provider = CreateMeterProvider(MakeMeterConfigurator(enabled_config)); + + metrics_api::Provider::SetMeterProvider( + nostd::shared_ptr(sdk_meter_provider)); + + // Instantiate the application. This creates the meters and instruments for all three scopes. + // All meters are enabled, so all three scopes get working instruments. + MyApplication my_app; + + std::cout << "Stage 1: startup, all meters enabled\n"; + std::cout << " my_application, my_library and external_library report metrics\n"; + my_app.Execute(); + sdk_meter_provider->ForceFlush(); + + // Stage 2: Steady state. Disable the noisy external_library meter. + std::cout << "\nStage 2: steady state, external_library meter disabled\n"; + std::cout << " only my_application and my_library report metrics\n"; + + sdk_meter_provider->UpdateMeterConfigurator( + MakeMeterConfigurator(enabled_config, {{"external_library", disabled_config}})); + + my_app.Execute(); + sdk_meter_provider->ForceFlush(); + + // Stage 3: A user reports unexpected behavior. Re-enable the external_library meter. + std::cout << "\nStage 3: investigating, external_library meter re-enabled\n"; + std::cout << " all three scopes report metrics again\n"; + + sdk_meter_provider->UpdateMeterConfigurator(MakeMeterConfigurator(enabled_config)); + + my_app.Execute(); + sdk_meter_provider->ForceFlush(); + + // Stage 4: Investigation complete. Disable the external_library meter again. + std::cout << "\nStage 4: investigation complete, external_library meter disabled again\n"; + std::cout << " only my_application and my_library report metrics\n"; + + sdk_meter_provider->UpdateMeterConfigurator( + MakeMeterConfigurator(enabled_config, {{"external_library", disabled_config}})); + + my_app.Execute(); + sdk_meter_provider->ForceFlush(); + + sdk_meter_provider->Shutdown(); + + const nostd::shared_ptr none; + metrics_api::Provider::SetMeterProvider(none); + return 0; +} diff --git a/sdk/include/opentelemetry/sdk/instrumentationscope/scope_configurator.h b/sdk/include/opentelemetry/sdk/instrumentationscope/scope_configurator.h index 7b07321741..170924b57e 100644 --- a/sdk/include/opentelemetry/sdk/instrumentationscope/scope_configurator.h +++ b/sdk/include/opentelemetry/sdk/instrumentationscope/scope_configurator.h @@ -3,7 +3,9 @@ #pragma once #include +#include #include +#include #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/version.h" diff --git a/sdk/include/opentelemetry/sdk/metrics/meter.h b/sdk/include/opentelemetry/sdk/metrics/meter.h index 2fdc6c121c..6924587cb1 100644 --- a/sdk/include/opentelemetry/sdk/metrics/meter.h +++ b/sdk/include/opentelemetry/sdk/metrics/meter.h @@ -21,6 +21,7 @@ #include "opentelemetry/sdk/metrics/instruments.h" #include "opentelemetry/sdk/metrics/meter_config.h" #include "opentelemetry/sdk/metrics/meter_context.h" +#include "opentelemetry/sdk/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/async_metric_storage.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk_config.h" @@ -140,6 +141,20 @@ class Meter final : public opentelemetry::metrics::Meter void DeregisterCallback(uintptr_t callback_id) noexcept override; #endif private: + // MeterProvider needs access to UpdateMeterConfig to propagate configuration updates to + // existing meters. + friend class MeterProvider; + + /** + * Update this meter's MeterConfig. Called only by + * MeterProvider::UpdateMeterConfigurator when the provider-level MeterConfigurator is + * replaced at runtime. + */ + void UpdateMeterConfig(MeterConfig config) noexcept; + + /** Returns whether this meter is enabled by its current MeterConfig. */ + bool IsEnabled() const noexcept { return meter_enabled_state_->IsEnabled(); } + // order of declaration is important here - instrumentation scope should destroy after // meter-context. std::unique_ptr scope_; @@ -151,15 +166,14 @@ class Meter final : public opentelemetry::metrics::Meter InstrumentEqualNameCaseInsensitive>; MetricStorageMap storage_registry_; std::shared_ptr observable_registry_; - MeterConfig meter_config_; + // Shared with this meter's instruments so updates reach them; atomic so recording never blocks. + std::shared_ptr meter_enabled_state_{new MeterEnabledState()}; std::unique_ptr RegisterSyncMetricStorage( InstrumentDescriptor &instrument_descriptor); std::unique_ptr RegisterAsyncMetricStorage( InstrumentDescriptor &instrument_descriptor); opentelemetry::common::SpinLockMutex storage_lock_; - static opentelemetry::metrics::NoopMeter kNoopMeter; - static nostd::shared_ptr GetNoopObservableInsrument() { diff --git a/sdk/include/opentelemetry/sdk/metrics/meter_context.h b/sdk/include/opentelemetry/sdk/metrics/meter_context.h index 9ccd766565..9e48243be5 100644 --- a/sdk/include/opentelemetry/sdk/metrics/meter_context.h +++ b/sdk/include/opentelemetry/sdk/metrics/meter_context.h @@ -88,6 +88,15 @@ class MeterContext : public std::enable_shared_from_this */ const instrumentationscope::ScopeConfigurator &GetMeterConfigurator() const noexcept; + /** + * Replace the ScopeConfigurator for this meter context. + * + * Note: Not thread safe. Called via MeterProvider::UpdateMeterConfigurator, which holds lock_. + * @param meter_configurator The new configurator. + */ + void SetMeterConfigurator(std::unique_ptr> + meter_configurator) noexcept; + /** * NOTE - INTERNAL method, can change in the future. * Process callback for each meter in thread-safe manner diff --git a/sdk/include/opentelemetry/sdk/metrics/meter_enabled_state.h b/sdk/include/opentelemetry/sdk/metrics/meter_enabled_state.h new file mode 100644 index 0000000000..8afe5b7c06 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/meter_enabled_state.h @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +/** + * A Meter's enabled flag, shared with every instrument it creates so a configurator update + * reaches instruments that already exist. Relaxed ordering: no dependent data, only visibility. + */ +class MeterEnabledState +{ +public: + explicit MeterEnabledState(bool enabled = true) noexcept : enabled_(enabled) {} + + MeterEnabledState(const MeterEnabledState &) = delete; + MeterEnabledState(MeterEnabledState &&) = delete; + MeterEnabledState &operator=(const MeterEnabledState &) = delete; + MeterEnabledState &operator=(MeterEnabledState &&) = delete; + + bool IsEnabled() const noexcept { return enabled_.load(std::memory_order_relaxed); } + + void SetEnabled(bool enabled) noexcept { enabled_.store(enabled, std::memory_order_relaxed); } + +private: + std::atomic enabled_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/meter_provider.h b/sdk/include/opentelemetry/sdk/metrics/meter_provider.h index fccaae690b..59e51980c2 100644 --- a/sdk/include/opentelemetry/sdk/metrics/meter_provider.h +++ b/sdk/include/opentelemetry/sdk/metrics/meter_provider.h @@ -122,6 +122,19 @@ class OPENTELEMETRY_EXPORT MeterProvider final : public opentelemetry::metrics:: std::unique_ptr meter_selector, std::unique_ptr view) noexcept; + /** + * Update the MeterConfigurator for this provider, recreate and propagate the resulting + * MeterConfig to all existing Meters while new Meters will use the updated configuration. + * + * @param meter_configurator The new configurator. + * + * @note Calling MeterProvider::GetMeter from within the + * ScopeConfigurator::ComputeConfig function (as a scope_matcher callback set with + * ScopeConfigurator::AddCondition) is not supported and will result in a deadlock. + */ + void UpdateMeterConfigurator(std::unique_ptr> + meter_configurator) noexcept; + #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW void SetExemplarFilter(metrics::ExemplarFilterType exemplar_filter_type = diff --git a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h index e9aaebe6f5..3fa8eb10a3 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h @@ -24,6 +24,7 @@ #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/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" #include "opentelemetry/sdk/metrics/state/metric_collector.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" @@ -69,7 +70,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage ExemplarFilterType exempler_filter_type, nostd::shared_ptr &&exemplar_reservoir, #endif - const AggregationConfig *aggregation_config) + const AggregationConfig *aggregation_config, + std::shared_ptr meter_enabled_state = nullptr) : instrument_descriptor_(instrument_descriptor), aggregation_config_(AggregationConfig::GetOrDefault(aggregation_config)), attributes_hashmap_( @@ -79,7 +81,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage exemplar_filter_type_(exempler_filter_type), exemplar_reservoir_(std::move(exemplar_reservoir)), #endif - temporal_metric_storage_(instrument_descriptor, aggregation_type, aggregation_config) + temporal_metric_storage_(instrument_descriptor, aggregation_type, aggregation_config), + meter_enabled_state_(std::move(meter_enabled_state)) { create_default_aggregation_ = [&, aggregation_type, aggregation_config]() -> std::unique_ptr { @@ -300,6 +303,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage nostd::shared_ptr exemplar_reservoir_; #endif TemporalMetricStorage temporal_metric_storage_; + // Owning Meter's enabled flag; null (tests/benchmarks) means enabled. Gates Bind(). + std::shared_ptr meter_enabled_state_; opentelemetry::common::SpinLockMutex attribute_hashmap_lock_; #ifdef OPENTELEMETRY_HAVE_METRICS_BOUND_INSTRUMENTS_PREVIEW // NOTE: ENABLE_METRICS_BOUND_INSTRUMENTS_PREVIEW changes the layout and diff --git a/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h index 315accfc41..293379aedc 100644 --- a/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h +++ b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h @@ -11,6 +11,7 @@ #include "opentelemetry/context/context.h" #include "opentelemetry/metrics/sync_instruments.h" #include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" #include "opentelemetry/version.h" @@ -24,20 +25,31 @@ class Synchronous { public: Synchronous(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : instrument_descriptor_(instrument_descriptor), storage_(std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : instrument_descriptor_(instrument_descriptor), + storage_(std::move(storage)), + meter_enabled_state_(std::move(meter_enabled_state)) {} + /** True while the Meter that created this instrument is enabled. Null state means enabled. */ + bool IsEnabled() const noexcept + { + return !meter_enabled_state_ || meter_enabled_state_->IsEnabled(); + } + protected: InstrumentDescriptor instrument_descriptor_; std::unique_ptr storage_; + std::shared_ptr meter_enabled_state_; }; class LongCounter : public Synchronous, public opentelemetry::metrics::Counter { public: LongCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Add(uint64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -61,7 +73,8 @@ class DoubleCounter : public Synchronous, public opentelemetry::metrics::Counter public: DoubleCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Add(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -82,7 +95,8 @@ class LongUpDownCounter : public Synchronous, public opentelemetry::metrics::UpD { public: LongUpDownCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Add(int64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -98,7 +112,8 @@ class DoubleUpDownCounter : public Synchronous, public opentelemetry::metrics::U { public: DoubleUpDownCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Add(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -115,7 +130,8 @@ class LongGauge : public Synchronous, public opentelemetry::metrics::Gauge storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Record(int64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -131,7 +147,8 @@ class DoubleGauge : public Synchronous, public opentelemetry::metrics::Gauge storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); void Record(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; @@ -148,7 +165,8 @@ class LongHistogram : public Synchronous, public opentelemetry::metrics::Histogr { public: LongHistogram(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 void Record(uint64_t value, @@ -173,7 +191,8 @@ class DoubleHistogram : public Synchronous, public opentelemetry::metrics::Histo { public: DoubleHistogram(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage); + std::unique_ptr storage, + std::shared_ptr meter_enabled_state); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 void Record(double value, diff --git a/sdk/src/metrics/meter.cc b/sdk/src/metrics/meter.cc index 4a4c2f6b68..a9ab302f7b 100644 --- a/sdk/src/metrics/meter.cc +++ b/sdk/src/metrics/meter.cc @@ -28,6 +28,7 @@ #include "opentelemetry/sdk/metrics/meter.h" #include "opentelemetry/sdk/metrics/meter_config.h" #include "opentelemetry/sdk/metrics/meter_context.h" +#include "opentelemetry/sdk/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/async_metric_storage.h" #include "opentelemetry/sdk/metrics/state/metric_collector.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" @@ -94,37 +95,35 @@ namespace metrics namespace metrics = opentelemetry::metrics; -metrics::NoopMeter Meter::kNoopMeter = metrics::NoopMeter(); - Meter::Meter( std::weak_ptr meter_context, std::unique_ptr instrumentation_scope) noexcept : scope_{std::move(instrumentation_scope)}, meter_context_{std::move(meter_context)}, - observable_registry_(new ObservableRegistry()), - meter_config_(MeterConfig::Default()) + observable_registry_(new ObservableRegistry()) { if (auto meter_context_locked_ptr = meter_context_.lock()) { - meter_config_ = meter_context_locked_ptr->GetMeterConfigurator().ComputeConfig(*scope_); + UpdateMeterConfig(meter_context_locked_ptr->GetMeterConfigurator().ComputeConfig(*scope_)); } else { + UpdateMeterConfig(MeterConfig::Default()); OTEL_INTERNAL_LOG_ERROR("[Meter::Meter()] - Error during initialization." << "The metric context is invalid") } } +void Meter::UpdateMeterConfig(MeterConfig config) noexcept +{ + meter_enabled_state_->SetEnabled(config.IsEnabled()); +} + opentelemetry::nostd::unique_ptr> Meter::CreateUInt64Counter( opentelemetry::nostd::string_view name, opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateUInt64Counter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateUInt64Counter - failed. Invalid parameters." @@ -138,7 +137,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateUInt64 std::string{unit.data(), unit.size()}, InstrumentType::kCounter, InstrumentValueType::kLong}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>( - new LongCounter(instrument_descriptor, std::move(storage))); + new LongCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)); } opentelemetry::nostd::unique_ptr> Meter::CreateDoubleCounter( @@ -146,11 +145,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDoubleCo opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleCounter - failed. Invalid parameters." @@ -165,7 +159,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDoubleCo InstrumentValueType::kDouble}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new DoubleCounter(instrument_descriptor, std::move(storage))}; + new DoubleCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } opentelemetry::nostd::shared_ptr @@ -173,11 +167,6 @@ Meter::CreateInt64ObservableCounter(opentelemetry::nostd::string_view name, opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateInt64ObservableCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64ObservableCounter - failed. Invalid parameters." @@ -199,11 +188,6 @@ Meter::CreateDoubleObservableCounter(opentelemetry::nostd::string_view name, opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleObservableCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleObservableCounter - failed. Invalid parameters." @@ -225,11 +209,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateUInt opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateUInt64Histogram(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateUInt64Histogram - failed. Invalid parameters." @@ -244,7 +223,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateUInt InstrumentValueType::kLong}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new LongHistogram(instrument_descriptor, std::move(storage))}; + new LongHistogram(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> Meter::CreateDoubleHistogram( @@ -252,11 +231,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDouble opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleHistogram(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleHistogram - failed. Invalid parameters." @@ -271,7 +245,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDouble InstrumentValueType::kDouble}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new DoubleHistogram(instrument_descriptor, std::move(storage))}; + new DoubleHistogram(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -280,11 +254,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateInt64Gaug opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateInt64Gauge(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64Gauge - failed. Invalid parameters." @@ -298,7 +267,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateInt64Gaug std::string{unit.data(), unit.size()}, InstrumentType::kGauge, InstrumentValueType::kLong}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new LongGauge(instrument_descriptor, std::move(storage))}; + new LongGauge(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> Meter::CreateDoubleGauge( @@ -306,11 +275,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDoubleGaug opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleGauge(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleGauge - failed. Invalid parameters." @@ -324,7 +288,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDoubleGaug std::string{unit.data(), unit.size()}, InstrumentType::kGauge, InstrumentValueType::kDouble}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new DoubleGauge(instrument_descriptor, std::move(storage))}; + new DoubleGauge(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } #endif @@ -333,11 +297,6 @@ Meter::CreateInt64ObservableGauge(opentelemetry::nostd::string_view name, opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateInt64ObservableGauge(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64ObservableGauge - failed. Invalid parameters." @@ -359,11 +318,6 @@ Meter::CreateDoubleObservableGauge(opentelemetry::nostd::string_view name, opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleObservableGauge(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleObservableGauge - failed. Invalid parameters." @@ -385,11 +339,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateI opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateInt64UpDownCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64UpDownCounter - failed. Invalid parameters." @@ -404,7 +353,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateI InstrumentValueType::kLong}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new LongUpDownCounter(instrument_descriptor, std::move(storage))}; + new LongUpDownCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> Meter::CreateDoubleUpDownCounter( @@ -412,11 +361,6 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDo opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleUpDownCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleUpDownCounter - failed. Invalid parameters." @@ -431,7 +375,7 @@ opentelemetry::nostd::unique_ptr> Meter::CreateDo InstrumentValueType::kDouble}; auto storage = RegisterSyncMetricStorage(instrument_descriptor); return opentelemetry::nostd::unique_ptr>{ - new DoubleUpDownCounter(instrument_descriptor, std::move(storage))}; + new DoubleUpDownCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)}; } opentelemetry::nostd::shared_ptr @@ -439,11 +383,6 @@ Meter::CreateInt64ObservableUpDownCounter(opentelemetry::nostd::string_view name opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateInt64ObservableUpDownCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR( @@ -465,11 +404,6 @@ Meter::CreateDoubleObservableUpDownCounter(opentelemetry::nostd::string_view nam opentelemetry::nostd::string_view description, opentelemetry::nostd::string_view unit) noexcept { - if (!meter_config_.IsEnabled()) - { - return kNoopMeter.CreateDoubleObservableUpDownCounter(name, description, unit); - } - if (!ValidateInstrument(name, description, unit)) { OTEL_INTERNAL_LOG_ERROR( @@ -533,7 +467,10 @@ std::unique_ptr Meter::RegisterSyncMetricStorage( auto storage_iter = storage_registry_.find(view_instr_desc); if (storage_iter != storage_registry_.end()) { - WarnOnNameCaseConflict(GetInstrumentationScope(), storage_iter->first, view_instr_desc); + if (IsEnabled()) + { + WarnOnNameCaseConflict(GetInstrumentationScope(), storage_iter->first, view_instr_desc); + } // static_pointer_cast is okay here. If storage_registry_.find is successful // InstrumentEqualNameCaseInsensitive ensures that the // instrument type and value type are the same for the existing and new instrument. @@ -541,7 +478,11 @@ std::unique_ptr Meter::RegisterSyncMetricStorage( } else { - WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, view_instr_desc); + if (IsEnabled()) + { + WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, + view_instr_desc); + } sync_storage = std::shared_ptr(new SyncMetricStorage( view_instr_desc, view.GetAggregationType(), view.GetAttributesProcessor(), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW @@ -549,7 +490,7 @@ std::unique_ptr Meter::RegisterSyncMetricStorage( GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(), view_instr_desc), #endif - view.GetAggregationConfig())); + view.GetAggregationConfig(), meter_enabled_state_)); storage_registry_.insert({view_instr_desc, sync_storage}); } auto sync_multi_storage = static_cast(storages.get()); @@ -606,7 +547,10 @@ std::unique_ptr Meter::RegisterAsyncMetricStorage( auto storage_iter = storage_registry_.find(view_instr_desc); if (storage_iter != storage_registry_.end()) { - WarnOnNameCaseConflict(GetInstrumentationScope(), storage_iter->first, view_instr_desc); + if (IsEnabled()) + { + WarnOnNameCaseConflict(GetInstrumentationScope(), storage_iter->first, view_instr_desc); + } // static_pointer_cast is okay here. If storage_registry_.find is successful // InstrumentEqualNameCaseInsensitive ensures that the // instrument type and value type are the same for the existing and new instrument. @@ -614,7 +558,11 @@ std::unique_ptr Meter::RegisterAsyncMetricStorage( } else { - WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, view_instr_desc); + if (IsEnabled()) + { + WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, + view_instr_desc); + } async_storage = std::shared_ptr(new AsyncMetricStorage( view_instr_desc, view.GetAggregationType(), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW @@ -642,7 +590,7 @@ std::unique_ptr Meter::RegisterAsyncMetricStorage( std::vector Meter::Collect(CollectorHandle *collector, opentelemetry::common::SystemTimestamp collect_ts) noexcept { - if (!meter_config_.IsEnabled()) + if (!IsEnabled()) { return std::vector(); } diff --git a/sdk/src/metrics/meter_context.cc b/sdk/src/metrics/meter_context.cc index 863ef21839..b76222714f 100644 --- a/sdk/src/metrics/meter_context.cc +++ b/sdk/src/metrics/meter_context.cc @@ -66,6 +66,19 @@ const instrumentationscope::ScopeConfigurator &MeterContext::GetMet return *meter_configurator_; } +void MeterContext::SetMeterConfigurator( + std::unique_ptr> + meter_configurator) noexcept +{ + if (!meter_configurator) + { + OTEL_INTERNAL_LOG_ERROR( + "[MeterContext::SetMeterConfigurator] meter_configurator must not be null, ignoring."); + return; + } + meter_configurator_ = std::move(meter_configurator); +} + bool MeterContext::ForEachMeter( nostd::function_ref &meter)> callback) noexcept { diff --git a/sdk/src/metrics/meter_context_factory.cc b/sdk/src/metrics/meter_context_factory.cc index 27efbc965f..bb63f045cb 100644 --- a/sdk/src/metrics/meter_context_factory.cc +++ b/sdk/src/metrics/meter_context_factory.cc @@ -4,7 +4,6 @@ #include #include -#include #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/metrics/meter_config.h" #include "opentelemetry/sdk/metrics/meter_context.h" diff --git a/sdk/src/metrics/meter_provider.cc b/sdk/src/metrics/meter_provider.cc index f550d9bafa..2a2bbc3ad2 100644 --- a/sdk/src/metrics/meter_provider.cc +++ b/sdk/src/metrics/meter_provider.cc @@ -130,6 +130,34 @@ void MeterProvider::AddView(std::unique_ptr instrument_selec context_->AddView(std::move(instrument_selector), std::move(meter_selector), std::move(view)); } +void MeterProvider::UpdateMeterConfigurator( + std::unique_ptr> + meter_configurator) noexcept +{ + if (!meter_configurator) + { + OTEL_INTERNAL_LOG_ERROR( + "[MeterProvider::UpdateMeterConfigurator] meter_configurator must not be null, " + "ignoring."); + return; + } + + // Shares the lock with GetMeter so a Meter is never returned while its MeterConfig is out of + // date with the provider configurator. + const std::lock_guard guard(lock_); + context_->SetMeterConfigurator(std::move(meter_configurator)); + + // Meter construction and Meter::UpdateMeterConfig both hold lock_, so this span is safe. Do NOT + // use ForEachMeter: it takes meter_lock_, which the collect path holds before lock_, so + // deadlocks. + for (auto &meter : context_->GetMeters()) + { + MeterConfig new_config = + context_->GetMeterConfigurator().ComputeConfig(*meter->GetInstrumentationScope()); + meter->UpdateMeterConfig(new_config); + } +} + #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW void MeterProvider::SetExemplarFilter(metrics::ExemplarFilterType exemplar_filter_type) noexcept diff --git a/sdk/src/metrics/meter_provider_factory.cc b/sdk/src/metrics/meter_provider_factory.cc index 052639e341..2cc7d0b95e 100644 --- a/sdk/src/metrics/meter_provider_factory.cc +++ b/sdk/src/metrics/meter_provider_factory.cc @@ -4,7 +4,6 @@ #include #include -#include #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/metrics/meter_config.h" #include "opentelemetry/sdk/metrics/meter_context.h" diff --git a/sdk/src/metrics/state/sync_metric_storage.cc b/sdk/src/metrics/state/sync_metric_storage.cc index 80ec279d10..8c2595fc6f 100644 --- a/sdk/src/metrics/state/sync_metric_storage.cc +++ b/sdk/src/metrics/state/sync_metric_storage.cc @@ -10,7 +10,6 @@ #include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" -#include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" #include "opentelemetry/sdk/metrics/state/sync_metric_storage.h" #include "opentelemetry/sdk/metrics/state/temporal_metric_storage.h" @@ -27,6 +26,7 @@ # include "opentelemetry/sdk/metrics/aggregation/aggregation.h" # include "opentelemetry/sdk/metrics/data/exemplar_data.h" # include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/meter_enabled_state.h" #endif OPENTELEMETRY_BEGIN_NAMESPACE @@ -186,6 +186,13 @@ bool SyncMetricStorage::Collect(CollectorHandle *collector, std::shared_ptr SyncMetricStorage::Bind( const opentelemetry::common::KeyValueIterable &attributes) noexcept { + // Bound entries are only GC'd during Collect(), which a disabled Meter skips, so binding here + // would leak cardinality slots permanently. nullptr is the documented "unsupported" contract. + if (meter_enabled_state_ && !meter_enabled_state_->IsEnabled()) + { + return nullptr; + } + // Filter attributes once, at bind time. MetricAttributes filtered{attributes, attributes_processor_.get()}; diff --git a/sdk/src/metrics/sync_instruments.cc b/sdk/src/metrics/sync_instruments.cc index ae19a46f2f..4ac7b23ca8 100644 --- a/sdk/src/metrics/sync_instruments.cc +++ b/sdk/src/metrics/sync_instruments.cc @@ -17,6 +17,7 @@ #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" #include "opentelemetry/sdk/metrics/sync_instruments.h" @@ -42,8 +43,9 @@ bool ToInt64Value(uint64_t value, const char *operation, int64_t &converted) noe } // namespace LongCounter::LongCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -55,6 +57,10 @@ LongCounter::LongCounter(const InstrumentDescriptor &instrument_descriptor, void LongCounter::Add(uint64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -73,6 +79,10 @@ void LongCounter::Add(uint64_t value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongCounter::Add(V,A,C)] Value not recorded - invalid storage for: " @@ -88,6 +98,10 @@ void LongCounter::Add(uint64_t value, void LongCounter::Add(uint64_t value) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -104,6 +118,10 @@ void LongCounter::Add(uint64_t value) noexcept void LongCounter::Add(uint64_t value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongCounter::Add(V,C)] Value not recorded - invalid storage for: " @@ -118,8 +136,9 @@ void LongCounter::Add(uint64_t value, const opentelemetry::context::Context &con } DoubleCounter::DoubleCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -131,6 +150,10 @@ DoubleCounter::DoubleCounter(const InstrumentDescriptor &instrument_descriptor, void DoubleCounter::Add(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[DoubleCounter::Add(V,A)] Value not recorded - negative value for: " @@ -151,6 +174,10 @@ void DoubleCounter::Add(double value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[DoubleCounter::Add(V,A,C)] Value not recorded - negative value for: " @@ -168,6 +195,10 @@ void DoubleCounter::Add(double value, void DoubleCounter::Add(double value) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[DoubleCounter::Add(V)] Value not recorded - negative value for: " @@ -186,6 +217,10 @@ void DoubleCounter::Add(double value) noexcept void DoubleCounter::Add(double value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[DoubleCounter::Add(V)] Value not recorded - negative value for: " @@ -202,8 +237,9 @@ void DoubleCounter::Add(double value, const opentelemetry::context::Context &con } LongUpDownCounter::LongUpDownCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -216,6 +252,10 @@ LongUpDownCounter::LongUpDownCounter(const InstrumentDescriptor &instrument_desc void LongUpDownCounter::Add(int64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -231,6 +271,10 @@ void LongUpDownCounter::Add(int64_t value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -243,6 +287,10 @@ void LongUpDownCounter::Add(int64_t value, void LongUpDownCounter::Add(int64_t value) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -255,6 +303,10 @@ void LongUpDownCounter::Add(int64_t value) noexcept void LongUpDownCounter::Add(int64_t value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -266,8 +318,9 @@ void LongUpDownCounter::Add(int64_t value, const opentelemetry::context::Context } DoubleUpDownCounter::DoubleUpDownCounter(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -280,6 +333,10 @@ DoubleUpDownCounter::DoubleUpDownCounter(const InstrumentDescriptor &instrument_ void DoubleUpDownCounter::Add(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -294,6 +351,10 @@ void DoubleUpDownCounter::Add(double value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -306,6 +367,10 @@ void DoubleUpDownCounter::Add(double value, void DoubleUpDownCounter::Add(double value) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -319,6 +384,10 @@ void DoubleUpDownCounter::Add(double value) noexcept void DoubleUpDownCounter::Add(double value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -331,8 +400,9 @@ void DoubleUpDownCounter::Add(double value, const opentelemetry::context::Contex #if OPENTELEMETRY_ABI_VERSION_NO >= 2 LongGauge::LongGauge(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -344,6 +414,10 @@ LongGauge::LongGauge(const InstrumentDescriptor &instrument_descriptor, void LongGauge::Record(int64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -358,6 +432,10 @@ void LongGauge::Record(int64_t value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongGauge::Record(V,A,C)] Value not recorded - invalid storage for: " @@ -369,6 +447,10 @@ void LongGauge::Record(int64_t value, void LongGauge::Record(int64_t value) noexcept { + if (!IsEnabled()) + { + return; + } auto context = opentelemetry::context::Context{}; if (!storage_) { @@ -381,6 +463,10 @@ void LongGauge::Record(int64_t value) noexcept void LongGauge::Record(int64_t value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongGauge::Record(V,C)] Value not recorded - invalid storage for: " @@ -391,8 +477,9 @@ void LongGauge::Record(int64_t value, const opentelemetry::context::Context &con } DoubleGauge::DoubleGauge(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -404,6 +491,10 @@ DoubleGauge::DoubleGauge(const InstrumentDescriptor &instrument_descriptor, void DoubleGauge::Record(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[DoubleGauge::Record(V,A)] Value not recorded - invalid storage for: " @@ -417,6 +508,10 @@ void DoubleGauge::Record(double value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[DoubleGauge::Record(V,A,C)] Value not recorded - invalid storage for: " @@ -428,6 +523,10 @@ void DoubleGauge::Record(double value, void DoubleGauge::Record(double value) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[DoubleGauge::Record(V)] Value not recorded - invalid storage for: " @@ -440,6 +539,10 @@ void DoubleGauge::Record(double value) noexcept void DoubleGauge::Record(double value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[DoubleGauge::Record(V,C)] Value not recorded - invalid storage for: " @@ -451,8 +554,9 @@ void DoubleGauge::Record(double value, const opentelemetry::context::Context &co #endif LongHistogram::LongHistogram(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -465,6 +569,10 @@ void LongHistogram::Record(uint64_t value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN( @@ -481,6 +589,10 @@ void LongHistogram::Record(uint64_t value, void LongHistogram::Record(uint64_t value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongHistogram::Record(V,C)] Value not recorded - invalid storage for: " @@ -498,6 +610,10 @@ void LongHistogram::Record(uint64_t value, const opentelemetry::context::Context void LongHistogram::Record(uint64_t value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongHistogram::Record(V,A)] Value not recorded - invalid storage for: " @@ -514,6 +630,10 @@ void LongHistogram::Record(uint64_t value, void LongHistogram::Record(uint64_t value) noexcept { + if (!IsEnabled()) + { + return; + } if (!storage_) { OTEL_INTERNAL_LOG_WARN("[LongHistogram::Record(V)] Value not recorded - invalid storage for: " @@ -530,8 +650,9 @@ void LongHistogram::Record(uint64_t value) noexcept #endif DoubleHistogram::DoubleHistogram(const InstrumentDescriptor &instrument_descriptor, - std::unique_ptr storage) - : Synchronous(instrument_descriptor, std::move(storage)) + std::unique_ptr storage, + std::shared_ptr meter_enabled_state) + : Synchronous(instrument_descriptor, std::move(storage), std::move(meter_enabled_state)) { if (!storage_) { @@ -545,6 +666,10 @@ void DoubleHistogram::Record(double value, const opentelemetry::common::KeyValueIterable &attributes, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN( @@ -564,6 +689,10 @@ void DoubleHistogram::Record(double value, void DoubleHistogram::Record(double value, const opentelemetry::context::Context &context) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN( @@ -585,6 +714,10 @@ void DoubleHistogram::Record(double value, const opentelemetry::context::Context void DoubleHistogram::Record(double value, const opentelemetry::common::KeyValueIterable &attributes) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN( @@ -605,6 +738,10 @@ void DoubleHistogram::Record(double value, void DoubleHistogram::Record(double value) noexcept { + if (!IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[DoubleHistogram::Record(V)] Value not recorded - negative value for: " @@ -629,11 +766,16 @@ namespace class BoundLongCounterImpl : public opentelemetry::metrics::BoundCounter { public: - explicit BoundLongCounterImpl(std::shared_ptr storage) noexcept - : storage_(std::move(storage)) + BoundLongCounterImpl(std::shared_ptr storage, + std::shared_ptr meter_enabled_state) noexcept + : storage_(std::move(storage)), meter_enabled_state_(std::move(meter_enabled_state)) {} void Add(uint64_t value) noexcept override { + if (meter_enabled_state_ && !meter_enabled_state_->IsEnabled()) + { + return; + } if (storage_) { int64_t converted = 0; @@ -646,16 +788,22 @@ class BoundLongCounterImpl : public opentelemetry::metrics::BoundCounter storage_; + std::shared_ptr meter_enabled_state_; }; class BoundDoubleCounterImpl : public opentelemetry::metrics::BoundCounter { public: - explicit BoundDoubleCounterImpl(std::shared_ptr storage) noexcept - : storage_(std::move(storage)) + BoundDoubleCounterImpl(std::shared_ptr storage, + std::shared_ptr meter_enabled_state) noexcept + : storage_(std::move(storage)), meter_enabled_state_(std::move(meter_enabled_state)) {} void Add(double value) noexcept override { + if (meter_enabled_state_ && !meter_enabled_state_->IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN("[BoundDoubleCounter::Add(V)] Value not recorded - negative value"); @@ -669,16 +817,22 @@ class BoundDoubleCounterImpl : public opentelemetry::metrics::BoundCounter storage_; + std::shared_ptr meter_enabled_state_; }; class BoundLongHistogramImpl : public opentelemetry::metrics::BoundHistogram { public: - explicit BoundLongHistogramImpl(std::shared_ptr storage) noexcept - : storage_(std::move(storage)) + BoundLongHistogramImpl(std::shared_ptr storage, + std::shared_ptr meter_enabled_state) noexcept + : storage_(std::move(storage)), meter_enabled_state_(std::move(meter_enabled_state)) {} void Record(uint64_t value) noexcept override { + if (meter_enabled_state_ && !meter_enabled_state_->IsEnabled()) + { + return; + } if (storage_) { int64_t converted = 0; @@ -691,17 +845,22 @@ class BoundLongHistogramImpl : public opentelemetry::metrics::BoundHistogram storage_; + std::shared_ptr meter_enabled_state_; }; class BoundDoubleHistogramImpl : public opentelemetry::metrics::BoundHistogram { public: - explicit BoundDoubleHistogramImpl( - std::shared_ptr storage) noexcept - : storage_(std::move(storage)) + BoundDoubleHistogramImpl(std::shared_ptr storage, + std::shared_ptr meter_enabled_state) noexcept + : storage_(std::move(storage)), meter_enabled_state_(std::move(meter_enabled_state)) {} void Record(double value) noexcept override { + if (meter_enabled_state_ && !meter_enabled_state_->IsEnabled()) + { + return; + } if (value < 0) { OTEL_INTERNAL_LOG_WARN( @@ -716,6 +875,7 @@ class BoundDoubleHistogramImpl : public opentelemetry::metrics::BoundHistogram storage_; + std::shared_ptr meter_enabled_state_; }; } // namespace @@ -729,7 +889,7 @@ opentelemetry::nostd::unique_ptr> bound = storage_->Bind(attributes); } return opentelemetry::nostd::unique_ptr>{ - new BoundLongCounterImpl(std::move(bound))}; + new BoundLongCounterImpl(std::move(bound), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> DoubleCounter::Bind( @@ -741,7 +901,7 @@ opentelemetry::nostd::unique_ptr> D bound = storage_->Bind(attributes); } return opentelemetry::nostd::unique_ptr>{ - new BoundDoubleCounterImpl(std::move(bound))}; + new BoundDoubleCounterImpl(std::move(bound), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> @@ -753,7 +913,7 @@ LongHistogram::Bind(const opentelemetry::common::KeyValueIterable &attributes) n bound = storage_->Bind(attributes); } return opentelemetry::nostd::unique_ptr>{ - new BoundLongHistogramImpl(std::move(bound))}; + new BoundLongHistogramImpl(std::move(bound), meter_enabled_state_)}; } opentelemetry::nostd::unique_ptr> @@ -765,7 +925,7 @@ DoubleHistogram::Bind(const opentelemetry::common::KeyValueIterable &attributes) bound = storage_->Bind(attributes); } return opentelemetry::nostd::unique_ptr>{ - new BoundDoubleHistogramImpl(std::move(bound))}; + new BoundDoubleHistogramImpl(std::move(bound), meter_enabled_state_)}; } #endif diff --git a/sdk/test/metrics/bound_sync_instruments_test.cc b/sdk/test/metrics/bound_sync_instruments_test.cc index 756030437e..d46871b5af 100644 --- a/sdk/test/metrics/bound_sync_instruments_test.cc +++ b/sdk/test/metrics/bound_sync_instruments_test.cc @@ -31,6 +31,7 @@ # include "opentelemetry/sdk/metrics/data/metric_data.h" # include "opentelemetry/sdk/metrics/data/point_data.h" # include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/meter_enabled_state.h" # include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" # include "opentelemetry/sdk/metrics/state/metric_collector.h" # include "opentelemetry/sdk/metrics/state/metric_storage.h" @@ -193,7 +194,7 @@ TEST(BoundSyncInstruments, BoundCounterBindInitializerList) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongCounter counter(desc, std::move(storage)); + LongCounter counter(desc, std::move(storage), std::make_shared()); opentelemetry::metrics::Counter &api_counter = counter; auto bound = api_counter.Bind({{"key", "v"}}); @@ -245,7 +246,7 @@ TEST(BoundSyncInstruments, UnboundCounterDropsValueAboveInt64Max) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongCounter counter(desc, std::move(storage)); + LongCounter counter(desc, std::move(storage), std::make_shared()); M attrs = {{"key", "v"}}; auto kv = KeyValueIterableView(attrs); @@ -268,7 +269,7 @@ TEST(BoundSyncInstruments, BoundCounterDropsValueAboveInt64Max) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongCounter counter(desc, std::move(storage)); + LongCounter counter(desc, std::move(storage), std::make_shared()); M attrs = {{"key", "v"}}; auto bound = counter.Bind(KeyValueIterableView(attrs)); ASSERT_NE(bound, nullptr); @@ -322,7 +323,7 @@ TEST(BoundSyncInstruments, UnboundHistogramDropsValueAboveInt64Max) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongHistogram histogram(desc, std::move(storage)); + LongHistogram histogram(desc, std::move(storage), std::make_shared()); M attrs = {{"key", "v"}}; auto kv = KeyValueIterableView(attrs); @@ -361,7 +362,7 @@ TEST(BoundSyncInstruments, BoundHistogramDropsValueAboveInt64Max) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongHistogram histogram(desc, std::move(storage)); + LongHistogram histogram(desc, std::move(storage), std::make_shared()); M attrs = {{"key", "v"}}; auto bound = histogram.Bind(KeyValueIterableView(attrs)); ASSERT_NE(bound, nullptr); @@ -401,7 +402,7 @@ TEST(BoundSyncInstruments, BoundHistogramBindInitializerList) # endif &cfg)); SyncMetricStorage *storage_ptr = storage.get(); - LongHistogram histogram(desc, std::move(storage)); + LongHistogram histogram(desc, std::move(storage), std::make_shared()); opentelemetry::metrics::Histogram &api_histogram = histogram; auto bound = api_histogram.Bind({{"key", "v"}}); @@ -1126,4 +1127,84 @@ TEST(BoundSyncInstruments, OverflowParityAllowsFillingRemainingSlot) EXPECT_FALSE(overflow_seen); } +// A bound instrument caches its storage at Bind time, so it must still observe the live state. +TEST(BoundSyncInstruments, BoundCounterObservesMeterEnabledState) +{ + InstrumentDescriptor desc{"name", "desc", "1unit", InstrumentType::kCounter, + InstrumentValueType::kLong}; + std::shared_ptr proc(new DefaultAttributesProcessor{}); + AggregationConfig cfg; + std::unique_ptr storage(new SyncMetricStorage( + desc, AggregationType::kSum, proc, +# ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW + ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), +# endif + &cfg)); + SyncMetricStorage *storage_ptr = storage.get(); + + auto meter_enabled_state = std::make_shared(false); + LongCounter counter(desc, std::move(storage), meter_enabled_state); + opentelemetry::metrics::Counter &api_counter = counter; + + auto bound = api_counter.Bind({{"key", "v"}}); + ASSERT_NE(bound, nullptr); + + M attrs = {{"key", "v"}}; + bound->Add(5); + EXPECT_EQ(SumLongFor(*storage_ptr, AggregationTemporality::kDelta, attrs), 0); + + meter_enabled_state->SetEnabled(true); + bound->Add(5); + EXPECT_EQ(SumLongFor(*storage_ptr, AggregationTemporality::kDelta, attrs), 5); + + meter_enabled_state->SetEnabled(false); + bound->Add(5); + EXPECT_EQ(SumLongFor(*storage_ptr, AggregationTemporality::kDelta, attrs), 0); +} + +// Binding while disabled must not consume cardinality: entries are only GC'd during Collect(), +// which a disabled meter skips, so an eager bind would leak slots permanently. +TEST(BoundSyncInstruments, BindOnDisabledMeterDoesNotConsumeCardinality) +{ + InstrumentDescriptor desc{"name", "desc", "1unit", InstrumentType::kCounter, + InstrumentValueType::kLong}; + std::shared_ptr proc(new DefaultAttributesProcessor{}); + // Cardinality limit of 2 so exhaustion is easy to observe. + AggregationConfig cfg(2); + auto meter_enabled_state = std::make_shared(false); + std::unique_ptr storage(new SyncMetricStorage( + desc, AggregationType::kSum, proc, +# ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW + ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), +# endif + &cfg, meter_enabled_state)); + SyncMetricStorage *storage_ptr = storage.get(); + LongCounter counter(desc, std::move(storage), meter_enabled_state); + opentelemetry::metrics::Counter &api_counter = counter; + + // Retain the handles; dropped ones are GC'd during Collect() and would mask the leak. + std::vector>> + held; + for (int i = 0; i < 50; ++i) + { + auto bound = api_counter.Bind({{"key", std::to_string(i)}}); + if (bound) + { + bound->Add(1); + held.push_back(std::move(bound)); + } + } + EXPECT_EQ(CollectAndCountPoints(*storage_ptr, AggregationTemporality::kDelta), 0u); + EXPECT_FALSE(HasOverflowPoint(*storage_ptr, AggregationTemporality::kDelta)); + + // Once enabled, binding works normally and the budget was never poisoned. + meter_enabled_state->SetEnabled(true); + auto bound = api_counter.Bind({{"key", "after-enable"}}); + ASSERT_NE(bound, nullptr); + bound->Add(7); + + M attrs = {{"key", "after-enable"}}; + EXPECT_EQ(SumLongFor(*storage_ptr, AggregationTemporality::kDelta, attrs), 7); +} + #endif // OPENTELEMETRY_HAVE_METRICS_BOUND_INSTRUMENTS_PREVIEW diff --git a/sdk/test/metrics/meter_provider_sdk_test.cc b/sdk/test/metrics/meter_provider_sdk_test.cc index dd0e02920c..b5d8899349 100644 --- a/sdk/test/metrics/meter_provider_sdk_test.cc +++ b/sdk/test/metrics/meter_provider_sdk_test.cc @@ -2,18 +2,35 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include +#include +#include +#include #include +#include #include #include #include "common.h" #include "opentelemetry/common/macros.h" #include "opentelemetry/metrics/meter.h" +#include "opentelemetry/metrics/observer_result.h" +#include "opentelemetry/metrics/sync_instruments.h" +#include "opentelemetry/nostd/function_ref.h" +#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" #include "opentelemetry/sdk/metrics/instruments.h" #include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/meter_config.h" +#include "opentelemetry/sdk/metrics/meter_context.h" #include "opentelemetry/sdk/metrics/meter_provider.h" #include "opentelemetry/sdk/metrics/meter_provider_factory.h" #include "opentelemetry/sdk/metrics/metric_reader.h" @@ -21,18 +38,16 @@ #include "opentelemetry/sdk/metrics/view/instrument_selector.h" #include "opentelemetry/sdk/metrics/view/meter_selector.h" #include "opentelemetry/sdk/metrics/view/view.h" +#include "opentelemetry/sdk/metrics/view/view_registry.h" +#include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/test_common/sdk/common/scoped_test_log_handler.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -# include -# include # include # include # include "opentelemetry/common/attribute_value.h" # include "opentelemetry/nostd/utility.h" -# include "opentelemetry/nostd/variant.h" -# include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #endif /* OPENTELEMETRY_ABI_VERSION_NO >= 2 */ using namespace opentelemetry::sdk::metrics; @@ -343,3 +358,401 @@ TEST(MeterProvider, ExplicitShutdownNotWarnOnDestructionCheck) logs = log_handler.Drain(); EXPECT_TRUE(logs.empty()); } + +namespace +{ + +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; + +// Returns a ScopeConfigurator that enables all meters (default config). +std::unique_ptr> EnableAll() +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder(MeterConfig::Default()).Build()); +} + +// Returns a ScopeConfigurator that disables all meters. +std::unique_ptr> DisableAll() +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder(MeterConfig::Disabled()).Build()); +} + +// Returns a ScopeConfigurator where the named scope is disabled and all other scopes are enabled. +std::unique_ptr> DisableByName( + opentelemetry::nostd::string_view name) +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder(MeterConfig::Default()) + .AddConditionNameEquals(name, MeterConfig::Disabled()) + .Build()); +} + +// Builds a MeterProvider whose MetricReader pointer is returned via the out parameter. +std::shared_ptr MakeProvider( + MetricReader *&reader_out, + std::unique_ptr> configurator = EnableAll()) +{ + std::unique_ptr reader{new MockMetricReader()}; + reader_out = reader.get(); + auto provider = std::make_shared( + std::unique_ptr(new ViewRegistry()), + opentelemetry::sdk::resource::Resource::Create({}), std::move(configurator)); + provider->AddMetricReader(std::move(reader)); + return provider; +} + +// Returns the set of instrumentation scope names present in a collection. +std::set CollectScopeNames(MetricReader *reader) +{ + std::set scope_names; + reader->Collect([&scope_names](ResourceMetrics &metric_data) { + for (const auto &scope_metrics : metric_data.scope_metric_data_) + { + scope_names.insert(scope_metrics.scope_->GetName()); + } + return true; + }); + return scope_names; +} + +// Collected sum of a uint64 counter, or -1 if absent (distinguishes "nothing" from "sum 0"). +int64_t CollectCounterSum(MetricReader *reader, + const std::string &scope_name, + const std::string &instrument_name) +{ + int64_t sum = -1; + reader->Collect([&](ResourceMetrics &metric_data) { + for (const auto &scope_metrics : metric_data.scope_metric_data_) + { + if (scope_metrics.scope_->GetName() != scope_name) + { + continue; + } + for (const auto &md : scope_metrics.metric_data_) + { + if (md.instrument_descriptor.name_ != instrument_name) + { + continue; + } + for (const auto &pd : md.point_data_attr_) + { + const auto *sum_point = opentelemetry::nostd::get_if(&pd.point_data); + if (sum_point != nullptr) + { + const auto *value = opentelemetry::nostd::get_if(&sum_point->value_); + if (value != nullptr) + { + sum = (sum == -1) ? *value : sum + *value; + } + } + } + } + } + return true; + }); + return sum; +} + +} // namespace + +TEST(MeterProvider, UpdateMeterConfiguratorDisableByName) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + auto meter_disabled_by_update = provider->GetMeter("scope.disabled"); + auto meter_unaffected = provider->GetMeter("scope.unaffected"); + + // Both meters are initially enabled, so both produce working instruments. + auto counter_disabled_by_update = meter_disabled_by_update->CreateUInt64Counter("counter.a"); + auto counter_unaffected = meter_unaffected->CreateUInt64Counter("counter.b"); + + counter_disabled_by_update->Add(1); + counter_unaffected->Add(1); + EXPECT_EQ(CollectScopeNames(reader), + (std::set{"scope.disabled", "scope.unaffected"})); + + provider->UpdateMeterConfigurator(DisableByName("scope.disabled")); + + // The disabled meter is no longer collected, the other scope is untouched. + counter_disabled_by_update->Add(1); + counter_unaffected->Add(1); + EXPECT_EQ(CollectScopeNames(reader), (std::set{"scope.unaffected"})); +} + +TEST(MeterProvider, UpdateMeterConfiguratorReEnable) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + auto meter = provider->GetMeter("scope.toggle"); + auto counter = meter->CreateUInt64Counter("counter.toggle"); + + provider->UpdateMeterConfigurator(DisableAll()); + counter->Add(1); + EXPECT_TRUE(CollectScopeNames(reader).empty()); + + provider->UpdateMeterConfigurator(EnableAll()); + counter->Add(1); + EXPECT_EQ(CollectScopeNames(reader), (std::set{"scope.toggle"})); + + // The measurement recorded while the meter was disabled must not be retained: only the single + // Add made after re-enabling counts towards the cumulative sum. + EXPECT_EQ(1, CollectCounterSum(reader, "scope.toggle", "counter.toggle")); +} + +// Library code holds instruments for the process lifetime, so re-enabling a meter must not depend +// on the instrument being recreated. +TEST(MeterProvider, UpdateMeterConfiguratorInstrumentCreatedWhileDisabledRecordsAfterEnable) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader, DisableAll()); + ASSERT_NE(nullptr, reader); + + auto meter = provider->GetMeter("scope.revived"); + auto counter = meter->CreateUInt64Counter("counter.revived"); + + counter->Add(1); + EXPECT_TRUE(CollectScopeNames(reader).empty()); + + provider->UpdateMeterConfigurator(EnableAll()); + + counter->Add(1); + EXPECT_EQ(1, CollectCounterSum(reader, "scope.revived", "counter.revived")); +} + +// The mirror case: recording must stop when the meter is disabled. +TEST(MeterProvider, UpdateMeterConfiguratorInstrumentStopsRecordingAfterDisable) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + auto meter = provider->GetMeter("scope.silenced"); + auto counter = meter->CreateUInt64Counter("counter.silenced"); + + counter->Add(1); + EXPECT_EQ(1, CollectCounterSum(reader, "scope.silenced", "counter.silenced")); + + provider->UpdateMeterConfigurator(DisableAll()); + counter->Add(1); + EXPECT_TRUE(CollectScopeNames(reader).empty()); + + // Re-enabling must not reveal the measurement that was recorded while disabled. + provider->UpdateMeterConfigurator(EnableAll()); + EXPECT_EQ(1, CollectCounterSum(reader, "scope.silenced", "counter.silenced")); +} + +TEST(MeterProvider, UpdateMeterConfiguratorAppliesToAllExistingMeters) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + std::vector> meters; + std::vector>> counters; + for (const auto &name : {"scope.one", "scope.two", "scope.three"}) + { + meters.push_back(provider->GetMeter(name)); + counters.push_back(meters.back()->CreateUInt64Counter("counter")); + } + + provider->UpdateMeterConfigurator(DisableAll()); + for (auto &counter : counters) + { + counter->Add(1); + } + EXPECT_TRUE(CollectScopeNames(reader).empty()); + + provider->UpdateMeterConfigurator(EnableAll()); + for (auto &counter : counters) + { + counter->Add(1); + } + EXPECT_EQ(CollectScopeNames(reader), + (std::set{"scope.one", "scope.two", "scope.three"})); +} + +TEST(MeterProvider, UpdateMeterConfiguratorNewMeterUsesUpdatedConfig) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + provider->UpdateMeterConfigurator(DisableByName("scope.disabled")); + + // Meters created after the update use the updated configurator. A meter that is disabled at + // instrument creation time returns no-op instruments. + auto meter_disabled = provider->GetMeter("scope.disabled"); + auto meter_enabled = provider->GetMeter("scope.enabled"); + + auto counter_disabled = meter_disabled->CreateUInt64Counter("counter.a"); + auto counter_enabled = meter_enabled->CreateUInt64Counter("counter.b"); + + counter_disabled->Add(1); + counter_enabled->Add(1); + + EXPECT_EQ(CollectScopeNames(reader), (std::set{"scope.enabled"})); +} + +TEST(MeterProvider, UpdateMeterConfiguratorNullIgnored) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader, DisableByName("scope.disabled")); + ASSERT_NE(nullptr, reader); + + auto meter = provider->GetMeter("scope.enabled"); + auto counter = meter->CreateUInt64Counter("counter"); + + provider->UpdateMeterConfigurator(nullptr); + + // The existing configurator is retained. + counter->Add(1); + EXPECT_EQ(CollectScopeNames(reader), (std::set{"scope.enabled"})); + + auto meter_disabled = provider->GetMeter("scope.disabled"); + auto counter_disabled = meter_disabled->CreateUInt64Counter("counter.disabled"); + counter_disabled->Add(1); + EXPECT_EQ(CollectScopeNames(reader), (std::set{"scope.enabled"})); +} + +TEST(MeterProvider, UpdateMeterConfiguratorConcurrentGetMeter) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + constexpr int kUpdateCount = 200; + + std::atomic stop{false}; + std::promise worker_ready; + std::future worker_ready_future = worker_ready.get_future(); + + // Worker: create meters and record measurements while the configurator is being replaced. + std::thread worker([&] { + worker_ready.set_value(); + int i = 0; + while (!stop.load(std::memory_order_relaxed)) + { + auto meter = provider->GetMeter("scope." + std::to_string(i++ % 8)); + auto counter = meter->CreateUInt64Counter("counter"); + counter->Add(1); + } + }); + + worker_ready_future.wait(); + + for (int i = 0; i < kUpdateCount; ++i) + { + provider->UpdateMeterConfigurator(i % 2 == 0 ? DisableAll() : EnableAll()); + } + + stop.store(true, std::memory_order_relaxed); + worker.join(); + + // With the final configurator enabling all scopes, every existing meter must be collectable. + provider->UpdateMeterConfigurator(EnableAll()); + auto meter = provider->GetMeter("scope.final"); + auto counter = meter->CreateUInt64Counter("counter.final"); + counter->Add(1); + EXPECT_FALSE(CollectScopeNames(reader).empty()); +} + +// Guards the lock order between UpdateMeterConfigurator (lock_) and collection (meter_lock_ then +// lock_, via an observable callback calling GetMeter). Hangs rather than fails on regression. +TEST(MeterProvider, UpdateMeterConfiguratorConcurrentWithCollectDoesNotDeadlock) +{ + MetricReader *reader{}; + auto provider = MakeProvider(reader); + ASSERT_NE(nullptr, reader); + + auto meter = provider->GetMeter("scope.observable"); + + // Re-entering the provider from inside collection is what takes lock_ under meter_lock_. + static MeterProvider *callback_provider = provider.get(); + auto observable = meter->CreateInt64ObservableCounter("obs.counter"); + observable->AddCallback( + [](opentelemetry::metrics::ObserverResult, void *) noexcept { + if (callback_provider != nullptr) + { + auto reentrant = callback_provider->GetMeter("scope.observable"); + (void)reentrant; + } + }, + nullptr); + + constexpr int kUpdateCount = 500; + + std::atomic stop{false}; + std::promise collector_ready; + std::future collector_ready_future = collector_ready.get_future(); + + std::thread collector([&] { + collector_ready.set_value(); + while (!stop.load(std::memory_order_relaxed)) + { + reader->Collect([](ResourceMetrics &) { return true; }); + } + }); + + collector_ready_future.wait(); + + for (int i = 0; i < kUpdateCount; ++i) + { + provider->UpdateMeterConfigurator(i % 2 == 0 ? DisableAll() : EnableAll()); + } + + stop.store(true, std::memory_order_relaxed); + collector.join(); + + callback_provider = nullptr; + + provider->UpdateMeterConfigurator(EnableAll()); +} + +TEST(MeterProvider, SetMeterConfiguratorNullIgnoredOnContext) +{ + ScopedTestLogHandler log_handler{LogLevel::Error}; + + auto context = std::make_shared(std::unique_ptr(new ViewRegistry()), + opentelemetry::sdk::resource::Resource::Create({}), + DisableAll()); + + context->SetMeterConfigurator(nullptr); + + auto logs = log_handler.Drain(); + ASSERT_EQ(logs.size(), 1); + EXPECT_NE(logs[0].msg.find("must not be null"), std::string::npos); + + // The configurator passed at construction is retained. + auto scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create("scope"); + EXPECT_FALSE(context->GetMeterConfigurator().ComputeConfig(*scope).IsEnabled()); +} + +TEST(MeterProvider, MeterWithExpiredContextIsEnabledByDefault) +{ + ScopedTestLogHandler log_handler{LogLevel::Error}; + + std::weak_ptr expired_context; + { + auto context = std::make_shared( + std::unique_ptr(new ViewRegistry()), + opentelemetry::sdk::resource::Resource::Create({}), DisableAll()); + expired_context = context; + } + ASSERT_TRUE(expired_context.expired()); + + // A Meter cannot compute its config without a context, so it falls back to the default config. + Meter meter{expired_context, + opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create("scope")}; + + auto logs = log_handler.Drain(); + ASSERT_EQ(logs.size(), 1); + EXPECT_NE(logs[0].msg.find("The metric context is invalid"), std::string::npos); + + // MeterConfig::Default() is enabled, so instrument creation is not short circuited. + EXPECT_NE(nullptr, meter.CreateUInt64Counter("counter")); +} diff --git a/sdk/test/metrics/sync_instruments_test.cc b/sdk/test/metrics/sync_instruments_test.cc index 5128a43d80..e129fb4b25 100644 --- a/sdk/test/metrics/sync_instruments_test.cc +++ b/sdk/test/metrics/sync_instruments_test.cc @@ -12,6 +12,7 @@ #include "opentelemetry/context/context.h" #include "opentelemetry/nostd/utility.h" #include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_enabled_state.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" #include "opentelemetry/sdk/metrics/state/multi_metric_storage.h" #include "opentelemetry/sdk/metrics/sync_instruments.h" @@ -26,7 +27,8 @@ TEST(SyncInstruments, LongCounter) InstrumentDescriptor instrument_descriptor = { "long_counter", "description", "1", InstrumentType::kCounter, InstrumentValueType::kLong}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - LongCounter counter(instrument_descriptor, std::move(metric_storage)); + LongCounter counter(instrument_descriptor, std::move(metric_storage), + std::make_shared()); counter.Add(10); counter.Add(10, opentelemetry::context::Context{}); @@ -43,7 +45,8 @@ TEST(SyncInstruments, DoubleCounter) InstrumentDescriptor instrument_descriptor = { "double_counter", "description", "1", InstrumentType::kCounter, InstrumentValueType::kDouble}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - DoubleCounter counter(instrument_descriptor, std::move(metric_storage)); + DoubleCounter counter(instrument_descriptor, std::move(metric_storage), + std::make_shared()); counter.Add(10.10); counter.Add(10.10, opentelemetry::context::Context{}); @@ -63,7 +66,8 @@ TEST(SyncInstruments, LongUpDownCounter) InstrumentType::kUpDownCounter, InstrumentValueType::kLong}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - LongUpDownCounter counter(instrument_descriptor, std::move(metric_storage)); + LongUpDownCounter counter(instrument_descriptor, std::move(metric_storage), + std::make_shared()); counter.Add(10); counter.Add(10, opentelemetry::context::Context{}); @@ -93,7 +97,8 @@ TEST(SyncInstruments, DoubleUpDownCounter) InstrumentType::kUpDownCounter, InstrumentValueType::kDouble}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - DoubleUpDownCounter counter(instrument_descriptor, std::move(metric_storage)); + DoubleUpDownCounter counter(instrument_descriptor, std::move(metric_storage), + std::make_shared()); counter.Add(10.10); counter.Add(10.10, opentelemetry::context::Context{}); @@ -113,7 +118,8 @@ TEST(SyncInstruments, LongGauge) InstrumentDescriptor instrument_descriptor = {"long_gauge", "description", "1", InstrumentType::kGauge, InstrumentValueType::kLong}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - LongGauge gauge(instrument_descriptor, std::move(metric_storage)); + LongGauge gauge(instrument_descriptor, std::move(metric_storage), + std::make_shared()); gauge.Record(10); gauge.Record(10, opentelemetry::context::Context{}); @@ -131,7 +137,8 @@ TEST(SyncInstruments, DoubleGauge) InstrumentDescriptor instrument_descriptor = { "double_gauge", "description", "1", InstrumentType::kGauge, InstrumentValueType::kDouble}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - DoubleGauge gauge(instrument_descriptor, std::move(metric_storage)); + DoubleGauge gauge(instrument_descriptor, std::move(metric_storage), + std::make_shared()); gauge.Record(10.10); gauge.Record(10.10, opentelemetry::context::Context{}); @@ -151,7 +158,8 @@ TEST(SyncInstruments, LongHistogram) InstrumentDescriptor instrument_descriptor = { "long_histogram", "description", "1", InstrumentType::kHistogram, InstrumentValueType::kLong}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - LongHistogram histogram(instrument_descriptor, std::move(metric_storage)); + LongHistogram histogram(instrument_descriptor, std::move(metric_storage), + std::make_shared()); histogram.Record(10, opentelemetry::context::Context{}); histogram.Record(10, @@ -172,7 +180,8 @@ TEST(SyncInstruments, DoubleHistogram) InstrumentType::kHistogram, InstrumentValueType::kDouble}; std::unique_ptr metric_storage(new SyncMultiMetricStorage()); - DoubleHistogram histogram(instrument_descriptor, std::move(metric_storage)); + DoubleHistogram histogram(instrument_descriptor, std::move(metric_storage), + std::make_shared()); histogram.Record(10.10, opentelemetry::context::Context{}); histogram.Record(-10.10, opentelemetry::context::Context{}); // This is ignored. histogram.Record(std::numeric_limits::quiet_NaN(), @@ -186,3 +195,23 @@ TEST(SyncInstruments, DoubleHistogram) histogram.Record(10.10, opentelemetry::common::KeyValueIterableView({}), opentelemetry::context::Context{}); } + +TEST(SyncInstruments, DisabledMeterStateSuppressesRecording) +{ + InstrumentDescriptor instrument_descriptor = { + "long_counter", "description", "1", InstrumentType::kCounter, InstrumentValueType::kLong}; + std::unique_ptr metric_storage(new SyncMultiMetricStorage()); + auto meter_enabled_state = std::make_shared(false); + LongCounter counter(instrument_descriptor, std::move(metric_storage), meter_enabled_state); + + EXPECT_FALSE(counter.IsEnabled()); + counter.Add(10); + + // Enabling the meter enables the instrument without recreating it. + meter_enabled_state->SetEnabled(true); + EXPECT_TRUE(counter.IsEnabled()); + counter.Add(10); + + meter_enabled_state->SetEnabled(false); + EXPECT_FALSE(counter.IsEnabled()); +}