Skip to content
Merged
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)

* [SDK] `BatchLogRecordProcessor` now exports at most one batch on normal
wakeups instead of continuously draining the buffer, matching
`BatchSpanProcessor`
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
62 changes: 60 additions & 2 deletions api/include/opentelemetry/metrics/sync_instruments.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,70 @@ namespace metrics
class SynchronousInstrument
{
public:
SynchronousInstrument() = default;
SynchronousInstrument() = default;
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
// Copies share the SDK flag, but never point at the source's own flag, which is destroyed with
// the source.
SynchronousInstrument(const SynchronousInstrument &other) noexcept
: enabled_(other.EnabledFlagForCopy(*this))
{}
SynchronousInstrument(SynchronousInstrument &&other) noexcept
: enabled_(other.EnabledFlagForCopy(*this))
{}
SynchronousInstrument &operator=(const SynchronousInstrument &other) noexcept
{
if (this != &other)
{
enabled_ = other.EnabledFlagForCopy(*this);
}
return *this;
}
SynchronousInstrument &operator=(SynchronousInstrument &&other) noexcept
{
enabled_ = other.EnabledFlagForCopy(*this);
return *this;
}
#else
SynchronousInstrument(const SynchronousInstrument &) = default;
SynchronousInstrument(SynchronousInstrument &&) noexcept = default;
SynchronousInstrument &operator=(const SynchronousInstrument &) = default;
SynchronousInstrument &operator=(SynchronousInstrument &&) noexcept = default;
virtual ~SynchronousInstrument() = default;
#endif
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:
/**
* Points Enabled() at a flag owned by the SDK. The flag must outlive this instrument, and must
* only be changed with atomic writes such as OPENTELEMETRY_ATOMIC_WRITE_8, since Enabled() can
* read it concurrently.
*
* @since ABI_VERSION 2
*/
void SetEnabledFlag(const bool *enabled) noexcept { enabled_ = enabled; }
Comment thread
adityabavadekar marked this conversation as resolved.
Resolved

private:
const bool *EnabledFlagForCopy(const SynchronousInstrument &copy) const noexcept
{
return enabled_ == &enabled_in_api_ ? &copy.enabled_in_api_ : enabled_;
}

// By default enabled_ points at enabled_in_api_, which is always false. An SDK points it at
// its own flag with SetEnabledFlag().
bool enabled_in_api_ = false;
const bool *enabled_ = &enabled_in_api_;
#endif
};

#ifdef OPENTELEMETRY_HAVE_METRICS_BOUND_INSTRUMENTS_PREVIEW
Expand Down
55 changes: 55 additions & 0 deletions api/test/metrics/noop_sync_instrument_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/nostd/utility.h"

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
# include "opentelemetry/common/macros.h"
#endif

TEST(Counter, Add)
{
std::shared_ptr<opentelemetry::metrics::Counter<uint64_t>> counter{
Expand Down Expand Up @@ -53,3 +57,54 @@ 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());
}

namespace
{
// An instrument reading an external flag, the way an SDK instrument does.
class FlagCounter : public opentelemetry::metrics::NoopCounter<uint64_t>
{
public:
explicit FlagCounter(const bool *flag) : NoopCounter("test", "none", "unitless")
{
SetEnabledFlag(flag);
}
};
} // namespace

// A copy must not read the default flag stored in the instrument it was copied from.
TEST(SynchronousInstrument, CopyOfDefaultInstrumentIsDisabled)
{
std::unique_ptr<opentelemetry::metrics::NoopCounter<uint64_t>> original{
new opentelemetry::metrics::NoopCounter<uint64_t>("test", "none", "unitless")};
opentelemetry::metrics::NoopCounter<uint64_t> copy(*original);
original.reset();
EXPECT_FALSE(copy.Enabled());
}

// A copy keeps reading the external flag after the instrument it was copied from is destroyed.
TEST(SynchronousInstrument, CopySharesExternalFlag)
{
bool flag = true;
std::unique_ptr<FlagCounter> original{new FlagCounter(&flag)};
FlagCounter copy(*original);
original.reset();
EXPECT_TRUE(copy.Enabled());

OPENTELEMETRY_ATOMIC_WRITE_8(&flag, false);
EXPECT_FALSE(copy.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
Loading
Loading