Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
52 changes: 52 additions & 0 deletions api/test/metrics/noop_sync_instrument_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <string>
#include <utility>

#include "opentelemetry/common/macros.h"
Comment thread
marcalff marked this conversation as resolved.
Resolved
#include "opentelemetry/context/context.h"
#include "opentelemetry/metrics/noop.h"
#include "opentelemetry/metrics/sync_instruments.h"
Expand Down Expand Up @@ -53,3 +54,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