Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Increment the:

## [Unreleased]

* [API/SDK] Add `Enabled()` to synchronous metric instruments (ABI v2). It
returns `false` when the Meter is disabled or every matching view uses the
Drop aggregation
[#4721](https://github.com/open-telemetry/opentelemetry-cpp/pull/4721)

* [API/SDK] Add ASCII helpers and remove regex from InstrumentMetaDataValidator
[#4715](https://github.com/open-telemetry/opentelemetry-cpp/pull/4715)

Expand Down
4 changes: 4 additions & 0 deletions api/include/opentelemetry/common/macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ point.

# define OPENTELEMETRY_ATOMIC_READ_8(ptr) __atomic_load_n(ptr, __ATOMIC_SEQ_CST)
# define OPENTELEMETRY_ATOMIC_WRITE_8(ptr, value) __atomic_store_n(ptr, value, __ATOMIC_SEQ_CST)
# define OPENTELEMETRY_ATOMIC_READ_RELAXED_8(ptr) __atomic_load_n(ptr, __ATOMIC_RELAXED)

#elif defined(_MSC_VER)

Expand All @@ -384,6 +385,9 @@ point.
static_cast<uint8_t>(_InterlockedCompareExchange8(reinterpret_cast<char *>(ptr), 0, 0))
# define OPENTELEMETRY_ATOMIC_WRITE_8(ptr, value) \
_InterlockedExchange8(reinterpret_cast<char *>(ptr), static_cast<char>(value))
// A plain load, unlike OPENTELEMETRY_ATOMIC_READ_8 which locks the cache line on every read.
# define OPENTELEMETRY_ATOMIC_READ_RELAXED_8(ptr) \
static_cast<uint8_t>(__iso_volatile_load8(reinterpret_cast<const volatile char *>(ptr)))

#else
# error port atomics read/write for the current platform
Expand Down
22 changes: 18 additions & 4 deletions api/include/opentelemetry/metrics/noop.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ class NoopCounter : public Counter<T>
NoopCounter(nostd::string_view /* name */,
nostd::string_view /* description */,
nostd::string_view /* unit */) noexcept
{}
{
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
this->SetEnabledFlag(this->DisabledFlag());
#endif
}
void Add(T /* value */) noexcept override {}
void Add(T /* value */, const context::Context & /* context */) noexcept override {}
void Add(T /* value */, const common::KeyValueIterable & /* attributes */) noexcept override {}
Expand All @@ -63,7 +67,11 @@ class NoopHistogram : public Histogram<T>
NoopHistogram(nostd::string_view /* name */,
nostd::string_view /* description */,
nostd::string_view /* unit */) noexcept
{}
{
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
this->SetEnabledFlag(this->DisabledFlag());
#endif
}
void Record(T /* value */, const context::Context & /* context */) noexcept override {}
void Record(T /* value */,
const common::KeyValueIterable & /* attributes */,
Expand Down Expand Up @@ -93,7 +101,11 @@ class NoopUpDownCounter : public UpDownCounter<T>
NoopUpDownCounter(nostd::string_view /* name */,
nostd::string_view /* description */,
nostd::string_view /* unit */) noexcept
{}
{
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
this->SetEnabledFlag(this->DisabledFlag());
#endif
}
void Add(T /* value */) noexcept override {}
void Add(T /* value */, const context::Context & /* context */) noexcept override {}
void Add(T /* value */, const common::KeyValueIterable & /* attributes */) noexcept override {}
Expand All @@ -111,7 +123,9 @@ class NoopGauge : public Gauge<T>
NoopGauge(nostd::string_view /* name */,
nostd::string_view /* description */,
nostd::string_view /* unit */) noexcept
{}
{
this->SetEnabledFlag(this->DisabledFlag());
}
void Record(T /* value */) noexcept override {}
void Record(T /* value */, const context::Context & /* context */) noexcept override {}
void Record(T /* value */, const common::KeyValueIterable & /* attributes */) noexcept override {}
Expand Down
40 changes: 40 additions & 0 deletions api/include/opentelemetry/metrics/sync_instruments.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,46 @@ class SynchronousInstrument
SynchronousInstrument &operator=(const SynchronousInstrument &) = default;
SynchronousInstrument &operator=(SynchronousInstrument &&) noexcept = default;
virtual ~SynchronousInstrument() = default;

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
/**
* Reports if the instrument is enabled or not. A disabled instrument drops its measurements.
*
* The instrumentation authors should call this method each time before recording a
* measurement, to potentially avoid performing computationally expensive operations for
* disabled instruments. The returned value can change over time.
*
* @since ABI_VERSION 2
*/
bool Enabled() const noexcept { return OPENTELEMETRY_ATOMIC_READ_RELAXED_8(enabled_) != 0; }

protected:
/**
* Sets the flag read by Enabled(). SDKs point it at a flag they update; it must outlive this
* instrument.
*
* @since ABI_VERSION 2
*/
void SetEnabledFlag(const bool *enabled) noexcept { enabled_ = enabled; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking - Could we mention that concurrent updates to this flag must use atomic writes? This would help other SDK implementations use it safely.


/** A flag that is always false, for instruments that are never enabled. */
static const bool *DisabledFlag() noexcept
{
static const bool kDisabled = false;
return &kDisabled;
}

private:
static const bool *EnabledFlag() noexcept
{
static const bool kEnabled = true;
return &kEnabled;
}

// Variable to support implementation of Enabled method introduced in ABI V2.
// A pointer, so an SDK can flip many instruments with a single write.
const bool *enabled_ = EnabledFlag();

@marcalff marcalff Oct 24, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposal:

bool enabled_in_api_ = false;
const bool *enabled_ = &enabled_in_api_;

This:

  • disable the flag by default, when no SDK is present
  • avoids to call this->SetEnabledFlag(this->DisabledFlag()); from noop.h, since the flag is already disabled.
  • avoid header only singletons like the static EnabledFlag() and DisabledFlag(), which are a problem in general.

For instruments implemented in an SDK, SetEnabledFlag() will work as before, pointing to the shared meter flag.

#endif
};

#ifdef OPENTELEMETRY_HAVE_METRICS_BOUND_INSTRUMENTS_PREVIEW
Expand Down
15 changes: 15 additions & 0 deletions api/test/metrics/noop_sync_instrument_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,18 @@ TEST(UpDownCountr, Record)
counter->Add(10, {{"k1", "1"}, {"k2", 2}});
counter->Add(10, {{"k1", "1"}, {"k2", 2}}, opentelemetry::context::Context{});
}

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
TEST(SynchronousInstrument, NoopInstrumentsAreDisabled)
{
opentelemetry::metrics::NoopCounter<uint64_t> counter("test", "none", "unitless");
opentelemetry::metrics::NoopHistogram<double> histogram("test", "none", "unitless");
opentelemetry::metrics::NoopUpDownCounter<int64_t> up_down_counter("test", "none", "unitless");
opentelemetry::metrics::NoopGauge<double> gauge("test", "none", "unitless");

EXPECT_FALSE(counter.Enabled());
EXPECT_FALSE(histogram.Enabled());
EXPECT_FALSE(up_down_counter.Enabled());
EXPECT_FALSE(gauge.Enabled());
}
#endif
8 changes: 7 additions & 1 deletion sdk/include/opentelemetry/sdk/metrics/meter.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace metrics

class MetricStorage;
class SyncWritableMetricStorage;
class SyncMultiMetricStorage;
class AsyncWritableMetricsStorge;
class ObservableRegistry;

Expand Down Expand Up @@ -169,8 +170,13 @@ class Meter final : public opentelemetry::metrics::Meter
std::shared_ptr<ObservableRegistry> observable_registry_;
// Shared with this meter's instruments so updates reach them; atomic so recording never blocks.
std::shared_ptr<MeterEnabledState> meter_enabled_state_{new MeterEnabledState()};
std::unique_ptr<SyncWritableMetricStorage> RegisterSyncMetricStorage(
// Shared with instruments whose matching views all use the Drop aggregation. Never enabled.
std::shared_ptr<MeterEnabledState> always_disabled_state_{new MeterEnabledState(false)};
std::unique_ptr<SyncMultiMetricStorage> RegisterSyncMetricStorage(
InstrumentDescriptor &instrument_descriptor);
/** Returns the enabled state an instrument with this storage should share. */
std::shared_ptr<MeterEnabledState> GetInstrumentEnabledState(
const SyncMultiMetricStorage *storage) const noexcept;
std::unique_ptr<AsyncWritableMetricStorage> RegisterAsyncMetricStorage(
InstrumentDescriptor &instrument_descriptor);
std::mutex storage_lock_;
Expand Down
26 changes: 20 additions & 6 deletions sdk/include/opentelemetry/sdk/metrics/meter_enabled_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

#pragma once

#include <atomic>

#include "opentelemetry/common/macros.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
Expand All @@ -16,24 +15,39 @@ 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.
* A plain bool, not std::atomic, because API instruments read it through Flag().
*/
class MeterEnabledState
{
public:
explicit MeterEnabledState(bool enabled = true) noexcept : enabled_(enabled) {}
explicit MeterEnabledState(bool enabled = true) noexcept { flag_.enabled = enabled; }

~MeterEnabledState() = default;
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); }
bool IsEnabled() const noexcept
{
return OPENTELEMETRY_ATOMIC_READ_RELAXED_8(&flag_.enabled) != 0;
}

void SetEnabled(bool enabled) noexcept { OPENTELEMETRY_ATOMIC_WRITE_8(&flag_.enabled, enabled); }

void SetEnabled(bool enabled) noexcept { enabled_.store(enabled, std::memory_order_relaxed); }
/** The flag itself, valid for the lifetime of this object. */
const bool *Flag() const noexcept { return &flag_.enabled; }

private:
std::atomic<bool> enabled_;
// Padding keeps the flag off cache lines that neighbouring heap objects write to, since every
// instrument of the meter reads it on each Enabled() call.
struct PaddedFlag
{
char padding_before[64];
bool enabled;
char padding_after[63];
};
PaddedFlag flag_{};
};

} // namespace metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class SyncMultiMetricStorage : public SyncWritableMetricStorage
return true;
}

