#[cfg(test)]
use std::future::Future;
use std::marker::PhantomData;
#[cfg(test)]
use std::pin::Pin;
use std::sync::OnceLock;
#[cfg(test)]
use futures::FutureExt;
use opentelemetry::metrics::InstrumentProvider;
use crate::metrics::aggregation::AggregateMeterProvider;
pub(crate) mod aggregation;
pub(crate) mod filter;
#[derive(Debug)]
#[doc(hidden)]
#[must_use = "without holding the guard updown counters will immediately zero out"]
pub struct UpDownCounterGuard<T>
where
T: std::ops::Neg<Output = T> + Copy,
{
counter: opentelemetry::metrics::UpDownCounter<T>,
value: T,
attributes: Vec<opentelemetry::KeyValue>,
}
impl<T> UpDownCounterGuard<T>
where
T: std::ops::Neg<Output = T> + Copy,
{
#[doc(hidden)]
pub fn new(
counter: std::sync::Arc<opentelemetry::metrics::UpDownCounter<T>>,
value: T,
attributes: &[opentelemetry::KeyValue],
) -> Self {
Self {
counter: (*counter).clone(),
value,
attributes: attributes.to_vec(),
}
}
}
impl<T> Drop for UpDownCounterGuard<T>
where
T: std::ops::Neg<Output = T> + Copy,
{
fn drop(&mut self) {
self.counter.add(-self.value, &self.attributes);
}
}
#[doc(hidden)]
pub struct NoopGuard<I, T> {
_phantom: PhantomData<(I, T)>,
}
impl<I, T> NoopGuard<I, T> {
#[doc(hidden)]
pub fn new(_instrument: I, _value: T, _attributes: &[opentelemetry::KeyValue]) -> Self {
NoopGuard {
_phantom: Default::default(),
}
}
}
struct NoopInstrumentProvider;
impl InstrumentProvider for NoopInstrumentProvider {}
#[cfg(test)]
pub(crate) mod test_utils {
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::fmt::Display;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::Weak;
use itertools::Itertools;
use num_traits::NumCast;
use num_traits::ToPrimitive;
use opentelemetry::Array;
use opentelemetry::KeyValue;
use opentelemetry::StringValue;
use opentelemetry::Value;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::metrics::InstrumentKind;
use opentelemetry_sdk::metrics::ManualReader;
use opentelemetry_sdk::metrics::MeterProviderBuilder;
use opentelemetry_sdk::metrics::Pipeline;
use opentelemetry_sdk::metrics::Temporality;
use opentelemetry_sdk::metrics::data::AggregatedMetrics;
use opentelemetry_sdk::metrics::data::GaugeDataPoint;
use opentelemetry_sdk::metrics::data::HistogramDataPoint;
use opentelemetry_sdk::metrics::data::Metric;
use opentelemetry_sdk::metrics::data::MetricData;
use opentelemetry_sdk::metrics::data::ResourceMetrics;
use opentelemetry_sdk::metrics::data::SumDataPoint;
use opentelemetry_sdk::metrics::reader::MetricReader;
use serde::Serialize;
use tokio::task_local;
use crate::metrics::aggregation::AggregateMeterProvider;
use crate::metrics::aggregation::MeterProviderType;
use crate::metrics::filter::FilterMeterProvider;
task_local! {
pub(crate) static AGGREGATE_METER_PROVIDER_ASYNC: OnceLock<(AggregateMeterProvider, ClonableManualReader)>;
}
thread_local! {
pub(crate) static AGGREGATE_METER_PROVIDER: OnceLock<(AggregateMeterProvider, ClonableManualReader)> = const { OnceLock::new() };
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ClonableManualReader {
reader: Arc<ManualReader>,
}
impl MetricReader for ClonableManualReader {
fn register_pipeline(&self, pipeline: Weak<Pipeline>) {
self.reader.register_pipeline(pipeline)
}
fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult {
self.reader.collect(rm)
}
fn force_flush(&self) -> OTelSdkResult {
self.reader.force_flush()
}
fn shutdown_with_timeout(&self, timeout: std::time::Duration) -> OTelSdkResult {
self.reader.shutdown_with_timeout(timeout)
}
fn temporality(&self, kind: InstrumentKind) -> Temporality {
self.reader.temporality(kind)
}
}
fn create_test_meter_provider() -> (AggregateMeterProvider, ClonableManualReader) {
{
let meter_provider = AggregateMeterProvider::default();
let reader = ClonableManualReader::default();
meter_provider.set(
MeterProviderType::Public,
FilterMeterProvider::all(
MeterProviderBuilder::default()
.with_reader(reader.clone())
.build(),
),
);
(meter_provider, reader)
}
}
pub(crate) fn meter_provider_and_readers() -> (AggregateMeterProvider, ClonableManualReader) {
if tokio::runtime::Handle::try_current().is_ok() {
AGGREGATE_METER_PROVIDER_ASYNC
.try_with(|cell| cell.get_or_init(create_test_meter_provider).clone())
.unwrap_or_default()
} else {
AGGREGATE_METER_PROVIDER
.with(|cell| cell.get_or_init(create_test_meter_provider).clone())
}
}
#[derive(Default)]
pub(crate) struct Metrics {
resource_metrics: ResourceMetrics,
}
pub(crate) fn collect_metrics() -> Metrics {
let mut metrics = Metrics::default();
let (_, reader) = meter_provider_and_readers();
reader
.collect(&mut metrics.resource_metrics)
.expect("Failed to collect metrics. Did you forget to use `async{}.with_metrics()`? See dev-docs/metrics.md");
metrics
}
impl Metrics {
pub(crate) fn find(&self, name: &str) -> Option<&opentelemetry_sdk::metrics::data::Metric> {
self.resource_metrics
.scope_metrics()
.flat_map(|scope_metrics| {
scope_metrics
.metrics()
.filter(|metric| metric.name() == name)
})
.next()
}
pub(crate) fn assert<T: NumCast + Display + 'static>(
&self,
name: &str,
ty: MetricType,
value: T,
count: bool,
attributes: &[KeyValue],
) -> bool {
if let Some(value) = value.to_u64()
&& self.metric_matches_u64(name, &ty, value, count, attributes)
{
return true;
}
if let Some(value) = value.to_i64()
&& self.metric_matches_i64(name, &ty, value, count, attributes)
{
return true;
}
if let Some(value) = value.to_f64()
&& self.metric_matches_f64(name, &ty, value, count, attributes)
{
return true;
}
false
}
fn metric_matches_u64(
&self,
name: &str,
ty: &MetricType,
value: u64,
count: bool,
attributes: &[KeyValue],
) -> bool {
for scope_metrics in self.resource_metrics.scope_metrics() {
for metric in scope_metrics.metrics().filter(|m| m.name() == name) {
if let AggregatedMetrics::U64(metric_data) = metric.data()
&& Self::check_metric_data(metric_data, ty, value, count, attributes)
{
return true;
}
}
}
false
}
fn metric_matches_i64(
&self,
name: &str,
ty: &MetricType,
value: i64,
count: bool,
attributes: &[KeyValue],
) -> bool {
for scope_metrics in self.resource_metrics.scope_metrics() {
for metric in scope_metrics.metrics().filter(|m| m.name() == name) {
if let AggregatedMetrics::I64(metric_data) = metric.data()
&& Self::check_metric_data(metric_data, ty, value, count, attributes)
{
return true;
}
}
}
false
}
fn metric_matches_f64(
&self,
name: &str,
ty: &MetricType,
value: f64,
count: bool,
attributes: &[KeyValue],
) -> bool {
for scope_metrics in self.resource_metrics.scope_metrics() {
for metric in scope_metrics.metrics().filter(|m| m.name() == name) {
if let AggregatedMetrics::F64(metric_data) = metric.data()
&& Self::check_metric_data(metric_data, ty, value, count, attributes)
{
return true;
}
}
}
false
}
fn check_metric_data<T: Debug + PartialEq + Display + Copy + ToPrimitive + 'static>(
metric_data: &MetricData<T>,
ty: &MetricType,
value: T,
count: bool,
attributes: &[KeyValue],
) -> bool {
match metric_data {
MetricData::Gauge(gauge) => {
if matches!(ty, MetricType::Gauge) {
return gauge.data_points().any(|datapoint| {
datapoint.value() == value
&& Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
MetricData::Sum(sum) => {
if matches!(ty, MetricType::Counter | MetricType::UpDownCounter) {
return sum.data_points().any(|datapoint| {
datapoint.value() == value
&& Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
MetricData::Histogram(histogram) => {
if matches!(ty, MetricType::Histogram) {
if count {
return histogram.data_points().any(|datapoint| {
datapoint.count() == value.to_u64().unwrap()
&& Self::equal_attributes(attributes, datapoint.attributes())
});
} else {
return histogram.data_points().any(|datapoint| {
datapoint.sum() == value
&& Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
}
MetricData::ExponentialHistogram(_) => {}
}
false
}
#[must_use]
pub(crate) fn metric_exists(
&self,
name: &str,
ty: MetricType,
attributes: &[KeyValue],
) -> bool {
if let Some(metric) = self.find(name) {
match metric.data() {
AggregatedMetrics::U64(metric_data) => {
return Self::check_metric_exists(metric_data, &ty, attributes);
}
AggregatedMetrics::I64(metric_data) => {
return Self::check_metric_exists(metric_data, &ty, attributes);
}
AggregatedMetrics::F64(metric_data) => {
return Self::check_metric_exists(metric_data, &ty, attributes);
}
}
}
false
}
fn check_metric_exists<T: Debug + PartialEq + Display + Copy + 'static>(
metric_data: &MetricData<T>,
ty: &MetricType,
attributes: &[KeyValue],
) -> bool {
match metric_data {
MetricData::Gauge(gauge) => {
if matches!(ty, MetricType::Gauge) {
return gauge.data_points().any(|datapoint| {
Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
MetricData::Sum(sum) => {
if matches!(ty, MetricType::Counter | MetricType::UpDownCounter) {
return sum.data_points().any(|datapoint| {
Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
MetricData::Histogram(histogram) => {
if matches!(ty, MetricType::Histogram) {
return histogram.data_points().any(|datapoint| {
Self::equal_attributes(attributes, datapoint.attributes())
});
}
}
MetricData::ExponentialHistogram(_) => {}
}
false
}
#[allow(dead_code)]
pub(crate) fn all(&self) -> Vec<SerdeMetric> {
self.resource_metrics
.scope_metrics()
.flat_map(|scope_metrics| {
scope_metrics.metrics().map(|metric| {
let serde_metric: SerdeMetric = metric.into();
serde_metric
})
})
.sorted()
.collect()
}
#[allow(dead_code)]
pub(crate) fn non_zero(&self) -> Vec<SerdeMetric> {
self.all()
.into_iter()
.filter(|m| {
m.data.datapoints.iter().any(|d| {
d.value
.as_ref()
.map(|v| v.as_f64().unwrap_or_default() > 0.0)
.unwrap_or_default()
|| d.sum
.as_ref()
.map(|v| v.as_f64().unwrap_or_default() > 0.0)
.unwrap_or_default()
})
})
.collect()
}
fn equal_attributes<'a>(
expected: &[KeyValue],
actual: impl Iterator<Item = &'a KeyValue>,
) -> bool {
let mut actual_vec: Vec<_> = actual.collect();
if expected.len() != actual_vec.len() {
return false;
}
let mut expected_sorted: Vec<_> = expected.iter().collect();
expected_sorted.sort_by(|a, b| a.key.cmp(&b.key));
actual_vec.sort_by(|a, b| a.key.cmp(&b.key));
expected_sorted
.iter()
.zip(actual_vec.iter())
.all(|(exp, act)| {
exp.key == act.key
&& (exp.value == act.value
|| exp.value == Value::String(StringValue::from("<any>")))
})
}
}
#[derive(Serialize, Eq, PartialEq)]
pub(crate) struct SerdeMetric {
pub(crate) name: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub(crate) description: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub(crate) unit: String,
pub(crate) data: SerdeMetricData,
}
impl Ord for SerdeMetric {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for SerdeMetric {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Serialize, Eq, PartialEq, Default)]
pub(crate) struct SerdeMetricData {
pub(crate) datapoints: Vec<SerdeMetricDataPoint>,
}
#[derive(Clone, Serialize, Eq, PartialEq)]
pub(crate) struct SerdeMetricDataPoint {
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) value: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) sum: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) count: Option<u64>,
pub(crate) attributes: BTreeMap<String, serde_json::Value>,
}
impl Ord for SerdeMetricDataPoint {
fn cmp(&self, other: &Self) -> Ordering {
let self_string = serde_json::to_string(&self.attributes).expect("serde failed");
let other_string = serde_json::to_string(&other.attributes).expect("serde failed");
self_string.cmp(&other_string)
}
}
impl PartialOrd for SerdeMetricDataPoint {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl SerdeMetricData {
fn extract_datapoints_f64(metric_data: &mut SerdeMetricData, value: &MetricData<f64>) {
match value {
MetricData::Gauge(gauge) => {
gauge.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Sum(sum) => {
sum.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Histogram(histogram) => {
histogram.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::ExponentialHistogram(_) => {}
}
}
fn extract_datapoints_u64(metric_data: &mut SerdeMetricData, value: &MetricData<u64>) {
match value {
MetricData::Gauge(gauge) => {
gauge.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Sum(sum) => {
sum.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Histogram(histogram) => {
histogram.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::ExponentialHistogram(_) => {}
}
}
fn extract_datapoints_i64(metric_data: &mut SerdeMetricData, value: &MetricData<i64>) {
match value {
MetricData::Gauge(gauge) => {
gauge.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Sum(sum) => {
sum.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::Histogram(histogram) => {
histogram.data_points().for_each(|datapoint| {
metric_data.datapoints.push(datapoint.into());
});
}
MetricData::ExponentialHistogram(_) => {}
}
}
}
impl From<&Metric> for SerdeMetric {
fn from(value: &Metric) -> Self {
let mut serde_metric = SerdeMetric {
name: value.name().to_string(),
description: value.description().to_string(),
unit: value.unit().to_string(),
data: value.data().into(),
};
serde_metric.data.datapoints.sort();
if serde_metric.name.ends_with(".duration") {
serde_metric
.data
.datapoints
.iter_mut()
.for_each(|datapoint| {
if let Some(sum) = &datapoint.sum
&& sum.as_f64().unwrap_or_default() > 0.0
{
datapoint.sum = Some(0.1.into());
}
});
}
serde_metric
}
}
impl<T> From<&GaugeDataPoint<T>> for SerdeMetricDataPoint
where
T: Into<serde_json::Value> + Copy,
{
fn from(value: &GaugeDataPoint<T>) -> Self {
SerdeMetricDataPoint {
value: Some(value.value().into()),
sum: None,
count: None,
attributes: value
.attributes()
.map(|kv| (kv.key.to_string(), Self::convert(&kv.value)))
.collect(),
}
}
}
impl<T> From<&SumDataPoint<T>> for SerdeMetricDataPoint
where
T: Into<serde_json::Value> + Copy,
{
fn from(value: &SumDataPoint<T>) -> Self {
SerdeMetricDataPoint {
value: Some(value.value().into()),
sum: None,
count: None,
attributes: value
.attributes()
.map(|kv| (kv.key.to_string(), Self::convert(&kv.value)))
.collect(),
}
}
}
impl SerdeMetricDataPoint {
pub(crate) fn convert(v: &Value) -> serde_json::Value {
match v.clone() {
Value::Bool(v) => v.into(),
Value::I64(v) => v.into(),
Value::F64(v) => v.into(),
Value::String(v) => v.to_string().into(),
Value::Array(v) => match v {
Array::Bool(v) => v.into(),
Array::I64(v) => v.into(),
Array::F64(v) => v.into(),
Array::String(v) => v.iter().map(|v| v.to_string()).collect::<Vec<_>>().into(),
_ => unreachable!("unexpected opentelemetry::Array variant"),
},
_ => unreachable!("unexpected opentelemetry::Value variant"),
}
}
}
impl<T> From<&HistogramDataPoint<T>> for SerdeMetricDataPoint
where
T: Into<serde_json::Value> + Copy,
{
fn from(value: &HistogramDataPoint<T>) -> Self {
SerdeMetricDataPoint {
sum: Some(value.sum().into()),
value: None,
count: Some(value.count()),
attributes: value
.attributes()
.map(|kv| (kv.key.to_string(), Self::convert(&kv.value)))
.collect(),
}
}
}
impl From<&AggregatedMetrics> for SerdeMetricData {
fn from(value: &AggregatedMetrics) -> Self {
let mut metric_data = SerdeMetricData::default();
match value {
AggregatedMetrics::F64(data) => {
Self::extract_datapoints_f64(&mut metric_data, data)
}
AggregatedMetrics::U64(data) => {
Self::extract_datapoints_u64(&mut metric_data, data)
}
AggregatedMetrics::I64(data) => {
Self::extract_datapoints_i64(&mut metric_data, data)
}
}
metric_data
}
}
pub(crate) enum MetricType {
Counter,
UpDownCounter,
Histogram,
Gauge,
}
}
#[cfg(test)]
pub(crate) fn meter_provider_internal() -> AggregateMeterProvider {
test_utils::meter_provider_and_readers().0
}
#[cfg(test)]
pub(crate) use test_utils::collect_metrics;
#[cfg(not(test))]
static AGGREGATE_METER_PROVIDER: OnceLock<AggregateMeterProvider> = OnceLock::new();
#[cfg(not(test))]
pub(crate) fn meter_provider_internal() -> AggregateMeterProvider {
AGGREGATE_METER_PROVIDER
.get_or_init(Default::default)
.clone()
}
pub fn meter_provider() -> impl opentelemetry::metrics::MeterProvider {
meter_provider_internal()
}
macro_rules! parse_attributes {
($($attr_key:literal = $attr_value:expr),+) => {[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+]};
($($($attr_key:ident).+ = $attr_value:expr),+) => {[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+]};
($attrs:expr) => {$attrs};
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `u64_counter_with_unit` instead")]
macro_rules! u64_counter {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, []);
}
}
#[allow(unused_macros)]
macro_rules! u64_counter_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, []);
}
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `f64_counter_with_unit` instead")]
macro_rules! f64_counter {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, []);
}
}
#[allow(unused_macros)]
macro_rules! f64_counter_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, []);
}
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `i64_up_down_counter_with_unit` instead")]
macro_rules! i64_up_down_counter {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $value: expr) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $value, [])
};
}
#[allow(unused_macros)]
macro_rules! i64_up_down_counter_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $unit, $value, [])
}
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `f64_up_down_counter_with_unit` instead")]
macro_rules! f64_up_down_counter {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $value: expr) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $value, [])
};
}
#[allow(unused_macros)]
macro_rules! f64_up_down_counter_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*))
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $unit, $value, [])
}
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `f64_histogram_with_unit` instead")]
macro_rules! f64_histogram {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, histogram, crate::metrics::NoopGuard,record, $name, $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr) => {
metric!(f64, histogram, crate::metrics::NoopGuard,record, $name, $description, $value, []);
};
}
#[allow(unused_macros)]
macro_rules! f64_histogram_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(f64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(f64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, []);
};
}
#[allow(unused_macros)]
#[deprecated(since = "TBD", note = "use `u64_histogram_with_unit` instead")]
macro_rules! u64_histogram {
($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $value: expr) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $value, []);
};
}
#[allow(unused_macros)]
macro_rules! u64_histogram_with_unit {
($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
};
($name:literal, $description:literal, $unit:literal, $value: expr) => {
metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, []);
};
}
thread_local! {
#[cfg(test)]
pub(crate) static CACHE_CALLSITE: std::sync::atomic::AtomicBool = const {std::sync::atomic::AtomicBool::new(false)};
}
macro_rules! metric {
($ty:ident, $instrument:ident, $guard: ty, $mutation:ident, $name:expr, $description:literal, $unit:literal, $value:expr, $attrs:expr) => {
paste::paste! {
{
#[cfg(test)]
let cache_callsite = crate::metrics::CACHE_CALLSITE.with(|cell| cell.load(std::sync::atomic::Ordering::SeqCst));
#[cfg(not(test))]
let cache_callsite = true;
let create_instrument_fn = |meter: opentelemetry::metrics::Meter| {
let mut builder = meter.[<$ty _ $instrument>]($name);
builder = builder.with_description($description);
if !$unit.is_empty() {
builder = builder.with_unit($unit);
}
builder.build()
};
if cache_callsite {
static INSTRUMENT_CACHE: std::sync::OnceLock<parking_lot::Mutex<std::sync::Weak<opentelemetry::metrics::[<$instrument:camel>]<$ty>>>> = std::sync::OnceLock::new();
let mut instrument_guard = INSTRUMENT_CACHE
.get_or_init(|| {
let meter_provider = crate::metrics::meter_provider_internal();
let instrument_ref = meter_provider.create_registered_instrument(|p| create_instrument_fn(p.meter("apollo/router")));
parking_lot::Mutex::new(std::sync::Arc::downgrade(&instrument_ref))
})
.lock();
let instrument = if let Some(instrument) = instrument_guard.upgrade() {
drop(instrument_guard);
instrument
} else {
let meter_provider = crate::metrics::meter_provider_internal();
let instrument_ref = meter_provider.create_registered_instrument(|p| create_instrument_fn(p.meter("apollo/router")));
*instrument_guard = std::sync::Arc::downgrade(&instrument_ref);
drop(instrument_guard);
instrument_ref
};
let attrs : &[opentelemetry::KeyValue] = &$attrs;
instrument.$mutation($value, attrs);
$guard::new(instrument.clone(), $value, attrs)
}
else {
let meter_provider = crate::metrics::meter_provider();
let meter = opentelemetry::metrics::MeterProvider::meter(&meter_provider, "apollo/router");
let instrument = create_instrument_fn(meter);
let attrs : &[opentelemetry::KeyValue] = &$attrs;
instrument.$mutation($value, attrs);
$guard::new(std::sync::Arc::new(instrument.clone()), $value, attrs)
}
}
}
};
($ty:ident, $instrument:ident, $guard: ty, $mutation:ident, $name:expr, $description:literal, $value: expr, $attrs: expr) => {
metric!($ty, $instrument, $guard, $mutation, $name, $description, "", $value, $attrs)
}
}
#[cfg(test)]
macro_rules! assert_metric {
($result:expr, $name:expr, $value:expr, $sum:expr, $count:expr, $attrs:expr) => {
if !$result {
let metric = crate::metrics::test_utils::SerdeMetric {
name: $name.to_string(),
description: "".to_string(),
unit: "".to_string(),
data: crate::metrics::test_utils::SerdeMetricData {
datapoints: [crate::metrics::test_utils::SerdeMetricDataPoint {
value: $value,
sum: $sum,
count: $count,
attributes: $attrs
.iter()
.map(|kv: &opentelemetry::KeyValue| {
(
kv.key.to_string(),
crate::metrics::test_utils::SerdeMetricDataPoint::convert(
&kv.value,
),
)
})
.collect::<std::collections::BTreeMap<_, _>>(),
}]
.to_vec(),
},
};
panic!(
"metric not found:\n{}\nmetrics present:\n{}",
serde_yaml::to_string(&metric).unwrap(),
serde_yaml::to_string(&crate::metrics::collect_metrics().all()).unwrap()
)
}
};
}
#[cfg(test)]
macro_rules! assert_no_metric {
($result:expr, $name:expr, $value:expr, $sum:expr, $count:expr, $attrs:expr) => {
if $result {
let metric = crate::metrics::test_utils::SerdeMetric {
name: $name.to_string(),
description: "".to_string(),
unit: "".to_string(),
data: crate::metrics::test_utils::SerdeMetricData {
datapoints: [crate::metrics::test_utils::SerdeMetricDataPoint {
value: $value,
sum: $sum,
count: $count,
attributes: $attrs
.iter()
.map(|kv: &opentelemetry::KeyValue| {
(
kv.key.to_string(),
crate::metrics::test_utils::SerdeMetricDataPoint::convert(
&kv.value,
),
)
})
.collect::<std::collections::BTreeMap<_, _>>(),
}]
.to_vec(),
},
};
panic!(
"unexpected metric found:\n{}\nmetrics present:\n{}",
serde_yaml::to_string(&metric).unwrap(),
serde_yaml::to_string(&crate::metrics::collect_metrics().all()).unwrap()
)
}
};
}
#[cfg(test)]
macro_rules! assert_counter {
($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let name = stringify!($($name).+);
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
assert_metric!(result, name, Some($value.into()), None, None, &attributes);
};
($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let name = stringify!($($name).+);
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
assert_metric!(result, name, Some($value.into()), None, None, &attributes);
};
($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, &attributes);
};
($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, &attributes);
};
($name:literal, $value: expr, $attributes: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, $attributes);
assert_metric!(result, $name, Some($value.into()), None, None, &$attributes);
};
($name:literal, $value: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, &[]);
assert_metric!(result, $name, Some($value.into()), None, None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_counter_not_exists {
($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Counter, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Counter, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Counter, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Counter, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $attributes: expr) => {
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Counter, $attributes);
assert_no_metric!(result, $name, None, None, None, &$attributes);
};
($name:literal, $value: ty) => {
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Counter, &[]);
assert_no_metric!(result, $name, None, None, None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_up_down_counter {
($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, &[]);
assert_metric!(result, $name, Some($value.into()), None, None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_gauge {
($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
assert_metric!(result, $name, Some($value.into()), None, None, attributes);
};
($name:literal, $value: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, &[]);
assert_metric!(result, $name, Some($value.into()), None, None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_histogram_count {
($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
};
($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
};
($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
};
($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, attributes);
assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
};
($name:literal, $value: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, true, &[]);
assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), &[]);
};
}
#[cfg(test)]
macro_rules! assert_histogram_sum {
($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
assert_metric!(result, $name, None, Some($value.into()), None, attributes);
};
($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
assert_metric!(result, $name, None, Some($value.into()), None, attributes);
};
($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
assert_metric!(result, $name, None, Some($value.into()), None, attributes);
};
($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
assert_metric!(result, $name, None, Some($value.into()), None, attributes);
};
($name:literal, $value: expr) => {
let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, &[]);
assert_metric!(result, $name, None, Some($value.into()), None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_histogram_exists {
($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_metric!(result, $name, None, None, None, attributes);
};
($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty) => {
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, &[]);
assert_metric!(result, $name, None, None, None, &[]);
};
}
#[cfg(test)]
macro_rules! assert_histogram_not_exists {
($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
assert_no_metric!(result, $name, None, None, None, attributes);
};
($name:literal, $value: ty) => {
let result = crate::metrics::collect_metrics().metric_exists($name, crate::metrics::test_utils::MetricType::Histogram, &[]);
assert_no_metric!(result, $name, None, None, None, &[]);
};
}
#[cfg(test)]
#[allow(unused_macros)]
macro_rules! assert_metrics_snapshot {
($file_name: expr) => {
insta::with_settings!({sort_maps => true, snapshot_suffix => $file_name}, {
let metrics = crate::metrics::collect_metrics();
insta::assert_yaml_snapshot!(&metrics.all());
});
};
() => {
insta::with_settings!({sort_maps => true}, {
let metrics = crate::metrics::collect_metrics();
insta::assert_yaml_snapshot!(&metrics.all());
});
};
}
#[cfg(test)]
#[allow(unused_macros)]
macro_rules! assert_non_zero_metrics_snapshot {
($file_name: expr) => {
insta::with_settings!({sort_maps => true, snapshot_suffix => $file_name}, {
let metrics = crate::metrics::collect_metrics();
insta::assert_yaml_snapshot!(&metrics.non_zero());
});
};
() => {
insta::with_settings!({sort_maps => true}, {
let metrics = crate::metrics::collect_metrics();
insta::assert_yaml_snapshot!(&metrics.non_zero());
});
};
}
#[cfg(test)]
pub(crate) type MetricFuture<T> = Pin<Box<dyn Future<Output = <T as Future>::Output>>>;
pub(crate) trait FutureMetricsExt<T> {
#[cfg(test)]
fn with_metrics(
self,
) -> tokio::task::futures::TaskLocalFuture<
OnceLock<(AggregateMeterProvider, test_utils::ClonableManualReader)>,
MetricFuture<Self>,
>
where
Self: Sized + Future + 'static,
<Self as Future>::Output: 'static,
{
test_utils::AGGREGATE_METER_PROVIDER_ASYNC.scope(
Default::default(),
async move {
let _ = meter_provider_internal();
let result = self.await;
let _ = tokio::task::spawn_blocking(|| meter_provider_internal().shutdown()).await;
result
}
.boxed_local(),
)
}
#[cfg(test)]
fn with_current_meter_provider(
self,
) -> tokio::task::futures::TaskLocalFuture<
OnceLock<(AggregateMeterProvider, test_utils::ClonableManualReader)>,
Self,
>
where
Self: Sized + Future + 'static,
<Self as Future>::Output: 'static,
{
let meter_provider_set = test_utils::AGGREGATE_METER_PROVIDER_ASYNC
.try_with(|_| {})
.is_ok();
if meter_provider_set {
test_utils::AGGREGATE_METER_PROVIDER_ASYNC
.scope(test_utils::AGGREGATE_METER_PROVIDER_ASYNC.get(), self)
} else {
test_utils::AGGREGATE_METER_PROVIDER_ASYNC.scope(Default::default(), self)
}
}
#[cfg(not(test))]
fn with_current_meter_provider(self) -> Self
where
Self: Sized + Future + 'static,
{
self
}
}
impl<T> FutureMetricsExt<T> for T where T: Future {}
#[cfg(test)]
mod test {
use opentelemetry::KeyValue;
use opentelemetry::metrics::MeterProvider;
use crate::metrics::FutureMetricsExt;
use crate::metrics::meter_provider;
use crate::metrics::meter_provider_internal;
fn assert_unit(name: &str, unit: &str) {
let collected_metrics = crate::metrics::collect_metrics();
let metric = collected_metrics.find(name).unwrap();
assert_eq!(metric.unit(), unit);
}
#[test]
fn test_gauge() {
let _gauge = meter_provider()
.meter("test")
.u64_observable_gauge("test")
.with_callback(|m| m.observe(5, &[]))
.build();
assert_gauge!("test", 5);
}
#[test]
fn test_gauge_record() {
let gauge = meter_provider().meter("test").u64_gauge("test").build();
gauge.record(5, &[]);
assert_gauge!("test", 5);
}
#[test]
fn test_no_attributes() {
u64_counter!("test", "test description", 1);
assert_counter!("test", 1);
}
#[test]
fn test_dynamic_attributes() {
let attributes = vec![KeyValue::new("attr", "val")];
u64_counter!("test", "test description", 1, attributes);
assert_counter!("test", 1, "attr" = "val");
assert_counter!("test", 1, &attributes);
}
#[test]
fn test_multiple_calls() {
fn my_method(val: &'static str) {
u64_counter!("test", "test description", 1, "attr" = val);
}
my_method("jill");
my_method("jill");
my_method("bob");
assert_counter!("test", 2, "attr" = "jill");
assert_counter!("test", 1, "attr" = "bob");
}
#[test]
fn test_non_async() {
u64_counter!("test", "test description", 1, "attr" = "val");
assert_counter!("test", 1, "attr" = "val");
}
#[tokio::test(flavor = "multi_thread")]
async fn test_async_multi() {
async {
u64_counter!("test", "test description", 1, "attr" = "val");
assert_counter!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_async_single() {
async {
u64_counter!("test", "test description", 1, "attr" = "val");
assert_counter!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_u64_counter() {
async {
u64_counter!("test", "test description", 1, attr = "val");
u64_counter!("test", "test description", 1, attr.test = "val");
u64_counter!("test", "test description", 1, attr.test_underscore = "val");
u64_counter!(
test.dot,
"test description",
1,
"attr.test_underscore" = "val"
);
u64_counter!(
test.dot,
"test description",
1,
attr.test_underscore = "val"
);
assert_counter!("test", 1, "attr" = "val");
assert_counter!("test", 1, "attr.test" = "val");
assert_counter!("test", 1, attr.test_underscore = "val");
assert_counter!(test.dot, 2, attr.test_underscore = "val");
assert_counter!(test.dot, 2, "attr.test_underscore" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_counter() {
async {
f64_counter!("test", "test description", 1.5, "attr" = "val");
assert_counter!("test", 1.5, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_i64_up_down_counter() {
async {
let _guard = i64_up_down_counter!("test", "test description", 1, "attr" = "val");
assert_up_down_counter!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_up_down_counter() {
async {
let _guard = f64_up_down_counter!("test", "test description", 1.5, "attr" = "val");
assert_up_down_counter!("test", 1.5, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_i64_up_down_counter_guard_auto_decrement() {
async {
{
let _guard =
i64_up_down_counter!("test_guard", "test description", 1, "attr" = "val");
assert_up_down_counter!("test_guard", 1, "attr" = "val");
}
assert_up_down_counter!("test_guard", 0, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_i64_up_down_counter_guard_multiple() {
async {
let _guard1 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
assert_up_down_counter!("test_multi", 1, "attr" = "val");
let _guard2 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
assert_up_down_counter!("test_multi", 2, "attr" = "val");
let _guard3 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
assert_up_down_counter!("test_multi", 3, "attr" = "val");
drop(_guard2);
assert_up_down_counter!("test_multi", 2, "attr" = "val");
drop(_guard1);
assert_up_down_counter!("test_multi", 1, "attr" = "val");
drop(_guard3);
assert_up_down_counter!("test_multi", 0, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_i64_up_down_counter_guard_different_attributes() {
async {
let _guard1 =
i64_up_down_counter!("test_attrs", "test description", 1, "attr" = "val1");
let _guard2 =
i64_up_down_counter!("test_attrs", "test description", 1, "attr" = "val2");
assert_up_down_counter!("test_attrs", 1, "attr" = "val1");
assert_up_down_counter!("test_attrs", 1, "attr" = "val2");
drop(_guard1);
assert_up_down_counter!("test_attrs", 0, "attr" = "val1");
assert_up_down_counter!("test_attrs", 1, "attr" = "val2");
drop(_guard2);
assert_up_down_counter!("test_attrs", 0, "attr" = "val2");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_up_down_counter_guard_auto_decrement() {
async {
{
let _guard =
f64_up_down_counter!("test_f64_guard", "test description", 2.5, "attr" = "val");
assert_up_down_counter!("test_f64_guard", 2.5, "attr" = "val");
}
assert_up_down_counter!("test_f64_guard", 0.0, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_u64_histogram() {
async {
u64_histogram!("test", "test description", 1, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_histogram() {
async {
f64_histogram!("test", "test description", 1.0, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
#[should_panic]
async fn test_type_histogram() {
async {
f64_histogram!("test", "test description", 1.0, "attr" = "val");
assert_counter!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
#[should_panic]
async fn test_type_counter() {
async {
f64_counter!("test", "test description", 1.0, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
#[should_panic]
async fn test_type_up_down_counter() {
async {
let _ = f64_up_down_counter!("test", "test description", 1.0, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[tokio::test]
#[should_panic]
async fn test_type_gauge() {
async {
let _gauge = meter_provider()
.meter("test")
.u64_observable_gauge("test")
.with_callback(|m| m.observe(5, &[]))
.build();
assert_histogram_sum!("test", 1, "attr" = "val");
}
.with_metrics()
.await;
}
#[test]
fn parse_attributes_should_handle_multiple_input_types() {
let variable = 123;
let parsed_idents = parse_attributes!(hello = "world", my.variable = variable);
let parsed_literals = parse_attributes!("hello" = "world", "my.variable" = variable);
let parsed_provided = parse_attributes!(vec![
KeyValue::new("hello", "world"),
KeyValue::new("my.variable", variable)
]);
assert_eq!(parsed_idents, parsed_literals);
assert_eq!(parsed_idents.as_slice(), parsed_provided.as_slice());
assert_eq!(parsed_literals.as_slice(), parsed_provided.as_slice());
}
#[test]
fn test_callsite_caching() {
super::CACHE_CALLSITE.with(|cell| cell.store(true, std::sync::atomic::Ordering::SeqCst));
fn test() {
u64_counter!("test", "test description", 1, "attr" = "val");
}
assert_eq!(meter_provider_internal().registered_instruments(), 0);
test();
assert_counter!("test", 1, "attr" = "val");
assert_eq!(meter_provider_internal().registered_instruments(), 1);
test();
assert_counter!("test", 2, "attr" = "val");
assert_eq!(meter_provider_internal().registered_instruments(), 1);
meter_provider_internal().invalidate();
assert_eq!(meter_provider_internal().registered_instruments(), 0);
test();
assert_eq!(meter_provider_internal().registered_instruments(), 1);
test();
assert_eq!(meter_provider_internal().registered_instruments(), 1);
}
#[tokio::test]
async fn test_f64_histogram_with_unit() {
async {
f64_histogram_with_unit!("test", "test description", "m/s", 1.0, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
assert_unit("test", "m/s");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_u64_counter_with_unit() {
async {
u64_counter_with_unit!("test", "test description", "Hz", 1, attr = "val");
assert_counter!("test", 1, "attr" = "val");
assert_unit("test", "Hz");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_i64_up_down_counter_with_unit() {
async {
let _guard = i64_up_down_counter_with_unit!(
"test",
"test description",
"{request}",
1,
attr = "val"
);
assert_up_down_counter!("test", 1, "attr" = "val");
assert_unit("test", "{request}");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_up_down_counter_with_unit() {
async {
let _guard = f64_up_down_counter_with_unit!(
"test",
"test description",
"kg",
1.5,
"attr" = "val"
);
assert_up_down_counter!("test", 1.5, "attr" = "val");
assert_unit("test", "kg");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_u64_histogram_with_unit() {
async {
u64_histogram_with_unit!("test", "test description", "{packet}", 1, "attr" = "val");
assert_histogram_sum!("test", 1, "attr" = "val");
assert_unit("test", "{packet}");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_f64_counter_with_unit() {
async {
f64_counter_with_unit!("test", "test description", "s", 1.5, "attr" = "val");
assert_counter!("test", 1.5, "attr" = "val");
assert_unit("test", "s");
}
.with_metrics()
.await;
}
#[tokio::test]
async fn test_metrics_across_tasks() {
async {
u64_counter!("apollo.router.test", "metric", 1);
assert_counter!("apollo.router.test", 1);
let handle = tokio::spawn(
async move {
u64_counter!("apollo.router.test", "metric", 2);
}
.with_current_meter_provider(),
);
handle.await.unwrap();
assert_counter!("apollo.router.test", 3);
}
.with_metrics()
.await;
}
}