Skip to content

Commit 6256773

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 064dfe3 commit 6256773

File tree

13 files changed

+752
-63
lines changed

13 files changed

+752
-63
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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/function_ref.h"
12+
#include "opentelemetry/nostd/variant.h"
13+
#include "opentelemetry/sdk/metrics/observer_result.h"
14+
#include "opentelemetry/version.h"
15+
16+
OPENTELEMETRY_BEGIN_NAMESPACE
17+
namespace sdk
18+
{
19+
namespace metrics
20+
{
21+
class OPENTELEMETRY_EXPORT MultiObserverResult final
22+
: public opentelemetry::metrics::MultiObserverResult
23+
{
24+
public:
25+
void RegisterInstrument(opentelemetry::metrics::ObservableInstrument *instrument);
26+
void DeregisterInstrument(opentelemetry::metrics::ObservableInstrument *instrument);
27+
size_t InstrumentCount() const;
28+
bool HasInstrument(const opentelemetry::metrics::ObservableInstrument *instrument) const;
29+
void GetInstruments(
30+
nostd::function_ref<void(opentelemetry::metrics::ObservableInstrument *)> callback);
31+
void Reset();
32+
void StoreResults(opentelemetry::common::SystemTimestamp collection_ts);
33+
34+
protected:
35+
opentelemetry::metrics::ObserverResultT<double> &ForInstrumentDouble(
36+
const opentelemetry::metrics::ObservableInstrument *instrument) override;
37+
opentelemetry::metrics::ObserverResultT<int64_t> &ForInstrumentInt64(
38+
const opentelemetry::metrics::ObservableInstrument *instrument) override;
39+
40+
private:
41+
// This is _different_ to opentelemetry::metrics::ObserverResult because this variant is
42+
// a variant directly of ObserverResultT, not of _pointers_ to ObserverResultT.
43+
// This allows us to avoid an unnescessary layer of inderection and a bunch of allocations.
44+
using ObserverResultDirect =
45+
nostd::variant<nostd::monostate, ObserverResultT<double>, ObserverResultT<int64_t>>;
46+
std::unordered_map<opentelemetry::metrics::ObservableInstrument *, ObserverResultDirect>
47+
observer_results_;
48+
};
49+
} // namespace metrics
50+
} // namespace sdk
51+
52+
OPENTELEMETRY_END_NAMESPACE

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,52 @@
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
{
1725
namespace metrics
1826
{
1927

20-
struct ObservableCallbackRecord
21-
{
22-
opentelemetry::metrics::ObservableCallbackPtr callback;
23-
void *state;
24-
opentelemetry::metrics::ObservableInstrument *instrument;
25-
};
28+
struct ObservableCallbackRecord;
2629

2730
class ObservableRegistry
2831
{
2932
public:
33+
// Constructor & destructor need to be defined in the observable_registry.cc TU, rather
34+
// than implicitly defaulted here, so that we can have a unique_ptr to an incomplete
35+
// class as a member.
36+
ObservableRegistry();
37+
~ObservableRegistry();
38+
39+
// Add a callback of the single-instrument form
3040
void AddCallback(opentelemetry::metrics::ObservableCallbackPtr callback,
3141
void *state,
3242
opentelemetry::metrics::ObservableInstrument *instrument);
33-
43+
// Add a callback with the multi-instrument signature
44+
uintptr_t AddCallback(opentelemetry::metrics::MultiObservableCallbackPtr callback,
45+
void *state,
46+
nostd::span<opentelemetry::metrics::ObservableInstrument *> instruments);
47+
// Callbacks added with Meter::RegisterCallback have can be removed by passing back the handle
48+
// returned
49+
void RemoveCallback(uintptr_t id);
50+
// Callbacks added with ObservableInstrument::AddCallback can be removed by passing back the
51+
// original (callback function, state, instrument).
3452
void RemoveCallback(opentelemetry::metrics::ObservableCallbackPtr callback,
3553
void *state,
3654
opentelemetry::metrics::ObservableInstrument *instrument);
@@ -40,7 +58,7 @@ class ObservableRegistry
4058
void Observe(opentelemetry::common::SystemTimestamp collection_ts);
4159

4260
private:
43-
std::vector<std::unique_ptr<ObservableCallbackRecord>> callbacks_;
61+
std::unordered_map<uintptr_t, std::unique_ptr<ObservableCallbackRecord>> callbacks_;
4462
std::mutex callbacks_m_;
4563
};
4664

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_->AddCallback(callback, state, instruments);
677+
}
678+
679+
void Meter::DeregisterCallback(uintptr_t callback_id) noexcept
680+
{
681+
observable_registry_->RemoveCallback(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)