use std::{
fmt::{self, Debug},
ops::{AddAssign, SubAssign},
sync::{Arc, atomic::*},
};
use crate::{
encoder::{EncodeGaugeValue, EncodeMetric, MetricEncoder},
error::Result,
metrics::internal::lazy::{LazySource, PlainLazySource},
raw::{Atomic, MetricLabelSet, MetricType, Number, TypedMetric},
};
pub trait GaugeValue<Rhs = Self>: Number + AddAssign<Rhs> + SubAssign<Rhs> {
type Atomic: Atomic<Self>;
}
macro_rules! impl_gauge_value_for {
($($num:ident => $atomic:ident);* $(;)?) => ($(
impl GaugeValue for $num {
type Atomic = $atomic;
}
)*);
}
impl_gauge_value_for! {
i32 => AtomicI32;
i64 => AtomicI64;
isize => AtomicIsize;
f32 => AtomicU32;
f64 => AtomicU64;
}
pub trait SaturatingGaugeValue: GaugeValue {
fn saturating_add(self, rhs: Self) -> Self;
fn saturating_sub(self, rhs: Self) -> Self;
}
macro_rules! impl_saturating_gauge_value_for_integer {
($($ty:ty),* $(,)?) => {
$(
impl SaturatingGaugeValue for $ty {
#[inline]
fn saturating_add(self, rhs: Self) -> Self {
<$ty>::saturating_add(self, rhs)
}
#[inline]
fn saturating_sub(self, rhs: Self) -> Self {
<$ty>::saturating_sub(self, rhs)
}
}
)*
};
}
impl_saturating_gauge_value_for_integer! { i32, i64, isize }
pub struct Gauge<N: GaugeValue = i64> {
value: Arc<N::Atomic>,
}
impl<N: GaugeValue> Clone for Gauge<N> {
fn clone(&self) -> Self {
Self { value: self.value.clone() }
}
}
impl<N: GaugeValue> Debug for Gauge<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Gauge").field("value", &self.get()).finish()
}
}
impl<N: GaugeValue> Default for Gauge<N> {
fn default() -> Self {
Self { value: Arc::new(Default::default()) }
}
}
impl<N: GaugeValue> Gauge<N> {
pub fn new(value: N) -> Self {
let this = Self::default();
this.set(value);
this
}
#[inline]
pub fn inc(&self) {
self.value.inc_by(N::ONE);
}
#[inline]
pub fn inc_by(&self, v: N) {
self.value.inc_by(v);
}
#[inline]
pub fn dec(&self) {
self.value.dec_by(N::ONE);
}
#[inline]
pub fn dec_by(&self, v: N) {
self.value.dec_by(v);
}
#[inline]
pub fn set(&self, v: N) {
self.value.set(v);
}
#[inline]
pub fn get(&self) -> N {
self.value.get()
}
}
impl<N: GaugeValue> TypedMetric for Gauge<N> {
const TYPE: MetricType = MetricType::Gauge;
}
impl<N: SaturatingGaugeValue> Gauge<N> {
#[inline]
pub fn saturating_inc(&self) {
self.saturating_inc_by(N::ONE);
}
#[inline]
pub fn saturating_inc_by(&self, v: N) {
self.value.update(|old| old.saturating_add(v));
}
#[inline]
pub fn saturating_dec(&self) {
self.saturating_dec_by(N::ONE);
}
#[inline]
pub fn saturating_dec_by(&self, v: N) {
self.value.update(|old| old.saturating_sub(v));
}
}
impl<N: GaugeValue> MetricLabelSet for Gauge<N> {
type LabelSet = ();
}
impl<N: EncodeGaugeValue + GaugeValue> EncodeMetric for Gauge<N> {
fn encode(&self, encoder: &mut dyn MetricEncoder) -> Result<()> {
encoder.encode_gauge(&self.get())
}
}
#[derive(Clone, Debug, Default)]
pub struct ConstGauge<N = i64> {
value: N,
}
impl<N: GaugeValue> ConstGauge<N> {
pub const fn new(value: N) -> Self {
Self { value }
}
#[inline]
pub const fn get(&self) -> N {
self.value
}
}
impl<N> TypedMetric for ConstGauge<N> {
const TYPE: MetricType = MetricType::Gauge;
}
impl<N> MetricLabelSet for ConstGauge<N> {
type LabelSet = ();
}
impl<N: EncodeGaugeValue + GaugeValue> EncodeMetric for ConstGauge<N> {
fn encode(&self, encoder: &mut dyn MetricEncoder) -> Result<()> {
encoder.encode_gauge(&self.get())
}
}
pub struct LazyGauge<N> {
source: Arc<dyn LazySource<N>>,
}
impl<N> Clone for LazyGauge<N> {
fn clone(&self) -> Self {
Self { source: self.source.clone() }
}
}
impl<N: GaugeValue + 'static> LazyGauge<N> {
pub(crate) fn from_source(source: Arc<dyn LazySource<N>>) -> Self {
Self { source }
}
pub fn new(fetch: impl Fn() -> N + Send + Sync + 'static) -> Self {
Self::from_source(Arc::new(PlainLazySource::new(Arc::new(fetch))))
}
#[inline]
pub fn fetch(&self) -> N {
self.source.load()
}
}
impl<N> TypedMetric for LazyGauge<N> {
const TYPE: MetricType = MetricType::Gauge;
}
impl<N> MetricLabelSet for LazyGauge<N> {
type LabelSet = ();
}
impl<N: EncodeGaugeValue + GaugeValue + 'static> EncodeMetric for LazyGauge<N> {
fn encode(&self, encoder: &mut dyn MetricEncoder) -> Result<()> {
let value = self.fetch();
encoder.encode_gauge(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{metrics::check_text_encoding, registry::Unit};
#[test]
fn test_gauge_initialization() {
let gauge = <Gauge>::default();
assert_eq!(gauge.get(), 0);
let gauge = Gauge::new(42_i64);
assert_eq!(gauge.get(), 42);
}
#[test]
fn test_gauge_inc_dec() {
let gauge = <Gauge>::default();
assert_eq!(gauge.get(), 0);
gauge.inc();
assert_eq!(gauge.get(), 1);
gauge.dec();
assert_eq!(gauge.get(), 0);
}
#[test]
fn test_gauge_inc_dec_by() {
let gauge = <Gauge>::default();
assert_eq!(gauge.get(), 0);
gauge.inc_by(5);
assert_eq!(gauge.get(), 5);
gauge.inc_by(-1);
assert_eq!(gauge.get(), 4);
gauge.dec_by(3);
assert_eq!(gauge.get(), 1);
gauge.dec_by(-1);
assert_eq!(gauge.get(), 2);
}
#[test]
fn test_gauge_set() {
let gauge = <Gauge>::default();
let clone = gauge.clone();
gauge.set(42);
assert_eq!(gauge.get(), 42);
assert_eq!(clone.get(), 42);
clone.set(-10);
assert_eq!(clone.get(), -10);
assert_eq!(gauge.get(), -10);
}
#[test]
fn test_gauge_saturating_inc_dec() {
let gauge = Gauge::new(i64::MAX);
gauge.saturating_inc();
assert_eq!(gauge.get(), i64::MAX);
gauge.saturating_inc_by(1);
assert_eq!(gauge.get(), i64::MAX);
gauge.saturating_inc_by(123);
assert_eq!(gauge.get(), i64::MAX);
let gauge = Gauge::new(i64::MIN);
gauge.saturating_dec();
assert_eq!(gauge.get(), i64::MIN);
gauge.saturating_dec_by(1);
assert_eq!(gauge.get(), i64::MIN);
gauge.saturating_dec_by(123);
assert_eq!(gauge.get(), i64::MIN);
let gauge = Gauge::new(10i64);
gauge.saturating_inc_by(5);
assert_eq!(gauge.get(), 15);
gauge.saturating_dec_by(3);
assert_eq!(gauge.get(), 12);
gauge.saturating_inc();
assert_eq!(gauge.get(), 13);
gauge.saturating_dec();
assert_eq!(gauge.get(), 12);
}
#[test]
fn test_gauge_thread_safe() {
let gauge = <Gauge>::default();
let clone = gauge.clone();
let handle = std::thread::spawn(move || {
for _ in 0..1000 {
clone.inc();
}
});
for _ in 0..1000 {
gauge.inc();
}
handle.join().unwrap();
assert_eq!(gauge.get(), 2000);
}
#[test]
fn test_const_gauge() {
let gauge = ConstGauge::new(42i64);
assert_eq!(gauge.get(), 42);
let clone = gauge.clone();
assert_eq!(clone.get(), 42);
}
#[test]
fn test_lazy_gauge() {
let value = Arc::new(AtomicI64::new(10));
let gauge = LazyGauge::new({
let value = value.clone();
move || value.load(Ordering::Relaxed)
});
assert_eq!(gauge.fetch(), 10);
}
#[test]
fn test_text_encoding() {
check_text_encoding(
|registry| {
let gauge = <Gauge>::default();
registry.register("my_gauge", "My gauge help", gauge.clone()).unwrap();
gauge.set(100);
},
|output| {
let expected = indoc::indoc! {r#"
# TYPE my_gauge gauge
# HELP my_gauge My gauge help
my_gauge 100
# EOF
"#};
assert_eq!(expected, output);
},
);
check_text_encoding(
|registry| {
let gauge = <Gauge>::default();
registry
.register_with_unit("my_gauge", "My gauge help", Unit::Bytes, gauge.clone())
.unwrap();
gauge.set(100);
},
|output| {
let expected = indoc::indoc! {r#"
# TYPE my_gauge_bytes gauge
# HELP my_gauge_bytes My gauge help
# UNIT my_gauge_bytes bytes
my_gauge_bytes 100
# EOF
"#};
assert_eq!(expected, output);
},
);
check_text_encoding(
|registry| {
let gauge = <ConstGauge>::new(42i64);
registry.register("my_gauge", "My gauge help", gauge.clone()).unwrap();
},
|output| {
let expected = indoc::indoc! {r#"
# TYPE my_gauge gauge
# HELP my_gauge My gauge help
my_gauge 42
# EOF
"#};
assert_eq!(expected, output);
},
);
}
}