metrics_prometheus/
metric.rs1use std::{iter, sync::Arc};
5
6use arc_swap::ArcSwap;
7use sealed::sealed;
8use smallvec::SmallVec;
9
10#[doc(inline)]
11pub use self::bundle::Bundle;
12use self::bundle::Either;
13
14#[derive(Clone, Copy, Debug)]
17pub struct Metric<M>(M);
18
19impl<M> Metric<M> {
20 #[must_use]
22 pub const fn wrap(metric: M) -> Self {
23 Self(metric)
24 }
25
26 #[must_use]
28 pub fn into_inner(self) -> M {
29 self.0
30 }
31}
32
33impl<M> AsRef<M> for Metric<M> {
34 fn as_ref(&self) -> &M {
35 &self.0
36 }
37}
38
39impl<M> AsMut<M> for Metric<M> {
40 fn as_mut(&mut self) -> &mut M {
41 &mut self.0
42 }
43}
44
45#[warn(clippy::missing_trait_methods)]
46impl metrics::CounterFn for Metric<prometheus::IntCounter> {
47 fn increment(&self, value: u64) {
48 self.0.inc_by(value);
49 }
50
51 fn absolute(&self, value: u64) {
52 self.0.reset();
62 self.0.inc_by(value);
63 }
64}
65
66#[warn(clippy::missing_trait_methods)]
67impl metrics::GaugeFn for Metric<prometheus::Gauge> {
68 fn increment(&self, value: f64) {
69 self.0.add(value);
70 }
71
72 fn decrement(&self, value: f64) {
73 self.0.sub(value);
74 }
75
76 fn set(&self, value: f64) {
77 self.0.set(value);
78 }
79}
80
81#[warn(clippy::missing_trait_methods)]
82impl metrics::HistogramFn for Metric<prometheus::Histogram> {
83 fn record(&self, value: f64) {
84 self.0.observe(value);
85 }
86
87 fn record_many(&self, value: f64, count: usize) {
88 for _ in 0..count {
89 self.record(value);
90 }
91 }
92}
93
94#[derive(Debug)]
103pub struct Fallible<M>(pub Arc<prometheus::Result<Arc<Metric<M>>>>);
104
105impl<M> Clone for Fallible<M> {
108 fn clone(&self) -> Self {
109 Self(Arc::clone(&self.0))
110 }
111}
112
113impl<M> From<prometheus::Result<Arc<Metric<M>>>> for Fallible<M> {
114 fn from(res: prometheus::Result<Arc<Metric<M>>>) -> Self {
115 Self(Arc::new(res))
116 }
117}
118
119impl<M> Fallible<M> {
120 pub fn as_ref(&self) -> Result<&Arc<Metric<M>>, &prometheus::Error> {
126 (*self.0).as_ref()
127 }
128}
129
130#[warn(clippy::missing_trait_methods)]
133impl<M> metrics::CounterFn for Fallible<M>
134where
135 Metric<M>: metrics::CounterFn,
136{
137 fn increment(&self, value: u64) {
138 if let Ok(m) = &*self.0 {
139 m.increment(value);
140 }
141 }
142
143 fn absolute(&self, value: u64) {
144 if let Ok(m) = &*self.0 {
145 m.absolute(value);
146 }
147 }
148}
149
150#[warn(clippy::missing_trait_methods)]
153impl<M> metrics::GaugeFn for Fallible<M>
154where
155 Metric<M>: metrics::GaugeFn,
156{
157 fn increment(&self, value: f64) {
158 if let Ok(m) = &*self.0 {
159 m.increment(value);
160 }
161 }
162
163 fn decrement(&self, value: f64) {
164 if let Ok(m) = &*self.0 {
165 m.decrement(value);
166 }
167 }
168
169 fn set(&self, value: f64) {
170 if let Ok(m) = &*self.0 {
171 m.set(value);
172 }
173 }
174}
175
176#[warn(clippy::missing_trait_methods)]
179impl<M> metrics::HistogramFn for Fallible<M>
180where
181 Metric<M>: metrics::HistogramFn,
182{
183 fn record(&self, value: f64) {
184 if let Ok(m) = &*self.0 {
185 m.record(value);
186 }
187 }
188
189 fn record_many(&self, value: f64, count: usize) {
190 if let Ok(m) = &*self.0 {
191 for _ in 0..count {
192 m.record(value);
193 }
194 }
195 }
196}
197
198#[derive(Clone, Debug, Default)]
203pub struct Describable<Metric> {
204 pub(crate) description: Arc<ArcSwap<String>>,
208
209 pub(crate) metric: Metric,
211}
212
213impl<M> Describable<M> {
214 #[must_use]
216 pub fn wrap(metric: M) -> Self {
217 Self { description: Arc::default(), metric }
218 }
219
220 #[must_use]
225 pub fn only_description(help: impl Into<String>) -> Self
226 where
227 M: Default,
228 {
229 Self {
230 description: Arc::new(ArcSwap::new(Arc::new(help.into()))),
231 metric: M::default(),
232 }
233 }
234
235 #[must_use]
240 pub fn map<Into>(self, into: impl FnOnce(M) -> Into) -> Describable<Into> {
241 Describable { description: self.description, metric: into(self.metric) }
242 }
243}
244
245impl<M> Describable<Option<M>> {
246 #[must_use]
249 pub fn transpose(self) -> Option<Describable<M>> {
250 self.metric
251 .map(|metric| Describable { description: self.description, metric })
252 }
253}
254
255#[warn(clippy::missing_trait_methods)]
256impl<M> prometheus::core::Collector for Describable<M>
257where
258 M: prometheus::core::Collector,
259{
260 fn desc(&self) -> Vec<&prometheus::core::Desc> {
261 self.metric.desc()
268 }
269
270 fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
271 let mut out = self.metric.collect();
272 let new_help = self.description.load_full();
273 if !new_help.is_empty() {
274 for mf in &mut out {
275 mf.set_help((*new_help).clone());
276 }
277 }
278 out
279 }
280}
281
282trait To<T> {
284 fn to(&self) -> T;
286}
287
288impl To<prometheus::Opts> for metrics::Key {
289 fn to(&self) -> prometheus::Opts {
290 prometheus::Opts::new(self.name(), self.name())
293 }
294}
295
296impl To<prometheus::HistogramOpts> for metrics::Key {
297 fn to(&self) -> prometheus::HistogramOpts {
298 prometheus::HistogramOpts::new(self.name(), self.name())
301 }
302}
303
304#[sealed]
306pub trait Bundled {
307 type Bundle: Bundle;
309
310 fn into_bundle(self) -> Self::Bundle;
312}
313
314#[sealed]
315impl Bundled for prometheus::IntCounter {
316 type Bundle = PrometheusIntCounter;
317
318 fn into_bundle(self) -> Self::Bundle {
319 PrometheusIntCounter::Single(self)
320 }
321}
322
323#[sealed]
324impl Bundled for prometheus::IntCounterVec {
325 type Bundle = PrometheusIntCounter;
326
327 fn into_bundle(self) -> Self::Bundle {
328 PrometheusIntCounter::Vec(self)
329 }
330}
331
332#[sealed]
333impl Bundled for prometheus::Gauge {
334 type Bundle = PrometheusGauge;
335
336 fn into_bundle(self) -> Self::Bundle {
337 PrometheusGauge::Single(self)
338 }
339}
340
341#[sealed]
342impl Bundled for prometheus::GaugeVec {
343 type Bundle = PrometheusGauge;
344
345 fn into_bundle(self) -> Self::Bundle {
346 PrometheusGauge::Vec(self)
347 }
348}
349
350#[sealed]
351impl Bundled for prometheus::Histogram {
352 type Bundle = PrometheusHistogram;
353
354 fn into_bundle(self) -> Self::Bundle {
355 PrometheusHistogram::Single(self)
356 }
357}
358
359#[sealed]
360impl Bundled for prometheus::HistogramVec {
361 type Bundle = PrometheusHistogram;
362
363 fn into_bundle(self) -> Self::Bundle {
364 PrometheusHistogram::Vec(self)
365 }
366}
367
368pub type PrometheusIntCounter =
370 Either<prometheus::IntCounter, prometheus::IntCounterVec>;
371
372impl TryFrom<&metrics::Key> for PrometheusIntCounter {
373 type Error = prometheus::Error;
374
375 fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
376 let mut labels_iter = key.labels();
377 Ok(if let Some(first_label) = labels_iter.next() {
378 let label_names = iter::once(first_label)
379 .chain(labels_iter)
380 .map(metrics::Label::key)
381 .collect::<SmallVec<[_; 10]>>();
382 Self::Vec(prometheus::IntCounterVec::new(key.to(), &label_names)?)
383 } else {
384 Self::Single(prometheus::IntCounter::with_opts(key.to())?)
385 })
386 }
387}
388
389pub type PrometheusGauge = Either<prometheus::Gauge, prometheus::GaugeVec>;
391
392impl TryFrom<&metrics::Key> for PrometheusGauge {
393 type Error = prometheus::Error;
394
395 fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
396 let mut labels_iter = key.labels();
397 Ok(if let Some(first_label) = labels_iter.next() {
398 let label_names = iter::once(first_label)
399 .chain(labels_iter)
400 .map(metrics::Label::key)
401 .collect::<SmallVec<[_; 10]>>();
402 Self::Vec(prometheus::GaugeVec::new(key.to(), &label_names)?)
403 } else {
404 Self::Single(prometheus::Gauge::with_opts(key.to())?)
405 })
406 }
407}
408
409pub type PrometheusHistogram =
411 Either<prometheus::Histogram, prometheus::HistogramVec>;
412
413impl TryFrom<&metrics::Key> for PrometheusHistogram {
414 type Error = prometheus::Error;
415
416 fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
417 let mut labels_iter = key.labels();
418 Ok(if let Some(first_label) = labels_iter.next() {
419 let label_names = iter::once(first_label)
420 .chain(labels_iter)
421 .map(metrics::Label::key)
422 .collect::<SmallVec<[_; 10]>>();
423 Self::Vec(prometheus::HistogramVec::new(key.to(), &label_names)?)
424 } else {
425 Self::Single(prometheus::Histogram::with_opts(key.to())?)
426 })
427 }
428}
429
430pub mod bundle {
432 use std::{collections::HashMap, hash::BuildHasher};
433
434 use sealed::sealed;
435
436 #[derive(Clone, Copy, Debug)]
442 pub enum Either<Single, Vec> {
443 Single(Single),
447
448 Vec(Vec),
453 }
454
455 #[warn(clippy::missing_trait_methods)]
456 impl<S, V> prometheus::core::Collector for Either<S, V>
457 where
458 S: prometheus::core::Collector,
459 V: prometheus::core::Collector,
460 {
461 fn desc(&self) -> Vec<&prometheus::core::Desc> {
462 match self {
463 Self::Single(m) => m.desc(),
464 Self::Vec(v) => v.desc(),
465 }
466 }
467
468 fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
469 match self {
470 Self::Single(m) => m.collect(),
471 Self::Vec(v) => v.collect(),
472 }
473 }
474 }
475
476 #[sealed]
481 pub trait MetricVec {
482 type Metric: prometheus::core::Metric;
486
487 fn get_metric_with<S: BuildHasher>(
498 &self,
499 labels: &HashMap<&str, &str, S>,
500 ) -> prometheus::Result<Self::Metric>;
501 }
502
503 #[sealed]
504 impl<M, B> MetricVec for prometheus::core::MetricVec<B>
505 where
506 M: prometheus::core::Metric,
507 B: prometheus::core::MetricVecBuilder<M = M>,
508 {
509 type Metric = M;
510
511 fn get_metric_with<S: BuildHasher>(
512 &self,
513 labels: &HashMap<&str, &str, S>,
514 ) -> prometheus::Result<M> {
515 self.get_metric_with(labels)
516 }
517 }
518
519 #[sealed]
527 pub trait Bundle {
528 type Single: prometheus::core::Metric;
533
534 type Vec: MetricVec<Metric = Self::Single>;
539
540 fn get_single_metric(
550 &self,
551 key: &metrics::Key,
552 ) -> prometheus::Result<Self::Single>;
553 }
554
555 #[sealed]
556 impl<M, B> Bundle for Either<M, prometheus::core::MetricVec<B>>
557 where
558 M: prometheus::core::Metric + Clone,
559 B: prometheus::core::MetricVecBuilder<M = M>,
560 {
561 type Single = M;
562 type Vec = prometheus::core::MetricVec<B>;
563
564 fn get_single_metric(
565 &self,
566 key: &metrics::Key,
567 ) -> prometheus::Result<M> {
568 match self {
569 Self::Single(c) => {
570 if key.labels().next().is_some() {
571 return Err(
572 prometheus::Error::InconsistentCardinality {
573 expect: 0,
574 got: key.labels().count(),
575 },
576 );
577 }
578 Ok(c.clone())
579 }
580 Self::Vec(v) => {
581 let labels = key
584 .labels()
585 .map(|l| (l.key(), l.value()))
586 .collect::<HashMap<_, _>>();
587 v.get_metric_with(&labels)
588 }
589 }
590 }
591 }
592}