prom 0.1.0
Client-independent C++23 Prometheus/OpenMetrics metric abstraction
Loading...
Searching...
No Matches
metric_base.hpp
Go to the documentation of this file.
1#pragma once
2
25
26#include <prom/adapter.hpp>
27#include <prom/dimval.hpp>
28#include <prom/global.hpp>
29#include <prom/labels.hpp>
30#include <prom/unit.hpp>
31
32#include <logman/logman.hpp>
33
34#include <atomic>
35#include <cstdint>
36#include <memory>
37#include <mutex>
38#include <string>
39#include <string_view>
40#include <utility>
41#include <vector>
42
43namespace prom {
44
52public:
53 ScopeState() = default;
54 ScopeState(const ScopeState&) = delete;
55 ScopeState& operator=(const ScopeState&) = delete;
58 virtual ~ScopeState() = default;
59
61 [[nodiscard]] virtual std::uint64_t version() const noexcept = 0;
63 [[nodiscard]] virtual std::string full_name(std::string_view base) const = 0;
65 [[nodiscard]] virtual Labels effective_labels(const Labels& own) const = 0;
67 [[nodiscard]] virtual comms::DisplayInfo
68 effective_display(const comms::DisplayInfo& own) const = 0;
73 [[nodiscard]] virtual bool decorates() const noexcept = 0;
74};
75
76namespace detail {
77
80[[nodiscard]] inline comms::DisplayInfo merge_display(const comms::DisplayInfo& base,
81 const comms::DisplayInfo& over) {
82 comms::DisplayInfo out = base;
83 if (over.name) {
84 out.name = over.name;
85 }
86 if (over.description) {
87 out.description = over.description;
88 }
89 if (over.icon) {
90 out.icon = over.icon;
91 }
92 if (over.color) {
93 out.color = over.color;
94 }
95 return out;
96}
97
99[[nodiscard]] inline bool display_empty(const comms::DisplayInfo& display) noexcept {
100 return !display.name && !display.description && !display.icon && !display.color;
101}
102
117public:
118 DecorationState() = default;
119 DecorationState(std::string prefix,
120 Labels const_labels,
121 comms::DisplayInfo display,
122 std::shared_ptr<ScopeState> parent = nullptr)
123 : prefix_(std::move(prefix)), const_labels_(std::move(const_labels)),
124 display_(std::move(display)), parent_(std::move(parent)) {}
125
126 [[nodiscard]] std::uint64_t version() const noexcept override {
127 const std::uint64_t self = version_.load(std::memory_order_acquire);
128 return parent_ ? self + parent_->version() : self;
129 }
130 [[nodiscard]] std::string full_name(const std::string_view base) const override {
131 std::string out;
132 {
133 const std::scoped_lock lock(mutex_);
134 out = prefix_ + std::string(base);
135 }
136 return parent_ ? parent_->full_name(out) : out;
137 }
138 [[nodiscard]] Labels effective_labels(const Labels& own) const override {
139 Labels merged;
140 {
141 const std::scoped_lock lock(mutex_);
142 merged = const_labels_.merged_with(own);
143 }
144 return parent_ ? parent_->effective_labels(merged) : merged;
145 }
146 [[nodiscard]] comms::DisplayInfo
147 effective_display(const comms::DisplayInfo& own) const override {
148 comms::DisplayInfo merged;
149 {
150 const std::scoped_lock lock(mutex_);
151 merged = merge_display(display_, own);
152 }
153 return parent_ ? parent_->effective_display(merged) : merged;
154 }
155 [[nodiscard]] bool decorates() const noexcept override {
156 {
157 const std::scoped_lock lock(mutex_);
158 if (!prefix_.empty() || !const_labels_.empty() || !display_empty(display_)) {
159 return true;
160 }
161 }
162 return parent_ && parent_->decorates();
163 }
164
165 [[nodiscard]] std::string prefix() const {
166 const std::scoped_lock lock(mutex_);
167 return prefix_;
168 }
169 void set_prefix(std::string prefix) {
170 {
171 const std::scoped_lock lock(mutex_);
172 prefix_ = std::move(prefix);
173 }
174 bump();
175 }
176 [[nodiscard]] Labels const_labels() const {
177 const std::scoped_lock lock(mutex_);
178 return const_labels_;
179 }
181 {
182 const std::scoped_lock lock(mutex_);
183 const_labels_ = std::move(labels);
184 }
185 bump();
186 }
187 void add_const_label(std::string name, std::string value) {
188 {
189 const std::scoped_lock lock(mutex_);
190 const_labels_.set(std::move(name), std::move(value));
191 }
192 bump();
193 }
194 [[nodiscard]] comms::DisplayInfo display() const {
195 const std::scoped_lock lock(mutex_);
196 return display_;
197 }
198 void set_display(comms::DisplayInfo display) {
199 {
200 const std::scoped_lock lock(mutex_);
201 display_ = std::move(display);
202 }
203 bump();
204 }
205
208 void configure(std::string prefix, Labels const_labels, comms::DisplayInfo display) {
209 {
210 const std::scoped_lock lock(mutex_);
211 prefix_ = std::move(prefix);
212 const_labels_ = std::move(const_labels);
213 display_ = std::move(display);
214 }
215 bump();
216 }
217
218private:
219 void bump() noexcept {
220 version_.fetch_add(1, std::memory_order_release);
221 }
222
223 mutable std::mutex mutex_;
224 std::string prefix_;
225 Labels const_labels_;
226 comms::DisplayInfo display_;
227 // Set once at construction (a scope chains onto the global decoration), then
228 // only read, so it needs no lock.
229 std::shared_ptr<ScopeState> parent_;
230 std::atomic<std::uint64_t> version_{1};
231};
232
238[[nodiscard]] inline std::shared_ptr<DecorationState> global_decoration() {
239 static auto decoration = std::make_shared<DecorationState>();
240 return decoration;
241}
242
243} // namespace detail
244
251namespace detail {
252template <class T>
253class AtomicSharedPtr {
254public:
255 AtomicSharedPtr() = default;
256
257 [[nodiscard]] std::shared_ptr<const T> load() const noexcept {
258#if defined(__cpp_lib_atomic_shared_ptr)
259 return value_.load(std::memory_order_acquire);
260#else
261 const std::scoped_lock lock(mutex_);
262 return value_;
263#endif
264 }
265
266 void store(std::shared_ptr<const T> desired) noexcept {
267#if defined(__cpp_lib_atomic_shared_ptr)
268 value_.store(std::move(desired), std::memory_order_release);
269#else
270 const std::scoped_lock lock(mutex_);
271 value_ = std::move(desired);
272#endif
273 }
274
275private:
276#if defined(__cpp_lib_atomic_shared_ptr)
277 std::atomic<std::shared_ptr<const T>> value_;
278#else
279 mutable std::mutex mutex_;
280 std::shared_ptr<const T> value_;
281#endif
282};
283} // namespace detail
284
291
292 // Stable storage backing the transient MetricMeta built at registration.
293 std::string name;
294 std::string help;
296 comms::DisplayInfo display;
297 std::vector<double> buckets;
298 std::vector<double> quantiles;
299 std::vector<std::string> states;
300
301 // Where this core reads its adapter from: the cell of the Registry that
302 // created it, or the process-wide global cell (standalone / scoped metrics).
303 std::shared_ptr<AdapterSource> source;
304
305 // The currently published binding. Null until first use for a standalone or
306 // scoped metric; set eagerly by a Registry. Read on the hot path, replaced
307 // wholesale under `bind_mutex` when re-registration is needed, so an
308 // in-flight caller's snapshot stays valid.
309 struct Bound {
312 std::uint64_t adapter_version = 0;
313 std::uint64_t scope_version = 0;
314 };
315 detail::AtomicSharedPtr<Bound> bound;
316
317 // Labeled children snapshot their binding at creation and never migrate.
318 bool pinned = false;
319
320 // Scoped metrics: when `scope` is set, the metric stays unbound at creation
321 // and (re-)registers whenever the scope's config version advances, so a
322 // runtime change to the scope's prefix / labels / display reconfigures every
323 // metric created from it. The `base_*` fields hold the metric's own,
324 // un-decorated values; `name` / `const_labels` / `display` above hold the
325 // decorated values that are currently registered. Guarded by `bind_mutex`.
326 std::shared_ptr<ScopeState> scope;
327 std::string base_name;
329 comms::DisplayInfo base_display;
330 std::mutex bind_mutex;
331
332 // The family this core belongs to: null when this core *is* the family,
333 // set to the parent family core for labeled children. Unit reconciliation
334 // is always performed against the family.
335 std::shared_ptr<MetricCore> family;
336
337 // Runtime unit state, guarded by unit_mutex. `has_unit` is true once a unit
338 // is known — either declared in the spec or latched from the first
339 // dimensional observation.
340 std::mutex unit_mutex;
341 bool has_unit = false;
342 bool unit_from_dimval = false;
343 std::string unit_name;
344 std::string unit_kind;
345 std::string unit_symbol;
346
348 [[nodiscard]] Unit unit_view() const {
349 if (!has_unit) {
350 return Unit{};
351 }
352 return Unit{unit_name, unit_kind, unit_symbol, unit_from_dimval};
353 }
354
358 [[nodiscard]] MetricMeta build_meta() const {
359 MetricMeta meta;
360 meta.type = type;
361 meta.name = name;
362 meta.help = help;
363 meta.unit = unit_view();
364 meta.const_labels = const_labels;
365 meta.display = display;
366 meta.buckets = buckets;
367 meta.quantiles = quantiles;
368 meta.states = states;
369 return meta;
370 }
371};
372
376template <class Derived>
378public:
380 [[nodiscard]] std::string_view name() const noexcept {
381 return core_->name;
382 }
383
385 [[nodiscard]] MetricType type() const noexcept {
386 return core_->type;
387 }
388
389protected:
395 MetricBase(const MetricType type, const std::string_view name, const std::string_view help)
396 : core_(std::make_shared<MetricCore>()) {
397 core_->type = type;
398 core_->name = std::string(name);
399 core_->base_name = std::string(name);
400 core_->help = std::string(help);
401 core_->source = detail::global_adapter_cell();
402 core_->scope = detail::global_decoration();
403 }
404
406 explicit MetricBase(std::shared_ptr<MetricCore> core) : core_(std::move(core)) {}
407
408 MetricBase(const MetricBase&) = default;
409 MetricBase& operator=(const MetricBase&) = default;
410 MetricBase(MetricBase&&) = default;
412 ~MetricBase() = default;
413
421
435 [[nodiscard]] Binding bind() const noexcept {
436 if (const auto b = core_->bound.load()) {
437 if (core_->pinned) {
438 return Binding{b->adapter, b->handle};
439 }
440 const std::uint64_t av = core_->source->version();
441 if (const std::uint64_t sv = core_->scope ? core_->scope->version() : 0;
442 b->adapter_version == av && b->scope_version == sv) {
443 return Binding{b->adapter, b->handle};
444 }
445 }
446 return rebind();
447 }
448
449 [[nodiscard]] const std::shared_ptr<MetricCore>& core() const noexcept {
450 return core_;
451 }
452
458 [[nodiscard]] Derived make_child(const Labels& dynamic) const noexcept {
459 const Binding b = bind();
460 auto child = std::make_shared<MetricCore>();
461 child->type = core_->type;
462 child->name = core_->name;
463 child->source = core_->source;
464 child->pinned = true;
465 child->family = family_core();
466 child->bound.store(std::make_shared<const MetricCore::Bound>(
467 MetricCore::Bound{b.adapter, b.adapter->resolve(b.handle, dynamic), 0, 0}));
468 return Derived{std::move(child)};
469 }
470
475 [[nodiscard]] bool reconcile_unit(const Unit& observed, Adapter& adapter) const noexcept {
476 if (observed.empty()) {
477 return true;
478 }
479 const std::shared_ptr<MetricCore> fam = family_core();
480 const std::scoped_lock lock(fam->unit_mutex);
481 if (!fam->has_unit) {
482 fam->unit_name = std::string(observed.name);
483 fam->unit_kind = std::string(observed.kind);
484 fam->unit_symbol = std::string(observed.symbol);
485 fam->unit_from_dimval = observed.from_dimval;
486 fam->has_unit = true;
487 MetricHandle handle;
488 if (const auto fb = fam->bound.load()) {
489 handle = fb->handle;
490 }
491 if (!handle) {
492 if (const auto cb = core_->bound.load()) {
493 handle = cb->handle;
494 }
495 }
496 adapter.set_unit(handle, fam->unit_view());
497 return true;
498 }
499 if (!fam->unit_kind.empty() && !observed.kind.empty() && fam->unit_kind != observed.kind) {
500 if (auto* lg = logger()) {
501 lg->warn("metric '{}' dropping sample: unit kind '{}' does not match "
502 "latched kind '{}'",
503 fam->name,
504 observed.kind,
505 fam->unit_kind);
506 }
507 return false;
508 }
509 return true;
510 }
511
514 [[nodiscard]] bool check_finite(const double value, std::string_view op) const noexcept {
515 if (std::isnan(value) || std::isinf(value)) {
516 if (auto* lg = logger()) {
517 lg->warn("metric '{}' dropping {}: non-finite value", core_->name, op);
518 }
519 return false;
520 }
521 return true;
522 }
523
525 [[nodiscard]] static spdlog::logger* logger() noexcept {
526 static std::shared_ptr<spdlog::logger> lg = logman::get("prom");
527 return lg.get();
528 }
529
530 std::shared_ptr<MetricCore> core_;
531
532private:
533 [[nodiscard]] std::shared_ptr<MetricCore> family_core() const noexcept {
534 return core_->family ? core_->family : core_;
535 }
536
541 [[nodiscard]] Binding rebind() const noexcept {
542 const std::scoped_lock lock(core_->bind_mutex);
543 const std::uint64_t av = core_->source->version();
544 const std::uint64_t sv = core_->scope ? core_->scope->version() : 0;
545 if (const auto b = core_->bound.load()) {
546 if (core_->pinned || (b->adapter_version == av && b->scope_version == sv)) {
547 return Binding{b->adapter, b->handle};
548 }
549 }
550 const AdapterPtr adapter = core_->source->adapter();
551 if (core_->scope) {
552 core_->name = core_->scope->full_name(core_->base_name);
553 core_->const_labels = core_->scope->effective_labels(core_->base_labels);
554 core_->display = core_->scope->effective_display(core_->base_display);
555 }
556 const MetricHandle handle = adapter->register_metric(core_->build_meta());
557 core_->bound.store(
558 std::make_shared<const MetricCore::Bound>(MetricCore::Bound{adapter, handle, av, sv}));
559 return Binding{adapter, handle};
560 }
561};
562
563} // namespace prom
::prometheus::Histogram::BucketBoundaries buckets
Definition adapter.cpp:65
std::string name
Metric name (state-set label key).
Definition adapter.cpp:68
::prometheus::Summary::Quantiles quantiles
Definition adapter.cpp:66
prom::MetricType type
Definition adapter.cpp:53
The pluggable backend.
Definition adapter.hpp:62
An immutable-by-convention set of labels, kept sorted by name with duplicates collapsed last-wins.
Definition labels.hpp:55
Labels merged_with(const Labels &other) const
Return *this overlaid with other; on a name collision other wins.
Definition labels.hpp:97
CRTP base shared by every metric type.
Definition metric_base.hpp:377
MetricBase(MetricBase &&)=default
MetricBase & operator=(const MetricBase &)=default
MetricType type() const noexcept
The metric kind.
Definition metric_base.hpp:385
Derived make_child(const Labels &dynamic) const noexcept
Resolve a labeled child of the same metric type.
Definition metric_base.hpp:458
bool reconcile_unit(const Unit &observed, Adapter &adapter) const noexcept
Reconcile an observed unit against the family's known unit.
Definition metric_base.hpp:475
static spdlog::logger * logger() noexcept
The shared per-process metrics logger.
Definition metric_base.hpp:525
MetricBase(const MetricType type, const std::string_view name, const std::string_view help)
Standalone, unbound construction from a name and help string.
Definition metric_base.hpp:395
std::string_view name() const noexcept
The metric's fully-qualified name.
Definition metric_base.hpp:380
const std::shared_ptr< MetricCore > & core() const noexcept
Definition metric_base.hpp:449
MetricBase(const MetricBase &)=default
MetricBase(std::shared_ptr< MetricCore > core)
Adopt an already-populated core (registered metrics and children).
Definition metric_base.hpp:406
bool check_finite(const double value, std::string_view op) const noexcept
Drop-and-log guard for a non-finite sample.
Definition metric_base.hpp:514
MetricBase & operator=(MetricBase &&)=default
std::shared_ptr< MetricCore > core_
Definition metric_base.hpp:530
~MetricBase()=default
Binding bind() const noexcept
Resolve the adapter and backend handle this metric should record against.
Definition metric_base.hpp:435
Read-only view a scoped metric uses to re-resolve its name, constant labels, and display metadata aga...
Definition metric_base.hpp:51
virtual std::string full_name(std::string_view base) const =0
The effective metric name for a base (un-prefixed) name.
ScopeState & operator=(const ScopeState &)=delete
virtual comms::DisplayInfo effective_display(const comms::DisplayInfo &own) const =0
The effective display metadata for a metric's own display.
virtual ~ScopeState()=default
ScopeState(const ScopeState &)=delete
virtual std::uint64_t version() const noexcept=0
Monotonic counter, bumped on every configuration change.
ScopeState()=default
ScopeState(ScopeState &&)=delete
virtual bool decorates() const noexcept=0
Whether this decoration actually changes anything (a non-empty prefix, constant labels,...
virtual Labels effective_labels(const Labels &own) const =0
The effective constant labels for a metric's own labels.
ScopeState & operator=(ScopeState &&)=delete
A live, thread-safe metric decoration — a name prefix, default constant labels, and default display m...
Definition metric_base.hpp:116
comms::DisplayInfo display() const
Definition metric_base.hpp:194
Labels effective_labels(const Labels &own) const override
The effective constant labels for a metric's own labels.
Definition metric_base.hpp:138
Labels const_labels() const
Definition metric_base.hpp:176
std::uint64_t version() const noexcept override
Monotonic counter, bumped on every configuration change.
Definition metric_base.hpp:126
bool decorates() const noexcept override
Whether this decoration actually changes anything (a non-empty prefix, constant labels,...
Definition metric_base.hpp:155
void set_prefix(std::string prefix)
Definition metric_base.hpp:169
void set_const_labels(Labels labels)
Definition metric_base.hpp:180
comms::DisplayInfo effective_display(const comms::DisplayInfo &own) const override
The effective display metadata for a metric's own display.
Definition metric_base.hpp:147
void set_display(comms::DisplayInfo display)
Definition metric_base.hpp:198
DecorationState(std::string prefix, Labels const_labels, comms::DisplayInfo display, std::shared_ptr< ScopeState > parent=nullptr)
Definition metric_base.hpp:119
void add_const_label(std::string name, std::string value)
Definition metric_base.hpp:187
void configure(std::string prefix, Labels const_labels, comms::DisplayInfo display)
Replace prefix, constant labels, and display in one shot (single version bump).
Definition metric_base.hpp:208
std::string prefix() const
Definition metric_base.hpp:165
std::string full_name(const std::string_view base) const override
The effective metric name for a base (un-prefixed) name.
Definition metric_base.hpp:130
Structural bridge to dimval: the DimensionalValue concept, NormalizedValue, and the normalize() overl...
The process-wide adapter cell and its install/swap semantics.
The backend boundary: MetricMeta, MetricState/MetricHandle, and the pure-virtual Adapter interface.
Label vocabulary: Label, the sorted/deduped Labels set, name validation, and an std::hash<Labels> spe...
comms::DisplayInfo merge_display(const comms::DisplayInfo &base, const comms::DisplayInfo &over)
Overlay a metric's own display fields onto a set of defaults; each per-metric field that is set wins ...
Definition metric_base.hpp:80
bool display_empty(const comms::DisplayInfo &display) noexcept
Whether a DisplayInfo carries any field at all.
Definition metric_base.hpp:99
std::shared_ptr< DecorationState > global_decoration()
The process-wide decoration shared by Registry::global(), every standalone metric,...
Definition metric_base.hpp:238
Definition adapter.hpp:24
MetricType
The OpenMetrics / Prometheus metric kinds prom understands.
Definition unit.hpp:15
std::shared_ptr< Adapter > AdapterPtr
Shared ownership of an Adapter.
Definition fwd.hpp:38
std::shared_ptr< MetricState > MetricHandle
Opaque, backend-owned handle to a registered metric family or a labeled child series.
Definition fwd.hpp:34
A resolved (adapter, handle) snapshot — the result of bind().
Definition metric_base.hpp:417
MetricHandle handle
Definition metric_base.hpp:419
AdapterPtr adapter
Definition metric_base.hpp:418
Definition metric_base.hpp:309
MetricHandle handle
Definition metric_base.hpp:311
AdapterPtr adapter
Definition metric_base.hpp:310
Shared, reference-counted state behind every metric handle.
Definition metric_base.hpp:289
MetricMeta build_meta() const
Assemble the transient registration metadata.
Definition metric_base.hpp:358
std::mutex bind_mutex
Definition metric_base.hpp:330
std::mutex unit_mutex
Definition metric_base.hpp:340
std::string unit_name
Definition metric_base.hpp:343
std::string unit_symbol
Definition metric_base.hpp:345
Unit unit_view() const
Build a non-owning view of the currently-known unit.
Definition metric_base.hpp:348
std::string help
Definition metric_base.hpp:294
std::shared_ptr< MetricCore > family
Definition metric_base.hpp:335
std::vector< double > buckets
Definition metric_base.hpp:297
std::string base_name
Definition metric_base.hpp:327
comms::DisplayInfo base_display
Definition metric_base.hpp:329
detail::AtomicSharedPtr< Bound > bound
Definition metric_base.hpp:315
Labels base_labels
Definition metric_base.hpp:328
Labels const_labels
Definition metric_base.hpp:295
std::string name
Definition metric_base.hpp:293
std::shared_ptr< AdapterSource > source
Definition metric_base.hpp:303
comms::DisplayInfo display
Definition metric_base.hpp:296
std::vector< double > quantiles
Definition metric_base.hpp:298
std::string unit_kind
Definition metric_base.hpp:344
std::shared_ptr< ScopeState > scope
Definition metric_base.hpp:326
std::vector< std::string > states
Definition metric_base.hpp:299
The complete, backend-agnostic description of a metric family handed to Adapter::register_metric.
Definition adapter.hpp:31
Unit unit
Declared unit (may be empty).
Definition adapter.hpp:35
std::span< const std::string > states
State-set member names (else empty).
Definition adapter.hpp:40
std::span< const double > quantiles
Summary quantiles (else empty).
Definition adapter.hpp:39
comms::DisplayInfo display
Optional UI metadata.
Definition adapter.hpp:37
std::span< const double > buckets
Histogram bucket bounds (else empty).
Definition adapter.hpp:38
std::string_view name
Fully-qualified metric name.
Definition adapter.hpp:33
MetricType type
Which kind of metric this is.
Definition adapter.hpp:32
std::string_view help
One-line description.
Definition adapter.hpp:34
Labels const_labels
Labels fixed for the whole family.
Definition adapter.hpp:36
OpenMetrics unit suffix plus optional dimensional metadata.
Definition unit.hpp:53
Metric kinds (MetricType) and the OpenMetrics Unit descriptor.