Skip to content

Commit c182ada

Browse files
[Metrics] Allow registering one callback for multiple instruments
This commit adds a pair of methods `Meter::RegisterCallback` and `Meter::DeregisterCallback` which allow you to set up one callback which can record observations for multiple observable instruments. The callback will be passed an object of type MultiObserverResult, which can be used to get an ObserverResultT for an instrument. This allows a single expensive operation to yield multiple different observable metrics without the need to call said expensive operation multiple times.
1 parent 5540dae commit c182ada

File tree

13 files changed

+606
-1
lines changed

13 files changed

+606
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Increment the:
1717

1818
* [TEST] Remove workaround for metrics cardinality limit test
1919
[#3663](https://github.com/open-telemetry/opentelemetry-cpp/pull/3663)
20+
* [METRICS] Allow registering one callback for multiple instruments
21+
[#3667](https://github.com/open-telemetry/opentelemetry-cpp/pull/3667)
2022

2123
## [1.23 2025-09-25]
2224

api/include/opentelemetry/metrics/meter.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
#pragma once
55

6+
#include <cstdint>
7+
68
#include "opentelemetry/nostd/shared_ptr.h"
9+
#include "opentelemetry/nostd/span.h"
710
#include "opentelemetry/nostd/string_view.h"
811
#include "opentelemetry/nostd/unique_ptr.h"
912
#include "opentelemetry/version.h"
@@ -25,6 +28,8 @@ template <typename T>
2528
class Gauge;
2629

2730
class ObservableInstrument;
31+
class MultiObserverResult;
32+
using MultiObservableCallbackPtr = void (*)(MultiObserverResult &, void *);
2833

2934
/**
3035
* Handles instrument creation and provides a facility for batch recording.
@@ -169,6 +174,32 @@ class Meter
169174
nostd::string_view name,
170175
nostd::string_view description = "",
171176
nostd::string_view unit = "") noexcept = 0;
177+
178+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
179+
180+
/**
181+
* Registers a callback to be invoked when metrics are collected by this meter. The callback will
182+
* be passed a MultiObserverResult which it can use to make observations for any or all of the
183+
* instruments provided in this registration. Any measurements recorded for instruments _not_ in
184+
* the initial RegisterCallback call will be discarded.
185+
*
186+
* @param callback the callback to be invoked.
187+
* @param state the state to be passed to the callback.
188+
* @param instruments the instruments to be observed.
189+
* @return a unique identifier for the registered callback, which can be used to unregister the
190+
* callback in DeregisterCallback.
191+
*/
192+
virtual uintptr_t RegisterCallback(MultiObservableCallbackPtr callback,
193+
void *state,
194+
nostd::span<ObservableInstrument *> instruments) noexcept = 0;
195+
196+
/**
197+
* Unregisters a callback previously registered with RegisterCallback.
198+
*
199+
* @param callback_id the unique identifier returned by RegisterCallback.
200+
*/
201+
virtual void DeregisterCallback(uintptr_t callback_id) noexcept = 0;
202+
#endif
172203
};
173204
} // namespace metrics
174205
OPENTELEMETRY_END_NAMESPACE
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#pragma once
5+
6+
#include <cstdint>
7+
8+
#include "opentelemetry/metrics/async_instruments.h"
9+
#include "opentelemetry/metrics/observer_result.h"
10+
#include "opentelemetry/version.h"
11+
12+
OPENTELEMETRY_BEGIN_NAMESPACE
13+
namespace metrics
14+
{
15+
16+
class MultiObserverResult
17+
{
18+
19+
public:
20+
virtual ~MultiObserverResult() = default;
21+
22+
/**
23+
* Obtain an ObserverResultT<T> for the given instrument, that can be used to record
24+
* a measurement on said instrument from a multi-observer callback registered with
25+
* Meter::RegisterCallback. The instrument _must_ have been included in the original
26+
* call to Meter::RegisterCallback; any data points set on other instruments will be
27+
* discarded.
28+
*
29+
* @param instrument The instrument for which to obtain an ObserverResult.
30+
* @return An ObserverResultT<T> for the given instrument.
31+
*/
32+
template <typename T>
33+
ObserverResultT<T> &ForInstrument(const ObservableInstrument *instrument) = delete;
34+
35+
protected:
36+
// You can't have a virtual template, and you can't overload on return type, so we need to
37+
// enumerate the options for the observer result type as separate methods to override.
38+
virtual ObserverResultT<double> &ForInstrumentDouble(const ObservableInstrument *instrument) = 0;
39+
virtual ObserverResultT<int64_t> &ForInstrumentInt64(const ObservableInstrument *instrument) = 0;
40+
};
41+
42+
template <>
43+
inline ObserverResultT<double> &MultiObserverResult::ForInstrument<double>(
44+
const ObservableInstrument *instrument)
45+
{
46+
return ForInstrumentDouble(instrument);
47+
}
48+
49+
template <>
50+
inline ObserverResultT<int64_t> &MultiObserverResult::ForInstrument<int64_t>(
51+
const ObservableInstrument *instrument)
52+
{
53+
return ForInstrumentInt64(instrument);
54+
}
55+
56+
} // namespace metrics
57+
OPENTELEMETRY_END_NAMESPACE

