apollo_router/metrics/
mod.rs

1//! APIs for integrating with the router's metrics.
2//!
3//! The macros contained here are a replacement for the telemetry crate's `MetricsLayer`. We will
4//! eventually convert all metrics to use these macros and deprecate the `MetricsLayer`.
5//! The reason for this is that the `MetricsLayer` has:
6//!
7//! * No support for dynamic attributes
8//! * No support dynamic metrics.
9//! * Imperfect mapping to metrics API that can only be checked at runtime.
10//!
11//! New metrics should be added using these macros.
12//!
13//! Prefer using `_with_unit` types for all new macros. Units should conform to the
14//! [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units),
15//! some of which has been copied here for reference:
16//! * Instruments that measure a count of something should only use annotations with curly braces to
17//!   give additional meaning. For example, use `{packet}`, `{error}`, `{fault}`, etc., not `packet`,
18//!   `error`, `fault`, etc.
19//! * Other instrument units should be specified using the UCUM case sensitive (c/s) variant. For
20//!   example, Cel for the unit with full name degree Celsius.
21//! * When instruments are measuring durations, seconds (i.e. s) should be used.
22//! * Instruments should use non-prefixed units (i.e. By instead of MiBy) unless there is good
23//!   technical reason to not do so.
24//!
25//! NB: we have not yet modified the existing metrics because some metric exporters (notably
26//! Prometheus) include the unit in the metric name, and changing the metric name will be a breaking
27//! change for customers.
28//!
29//! ## Compatibility
30//! This module uses types from the [opentelemetry] crates. Since OpenTelemetry for Rust is not yet
31//! API-stable, we may update it in a minor version, which may require code changes to plugins.
32//!
33//!
34//! # Examples
35//! ```ignore
36//! // Count a thing:
37//! u64_counter!(
38//!     "apollo.router.operations.frobbles",
39//!     "The amount of frobbles we've operated on",
40//!     1
41//! );
42//! // Count a thing with attributes:
43//! u64_counter!(
44//!     "apollo.router.operations.frobbles",
45//!     "The amount of frobbles we've operated on",
46//!     1,
47//!     frobbles.color = "blue"
48//! );
49//! // Count a thing with dynamic attributes:
50//! let attributes = vec![];
51//! if (frobbled) {
52//!     attributes.push(opentelemetry::KeyValue::new("frobbles.color".to_string(), "blue".into()));
53//! }
54//! u64_counter!(
55//!     "apollo.router.operations.frobbles",
56//!     "The amount of frobbles we've operated on",
57//!     1,
58//!     attributes
59//! );
60//! // Measure a thing with units:
61//! f64_histogram_with_unit!(
62//!     "apollo.router.operation.frobbles.time",
63//!     "Duration to operate on frobbles",
64//!     "s",
65//!     1.0,
66//!     frobbles.color = "red"
67//! );
68//! ```
69
70#[cfg(test)]
71use std::future::Future;
72use std::marker::PhantomData;
73#[cfg(test)]
74use std::pin::Pin;
75use std::sync::OnceLock;
76
77#[cfg(test)]
78use futures::FutureExt;
79
80use crate::metrics::aggregation::AggregateMeterProvider;
81
82pub(crate) mod aggregation;
83pub(crate) mod filter;
84
85/// A RAII guard for an up-down counter that automatically decrements on drop.
86///
87/// This guard implements the RAII (Resource Acquisition Is Initialization) pattern
88/// to ensure that up-down counters are properly decremented when the guard goes out
89/// of scope. This is particularly useful for tracking active operations, connections,
90/// or other resources where the counter should reflect the current state.
91///
92/// It is essential that the same instrument is used for the decrement as was used for an increment
93/// otherwise drift can occur.
94#[derive(Debug)]
95#[doc(hidden)]
96#[must_use = "without holding the guard updown counters will immediately zero out"]
97pub struct UpDownCounterGuard<T>
98where
99    T: std::ops::Neg<Output = T> + Copy,
100{
101    counter: opentelemetry::metrics::UpDownCounter<T>,
102    value: T,
103    attributes: Vec<opentelemetry::KeyValue>,
104}
105
106impl<T> UpDownCounterGuard<T>
107where
108    T: std::ops::Neg<Output = T> + Copy,
109{
110    /// Creates a new guard.
111    #[doc(hidden)]
112    pub fn new(
113        counter: std::sync::Arc<opentelemetry::metrics::UpDownCounter<T>>,
114        value: T,
115        attributes: &[opentelemetry::KeyValue],
116    ) -> Self {
117        // Note that increment will already have been called via the macro, we only deal with drops
118        // It is essential that we take the counter out of the arc otherwise it will break reload.
119        // Instruments rely on weak references to allow callsite invalidation.
120        // Therefore, if we hold onto the Arc then callsite invalidation won't work.
121        Self {
122            counter: (*counter).clone(),
123            value,
124            attributes: attributes.to_vec(),
125        }
126    }
127}
128
129impl<T> Drop for UpDownCounterGuard<T>
130where
131    T: std::ops::Neg<Output = T> + Copy,
132{
133    /// Decrements the counter when the guard is dropped.
134    ///
135    /// This automatically subtracts the original value from the counter,
136    /// ensuring the metric accurately reflects the current state.
137    fn drop(&mut self) {
138        self.counter.add(-self.value, &self.attributes);
139    }
140}
141
142/// Noop guard won't do anything. it serves to unify the logic UpDownCounterGuard
143#[doc(hidden)]
144pub struct NoopGuard<I, T> {
145    _phantom: PhantomData<(I, T)>,
146}
147impl<I, T> NoopGuard<I, T> {
148    /// Noop guard won't do anything. it serves to unify the logic UpDownCounterGuard
149    #[doc(hidden)]
150    pub fn new(_instrument: I, _value: T, _attributes: &[opentelemetry::KeyValue]) -> Self {
151        NoopGuard {
152            _phantom: Default::default(),
153        }
154    }
155}
156
157#[cfg(test)]
158pub(crate) mod test_utils {
159    use std::cmp::Ordering;
160    use std::collections::BTreeMap;
161    use std::fmt::Debug;
162    use std::fmt::Display;
163    use std::sync::Arc;
164    use std::sync::OnceLock;
165    use std::sync::Weak;
166
167    use itertools::Itertools;
168    use num_traits::NumCast;
169    use num_traits::ToPrimitive;
170    use opentelemetry::Array;
171    use opentelemetry::KeyValue;
172    use opentelemetry::StringValue;
173    use opentelemetry::Value;
174    use opentelemetry_sdk::metrics::Aggregation;
175    use opentelemetry_sdk::metrics::AttributeSet;
176    use opentelemetry_sdk::metrics::InstrumentKind;
177    use opentelemetry_sdk::metrics::ManualReader;
178    use opentelemetry_sdk::metrics::MeterProviderBuilder;
179    use opentelemetry_sdk::metrics::Pipeline;
180    use opentelemetry_sdk::metrics::data::DataPoint;
181    use opentelemetry_sdk::metrics::data::Gauge;
182    use opentelemetry_sdk::metrics::data::Histogram;
183    use opentelemetry_sdk::metrics::data::HistogramDataPoint;
184    use opentelemetry_sdk::metrics::data::Metric;
185    use opentelemetry_sdk::metrics::data::ResourceMetrics;
186    use opentelemetry_sdk::metrics::data::Sum;
187    use opentelemetry_sdk::metrics::data::Temporality;
188    use opentelemetry_sdk::metrics::reader::AggregationSelector;
189    use opentelemetry_sdk::metrics::reader::MetricReader;
190    use opentelemetry_sdk::metrics::reader::TemporalitySelector;
191    use serde::Serialize;
192    use tokio::task_local;
193
194    use crate::metrics::aggregation::AggregateMeterProvider;
195    use crate::metrics::aggregation::MeterProviderType;
196    use crate::metrics::filter::FilterMeterProvider;
197    task_local! {
198        pub(crate) static AGGREGATE_METER_PROVIDER_ASYNC: OnceLock<(AggregateMeterProvider, ClonableManualReader)>;
199    }
200    thread_local! {
201        pub(crate) static AGGREGATE_METER_PROVIDER: OnceLock<(AggregateMeterProvider, ClonableManualReader)> = const { OnceLock::new() };
202    }
203
204    #[derive(Debug, Clone, Default)]
205    pub(crate) struct ClonableManualReader {
206        reader: Arc<ManualReader>,
207    }
208
209    impl TemporalitySelector for ClonableManualReader {
210        fn temporality(&self, kind: InstrumentKind) -> Temporality {
211            self.reader.temporality(kind)
212        }
213    }
214
215    impl AggregationSelector for ClonableManualReader {
216        fn aggregation(&self, kind: InstrumentKind) -> Aggregation {
217            self.reader.aggregation(kind)
218        }
219    }
220    impl MetricReader for ClonableManualReader {
221        fn register_pipeline(&self, pipeline: Weak<Pipeline>) {
222            self.reader.register_pipeline(pipeline)
223        }
224
225        fn collect(&self, rm: &mut ResourceMetrics) -> opentelemetry::metrics::Result<()> {
226            self.reader.collect(rm)
227        }
228
229        fn force_flush(&self) -> opentelemetry::metrics::Result<()> {
230            self.reader.force_flush()
231        }
232
233        fn shutdown(&self) -> opentelemetry::metrics::Result<()> {
234            self.reader.shutdown()
235        }
236    }
237
238    fn create_test_meter_provider() -> (AggregateMeterProvider, ClonableManualReader) {
239        {
240            let meter_provider = AggregateMeterProvider::default();
241            let reader = ClonableManualReader::default();
242
243            meter_provider.set(
244                MeterProviderType::Public,
245                FilterMeterProvider::all(
246                    MeterProviderBuilder::default()
247                        .with_reader(reader.clone())
248                        .build(),
249                ),
250            );
251
252            (meter_provider, reader)
253        }
254    }
255    pub(crate) fn meter_provider_and_readers() -> (AggregateMeterProvider, ClonableManualReader) {
256        if tokio::runtime::Handle::try_current().is_ok() {
257            AGGREGATE_METER_PROVIDER_ASYNC
258                .try_with(|cell| cell.get_or_init(create_test_meter_provider).clone())
259                // We need to silently fail here.
260                // Otherwise we fail every multi-threaded test that touches metrics
261                .unwrap_or_default()
262        } else {
263            AGGREGATE_METER_PROVIDER
264                .with(|cell| cell.get_or_init(create_test_meter_provider).clone())
265        }
266    }
267
268    pub(crate) struct Metrics {
269        resource_metrics: ResourceMetrics,
270    }
271
272    impl Default for Metrics {
273        fn default() -> Self {
274            Metrics {
275                resource_metrics: ResourceMetrics {
276                    resource: Default::default(),
277                    scope_metrics: vec![],
278                },
279            }
280        }
281    }
282
283    pub(crate) fn collect_metrics() -> Metrics {
284        let mut metrics = Metrics::default();
285        let (_, reader) = meter_provider_and_readers();
286        reader
287            .collect(&mut metrics.resource_metrics)
288            .expect("Failed to collect metrics. Did you forget to use `async{}.with_metrics()`? See dev-docs/metrics.md");
289        metrics
290    }
291
292    impl Metrics {
293        pub(crate) fn find(&self, name: &str) -> Option<&opentelemetry_sdk::metrics::data::Metric> {
294            self.resource_metrics
295                .scope_metrics
296                .iter()
297                .flat_map(|scope_metrics| {
298                    scope_metrics
299                        .metrics
300                        .iter()
301                        .filter(|metric| metric.name == name)
302                })
303                .next()
304        }
305
306        pub(crate) fn assert<T: NumCast + Display + 'static>(
307            &self,
308            name: &str,
309            ty: MetricType,
310            value: T,
311            // Useful for histogram to check the count and not the sum
312            count: bool,
313            attributes: &[KeyValue],
314        ) -> bool {
315            let attributes = AttributeSet::from(attributes);
316            if let Some(value) = value.to_u64()
317                && self.metric_matches(name, &ty, value, count, &attributes)
318            {
319                return true;
320            }
321
322            if let Some(value) = value.to_i64()
323                && self.metric_matches(name, &ty, value, count, &attributes)
324            {
325                return true;
326            }
327
328            if let Some(value) = value.to_f64()
329                && self.metric_matches(name, &ty, value, count, &attributes)
330            {
331                return true;
332            }
333
334            false
335        }
336
337        pub(crate) fn metric_matches<T: Debug + PartialEq + Display + ToPrimitive + 'static>(
338            &self,
339            name: &str,
340            ty: &MetricType,
341            value: T,
342            count: bool,
343            attributes: &AttributeSet,
344        ) -> bool {
345            if let Some(metric) = self.find(name) {
346                // Try to downcast the metric to each type of aggregation and assert that the value is correct.
347                if let Some(gauge) = metric.data.as_any().downcast_ref::<Gauge<T>>() {
348                    // Find the datapoint with the correct attributes.
349                    if matches!(ty, MetricType::Gauge) {
350                        return gauge.data_points.iter().any(|datapoint| {
351                            datapoint.value == value
352                                && Self::equal_attributes(attributes, &datapoint.attributes)
353                        });
354                    }
355                } else if let Some(sum) = metric.data.as_any().downcast_ref::<Sum<T>>() {
356                    // Note that we can't actually tell if the sum is monotonic or not, so we just check if it's a sum.
357                    if matches!(ty, MetricType::Counter | MetricType::UpDownCounter) {
358                        return sum.data_points.iter().any(|datapoint| {
359                            datapoint.value == value
360                                && Self::equal_attributes(attributes, &datapoint.attributes)
361                        });
362                    }
363                } else if let Some(histogram) = metric.data.as_any().downcast_ref::<Histogram<T>>()
364                    && matches!(ty, MetricType::Histogram)
365                {
366                    if count {
367                        return histogram.data_points.iter().any(|datapoint| {
368                            datapoint.count == value.to_u64().unwrap()
369                                && Self::equal_attributes(attributes, &datapoint.attributes)
370                        });
371                    } else {
372                        return histogram.data_points.iter().any(|datapoint| {
373                            datapoint.sum == value
374                                && Self::equal_attributes(attributes, &datapoint.attributes)
375                        });
376                    }
377                }
378            }
379            false
380        }
381
382        pub(crate) fn metric_exists<T: Debug + PartialEq + Display + ToPrimitive + 'static>(
383            &self,
384            name: &str,
385            ty: MetricType,
386            attributes: &[KeyValue],
387        ) -> bool {
388            let attributes = AttributeSet::from(attributes);
389            if let Some(metric) = self.find(name) {
390                // Try to downcast the metric to each type of aggregation and assert that the value is correct.
391                if let Some(gauge) = metric.data.as_any().downcast_ref::<Gauge<T>>() {
392                    // Find the datapoint with the correct attributes.
393                    if matches!(ty, MetricType::Gauge) {
394                        return gauge.data_points.iter().any(|datapoint| {
395                            Self::equal_attributes(&attributes, &datapoint.attributes)
396                        });
397                    }
398                } else if let Some(sum) = metric.data.as_any().downcast_ref::<Sum<T>>() {
399                    // Note that we can't actually tell if the sum is monotonic or not, so we just check if it's a sum.
400                    if matches!(ty, MetricType::Counter | MetricType::UpDownCounter) {
401                        return sum.data_points.iter().any(|datapoint| {
402                            Self::equal_attributes(&attributes, &datapoint.attributes)
403                        });
404                    }
405                } else if let Some(histogram) = metric.data.as_any().downcast_ref::<Histogram<T>>()
406                    && matches!(ty, MetricType::Histogram)
407                {
408                    return histogram.data_points.iter().any(|datapoint| {
409                        Self::equal_attributes(&attributes, &datapoint.attributes)
410                    });
411                }
412            }
413            false
414        }
415
416        #[allow(dead_code)]
417        pub(crate) fn all(self) -> Vec<SerdeMetric> {
418            self.resource_metrics
419                .scope_metrics
420                .into_iter()
421                .flat_map(|scope_metrics| {
422                    scope_metrics.metrics.into_iter().map(|metric| {
423                        let serde_metric: SerdeMetric = metric.into();
424                        serde_metric
425                    })
426                })
427                .sorted()
428                .collect()
429        }
430
431        #[allow(dead_code)]
432        pub(crate) fn non_zero(self) -> Vec<SerdeMetric> {
433            self.all()
434                .into_iter()
435                .filter(|m| {
436                    m.data.datapoints.iter().any(|d| {
437                        d.value
438                            .as_ref()
439                            .map(|v| v.as_f64().unwrap_or_default() > 0.0)
440                            .unwrap_or_default()
441                            || d.sum
442                                .as_ref()
443                                .map(|v| v.as_f64().unwrap_or_default() > 0.0)
444                                .unwrap_or_default()
445                    })
446                })
447                .collect()
448        }
449
450        fn equal_attributes(expected: &AttributeSet, actual: &[KeyValue]) -> bool {
451            // If lengths are different, we can short circuit. This also accounts for a bug where
452            // an empty attributes list would always be considered "equal" due to zip capping at
453            // the shortest iter's length
454            if expected.iter().count() != actual.len() {
455                return false;
456            }
457            // This works because the attributes are always sorted
458            expected.iter().zip(actual.iter()).all(|((k, v), kv)| {
459                kv.key == *k
460                    && (kv.value == *v || kv.value == Value::String(StringValue::from("<any>")))
461            })
462        }
463    }
464
465    #[derive(Serialize, Eq, PartialEq)]
466    pub(crate) struct SerdeMetric {
467        pub(crate) name: String,
468        #[serde(skip_serializing_if = "String::is_empty")]
469        pub(crate) description: String,
470        #[serde(skip_serializing_if = "String::is_empty")]
471        pub(crate) unit: String,
472        pub(crate) data: SerdeMetricData,
473    }
474
475    impl Ord for SerdeMetric {
476        fn cmp(&self, other: &Self) -> Ordering {
477            self.name.cmp(&other.name)
478        }
479    }
480
481    impl PartialOrd for SerdeMetric {
482        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
483            Some(self.cmp(other))
484        }
485    }
486
487    #[derive(Clone, Serialize, Eq, PartialEq, Default)]
488    pub(crate) struct SerdeMetricData {
489        pub(crate) datapoints: Vec<SerdeMetricDataPoint>,
490    }
491
492    #[derive(Clone, Serialize, Eq, PartialEq)]
493    pub(crate) struct SerdeMetricDataPoint {
494        #[serde(skip_serializing_if = "Option::is_none")]
495        pub(crate) value: Option<serde_json::Value>,
496        #[serde(skip_serializing_if = "Option::is_none")]
497        pub(crate) sum: Option<serde_json::Value>,
498        #[serde(skip_serializing_if = "Option::is_none")]
499        pub(crate) count: Option<u64>,
500        pub(crate) attributes: BTreeMap<String, serde_json::Value>,
501    }
502
503    impl Ord for SerdeMetricDataPoint {
504        fn cmp(&self, other: &Self) -> Ordering {
505            //Horribly inefficient, but it's just for testing
506            let self_string = serde_json::to_string(&self.attributes).expect("serde failed");
507            let other_string = serde_json::to_string(&other.attributes).expect("serde failed");
508            self_string.cmp(&other_string)
509        }
510    }
511
512    impl PartialOrd for SerdeMetricDataPoint {
513        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
514            Some(self.cmp(other))
515        }
516    }
517
518    impl SerdeMetricData {
519        fn extract_datapoints<T: Into<serde_json::Value> + Clone + 'static>(
520            metric_data: &mut SerdeMetricData,
521            value: &dyn opentelemetry_sdk::metrics::data::Aggregation,
522        ) {
523            if let Some(gauge) = value.as_any().downcast_ref::<Gauge<T>>() {
524                gauge.data_points.iter().for_each(|datapoint| {
525                    metric_data.datapoints.push(datapoint.into());
526                });
527            }
528            if let Some(sum) = value.as_any().downcast_ref::<Sum<T>>() {
529                sum.data_points.iter().for_each(|datapoint| {
530                    metric_data.datapoints.push(datapoint.into());
531                });
532            }
533            if let Some(histogram) = value.as_any().downcast_ref::<Histogram<T>>() {
534                histogram.data_points.iter().for_each(|datapoint| {
535                    metric_data.datapoints.push(datapoint.into());
536                });
537            }
538        }
539    }
540
541    impl From<Metric> for SerdeMetric {
542        fn from(value: Metric) -> Self {
543            let mut serde_metric = SerdeMetric {
544                name: value.name.into_owned(),
545                description: value.description.into_owned(),
546                unit: value.unit.to_string(),
547                data: value.data.into(),
548            };
549            // Sort the datapoints so that we can compare them
550            serde_metric.data.datapoints.sort();
551
552            // Redact duration metrics;
553            if serde_metric.name.ends_with(".duration") {
554                serde_metric
555                    .data
556                    .datapoints
557                    .iter_mut()
558                    .for_each(|datapoint| {
559                        if let Some(sum) = &datapoint.sum
560                            && sum.as_f64().unwrap_or_default() > 0.0
561                        {
562                            datapoint.sum = Some(0.1.into());
563                        }
564                    });
565            }
566            serde_metric
567        }
568    }
569
570    impl<T> From<&DataPoint<T>> for SerdeMetricDataPoint
571    where
572        T: Into<serde_json::Value> + Clone,
573    {
574        fn from(value: &DataPoint<T>) -> Self {
575            SerdeMetricDataPoint {
576                value: Some(value.value.clone().into()),
577                sum: None,
578                count: None,
579                attributes: value
580                    .attributes
581                    .iter()
582                    .map(|kv| (kv.key.to_string(), Self::convert(&kv.value)))
583                    .collect(),
584            }
585        }
586    }
587
588    impl SerdeMetricDataPoint {
589        pub(crate) fn convert(v: &Value) -> serde_json::Value {
590            match v.clone() {
591                Value::Bool(v) => v.into(),
592                Value::I64(v) => v.into(),
593                Value::F64(v) => v.into(),
594                Value::String(v) => v.to_string().into(),
595                Value::Array(v) => match v {
596                    Array::Bool(v) => v.into(),
597                    Array::I64(v) => v.into(),
598                    Array::F64(v) => v.into(),
599                    Array::String(v) => v.iter().map(|v| v.to_string()).collect::<Vec<_>>().into(),
600                },
601            }
602        }
603    }
604
605    impl<T> From<&HistogramDataPoint<T>> for SerdeMetricDataPoint
606    where
607        T: Into<serde_json::Value> + Clone,
608    {
609        fn from(value: &HistogramDataPoint<T>) -> Self {
610            SerdeMetricDataPoint {
611                sum: Some(value.sum.clone().into()),
612                value: None,
613                count: Some(value.count),
614                attributes: value
615                    .attributes
616                    .iter()
617                    .map(|kv| (kv.key.to_string(), Self::convert(&kv.value)))
618                    .collect(),
619            }
620        }
621    }
622
623    impl From<Box<dyn opentelemetry_sdk::metrics::data::Aggregation>> for SerdeMetricData {
624        fn from(value: Box<dyn opentelemetry_sdk::metrics::data::Aggregation>) -> Self {
625            let mut metric_data = SerdeMetricData::default();
626            Self::extract_datapoints::<u64>(&mut metric_data, value.as_ref());
627            Self::extract_datapoints::<f64>(&mut metric_data, value.as_ref());
628            Self::extract_datapoints::<i64>(&mut metric_data, value.as_ref());
629            metric_data
630        }
631    }
632
633    pub(crate) enum MetricType {
634        Counter,
635        UpDownCounter,
636        Histogram,
637        Gauge,
638    }
639}
640
641/// Returns a MeterProvider, as a concrete type so we can use our own extensions.
642///
643/// During tests this is a task local so that we can test metrics without having to worry about other tests interfering.
644#[cfg(test)]
645pub(crate) fn meter_provider_internal() -> AggregateMeterProvider {
646    test_utils::meter_provider_and_readers().0
647}
648
649#[cfg(test)]
650pub(crate) use test_utils::collect_metrics;
651
652#[cfg(not(test))]
653static AGGREGATE_METER_PROVIDER: OnceLock<AggregateMeterProvider> = OnceLock::new();
654
655/// Returns the currently configured global MeterProvider, as a concrete type
656/// so we can use our own extensions.
657#[cfg(not(test))]
658pub(crate) fn meter_provider_internal() -> AggregateMeterProvider {
659    AGGREGATE_METER_PROVIDER
660        .get_or_init(Default::default)
661        .clone()
662}
663
664/// Returns the currently configured global [`MeterProvider`].
665///
666/// See the [module-level documentation] for important details on the semver-compatibility guarantees of this API.
667///
668/// [`MeterProvider`]: opentelemetry::metrics::MeterProvider
669/// [module-level documentation]: crate::metrics
670pub fn meter_provider() -> impl opentelemetry::metrics::MeterProvider {
671    meter_provider_internal()
672}
673
674/// Parse key/value attributes into `opentelemetry::KeyValue` structs. Should only be used within
675/// this module, as a helper for the various metric macros (ie `u64_counter!`).
676macro_rules! parse_attributes {
677    ($($attr_key:literal = $attr_value:expr),+) => {[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+]};
678    ($($($attr_key:ident).+ = $attr_value:expr),+) => {[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+]};
679    ($attrs:expr) => {$attrs};
680}
681
682/// Get or create a `u64` monotonic counter metric and add a value to it.
683/// The metric must include a description.
684///
685/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
686/// behind this API.
687#[allow(unused_macros)]
688#[deprecated(since = "TBD", note = "use `u64_counter_with_unit` instead")]
689macro_rules! u64_counter {
690    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
691        metric!(u64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
692    };
693
694    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
695        metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, parse_attributes!($($attrs)*));
696    };
697
698    ($name:literal, $description:literal, $value: expr) => {
699        metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, []);
700    }
701}
702
703/// Get or create a u64 monotonic counter metric and add a value to it.
704/// The metric must include a description and a unit.
705///
706/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
707///
708/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
709/// behind this API.
710#[allow(unused_macros)]
711macro_rules! u64_counter_with_unit {
712    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
713        metric!(u64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
714    };
715
716    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
717        metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
718    };
719
720    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
721        metric!(u64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, []);
722    }
723}
724
725/// Get or create a f64 monotonic counter metric and add a value to it.
726/// The metric must include a description.
727///
728/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
729/// behind this API.
730#[allow(unused_macros)]
731#[deprecated(since = "TBD", note = "use `f64_counter_with_unit` instead")]
732macro_rules! f64_counter {
733    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
734        metric!(f64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
735    };
736
737    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
738        metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, parse_attributes!($($attrs)*));
739    };
740
741    ($name:literal, $description:literal, $value: expr) => {
742        metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $value, []);
743    }
744}
745
746/// Get or create an f64 monotonic counter metric and add a value to it.
747/// The metric must include a description and a unit.
748///
749/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
750///
751/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
752/// behind this API.
753#[allow(unused_macros)]
754macro_rules! f64_counter_with_unit {
755    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
756        metric!(f64, counter, crate::metrics::NoopGuard, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
757    };
758
759    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
760        metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
761    };
762
763    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
764        metric!(f64, counter, crate::metrics::NoopGuard, add, $name, $description, $unit, $value, []);
765    }
766}
767
768/// Creates or retrieves an i64 up-down counter and returns a RAII guard.
769///
770/// This macro increments the counter immediately and returns an [`I64UpDownCounterGuard`]
771/// that automatically decrements the counter when dropped. This ensures accurate tracking
772/// of active resources, operations, or connections.
773///
774/// **Important:** The returned guard must be stored in a variable to keep the counter
775/// incremented. If the guard is immediately dropped, the counter will be decremented.
776///
777/// # Returns
778///
779/// An [`I64UpDownCounterGuard`] that decrements the counter on drop.
780///
781/// # Examples
782///
783/// ```ignore
784/// // Counter is incremented to 1
785/// let _guard = i64_up_down_counter!(
786///     "active_connections",
787///     "Number of active connections",
788///     1,
789///     connection.type = "websocket"
790/// );
791/// // Counter remains at 1 while _guard is in scope
792///
793/// // When _guard is dropped, counter is automatically decremented back to 0
794/// ```
795///
796/// See the [module-level documentation](crate::metrics) for more details.
797#[allow(unused_macros)]
798#[deprecated(since = "TBD", note = "use `i64_up_down_counter_with_unit` instead")]
799macro_rules! i64_up_down_counter {
800    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
801        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*))
802    };
803
804    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
805        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $value, parse_attributes!($($attrs)*))
806    };
807
808    ($name:literal, $description:literal, $value: expr) => {
809        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $value, [])
810    };
811}
812
813/// Creates or retrieves an i64 up-down counter with a unit and returns a RAII guard.
814///
815/// This macro increments the counter immediately and returns an [`UpDownCounterGuard<i64>`]
816/// that automatically decrements the counter when dropped. This ensures accurate tracking
817/// of active resources, operations, or connections.
818///
819/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
820///
821/// **Important:** The returned guard must be stored in a variable to keep the counter
822/// incremented. If the guard is immediately dropped, the counter will be decremented.
823///
824/// # Returns
825///
826/// An [`UpDownCounterGuard<i64>`] that decrements the counter on drop.
827///
828/// # Examples
829///
830/// ```ignore
831/// // Counter is incremented to 1
832/// let _active_job = i64_up_down_counter_with_unit!(
833///     "compute.active_jobs",
834///     "Number of active computation jobs",
835///     "{job}",
836///     1,
837///     job.type = "query_planning"
838/// );
839/// // Counter remains at 1 while _active_job is in scope
840///
841/// // When _active_job is dropped, counter is automatically decremented back to 0
842/// ```
843///
844/// See the [module-level documentation](crate::metrics) for more details.
845#[allow(unused_macros)]
846macro_rules! i64_up_down_counter_with_unit {
847    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
848        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*))
849    };
850
851    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
852        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*))
853    };
854
855    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
856        metric!(i64, up_down_counter, crate::metrics::UpDownCounterGuard::<i64>, add, $name, $description, $unit, $value, [])
857    }
858}
859
860/// Creates or retrieves an f64 up-down counter and returns a RAII guard.
861///
862/// This macro increments the counter immediately and returns an [`UpDownCounterGuard<f64>`]
863/// that automatically decrements the counter when dropped. This ensures accurate tracking
864/// of active resources, operations, or connections.
865///
866/// **Important:** The returned guard must be stored in a variable to keep the counter
867/// incremented. If the guard is immediately dropped, the counter will be decremented.
868///
869/// # Returns
870///
871/// An [`UpDownCounterGuard<f64>`] that decrements the counter on drop.
872///
873/// # Examples
874///
875/// ```ignore
876/// // Counter is incremented by 1.5
877/// let _guard = f64_up_down_counter!(
878///     "active_load",
879///     "Current system load",
880///     1.5,
881///     load.type = "cpu"
882/// );
883/// // Counter remains at 1.5 while _guard is in scope
884///
885/// // When _guard is dropped, counter is automatically decremented by 1.5
886/// ```
887///
888/// See the [module-level documentation](crate::metrics) for more details.
889#[allow(unused_macros)]
890#[deprecated(since = "TBD", note = "use `f64_up_down_counter_with_unit` instead")]
891macro_rules! f64_up_down_counter {
892    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
893        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*))
894    };
895
896    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
897        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $value, parse_attributes!($($attrs)*))
898    };
899
900    ($name:literal, $description:literal, $value: expr) => {
901        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $value, [])
902    };
903}
904
905/// Creates or retrieves an f64 up-down counter with a unit and returns a RAII guard.
906///
907/// This macro increments the counter immediately and returns an [`UpDownCounterGuard<f64>`]
908/// that automatically decrements the counter when dropped. This ensures accurate tracking
909/// of active resources, operations, or connections.
910///
911/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
912///
913/// **Important:** The returned guard must be stored in a variable to keep the counter
914/// incremented. If the guard is immediately dropped, the counter will be decremented.
915///
916/// # Returns
917///
918/// An [`UpDownCounterGuard<f64>`] that decrements the counter on drop.
919///
920/// # Examples
921///
922/// ```ignore
923/// // Counter is incremented by 2.5
924/// let _memory_usage = f64_up_down_counter_with_unit!(
925///     "memory.active_usage",
926///     "Active memory usage",
927///     "MB",
928///     2.5,
929///     memory.type = "heap"
930/// );
931/// // Counter remains at 2.5 while _memory_usage is in scope
932///
933/// // When _memory_usage is dropped, counter is automatically decremented by 2.5
934/// ```
935///
936/// See the [module-level documentation](crate::metrics) for more details.
937#[allow(unused_macros)]
938macro_rules! f64_up_down_counter_with_unit {
939    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
940        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*))
941    };
942
943    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
944        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $unit, $value, parse_attributes!($($attrs)*))
945    };
946
947    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
948        metric!(f64, up_down_counter, crate::metrics::UpDownCounterGuard::<f64>, add, $name, $description, $unit, $value, [])
949    }
950}
951
952/// Get or create an f64 histogram metric and add a value to it.
953/// The metric must include a description.
954///
955/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
956/// behind this API.
957#[allow(unused_macros)]
958#[deprecated(since = "TBD", note = "use `f64_histogram_with_unit` instead")]
959macro_rules! f64_histogram {
960    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
961        metric!(f64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
962    };
963
964    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
965        metric!(f64, histogram, crate::metrics::NoopGuard,record, $name, $description, $value, parse_attributes!($($attrs)*));
966    };
967
968    ($name:literal, $description:literal, $value: expr) => {
969        metric!(f64, histogram, crate::metrics::NoopGuard,record, $name, $description, $value, []);
970    };
971}
972
973/// Get or create an f64 histogram metric and add a value to it.
974/// The metric must include a description and a unit.
975///
976/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
977///
978/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
979/// behind this API.
980///
981/// ## Caveat
982///
983/// Two metrics with the same name but different descriptions and/or units will be created as
984/// _separate_ metrics.
985///
986/// ```ignore
987/// f64_histogram_with_unit!("test", "test description", "s", 1.0, "attr" = "val");
988/// assert_histogram_sum!("test", 1, "attr" = "val");
989///
990/// f64_histogram_with_unit!("test", "test description", "Hz", 1.0, "attr" = "val");
991/// assert_histogram_sum!("test", 1, "attr" = "val");
992/// ```
993#[allow(unused_macros)]
994macro_rules! f64_histogram_with_unit {
995    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
996        metric!(f64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
997    };
998
999    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
1000        metric!(f64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
1001    };
1002
1003    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
1004        metric!(f64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, []);
1005    };
1006}
1007
1008/// Get or create a u64 histogram metric and add a value to it.
1009/// The metric must include a description.
1010///
1011/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
1012/// behind this API.
1013#[allow(unused_macros)]
1014#[deprecated(since = "TBD", note = "use `u64_histogram_with_unit` instead")]
1015macro_rules! u64_histogram {
1016    ($($name:ident).+, $description:literal, $value: expr, $($attrs:tt)*) => {
1017        metric!(u64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $value, parse_attributes!($($attrs)*));
1018    };
1019
1020    ($name:literal, $description:literal, $value: expr, $($attrs:tt)*) => {
1021        metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $value, parse_attributes!($($attrs)*));
1022    };
1023
1024    ($name:literal, $description:literal, $value: expr) => {
1025        metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $value, []);
1026    };
1027}
1028
1029/// Get or create a u64 histogram metric and add a value to it.
1030/// The metric must include a description and a unit.
1031///
1032/// The units should conform to the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/#units).
1033///
1034/// See the [module-level documentation](crate::metrics) for examples and details on the reasoning
1035/// behind this API.
1036#[allow(unused_macros)]
1037macro_rules! u64_histogram_with_unit {
1038    ($($name:ident).+, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
1039        metric!(u64, histogram, crate::metrics::NoopGuard, record, stringify!($($name).+), $description, $unit, $value, parse_attributes!($($attrs)*));
1040    };
1041
1042    ($name:literal, $description:literal, $unit:literal, $value: expr, $($attrs:tt)*) => {
1043        metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, parse_attributes!($($attrs)*));
1044    };
1045
1046    ($name:literal, $description:literal, $unit:literal, $value: expr) => {
1047        metric!(u64, histogram, crate::metrics::NoopGuard, record, $name, $description, $unit, $value, []);
1048    };
1049}
1050
1051thread_local! {
1052    // This is used exactly once in testing callsite caching.
1053    #[cfg(test)]
1054    pub(crate) static CACHE_CALLSITE: std::sync::atomic::AtomicBool = const {std::sync::atomic::AtomicBool::new(false)};
1055}
1056macro_rules! metric {
1057    ($ty:ident, $instrument:ident, $guard: ty, $mutation:ident, $name:expr, $description:literal, $unit:literal, $value:expr, $attrs:expr) => {
1058        // The way this works is that we have a static at each call site that holds a weak reference to the instrument.
1059        // We make a call we try to upgrade the weak reference. If it succeeds we use the instrument.
1060        // Otherwise we create a new instrument and update the static.
1061        // The aggregate meter provider is used to hold on to references of all instruments that have been created and will clear references when the underlying configuration has changed.
1062        // There is a Mutex involved, however it is only locked for the duration of the upgrade once the instrument has been created.
1063        // The Reason a Mutex is used rather than an RwLock is that we are not holding the lock for any significant period of time and the cost of an RwLock is potentially higher.
1064        // If we profile and deem it's worth switching to RwLock then we can do that.
1065
1066        paste::paste! {
1067            {
1068                // There is a single test for caching callsites. Other tests do not cache because they will interfere with each other due to them using a task local meter provider to aid testing.
1069                #[cfg(test)]
1070                let cache_callsite = crate::metrics::CACHE_CALLSITE.with(|cell| cell.load(std::sync::atomic::Ordering::SeqCst));
1071
1072                // The compiler will optimize this in non test builds
1073                #[cfg(not(test))]
1074                let cache_callsite = true;
1075
1076                let create_instrument_fn = |meter: opentelemetry::metrics::Meter| {
1077                    let mut builder = meter.[<$ty _ $instrument>]($name);
1078                    builder = builder.with_description($description);
1079
1080                    if !$unit.is_empty() {
1081                        builder = builder.with_unit($unit);
1082                    }
1083
1084                    builder.init()
1085                };
1086
1087                if cache_callsite {
1088                    static INSTRUMENT_CACHE: std::sync::OnceLock<parking_lot::Mutex<std::sync::Weak<opentelemetry::metrics::[<$instrument:camel>]<$ty>>>> = std::sync::OnceLock::new();
1089
1090                    let mut instrument_guard = INSTRUMENT_CACHE
1091                        .get_or_init(|| {
1092                            let meter_provider = crate::metrics::meter_provider_internal();
1093                            let instrument_ref = meter_provider.create_registered_instrument(|p| create_instrument_fn(p.meter("apollo/router")));
1094                            parking_lot::Mutex::new(std::sync::Arc::downgrade(&instrument_ref))
1095                        })
1096                        .lock();
1097                    let instrument = if let Some(instrument) = instrument_guard.upgrade() {
1098                        // Fast path, we got the instrument, drop the mutex guard immediately.
1099                        drop(instrument_guard);
1100                        instrument
1101                    } else {
1102                        // Slow path, we need to obtain the instrument again.
1103                        let meter_provider = crate::metrics::meter_provider_internal();
1104                        let instrument_ref = meter_provider.create_registered_instrument(|p| create_instrument_fn(p.meter("apollo/router")));
1105                        *instrument_guard = std::sync::Arc::downgrade(&instrument_ref);
1106                        // We've updated the instrument and got a strong reference to it. We can drop the mutex guard now.
1107                        drop(instrument_guard);
1108                        instrument_ref
1109                    };
1110                    let attrs : &[opentelemetry::KeyValue] = &$attrs;
1111                    instrument.$mutation($value, attrs);
1112                    $guard::new(instrument.clone(), $value, attrs)
1113                }
1114                else {
1115                    // This is only for testing.
1116                    // The reason it is not cfg test is that we have a legitimate test for callsite caching though
1117                    // cache_callsite is always true for not test
1118                    let meter_provider = crate::metrics::meter_provider();
1119                    let meter = opentelemetry::metrics::MeterProvider::meter(&meter_provider, "apollo/router");
1120                    let instrument = create_instrument_fn(meter);
1121                    let attrs : &[opentelemetry::KeyValue] = &$attrs;
1122                    instrument.$mutation($value, attrs);
1123                    $guard::new(std::sync::Arc::new(instrument.clone()), $value, attrs)
1124                }
1125            }
1126        }
1127    };
1128
1129    ($ty:ident, $instrument:ident, $guard: ty, $mutation:ident, $name:expr, $description:literal, $value: expr, $attrs: expr) => {
1130        metric!($ty, $instrument, $guard, $mutation, $name, $description, "", $value, $attrs)
1131    }
1132}
1133
1134#[cfg(test)]
1135macro_rules! assert_metric {
1136    ($result:expr, $name:expr, $value:expr, $sum:expr, $count:expr, $attrs:expr) => {
1137        if !$result {
1138            let metric = crate::metrics::test_utils::SerdeMetric {
1139                name: $name.to_string(),
1140                description: "".to_string(),
1141                unit: "".to_string(),
1142                data: crate::metrics::test_utils::SerdeMetricData {
1143                    datapoints: [crate::metrics::test_utils::SerdeMetricDataPoint {
1144                        value: $value,
1145                        sum: $sum,
1146                        count: $count,
1147                        attributes: $attrs
1148                            .iter()
1149                            .map(|kv: &opentelemetry::KeyValue| {
1150                                (
1151                                    kv.key.to_string(),
1152                                    crate::metrics::test_utils::SerdeMetricDataPoint::convert(
1153                                        &kv.value,
1154                                    ),
1155                                )
1156                            })
1157                            .collect::<std::collections::BTreeMap<_, _>>(),
1158                    }]
1159                    .to_vec(),
1160                },
1161            };
1162            panic!(
1163                "metric not found:\n{}\nmetrics present:\n{}",
1164                serde_yaml::to_string(&metric).unwrap(),
1165                serde_yaml::to_string(&crate::metrics::collect_metrics().all()).unwrap()
1166            )
1167        }
1168    };
1169}
1170
1171#[cfg(test)]
1172macro_rules! assert_no_metric {
1173    ($result:expr, $name:expr, $value:expr, $sum:expr, $count:expr, $attrs:expr) => {
1174        if $result {
1175            let metric = crate::metrics::test_utils::SerdeMetric {
1176                name: $name.to_string(),
1177                description: "".to_string(),
1178                unit: "".to_string(),
1179                data: crate::metrics::test_utils::SerdeMetricData {
1180                    datapoints: [crate::metrics::test_utils::SerdeMetricDataPoint {
1181                        value: $value,
1182                        sum: $sum,
1183                        count: $count,
1184                        attributes: $attrs
1185                            .iter()
1186                            .map(|kv: &opentelemetry::KeyValue| {
1187                                (
1188                                    kv.key.to_string(),
1189                                    crate::metrics::test_utils::SerdeMetricDataPoint::convert(
1190                                        &kv.value,
1191                                    ),
1192                                )
1193                            })
1194                            .collect::<std::collections::BTreeMap<_, _>>(),
1195                    }]
1196                    .to_vec(),
1197                },
1198            };
1199            panic!(
1200                "unexpected metric found:\n{}\nmetrics present:\n{}",
1201                serde_yaml::to_string(&metric).unwrap(),
1202                serde_yaml::to_string(&crate::metrics::collect_metrics().all()).unwrap()
1203            )
1204        }
1205    };
1206}
1207
1208/// Assert the value of a counter metric that has the given name and attributes.
1209///
1210/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1211/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1212#[cfg(test)]
1213macro_rules! assert_counter {
1214    ($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1215        let name = stringify!($($name).+);
1216        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1217        let result = crate::metrics::collect_metrics().assert(name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
1218        assert_metric!(result, name, Some($value.into()), None, None, &attributes);
1219    };
1220
1221    ($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1222        let name = stringify!($($name).+);
1223        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1224        let result = crate::metrics::collect_metrics().assert(name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
1225        assert_metric!(result, name, Some($value.into()), None, None, &attributes);
1226    };
1227
1228    ($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1229        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1230        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
1231        assert_metric!(result, $name, Some($value.into()), None, None, &attributes);
1232    };
1233
1234    ($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1235        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1236        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, attributes);
1237        assert_metric!(result, $name, Some($value.into()), None, None, &attributes);
1238    };
1239
1240    ($name:literal, $value: expr, $attributes: expr) => {
1241        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, $attributes);
1242        assert_metric!(result, $name, Some($value.into()), None, None, &$attributes);
1243    };
1244
1245    ($name:literal, $value: expr) => {
1246        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Counter, $value, false, &[]);
1247        assert_metric!(result, $name, Some($value.into()), None, None, &[]);
1248    };
1249}
1250
1251/// Assert that a counter metric does not exist with the given name and attributes.
1252///
1253/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1254/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1255#[cfg(test)]
1256macro_rules! assert_counter_not_exists {
1257
1258    ($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1259        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1260        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Counter, attributes);
1261        assert_no_metric!(result, $name, None, None, None, attributes);
1262    };
1263
1264    ($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1265        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1266        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Counter, attributes);
1267        assert_no_metric!(result, $name, None, None, None, attributes);
1268    };
1269
1270    ($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1271        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1272        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Counter, attributes);
1273        assert_no_metric!(result, $name, None, None, None, attributes);
1274    };
1275
1276    ($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1277        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1278        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Counter, attributes);
1279        assert_no_metric!(result, $name, None, None, None, attributes);
1280    };
1281
1282
1283    ($name:literal, $value: ty, $attributes: expr) => {
1284        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Counter, $attributes);
1285        assert_no_metric!(result, $name, None, None, None, &$attributes);
1286    };
1287
1288    ($name:literal, $value: ty) => {
1289        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Counter, &[]);
1290        assert_no_metric!(result, $name, None, None, None, &[]);
1291    };
1292}
1293
1294/// Assert the value of a counter metric that has the given name and attributes.
1295///
1296/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1297/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1298#[cfg(test)]
1299macro_rules! assert_up_down_counter {
1300
1301    ($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1302        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1303        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
1304        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1305    };
1306
1307    ($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1308        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1309        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
1310        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1311    };
1312
1313    ($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1314        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1315        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
1316        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1317    };
1318
1319    ($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1320        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1321        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, attributes);
1322        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1323    };
1324
1325    ($name:literal, $value: expr) => {
1326        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::UpDownCounter, $value, false, &[]);
1327        assert_metric!(result, $name, Some($value.into()), None, None, &[]);
1328    };
1329}
1330
1331/// Assert the value of a gauge metric that has the given name and attributes.
1332///
1333/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1334/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1335#[cfg(test)]
1336macro_rules! assert_gauge {
1337
1338    ($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1339        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1340        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
1341        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1342    };
1343
1344    ($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1345        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1346        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
1347        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1348    };
1349
1350    ($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1351        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1352        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
1353        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1354    };
1355
1356    ($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1357        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1358        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, attributes);
1359        assert_metric!(result, $name, Some($value.into()), None, None, attributes);
1360    };
1361
1362    ($name:literal, $value: expr) => {
1363        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Gauge, $value, false, &[]);
1364        assert_metric!(result, $name, Some($value.into()), None, None, &[]);
1365    };
1366}
1367
1368#[cfg(test)]
1369macro_rules! assert_histogram_count {
1370
1371    ($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1372        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1373        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
1374        assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
1375    };
1376
1377    ($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1378        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1379        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
1380        assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
1381    };
1382
1383    ($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1384        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1385        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, true, attributes);
1386        assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
1387    };
1388
1389    ($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1390        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1391        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, attributes);
1392        assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), attributes);
1393    };
1394
1395    ($name:literal, $value: expr) => {
1396        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, true, &[]);
1397        assert_metric!(result, $name, None, Some($value.into()), Some(num_traits::ToPrimitive::to_u64(&$value).expect("count should be convertible to u64")), &[]);
1398    };
1399}
1400
1401/// Assert the sum value of a histogram metric with the given name and attributes.
1402///
1403/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1404/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1405#[cfg(test)]
1406macro_rules! assert_histogram_sum {
1407
1408    ($($name:ident).+, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1409        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1410        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
1411        assert_metric!(result, $name, None, Some($value.into()), None, attributes);
1412    };
1413
1414    ($($name:ident).+, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1415        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1416        let result = crate::metrics::collect_metrics().assert(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
1417        assert_metric!(result, $name, None, Some($value.into()), None, attributes);
1418    };
1419
1420    ($name:literal, $value: expr, $($attr_key:literal = $attr_value:expr),+) => {
1421        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1422        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
1423        assert_metric!(result, $name, None, Some($value.into()), None, attributes);
1424    };
1425
1426    ($name:literal, $value: expr, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1427        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1428        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, attributes);
1429        assert_metric!(result, $name, None, Some($value.into()), None, attributes);
1430    };
1431
1432    ($name:literal, $value: expr) => {
1433        let result = crate::metrics::collect_metrics().assert($name, crate::metrics::test_utils::MetricType::Histogram, $value, false, &[]);
1434        assert_metric!(result, $name, None, Some($value.into()), None, &[]);
1435    };
1436}
1437
1438/// Assert that a histogram metric exists with the given name and attributes.
1439///
1440/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1441/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1442#[cfg(test)]
1443macro_rules! assert_histogram_exists {
1444
1445    ($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1446        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1447        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
1448        assert_metric!(result, $name, None, None, None, attributes);
1449    };
1450
1451    ($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1452        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1453        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
1454        assert_metric!(result, $name, None, None, None, attributes);
1455    };
1456
1457    ($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1458        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1459        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
1460        assert_metric!(result, $name, None, None, None, attributes);
1461    };
1462
1463    ($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1464        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1465        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
1466        assert_metric!(result, $name, None, None, None, attributes);
1467    };
1468
1469    ($name:literal, $value: ty) => {
1470        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, &[]);
1471        assert_metric!(result, $name, None, None, None, &[]);
1472    };
1473}
1474
1475/// Assert that a histogram metric does not exist with the given name and attributes.
1476///
1477/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1478/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1479#[cfg(test)]
1480macro_rules! assert_histogram_not_exists {
1481
1482    ($($name:ident).+, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1483        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1484        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
1485        assert_no_metric!(result, $name, None, None, None, attributes);
1486    };
1487
1488    ($($name:ident).+, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1489        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1490        let result = crate::metrics::collect_metrics().metric_exists::<$value>(stringify!($($name).+), crate::metrics::test_utils::MetricType::Histogram, attributes);
1491        assert_no_metric!(result, $name, None, None, None, attributes);
1492    };
1493
1494    ($name:literal, $value: ty, $($attr_key:literal = $attr_value:expr),+) => {
1495        let attributes = &[$(opentelemetry::KeyValue::new($attr_key, $attr_value)),+];
1496        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
1497        assert_no_metric!(result, $name, None, None, None, attributes);
1498    };
1499
1500    ($name:literal, $value: ty, $($($attr_key:ident).+ = $attr_value:expr),+) => {
1501        let attributes = &[$(opentelemetry::KeyValue::new(stringify!($($attr_key).+), $attr_value)),+];
1502        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, attributes);
1503        assert_no_metric!(result, $name, None, None, None, attributes);
1504    };
1505
1506    ($name:literal, $value: ty) => {
1507        let result = crate::metrics::collect_metrics().metric_exists::<$value>($name, crate::metrics::test_utils::MetricType::Histogram, &[]);
1508        assert_no_metric!(result, $name, None, None, None, &[]);
1509    };
1510}
1511
1512/// Assert that all metrics match an [insta] snapshot.
1513///
1514/// Consider using [assert_non_zero_metrics_snapshot] to produce more grokkable snapshots if
1515/// zero-valued metrics are not relevant to your test.
1516///
1517/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1518/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1519#[cfg(test)]
1520#[allow(unused_macros)]
1521macro_rules! assert_metrics_snapshot {
1522    ($file_name: expr) => {
1523        insta::with_settings!({sort_maps => true, snapshot_suffix => $file_name}, {
1524            let metrics = crate::metrics::collect_metrics();
1525            insta::assert_yaml_snapshot!(&metrics.all());
1526        });
1527
1528    };
1529    () => {
1530        insta::with_settings!({sort_maps => true}, {
1531            let metrics = crate::metrics::collect_metrics();
1532            insta::assert_yaml_snapshot!(&metrics.all());
1533        });
1534    };
1535}
1536
1537/// Assert that all metrics with a non-zero value match an [insta] snapshot.
1538///
1539/// In asynchronous tests, you must use [`FutureMetricsExt::with_metrics`]. See dev-docs/metrics.md
1540/// for details: <https://github.com/apollographql/router/blob/4fc63d55104c81c77e6e0a3cca615eac28e39dc3/dev-docs/metrics.md#testing>
1541#[cfg(test)]
1542#[allow(unused_macros)]
1543macro_rules! assert_non_zero_metrics_snapshot {
1544    ($file_name: expr) => {
1545        insta::with_settings!({sort_maps => true, snapshot_suffix => $file_name}, {
1546            let metrics = crate::metrics::collect_metrics();
1547            insta::assert_yaml_snapshot!(&metrics.non_zero());
1548        });
1549    };
1550    () => {
1551        insta::with_settings!({sort_maps => true}, {
1552            let metrics = crate::metrics::collect_metrics();
1553            insta::assert_yaml_snapshot!(&metrics.non_zero());
1554        });
1555    };
1556}
1557
1558#[cfg(test)]
1559pub(crate) type MetricFuture<T> = Pin<Box<dyn Future<Output = <T as Future>::Output>>>;
1560
1561/// Extension trait for Futures that wish to test metrics.
1562pub(crate) trait FutureMetricsExt<T> {
1563    /// Wraps a Future with metrics collection capabilities.
1564    ///
1565    /// This method creates a new Future that will:
1566    /// 1. Initialize the meter provider before executing the Future
1567    /// 2. Execute the original Future
1568    /// 3. Shutdown the meter provider after completion
1569    ///
1570    /// This is useful for testing scenarios where you need to ensure metrics are properly
1571    /// collected throughout the entire Future's execution.
1572    ///
1573    /// # Example
1574    /// ```rust
1575    /// # use apollo_router::metrics::FutureMetricsExt;
1576    /// # async fn example() {
1577    /// let future = async { /* your async code that produces metrics */ };
1578    /// let result = future.with_metrics().await;
1579    /// # }
1580    /// ```
1581    #[cfg(test)]
1582    fn with_metrics(
1583        self,
1584    ) -> tokio::task::futures::TaskLocalFuture<
1585        OnceLock<(AggregateMeterProvider, test_utils::ClonableManualReader)>,
1586        MetricFuture<Self>,
1587    >
1588    where
1589        Self: Sized + Future + 'static,
1590        <Self as Future>::Output: 'static,
1591    {
1592        test_utils::AGGREGATE_METER_PROVIDER_ASYNC.scope(
1593            Default::default(),
1594            async move {
1595                // We want to eagerly create the meter provider, the reason is that this will be shared among subtasks that use `with_current_meter_provider`.
1596                let _ = meter_provider_internal();
1597                let result = self.await;
1598                let _ = tokio::task::spawn_blocking(|| {
1599                    meter_provider_internal().shutdown();
1600                })
1601                .await;
1602                result
1603            }
1604            .boxed_local(),
1605        )
1606    }
1607
1608    /// Propagates the current meter provider to child tasks during test execution.
1609    ///
1610    /// This method ensures that the meter provider is properly shared across tasks
1611    /// during test scenarios. In non-test contexts, it returns the original Future
1612    /// unchanged.
1613    ///
1614    /// # Example
1615    /// ```rust
1616    /// # use apollo_router::metrics::FutureMetricsExt;
1617    /// # async fn example() {
1618    /// let result = tokio::task::spawn(async { /* your async code that produces metrics */ }.with_current_meter_provider()).await;
1619    /// # }
1620    /// ```
1621    #[cfg(test)]
1622    fn with_current_meter_provider(
1623        self,
1624    ) -> tokio::task::futures::TaskLocalFuture<
1625        OnceLock<(AggregateMeterProvider, test_utils::ClonableManualReader)>,
1626        Self,
1627    >
1628    where
1629        Self: Sized + Future + 'static,
1630        <Self as Future>::Output: 'static,
1631    {
1632        // We need to determine if the meter was set. If not then we can use default provider which is empty
1633        let meter_provider_set = test_utils::AGGREGATE_METER_PROVIDER_ASYNC
1634            .try_with(|_| {})
1635            .is_ok();
1636        if meter_provider_set {
1637            test_utils::AGGREGATE_METER_PROVIDER_ASYNC
1638                .scope(test_utils::AGGREGATE_METER_PROVIDER_ASYNC.get(), self)
1639        } else {
1640            test_utils::AGGREGATE_METER_PROVIDER_ASYNC.scope(Default::default(), self)
1641        }
1642    }
1643
1644    #[cfg(not(test))]
1645    fn with_current_meter_provider(self) -> Self
1646    where
1647        Self: Sized + Future + 'static,
1648    {
1649        // This is intentionally a noop. In the real world meter provider is a global variable.
1650        self
1651    }
1652}
1653
1654impl<T> FutureMetricsExt<T> for T where T: Future {}
1655
1656#[cfg(test)]
1657mod test {
1658    use opentelemetry::KeyValue;
1659    use opentelemetry::metrics::MeterProvider;
1660
1661    use crate::metrics::FutureMetricsExt;
1662    use crate::metrics::meter_provider;
1663    use crate::metrics::meter_provider_internal;
1664
1665    fn assert_unit(name: &str, unit: &str) {
1666        let collected_metrics = crate::metrics::collect_metrics();
1667        let metric = collected_metrics.find(name).unwrap();
1668        assert_eq!(metric.unit, unit);
1669    }
1670
1671    #[test]
1672    fn test_gauge() {
1673        // Observables are cleaned up when they dropped, so keep this around.
1674        let _gauge = meter_provider()
1675            .meter("test")
1676            .u64_observable_gauge("test")
1677            .with_callback(|m| m.observe(5, &[]))
1678            .init();
1679        assert_gauge!("test", 5);
1680    }
1681
1682    #[test]
1683    fn test_gauge_record() {
1684        let gauge = meter_provider().meter("test").u64_gauge("test").init();
1685        gauge.record(5, &[]);
1686        assert_gauge!("test", 5);
1687    }
1688
1689    #[test]
1690    fn test_no_attributes() {
1691        u64_counter!("test", "test description", 1);
1692        assert_counter!("test", 1);
1693    }
1694
1695    #[test]
1696    fn test_dynamic_attributes() {
1697        let attributes = vec![KeyValue::new("attr", "val")];
1698        u64_counter!("test", "test description", 1, attributes);
1699        assert_counter!("test", 1, "attr" = "val");
1700        assert_counter!("test", 1, &attributes);
1701    }
1702
1703    #[test]
1704    fn test_multiple_calls() {
1705        fn my_method(val: &'static str) {
1706            u64_counter!("test", "test description", 1, "attr" = val);
1707        }
1708
1709        my_method("jill");
1710        my_method("jill");
1711        my_method("bob");
1712        assert_counter!("test", 2, "attr" = "jill");
1713        assert_counter!("test", 1, "attr" = "bob");
1714    }
1715
1716    #[test]
1717    fn test_non_async() {
1718        // Each test is run in a separate thread, metrics are stored in a thread local.
1719        u64_counter!("test", "test description", 1, "attr" = "val");
1720        assert_counter!("test", 1, "attr" = "val");
1721    }
1722
1723    #[tokio::test(flavor = "multi_thread")]
1724    async fn test_async_multi() {
1725        // Multi-threaded runtime needs to use a tokio task local to avoid tests interfering with each other
1726        async {
1727            u64_counter!("test", "test description", 1, "attr" = "val");
1728            assert_counter!("test", 1, "attr" = "val");
1729        }
1730        .with_metrics()
1731        .await;
1732    }
1733
1734    #[tokio::test]
1735    async fn test_async_single() {
1736        async {
1737            // It's a single threaded tokio runtime, so we can still use a thread local
1738            u64_counter!("test", "test description", 1, "attr" = "val");
1739            assert_counter!("test", 1, "attr" = "val");
1740        }
1741        .with_metrics()
1742        .await;
1743    }
1744
1745    #[tokio::test]
1746    async fn test_u64_counter() {
1747        async {
1748            u64_counter!("test", "test description", 1, attr = "val");
1749            u64_counter!("test", "test description", 1, attr.test = "val");
1750            u64_counter!("test", "test description", 1, attr.test_underscore = "val");
1751            u64_counter!(
1752                test.dot,
1753                "test description",
1754                1,
1755                "attr.test_underscore" = "val"
1756            );
1757            u64_counter!(
1758                test.dot,
1759                "test description",
1760                1,
1761                attr.test_underscore = "val"
1762            );
1763            assert_counter!("test", 1, "attr" = "val");
1764            assert_counter!("test", 1, "attr.test" = "val");
1765            assert_counter!("test", 1, attr.test_underscore = "val");
1766            assert_counter!(test.dot, 2, attr.test_underscore = "val");
1767            assert_counter!(test.dot, 2, "attr.test_underscore" = "val");
1768        }
1769        .with_metrics()
1770        .await;
1771    }
1772
1773    #[tokio::test]
1774    async fn test_f64_counter() {
1775        async {
1776            f64_counter!("test", "test description", 1.5, "attr" = "val");
1777            assert_counter!("test", 1.5, "attr" = "val");
1778        }
1779        .with_metrics()
1780        .await;
1781    }
1782
1783    #[tokio::test]
1784    async fn test_i64_up_down_counter() {
1785        async {
1786            let _guard = i64_up_down_counter!("test", "test description", 1, "attr" = "val");
1787            assert_up_down_counter!("test", 1, "attr" = "val");
1788        }
1789        .with_metrics()
1790        .await;
1791    }
1792
1793    #[tokio::test]
1794    async fn test_f64_up_down_counter() {
1795        async {
1796            let _guard = f64_up_down_counter!("test", "test description", 1.5, "attr" = "val");
1797            assert_up_down_counter!("test", 1.5, "attr" = "val");
1798        }
1799        .with_metrics()
1800        .await;
1801    }
1802
1803    #[tokio::test]
1804    async fn test_i64_up_down_counter_guard_auto_decrement() {
1805        async {
1806            // Test that dropping the guard decrements the counter
1807            {
1808                let _guard =
1809                    i64_up_down_counter!("test_guard", "test description", 1, "attr" = "val");
1810                assert_up_down_counter!("test_guard", 1, "attr" = "val");
1811            }
1812            // After guard is dropped, counter should be back to 0
1813            assert_up_down_counter!("test_guard", 0, "attr" = "val");
1814        }
1815        .with_metrics()
1816        .await;
1817    }
1818
1819    #[tokio::test]
1820    async fn test_i64_up_down_counter_guard_multiple() {
1821        async {
1822            // Test multiple guards with the same metric
1823            let _guard1 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
1824            assert_up_down_counter!("test_multi", 1, "attr" = "val");
1825
1826            let _guard2 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
1827            assert_up_down_counter!("test_multi", 2, "attr" = "val");
1828
1829            let _guard3 = i64_up_down_counter!("test_multi", "test description", 1, "attr" = "val");
1830            assert_up_down_counter!("test_multi", 3, "attr" = "val");
1831
1832            drop(_guard2);
1833            assert_up_down_counter!("test_multi", 2, "attr" = "val");
1834
1835            drop(_guard1);
1836            assert_up_down_counter!("test_multi", 1, "attr" = "val");
1837
1838            drop(_guard3);
1839            assert_up_down_counter!("test_multi", 0, "attr" = "val");
1840        }
1841        .with_metrics()
1842        .await;
1843    }
1844
1845    #[tokio::test]
1846    async fn test_i64_up_down_counter_guard_different_attributes() {
1847        async {
1848            // Test guards with different attributes
1849            let _guard1 =
1850                i64_up_down_counter!("test_attrs", "test description", 1, "attr" = "val1");
1851            let _guard2 =
1852                i64_up_down_counter!("test_attrs", "test description", 1, "attr" = "val2");
1853
1854            assert_up_down_counter!("test_attrs", 1, "attr" = "val1");
1855            assert_up_down_counter!("test_attrs", 1, "attr" = "val2");
1856
1857            drop(_guard1);
1858            assert_up_down_counter!("test_attrs", 0, "attr" = "val1");
1859            assert_up_down_counter!("test_attrs", 1, "attr" = "val2");
1860
1861            drop(_guard2);
1862            assert_up_down_counter!("test_attrs", 0, "attr" = "val2");
1863        }
1864        .with_metrics()
1865        .await;
1866    }
1867
1868    #[tokio::test]
1869    async fn test_f64_up_down_counter_guard_auto_decrement() {
1870        async {
1871            // Test that dropping the guard decrements the counter
1872            {
1873                let _guard =
1874                    f64_up_down_counter!("test_f64_guard", "test description", 2.5, "attr" = "val");
1875                assert_up_down_counter!("test_f64_guard", 2.5, "attr" = "val");
1876            }
1877            // After guard is dropped, counter should be back to 0
1878            assert_up_down_counter!("test_f64_guard", 0.0, "attr" = "val");
1879        }
1880        .with_metrics()
1881        .await;
1882    }
1883
1884    #[tokio::test]
1885    async fn test_u64_histogram() {
1886        async {
1887            u64_histogram!("test", "test description", 1, "attr" = "val");
1888            assert_histogram_sum!("test", 1, "attr" = "val");
1889        }
1890        .with_metrics()
1891        .await;
1892    }
1893
1894    #[tokio::test]
1895    async fn test_f64_histogram() {
1896        async {
1897            f64_histogram!("test", "test description", 1.0, "attr" = "val");
1898            assert_histogram_sum!("test", 1, "attr" = "val");
1899        }
1900        .with_metrics()
1901        .await;
1902    }
1903
1904    #[tokio::test]
1905    #[should_panic]
1906    async fn test_type_histogram() {
1907        async {
1908            f64_histogram!("test", "test description", 1.0, "attr" = "val");
1909            assert_counter!("test", 1, "attr" = "val");
1910        }
1911        .with_metrics()
1912        .await;
1913    }
1914
1915    #[tokio::test]
1916    #[should_panic]
1917    async fn test_type_counter() {
1918        async {
1919            f64_counter!("test", "test description", 1.0, "attr" = "val");
1920            assert_histogram_sum!("test", 1, "attr" = "val");
1921        }
1922        .with_metrics()
1923        .await;
1924    }
1925
1926    #[tokio::test]
1927    #[should_panic]
1928    async fn test_type_up_down_counter() {
1929        async {
1930            let _ = f64_up_down_counter!("test", "test description", 1.0, "attr" = "val");
1931            assert_histogram_sum!("test", 1, "attr" = "val");
1932        }
1933        .with_metrics()
1934        .await;
1935    }
1936
1937    #[tokio::test]
1938    #[should_panic]
1939    async fn test_type_gauge() {
1940        async {
1941            let _gauge = meter_provider()
1942                .meter("test")
1943                .u64_observable_gauge("test")
1944                .with_callback(|m| m.observe(5, &[]))
1945                .init();
1946            assert_histogram_sum!("test", 1, "attr" = "val");
1947        }
1948        .with_metrics()
1949        .await;
1950    }
1951
1952    #[test]
1953    fn parse_attributes_should_handle_multiple_input_types() {
1954        let variable = 123;
1955        let parsed_idents = parse_attributes!(hello = "world", my.variable = variable);
1956        let parsed_literals = parse_attributes!("hello" = "world", "my.variable" = variable);
1957        let parsed_provided = parse_attributes!(vec![
1958            KeyValue::new("hello", "world"),
1959            KeyValue::new("my.variable", variable)
1960        ]);
1961
1962        assert_eq!(parsed_idents, parsed_literals);
1963        assert_eq!(parsed_idents.as_slice(), parsed_provided.as_slice());
1964        assert_eq!(parsed_literals.as_slice(), parsed_provided.as_slice());
1965    }
1966
1967    #[test]
1968    fn test_callsite_caching() {
1969        // Creating instruments may be slow due to multiple levels of locking that needs to happen through the various metrics layers.
1970        // Callsite caching is implemented to prevent this happening on every call.
1971        // See the metric macro above to see more information.
1972        super::CACHE_CALLSITE.with(|cell| cell.store(true, std::sync::atomic::Ordering::SeqCst));
1973        fn test() {
1974            // This is a single callsite so should only have one metric
1975            u64_counter!("test", "test description", 1, "attr" = "val");
1976        }
1977
1978        // Callsite hasn't been used yet, so there should be no metrics
1979        assert_eq!(meter_provider_internal().registered_instruments(), 0);
1980
1981        // Call the metrics, it will be registered
1982        test();
1983        assert_counter!("test", 1, "attr" = "val");
1984        assert_eq!(meter_provider_internal().registered_instruments(), 1);
1985
1986        // Call the metrics again, but the second call will not register a new metric because it will have be retrieved from the static
1987        test();
1988        assert_counter!("test", 2, "attr" = "val");
1989        assert_eq!(meter_provider_internal().registered_instruments(), 1);
1990
1991        // Force invalidation of instruments
1992        meter_provider_internal().invalidate();
1993        assert_eq!(meter_provider_internal().registered_instruments(), 0);
1994
1995        // Slow path
1996        test();
1997        assert_eq!(meter_provider_internal().registered_instruments(), 1);
1998
1999        // Fast path
2000        test();
2001        assert_eq!(meter_provider_internal().registered_instruments(), 1);
2002    }
2003
2004    #[tokio::test]
2005    async fn test_f64_histogram_with_unit() {
2006        async {
2007            f64_histogram_with_unit!("test", "test description", "m/s", 1.0, "attr" = "val");
2008            assert_histogram_sum!("test", 1, "attr" = "val");
2009            assert_unit("test", "m/s");
2010        }
2011        .with_metrics()
2012        .await;
2013    }
2014
2015    #[tokio::test]
2016    async fn test_u64_counter_with_unit() {
2017        async {
2018            u64_counter_with_unit!("test", "test description", "Hz", 1, attr = "val");
2019            assert_counter!("test", 1, "attr" = "val");
2020            assert_unit("test", "Hz");
2021        }
2022        .with_metrics()
2023        .await;
2024    }
2025
2026    #[tokio::test]
2027    async fn test_i64_up_down_counter_with_unit() {
2028        async {
2029            let _guard = i64_up_down_counter_with_unit!(
2030                "test",
2031                "test description",
2032                "{request}",
2033                1,
2034                attr = "val"
2035            );
2036            assert_up_down_counter!("test", 1, "attr" = "val");
2037            assert_unit("test", "{request}");
2038        }
2039        .with_metrics()
2040        .await;
2041    }
2042
2043    #[tokio::test]
2044    async fn test_f64_up_down_counter_with_unit() {
2045        async {
2046            let _guard = f64_up_down_counter_with_unit!(
2047                "test",
2048                "test description",
2049                "kg",
2050                1.5,
2051                "attr" = "val"
2052            );
2053            assert_up_down_counter!("test", 1.5, "attr" = "val");
2054            assert_unit("test", "kg");
2055        }
2056        .with_metrics()
2057        .await;
2058    }
2059
2060    #[tokio::test]
2061    async fn test_u64_histogram_with_unit() {
2062        async {
2063            u64_histogram_with_unit!("test", "test description", "{packet}", 1, "attr" = "val");
2064            assert_histogram_sum!("test", 1, "attr" = "val");
2065            assert_unit("test", "{packet}");
2066        }
2067        .with_metrics()
2068        .await;
2069    }
2070
2071    #[tokio::test]
2072    async fn test_f64_counter_with_unit() {
2073        async {
2074            f64_counter_with_unit!("test", "test description", "s", 1.5, "attr" = "val");
2075            assert_counter!("test", 1.5, "attr" = "val");
2076            assert_unit("test", "s");
2077        }
2078        .with_metrics()
2079        .await;
2080    }
2081
2082    #[tokio::test]
2083    async fn test_metrics_across_tasks() {
2084        async {
2085            // Initial metric in the main task
2086            u64_counter!("apollo.router.test", "metric", 1);
2087            assert_counter!("apollo.router.test", 1);
2088
2089            // Spawn a task that also records metrics
2090            let handle = tokio::spawn(
2091                async move {
2092                    u64_counter!("apollo.router.test", "metric", 2);
2093                }
2094                .with_current_meter_provider(),
2095            );
2096
2097            // Wait for the spawned task to complete
2098            handle.await.unwrap();
2099
2100            // The metric should now be 3 since both tasks contributed
2101            assert_counter!("apollo.router.test", 3);
2102        }
2103        .with_metrics()
2104        .await;
2105    }
2106}