/** True when no storage was added, e.g. every matching view uses the Drop aggregation. */
bool Empty() const noexcept { return storages_.empty(); }

void RecordLong(int64_t value, const opentelemetry::context::Context &context) noexcept override
{
for (auto &s : storages_)
Expand Down
58 changes: 38 additions & 20 deletions sdk/src/metrics/meter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ void Meter::UpdateMeterConfig(MeterConfig config) noexcept
meter_enabled_state_->SetEnabled(config.IsEnabled());
}

std::shared_ptr<MeterEnabledState> Meter::GetInstrumentEnabledState(
const SyncMultiMetricStorage *storage) const noexcept
{
// Every matching view uses the Drop aggregation, so the instrument never records.
if (storage != nullptr && storage->Empty())
{
return always_disabled_state_;
}
return meter_enabled_state_;
}

opentelemetry::nostd::unique_ptr<metrics::Counter<uint64_t>> Meter::CreateUInt64Counter(
opentelemetry::nostd::string_view name,
opentelemetry::nostd::string_view description,
Expand All @@ -171,9 +182,10 @@ opentelemetry::nostd::unique_ptr<metrics::Counter<uint64_t>> Meter::CreateUInt64
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kCounter, InstrumentValueType::kLong};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Counter<uint64_t>>(
new LongCounter(instrument_descriptor, std::move(storage), meter_enabled_state_));
new LongCounter(instrument_descriptor, std::move(storage), std::move(enabled_state)));
}