api/include/opentelemetry/metrics/noop.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ class NoopMeter final : public Meter
229229
return nostd::shared_ptr<ObservableInstrument>(
230230
new NoopObservableInstrument(name, description, unit));
231231
}
232+
233+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
234+
uintptr_t RegisterCallback(
235+
MultiObservableCallbackPtr /* callback */,
236+
void * /* state */,
237+
nostd::span<ObservableInstrument *> /* instruments */) noexcept override
238+
{
239+
return 0;
240+
}
241+
242+
void DeregisterCallback(uintptr_t /* callback_id */) noexcept override {}
243+
#endif
232244
};
233245

234246
/**

sdk/include/opentelemetry/sdk/metrics/meter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ class Meter final : public opentelemetry::metrics::Meter
131131
std::vector<MetricData> Collect(CollectorHandle *collector,
132132
opentelemetry::common::SystemTimestamp collect_ts) noexcept;
133133

134+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
135+
uintptr_t RegisterCallback(
136+
opentelemetry::metrics::MultiObservableCallbackPtr callback,
137+
void *state,
138+
nostd::span<opentelemetry::metrics::ObservableInstrument *> instruments) noexcept override;
139+
140+
void DeregisterCallback(uintptr_t callback_id) noexcept override;
141+
#endif
134142
private:
135143
// order of declaration is important here - instrumentation scope should destroy after
136144
// meter-context.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#pragma once
5+
6+
#include <unordered_map>
7+
8+
#include "opentelemetry/metrics/async_instruments.h"
9+
#include "opentelemetry/metrics/multi_observer_result.h"
10+
#include "opentelemetry/metrics/observer_result.h"
11+
#include "opentelemetry/nostd/variant.h"
12+
#include "opentelemetry/sdk/metrics/observer_result.h"
13+
#include "opentelemetry/version.h"
14+
15+
OPENTELEMETRY_BEGIN_NAMESPACE
16+
namespace sdk
17+
{
18+
namespace metrics
19+
{
20+
class OPENTELEMETRY_EXPORT MultiObserverResult final
21+
: public opentelemetry::metrics::MultiObserverResult
22+
{
23+
public:
24+
void RegisterInstrument(opentelemetry::metrics::ObservableInstrument *instrument);
25+
void DeregisterInstrument(opentelemetry::metrics::ObservableInstrument *instrument);
26+
size_t InstrumentCount() const;
27+
void Reset();
28+
void StoreResults(opentelemetry::common::SystemTimestamp collection_ts);
29+
30+
protected:
31+
opentelemetry::metrics::ObserverResultT<double> &ForInstrumentDouble(
32+
const opentelemetry::metrics::ObservableInstrument *instrument) override;
33+
opentelemetry::metrics::ObserverResultT<int64_t> &ForInstrumentInt64(
34+
const opentelemetry::metrics::ObservableInstrument *instrument) override;
35+
36+
private:
37+
// This is _different_ to opentelemetry::metrics::ObserverResult because this variant is
38+
// a variant directly of ObserverResultT, not of _pointers_ to ObserverResultT.
39+
// This allows us to avoid an unnescessary layer of inderection and a bunch of allocations.
40+
using ObserverResultDirect =
41+
nostd::variant<nostd::monostate, ObserverResultT<double>, ObserverResultT<int64_t>>;
42+
std::unordered_map<opentelemetry::metrics::ObservableInstrument *, ObserverResultDirect>
43+
observer_results_;
44+
};
45+
} // namespace metrics
46+
} // namespace sdk
47+
48+
OPENTELEMETRY_END_NAMESPACE

sdk/include/opentelemetry/sdk/metrics/state/observable_registry.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33

44
#pragma once
55

6+
#include <cstdint>
67
#include <memory>
78
#include <mutex>
89
#include <vector>
910

1011
#include "opentelemetry/common/timestamp.h"
1112
#include "opentelemetry/metrics/async_instruments.h"
13+
#include "opentelemetry/metrics/meter.h"
14+
#include "opentelemetry/sdk/metrics/multi_observer_result.h"
1215
#include "opentelemetry/version.h"
1316

17+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
18+
# include <unordered_map>
19+
# include "opentelemetry/nostd/span.h"
20+
#endif
21+
1422
OPENTELEMETRY_BEGIN_NAMESPACE
1523
namespace sdk
1624
{
@@ -24,6 +32,13 @@ struct ObservableCallbackRecord
2432
opentelemetry::metrics::ObservableInstrument *instrument;
2533
};
2634

35+
struct MultiObservableCallbackRecord
36+
{
37+
opentelemetry::metrics::MultiObservableCallbackPtr callback;
38+
void *state;
39+
MultiObserverResult observable_result{};
40+
};
41+
2742
class ObservableRegistry
2843
{
2944
public:
@@ -34,6 +49,13 @@ class ObservableRegistry
3449
void RemoveCallback(opentelemetry::metrics::ObservableCallbackPtr callback,
3550
void *state,
3651
opentelemetry::metrics::ObservableInstrument *instrument);
52+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
53+
uintptr_t AddMultiCallback(
54+
opentelemetry::metrics::MultiObservableCallbackPtr callback,
55+
void *state,
56+
nostd::span<opentelemetry::metrics::ObservableInstrument *> instruments);
57+
void RemoveMultiCallback(uintptr_t id);
58+
#endif
3759

3860
void CleanupCallback(opentelemetry::metrics::ObservableInstrument *instrument);
3961

@@ -42,6 +64,9 @@ class ObservableRegistry
4264
private:
4365
std::vector<std::unique_ptr<ObservableCallbackRecord>> callbacks_;
4466
std::mutex callbacks_m_;
67+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
68+
std::unordered_map<uintptr_t, std::unique_ptr<MultiObservableCallbackRecord>> multi_callbacks_;
69+
#endif
4570
};
4671

4772
} // namespace metrics

sdk/src/metrics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_library(
1212
meter_context.cc
1313
meter_context_factory.cc
1414
metric_reader.cc
15+
multi_observer_result.cc
1516
instrument_metadata_validator.cc
1617
export/periodic_exporting_metric_reader.cc
1718
export/periodic_exporting_metric_reader_factory.cc

sdk/src/metrics/meter.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
# include "opentelemetry/sdk/metrics/exemplar/reservoir_utils.h"
4545
#endif
4646

47+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
48+
# include "opentelemetry/metrics/meter.h"
49+
#endif
50+
4751
namespace
4852
{
4953

@@ -663,6 +667,21 @@ std::vector<MetricData> Meter::Collect(CollectorHandle *collector,
663667
return metric_data_list;
664668
}
665669

670+
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
671+
uintptr_t Meter::RegisterCallback(
672+
opentelemetry::metrics::MultiObservableCallbackPtr callback,
673+
void *state,
674+
nostd::span<opentelemetry::metrics::ObservableInstrument *> instruments) noexcept
675+
{
676+
return observable_registry_->AddMultiCallback(callback, state, instruments);
677+
}
678+
679+
void Meter::DeregisterCallback(uintptr_t callback_id) noexcept
680+
{
681+
observable_registry_->RemoveMultiCallback(callback_id);
682+
}
683+
#endif
684+
666685
// Implementation of the log message recommended by the SDK specification for duplicate instruments.
667686
// See
668687
// https://github.com/open-telemetry/opentelemetry-specification/blob/9c8c30631b0e288de93df7452f91ed47f6fba330/specification/metrics/sdk.md?plain=1#L882

0 commit comments

Comments
 (0)