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