opentelemetry::nostd::unique_ptr<metrics::Counter<double>> Meter::CreateDoubleCounter(
Expand All @@ -193,9 +205,10 @@ opentelemetry::nostd::unique_ptr<metrics::Counter<double>> Meter::CreateDoubleCo
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kCounter,
InstrumentValueType::kDouble};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Counter<double>>{
new DoubleCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new DoubleCounter(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

opentelemetry::nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument>
Expand Down Expand Up @@ -257,9 +270,10 @@ opentelemetry::nostd::unique_ptr<metrics::Histogram<uint64_t>> Meter::CreateUInt
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kHistogram,
InstrumentValueType::kLong};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Histogram<uint64_t>>{
new LongHistogram(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new LongHistogram(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

opentelemetry::nostd::unique_ptr<metrics::Histogram<double>> Meter::CreateDoubleHistogram(
Expand All @@ -279,9 +293,10 @@ opentelemetry::nostd::unique_ptr<metrics::Histogram<double>> Meter::CreateDouble
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kHistogram,
InstrumentValueType::kDouble};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Histogram<double>>{
new DoubleHistogram(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new DoubleHistogram(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
Expand All @@ -301,9 +316,10 @@ opentelemetry::nostd::unique_ptr<metrics::Gauge<int64_t>> Meter::CreateInt64Gaug
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kGauge, InstrumentValueType::kLong};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Gauge<int64_t>>{
new LongGauge(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new LongGauge(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

opentelemetry::nostd::unique_ptr<metrics::Gauge<double>> Meter::CreateDoubleGauge(
Expand All @@ -322,9 +338,10 @@ opentelemetry::nostd::unique_ptr<metrics::Gauge<double>> Meter::CreateDoubleGaug
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kGauge, InstrumentValueType::kDouble};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::Gauge<double>>{
new DoubleGauge(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new DoubleGauge(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}
#endif

Expand Down Expand Up @@ -387,9 +404,10 @@ opentelemetry::nostd::unique_ptr<metrics::UpDownCounter<int64_t>> Meter::CreateI
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kUpDownCounter,
InstrumentValueType::kLong};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::UpDownCounter<int64_t>>{
new LongUpDownCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new LongUpDownCounter(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

opentelemetry::nostd::unique_ptr<metrics::UpDownCounter<double>> Meter::CreateDoubleUpDownCounter(
Expand All @@ -409,9 +427,10 @@ opentelemetry::nostd::unique_ptr<metrics::UpDownCounter<double>> Meter::CreateDo
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kUpDownCounter,
InstrumentValueType::kDouble};
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto storage = RegisterSyncMetricStorage(instrument_descriptor);
auto enabled_state = GetInstrumentEnabledState(storage.get());
return opentelemetry::nostd::unique_ptr<metrics::UpDownCounter<double>>{
new DoubleUpDownCounter(instrument_descriptor, std::move(storage), meter_enabled_state_)};
new DoubleUpDownCounter(instrument_descriptor, std::move(storage), std::move(enabled_state))};
}

opentelemetry::nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument>
Expand Down Expand Up @@ -462,7 +481,7 @@ const sdk::instrumentationscope::InstrumentationScope *Meter::GetInstrumentation
return scope_.get();
}

std::unique_ptr<SyncWritableMetricStorage> Meter::RegisterSyncMetricStorage(
std::unique_ptr<SyncMultiMetricStorage> Meter::RegisterSyncMetricStorage(
InstrumentDescriptor &instrument_descriptor)
{
std::lock_guard<std::mutex> guard(storage_lock_);
Expand All @@ -476,7 +495,7 @@ std::unique_ptr<SyncWritableMetricStorage> Meter::RegisterSyncMetricStorage(
}

auto view_registry = ctx->GetViewRegistry();
std::unique_ptr<SyncWritableMetricStorage> storages(new SyncMultiMetricStorage());
std::unique_ptr<SyncMultiMetricStorage> storages(new SyncMultiMetricStorage());

#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
auto exemplar_filter_type = ctx->GetExemplarFilter();
Expand Down Expand Up @@ -533,8 +552,7 @@ std::unique_ptr<SyncWritableMetricStorage> Meter::RegisterSyncMetricStorage(
view.GetAggregationConfig()));
storage_registry_.insert({view_instr_desc, sync_storage});
}
auto sync_multi_storage = static_cast<SyncMultiMetricStorage *>(storages.get());
if (!sync_multi_storage->AddStorage(sync_storage))
if (!storages->AddStorage(sync_storage))
{
WarnOnViewSemanticError(GetInstrumentationScope(), instrument_descriptor, view_instr_desc,
view);
Expand Down
Loading
Loading