opentelemetry_proto/transform/
metrics.rs

1// The prost currently will generate a non optional deprecated field for labels.
2// We cannot assign value to it otherwise clippy will complain.
3// We cannot ignore it as it's not an optional field.
4// We can remove this after we removed the labels field from proto.
5#[allow(deprecated)]
6#[cfg(feature = "gen-tonic-messages")]
7pub mod tonic {
8    use std::fmt::Debug;
9
10    use opentelemetry::{otel_debug, Key, Value};
11    use opentelemetry_sdk::metrics::data::{
12        AggregatedMetrics, Exemplar as SdkExemplar,
13        ExponentialHistogram as SdkExponentialHistogram, Gauge as SdkGauge,
14        Histogram as SdkHistogram, Metric as SdkMetric, MetricData, ResourceMetrics,
15        ScopeMetrics as SdkScopeMetrics, Sum as SdkSum,
16    };
17    use opentelemetry_sdk::metrics::Temporality;
18    use opentelemetry_sdk::Resource as SdkResource;
19
20    use crate::proto::tonic::{
21        collector::metrics::v1::ExportMetricsServiceRequest,
22        common::v1::KeyValue,
23        metrics::v1::{
24            exemplar, exemplar::Value as TonicExemplarValue,
25            exponential_histogram_data_point::Buckets as TonicBuckets,
26            metric::Data as TonicMetricData, number_data_point,
27            number_data_point::Value as TonicDataPointValue,
28            AggregationTemporality as TonicTemporality, AggregationTemporality,
29            DataPointFlags as TonicDataPointFlags, Exemplar as TonicExemplar,
30            ExponentialHistogram as TonicExponentialHistogram,
31            ExponentialHistogramDataPoint as TonicExponentialHistogramDataPoint,
32            Gauge as TonicGauge, Histogram as TonicHistogram,
33            HistogramDataPoint as TonicHistogramDataPoint, Metric as TonicMetric,
34            NumberDataPoint as TonicNumberDataPoint, ResourceMetrics as TonicResourceMetrics,
35            ScopeMetrics as TonicScopeMetrics, Sum as TonicSum,
36        },
37        resource::v1::Resource as TonicResource,
38    };
39    use crate::transform::common::to_nanos;
40
41    impl From<u64> for exemplar::Value {
42        fn from(value: u64) -> Self {
43            exemplar::Value::AsInt(i64::try_from(value).unwrap_or_default())
44        }
45    }
46
47    impl From<i64> for exemplar::Value {
48        fn from(value: i64) -> Self {
49            exemplar::Value::AsInt(value)
50        }
51    }
52
53    impl From<f64> for exemplar::Value {
54        fn from(value: f64) -> Self {
55            exemplar::Value::AsDouble(value)
56        }
57    }
58
59    impl From<u64> for number_data_point::Value {
60        fn from(value: u64) -> Self {
61            number_data_point::Value::AsInt(i64::try_from(value).unwrap_or_default())
62        }
63    }
64
65    impl From<i64> for number_data_point::Value {
66        fn from(value: i64) -> Self {
67            number_data_point::Value::AsInt(value)
68        }
69    }
70
71    impl From<f64> for number_data_point::Value {
72        fn from(value: f64) -> Self {
73            number_data_point::Value::AsDouble(value)
74        }
75    }
76
77    impl From<(&Key, &Value)> for KeyValue {
78        fn from(kv: (&Key, &Value)) -> Self {
79            KeyValue {
80                key: kv.0.to_string(),
81                value: Some(kv.1.clone().into()),
82            }
83        }
84    }
85
86    impl From<&opentelemetry::KeyValue> for KeyValue {
87        fn from(kv: &opentelemetry::KeyValue) -> Self {
88            KeyValue {
89                key: kv.key.to_string(),
90                value: Some(kv.value.clone().into()),
91            }
92        }
93    }
94
95    impl From<Temporality> for AggregationTemporality {
96        fn from(temporality: Temporality) -> Self {
97            match temporality {
98                Temporality::Cumulative => AggregationTemporality::Cumulative,
99                Temporality::Delta => AggregationTemporality::Delta,
100                other => {
101                    otel_debug!(
102                        name: "AggregationTemporality::Unknown",
103                        message = "Unknown temporality,using default instead.",
104                        unknown_temporality = format!("{:?}", other),
105                        default_temporality = format!("{:?}", Temporality::Cumulative)
106                    );
107                    AggregationTemporality::Cumulative
108                }
109            }
110        }
111    }
112
113    impl From<&ResourceMetrics> for ExportMetricsServiceRequest {
114        fn from(rm: &ResourceMetrics) -> Self {
115            ExportMetricsServiceRequest {
116                resource_metrics: vec![TonicResourceMetrics {
117                    resource: Some((rm.resource()).into()),
118                    scope_metrics: rm.scope_metrics().map(Into::into).collect(),
119                    schema_url: rm
120                        .resource()
121                        .schema_url()
122                        .map(Into::into)
123                        .unwrap_or_default(),
124                }],
125            }
126        }
127    }
128
129    impl From<&SdkResource> for TonicResource {
130        fn from(resource: &SdkResource) -> Self {
131            TonicResource {
132                attributes: resource.iter().map(Into::into).collect(),
133                dropped_attributes_count: 0,
134                entity_refs: vec![], // internal and currently unused
135            }
136        }
137    }
138
139    impl From<&SdkScopeMetrics> for TonicScopeMetrics {
140        fn from(sm: &SdkScopeMetrics) -> Self {
141            TonicScopeMetrics {
142                scope: Some((sm.scope(), None).into()),
143                metrics: sm.metrics().map(Into::into).collect(),
144                schema_url: sm
145                    .scope()
146                    .schema_url()
147                    .map(ToOwned::to_owned)
148                    .unwrap_or_default(),
149            }
150        }
151    }
152
153    impl From<&SdkMetric> for TonicMetric {
154        fn from(metric: &SdkMetric) -> Self {
155            TonicMetric {
156                name: metric.name().to_string(),
157                description: metric.description().to_string(),
158                unit: metric.unit().to_string(),
159                metadata: vec![], // internal and currently unused
160                data: Some(match metric.data() {
161                    AggregatedMetrics::F64(data) => data.into(),
162                    AggregatedMetrics::U64(data) => data.into(),
163                    AggregatedMetrics::I64(data) => data.into(),
164                }),
165            }
166        }
167    }
168
169    impl<T> From<&MetricData<T>> for TonicMetricData
170    where
171        T: Numeric + Debug,
172    {
173        fn from(data: &MetricData<T>) -> Self {
174            match data {
175                MetricData::Gauge(gauge) => TonicMetricData::Gauge(gauge.into()),
176                MetricData::Sum(sum) => TonicMetricData::Sum(sum.into()),
177                MetricData::Histogram(hist) => TonicMetricData::Histogram(hist.into()),
178                MetricData::ExponentialHistogram(hist) => {
179                    TonicMetricData::ExponentialHistogram(hist.into())
180                }
181            }
182        }
183    }
184
185    trait Numeric: Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy {
186        // lossy at large values for u64 and i64 but otlp histograms only handle float values
187        fn into_f64(self) -> f64;
188    }
189
190    impl Numeric for u64 {
191        fn into_f64(self) -> f64 {
192            self as f64
193        }
194    }
195
196    impl Numeric for i64 {
197        fn into_f64(self) -> f64 {
198            self as f64
199        }
200    }
201
202    impl Numeric for f64 {
203        fn into_f64(self) -> f64 {
204            self
205        }
206    }
207
208    impl<T> From<&SdkHistogram<T>> for TonicHistogram
209    where
210        T: Numeric,
211    {
212        fn from(hist: &SdkHistogram<T>) -> Self {
213            TonicHistogram {
214                data_points: hist
215                    .data_points()
216                    .map(|dp| TonicHistogramDataPoint {
217                        attributes: dp.attributes().map(Into::into).collect(),
218                        start_time_unix_nano: to_nanos(hist.start_time()),
219                        time_unix_nano: to_nanos(hist.time()),
220                        count: dp.count(),
221                        sum: Some(dp.sum().into_f64()),
222                        bucket_counts: dp.bucket_counts().collect(),
223                        explicit_bounds: dp.bounds().collect(),
224                        exemplars: dp.exemplars().map(Into::into).collect(),
225                        flags: TonicDataPointFlags::default() as u32,
226                        min: dp.min().map(Numeric::into_f64),
227                        max: dp.max().map(Numeric::into_f64),
228                    })
229                    .collect(),
230                aggregation_temporality: TonicTemporality::from(hist.temporality()).into(),
231            }
232        }
233    }
234
235    impl<T> From<&SdkExponentialHistogram<T>> for TonicExponentialHistogram
236    where
237        T: Numeric,
238    {
239        fn from(hist: &SdkExponentialHistogram<T>) -> Self {
240            TonicExponentialHistogram {
241                data_points: hist
242                    .data_points()
243                    .map(|dp| TonicExponentialHistogramDataPoint {
244                        attributes: dp.attributes().map(Into::into).collect(),
245                        start_time_unix_nano: to_nanos(hist.start_time()),
246                        time_unix_nano: to_nanos(hist.time()),
247                        count: dp.count() as u64,
248                        sum: Some(dp.sum().into_f64()),
249                        scale: dp.scale().into(),
250                        zero_count: dp.zero_count(),
251                        positive: Some(TonicBuckets {
252                            offset: dp.positive_bucket().offset(),
253                            bucket_counts: dp.positive_bucket().counts().collect(),
254                        }),
255                        negative: Some(TonicBuckets {
256                            offset: dp.negative_bucket().offset(),
257                            bucket_counts: dp.negative_bucket().counts().collect(),
258                        }),
259                        flags: TonicDataPointFlags::default() as u32,
260                        exemplars: dp.exemplars().map(Into::into).collect(),
261                        min: dp.min().map(Numeric::into_f64),
262                        max: dp.max().map(Numeric::into_f64),
263                        zero_threshold: dp.zero_threshold(),
264                    })
265                    .collect(),
266                aggregation_temporality: TonicTemporality::from(hist.temporality()).into(),
267            }
268        }
269    }
270
271    impl<T> From<&SdkSum<T>> for TonicSum
272    where
273        T: Debug + Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy,
274    {
275        fn from(sum: &SdkSum<T>) -> Self {
276            TonicSum {
277                data_points: sum
278                    .data_points()
279                    .map(|dp| TonicNumberDataPoint {
280                        attributes: dp.attributes().map(Into::into).collect(),
281                        start_time_unix_nano: to_nanos(sum.start_time()),
282                        time_unix_nano: to_nanos(sum.time()),
283                        exemplars: dp.exemplars().map(Into::into).collect(),
284                        flags: TonicDataPointFlags::default() as u32,
285                        value: Some(dp.value().into()),
286                    })
287                    .collect(),
288                aggregation_temporality: TonicTemporality::from(sum.temporality()).into(),
289                is_monotonic: sum.is_monotonic(),
290            }
291        }
292    }
293
294    impl<T> From<&SdkGauge<T>> for TonicGauge
295    where
296        T: Debug + Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy,
297    {
298        fn from(gauge: &SdkGauge<T>) -> Self {
299            TonicGauge {
300                data_points: gauge
301                    .data_points()
302                    .map(|dp| TonicNumberDataPoint {
303                        attributes: dp.attributes().map(Into::into).collect(),
304                        start_time_unix_nano: gauge.start_time().map(to_nanos).unwrap_or_default(),
305                        time_unix_nano: to_nanos(gauge.time()),
306                        exemplars: dp.exemplars().map(Into::into).collect(),
307                        flags: TonicDataPointFlags::default() as u32,
308                        value: Some(dp.value().into()),
309                    })
310                    .collect(),
311            }
312        }
313    }
314
315    impl<T> From<&SdkExemplar<T>> for TonicExemplar
316    where
317        T: Into<TonicExemplarValue> + Copy,
318    {
319        fn from(ex: &SdkExemplar<T>) -> Self {
320            TonicExemplar {
321                filtered_attributes: ex
322                    .filtered_attributes()
323                    .map(|kv| (&kv.key, &kv.value).into())
324                    .collect(),
325                time_unix_nano: to_nanos(ex.time()),
326                span_id: ex.span_id().into(),
327                trace_id: ex.trace_id().into(),
328                value: Some(ex.value.into()),
329            }
330        }
331    }
332}