1#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
46#![cfg_attr(docsrs, feature(doc_cfg))]
47
48#[cfg(feature = "prometheus")]
51#[cfg_attr(docsrs, doc(cfg(feature = "prometheus")))]
52pub mod prometheus;
53
54#[doc(hidden)]
55pub mod value;
56
57pub mod collector;
58
59pub use collector::{
60 CounterF64, CounterU64, GaugeF64, GaugeI64, GaugeU64, HistogramF64, HistogramU64, UpDownCounterF64, UpDownCounterI64,
61};
62pub use opentelemetry;
63pub use scuffle_metrics_derive::{metrics, MetricEnum};
64
65#[cfg(test)]
66#[cfg_attr(all(test, coverage_nightly), coverage(off))]
67mod tests {
68 use std::sync::Arc;
69
70 use opentelemetry::{Key, KeyValue, Value};
71 use opentelemetry_sdk::metrics::data::{ResourceMetrics, Sum};
72 use opentelemetry_sdk::metrics::reader::MetricReader;
73 use opentelemetry_sdk::metrics::{ManualReader, ManualReaderBuilder, SdkMeterProvider};
74 use opentelemetry_sdk::Resource;
75
76 #[test]
77 fn derive_enum() {
78 insta::assert_snapshot!(postcompile::compile! {
79 use scuffle_metrics::MetricEnum;
80
81 #[derive(MetricEnum)]
82 pub enum Kind {
83 Http,
84 Grpc,
85 }
86 });
87 }
88
89 #[test]
90 fn opentelemetry() {
91 #[derive(Debug, Clone)]
92 struct TestReader(Arc<ManualReader>);
93
94 impl TestReader {
95 fn new() -> Self {
96 Self(Arc::new(ManualReaderBuilder::new().build()))
97 }
98
99 fn read(&self) -> ResourceMetrics {
100 let mut metrics = ResourceMetrics {
101 resource: Resource::builder_empty().build(),
102 scope_metrics: vec![],
103 };
104
105 self.0.collect(&mut metrics).expect("collect");
106
107 metrics
108 }
109 }
110
111 impl opentelemetry_sdk::metrics::reader::MetricReader for TestReader {
112 fn register_pipeline(&self, pipeline: std::sync::Weak<opentelemetry_sdk::metrics::Pipeline>) {
113 self.0.register_pipeline(pipeline)
114 }
115
116 fn collect(
117 &self,
118 rm: &mut opentelemetry_sdk::metrics::data::ResourceMetrics,
119 ) -> opentelemetry_sdk::metrics::MetricResult<()> {
120 self.0.collect(rm)
121 }
122
123 fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult {
124 self.0.force_flush()
125 }
126
127 fn shutdown(&self) -> opentelemetry_sdk::error::OTelSdkResult {
128 self.0.shutdown()
129 }
130
131 fn temporality(
132 &self,
133 kind: opentelemetry_sdk::metrics::InstrumentKind,
134 ) -> opentelemetry_sdk::metrics::Temporality {
135 self.0.temporality(kind)
136 }
137 }
138
139 #[crate::metrics(crate_path = "crate")]
140 mod example {
141 use crate::{CounterU64, MetricEnum};
142
143 #[derive(MetricEnum)]
144 #[metrics(crate_path = "crate")]
145 pub enum Kind {
146 Http,
147 Grpc,
148 }
149
150 #[metrics(unit = "requests")]
151 pub fn request(kind: Kind) -> CounterU64;
152 }
153
154 let reader = TestReader::new();
155 let provider = SdkMeterProvider::builder()
156 .with_resource(
157 Resource::builder()
158 .with_attribute(KeyValue::new("service.name", "test_service"))
159 .build(),
160 )
161 .with_reader(reader.clone())
162 .build();
163 opentelemetry::global::set_meter_provider(provider);
164
165 let metrics = reader.read();
166
167 assert!(!metrics.resource.is_empty());
168 assert_eq!(
169 metrics.resource.get(&Key::from_static_str("service.name")),
170 Some(Value::from("test_service"))
171 );
172 assert_eq!(
173 metrics.resource.get(&Key::from_static_str("telemetry.sdk.name")),
174 Some(Value::from("opentelemetry"))
175 );
176 assert!(metrics.resource.get(&Key::from_static_str("telemetry.sdk.version")).is_some());
177 assert_eq!(
178 metrics.resource.get(&Key::from_static_str("telemetry.sdk.language")),
179 Some(Value::from("rust"))
180 );
181
182 assert!(metrics.scope_metrics.is_empty());
183
184 example::request(example::Kind::Http).incr();
185
186 let metrics = reader.read();
187
188 assert_eq!(metrics.scope_metrics.len(), 1);
189 assert_eq!(metrics.scope_metrics[0].scope.name(), "scuffle-metrics");
190 assert!(metrics.scope_metrics[0].scope.version().is_some());
191 assert_eq!(metrics.scope_metrics[0].metrics.len(), 1);
192 assert_eq!(metrics.scope_metrics[0].metrics[0].name, "example_request");
193 assert_eq!(metrics.scope_metrics[0].metrics[0].description, "");
194 assert_eq!(metrics.scope_metrics[0].metrics[0].unit, "requests");
195 let sum: &Sum<u64> = metrics.scope_metrics[0].metrics[0]
196 .data
197 .as_any()
198 .downcast_ref()
199 .expect("wrong data type");
200 assert_eq!(sum.temporality, opentelemetry_sdk::metrics::Temporality::Cumulative);
201 assert!(sum.is_monotonic);
202 assert_eq!(sum.data_points.len(), 1);
203 assert_eq!(sum.data_points[0].value, 1);
204 assert_eq!(sum.data_points[0].attributes.len(), 1);
205 assert_eq!(sum.data_points[0].attributes[0].key, Key::from_static_str("kind"));
206 assert_eq!(sum.data_points[0].attributes[0].value, Value::from("Http"));
207
208 example::request(example::Kind::Http).incr();
209
210 let metrics = reader.read();
211
212 assert_eq!(metrics.scope_metrics.len(), 1);
213 assert_eq!(metrics.scope_metrics[0].metrics.len(), 1);
214 let sum: &Sum<u64> = metrics.scope_metrics[0].metrics[0]
215 .data
216 .as_any()
217 .downcast_ref()
218 .expect("wrong data type");
219 assert_eq!(sum.data_points.len(), 1);
220 assert_eq!(sum.data_points[0].value, 2);
221 assert_eq!(sum.data_points[0].attributes.len(), 1);
222 assert_eq!(sum.data_points[0].attributes[0].key, Key::from_static_str("kind"));
223 assert_eq!(sum.data_points[0].attributes[0].value, Value::from("Http"));
224
225 example::request(example::Kind::Grpc).incr();
226
227 let metrics = reader.read();
228
229 assert_eq!(metrics.scope_metrics.len(), 1);
230 assert_eq!(metrics.scope_metrics[0].metrics.len(), 1);
231 let sum: &Sum<u64> = metrics.scope_metrics[0].metrics[0]
232 .data
233 .as_any()
234 .downcast_ref()
235 .expect("wrong data type");
236 assert_eq!(sum.data_points.len(), 2);
237 let grpc = sum
238 .data_points
239 .iter()
240 .find(|dp| {
241 dp.attributes.len() == 1
242 && dp.attributes[0].key == Key::from_static_str("kind")
243 && dp.attributes[0].value == Value::from("Grpc")
244 })
245 .expect("grpc data point not found");
246 assert_eq!(grpc.value, 1);
247 }
248}