1#![allow(dead_code)]
2use core::fmt;
3use std::{borrow::Cow, sync::Arc};
4
5use opentelemetry::{
6 metrics::{
7 AsyncInstrumentBuilder, Counter, Gauge, Histogram, HistogramBuilder, InstrumentBuilder,
8 InstrumentProvider, ObservableCounter, ObservableGauge, ObservableUpDownCounter,
9 UpDownCounter,
10 },
11 otel_error, InstrumentationScope,
12};
13
14use crate::metrics::{
15 instrument::{Instrument, InstrumentKind, Observable, ResolvedMeasures},
16 internal::{self, Number},
17 pipeline::{Pipelines, Resolver},
18 MetricError, MetricResult,
19};
20
21use super::noop::NoopSyncInstrument;
22
23const INSTRUMENT_NAME_MAX_LENGTH: usize = 255;
25const INSTRUMENT_UNIT_NAME_MAX_LENGTH: usize = 63;
27const INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS: [char; 4] = ['_', '.', '-', '/'];
29
30const INSTRUMENT_NAME_EMPTY: &str = "instrument name must be non-empty";
32const INSTRUMENT_NAME_LENGTH: &str = "instrument name must be less than 256 characters";
33const INSTRUMENT_NAME_INVALID_CHAR: &str =
34 "characters in instrument name must be ASCII and belong to the alphanumeric characters, '_', '.', '-' and '/'";
35const INSTRUMENT_NAME_FIRST_ALPHABETIC: &str =
36 "instrument name must start with an alphabetic character";
37
38const INSTRUMENT_UNIT_LENGTH: &str = "instrument unit must be less than 64 characters";
40const INSTRUMENT_UNIT_INVALID_CHAR: &str = "characters in instrument unit must be ASCII";
41
42pub(crate) struct SdkMeter {
52 scope: InstrumentationScope,
53 pipes: Arc<Pipelines>,
54 u64_resolver: Resolver<u64>,
55 i64_resolver: Resolver<i64>,
56 f64_resolver: Resolver<f64>,
57}
58
59impl SdkMeter {
60 pub(crate) fn new(scope: InstrumentationScope, pipes: Arc<Pipelines>) -> Self {
61 let view_cache = Default::default();
62
63 SdkMeter {
64 scope,
65 pipes: Arc::clone(&pipes),
66 u64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
67 i64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
68 f64_resolver: Resolver::new(pipes, view_cache),
69 }
70 }
71
72 fn create_counter<T>(
73 &self,
74 builder: InstrumentBuilder<'_, Counter<T>>,
75 resolver: &InstrumentResolver<'_, T>,
76 ) -> Counter<T>
77 where
78 T: Number,
79 {
80 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
81 if let Err(err) = validation_result {
82 otel_error!(
83 name: "InstrumentCreationFailed",
84 meter_name = self.scope.name(),
85 instrument_name = builder.name.as_ref(),
86 message = "Measurements from this Counter will be ignored.",
87 reason = format!("{}", err)
88 );
89 return Counter::new(Arc::new(NoopSyncInstrument::new()));
90 }
91
92 match resolver
93 .lookup(
94 InstrumentKind::Counter,
95 builder.name.clone(),
96 builder.description,
97 builder.unit,
98 None,
99 )
100 .map(|i| Counter::new(Arc::new(i)))
101 {
102 Ok(counter) => counter,
103 Err(err) => {
104 otel_error!(
105 name: "InstrumentCreationFailed",
106 meter_name = self.scope.name(),
107 instrument_name = builder.name.as_ref(),
108 message = "Measurements from this Counter will be ignored.",
109 reason = format!("{}", err)
110 );
111 Counter::new(Arc::new(NoopSyncInstrument::new()))
112 }
113 }
114 }
115
116 fn create_observable_counter<T>(
117 &self,
118 builder: AsyncInstrumentBuilder<'_, ObservableCounter<T>, T>,
119 resolver: &InstrumentResolver<'_, T>,
120 ) -> ObservableCounter<T>
121 where
122 T: Number,
123 {
124 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
125 if let Err(err) = validation_result {
126 otel_error!(
127 name: "InstrumentCreationFailed",
128 meter_name = self.scope.name(),
129 instrument_name = builder.name.as_ref(),
130 message = "Callbacks for this ObservableCounter will not be invoked.",
131 reason = format!("{}", err));
132 return ObservableCounter::new();
133 }
134
135 match resolver.measures(
136 InstrumentKind::ObservableCounter,
137 builder.name.clone(),
138 builder.description,
139 builder.unit,
140 None,
141 ) {
142 Ok(ms) => {
143 if ms.is_empty() {
144 otel_error!(
145 name: "InstrumentCreationFailed",
146 meter_name = self.scope.name(),
147 instrument_name = builder.name.as_ref(),
148 message = "Callbacks for this ObservableCounter will not be invoked. Check View Configuration."
149 );
150 return ObservableCounter::new();
151 }
152
153 let observable = Arc::new(Observable::new(ms));
154
155 for callback in builder.callbacks {
156 let cb_inst = Arc::clone(&observable);
157 self.pipes
158 .register_callback(move || callback(cb_inst.as_ref()));
159 }
160
161 ObservableCounter::new()
162 }
163 Err(err) => {
164 otel_error!(
165 name: "InstrumentCreationFailed",
166 meter_name = self.scope.name(),
167 instrument_name = builder.name.as_ref(),
168 message = "Callbacks for this ObservableCounter will not be invoked.",
169 reason = format!("{}", err));
170 ObservableCounter::new()
171 }
172 }
173 }
174
175 fn create_observable_updown_counter<T>(
176 &self,
177 builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<T>, T>,
178 resolver: &InstrumentResolver<'_, T>,
179 ) -> ObservableUpDownCounter<T>
180 where
181 T: Number,
182 {
183 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
184 if let Err(err) = validation_result {
185 otel_error!(
186 name: "InstrumentCreationFailed",
187 meter_name = self.scope.name(),
188 instrument_name = builder.name.as_ref(),
189 message = "Callbacks for this ObservableUpDownCounter will not be invoked.",
190 reason = format!("{}", err));
191 return ObservableUpDownCounter::new();
192 }
193
194 match resolver.measures(
195 InstrumentKind::ObservableUpDownCounter,
196 builder.name.clone(),
197 builder.description,
198 builder.unit,
199 None,
200 ) {
201 Ok(ms) => {
202 if ms.is_empty() {
203 otel_error!(
204 name: "InstrumentCreationFailed",
205 meter_name = self.scope.name(),
206 instrument_name = builder.name.as_ref(),
207 message = "Callbacks for this ObservableUpDownCounter will not be invoked. Check View Configuration."
208 );
209 return ObservableUpDownCounter::new();
210 }
211
212 let observable = Arc::new(Observable::new(ms));
213
214 for callback in builder.callbacks {
215 let cb_inst = Arc::clone(&observable);
216 self.pipes
217 .register_callback(move || callback(cb_inst.as_ref()));
218 }
219
220 ObservableUpDownCounter::new()
221 }
222 Err(err) => {
223 otel_error!(
224 name: "InstrumentCreationFailed",
225 meter_name = self.scope.name(),
226 instrument_name = builder.name.as_ref(),
227 message = "Callbacks for this ObservableUpDownCounter will not be invoked.",
228 reason = format!("{}", err));
229 ObservableUpDownCounter::new()
230 }
231 }
232 }
233
234 fn create_observable_gauge<T>(
235 &self,
236 builder: AsyncInstrumentBuilder<'_, ObservableGauge<T>, T>,
237 resolver: &InstrumentResolver<'_, T>,
238 ) -> ObservableGauge<T>
239 where
240 T: Number,
241 {
242 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
243 if let Err(err) = validation_result {
244 otel_error!(
245 name: "InstrumentCreationFailed",
246 meter_name = self.scope.name(),
247 instrument_name = builder.name.as_ref(),
248 message = "Callbacks for this ObservableGauge will not be invoked.",
249 reason = format!("{}", err));
250 return ObservableGauge::new();
251 }
252
253 match resolver.measures(
254 InstrumentKind::ObservableGauge,
255 builder.name.clone(),
256 builder.description,
257 builder.unit,
258 None,
259 ) {
260 Ok(ms) => {
261 if ms.is_empty() {
262 otel_error!(
263 name: "InstrumentCreationFailed",
264 meter_name = self.scope.name(),
265 instrument_name = builder.name.as_ref(),
266 message = "Callbacks for this ObservableGauge will not be invoked. Check View Configuration."
267 );
268 return ObservableGauge::new();
269 }
270
271 let observable = Arc::new(Observable::new(ms));
272
273 for callback in builder.callbacks {
274 let cb_inst = Arc::clone(&observable);
275 self.pipes
276 .register_callback(move || callback(cb_inst.as_ref()));
277 }
278
279 ObservableGauge::new()
280 }
281 Err(err) => {
282 otel_error!(
283 name: "InstrumentCreationFailed",
284 meter_name = self.scope.name(),
285 instrument_name = builder.name.as_ref(),
286 message = "Callbacks for this ObservableGauge will not be invoked.",
287 reason = format!("{}", err));
288 ObservableGauge::new()
289 }
290 }
291 }
292
293 fn create_updown_counter<T>(
294 &self,
295 builder: InstrumentBuilder<'_, UpDownCounter<T>>,
296 resolver: &InstrumentResolver<'_, T>,
297 ) -> UpDownCounter<T>
298 where
299 T: Number,
300 {
301 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
302 if let Err(err) = validation_result {
303 otel_error!(
304 name: "InstrumentCreationFailed",
305 meter_name = self.scope.name(),
306 instrument_name = builder.name.as_ref(),
307 message = "Measurements from this UpDownCounter will be ignored.",
308 reason = format!("{}", err)
309 );
310 return UpDownCounter::new(Arc::new(NoopSyncInstrument::new()));
311 }
312
313 match resolver
314 .lookup(
315 InstrumentKind::UpDownCounter,
316 builder.name.clone(),
317 builder.description,
318 builder.unit,
319 None,
320 )
321 .map(|i| UpDownCounter::new(Arc::new(i)))
322 {
323 Ok(updown_counter) => updown_counter,
324 Err(err) => {
325 otel_error!(
326 name: "InstrumentCreationFailed",
327 meter_name = self.scope.name(),
328 instrument_name = builder.name.as_ref(),
329 message = "Measurements from this UpDownCounter will be ignored.",
330 reason = format!("{}", err)
331 );
332 UpDownCounter::new(Arc::new(NoopSyncInstrument::new()))
333 }
334 }
335 }
336
337 fn create_gauge<T>(
338 &self,
339 builder: InstrumentBuilder<'_, Gauge<T>>,
340 resolver: &InstrumentResolver<'_, T>,
341 ) -> Gauge<T>
342 where
343 T: Number,
344 {
345 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
346 if let Err(err) = validation_result {
347 otel_error!(
348 name: "InstrumentCreationFailed",
349 meter_name = self.scope.name(),
350 instrument_name = builder.name.as_ref(),
351 message = "Measurements from this Gauge will be ignored.",
352 reason = format!("{}", err)
353 );
354 return Gauge::new(Arc::new(NoopSyncInstrument::new()));
355 }
356
357 match resolver
358 .lookup(
359 InstrumentKind::Gauge,
360 builder.name.clone(),
361 builder.description,
362 builder.unit,
363 None,
364 )
365 .map(|i| Gauge::new(Arc::new(i)))
366 {
367 Ok(gauge) => gauge,
368 Err(err) => {
369 otel_error!(
370 name: "InstrumentCreationFailed",
371 meter_name = self.scope.name(),
372 instrument_name = builder.name.as_ref(),
373 message = "Measurements from this Gauge will be ignored.",
374 reason = format!("{}", err)
375 );
376 Gauge::new(Arc::new(NoopSyncInstrument::new()))
377 }
378 }
379 }
380
381 fn create_histogram<T>(
382 &self,
383 builder: HistogramBuilder<'_, Histogram<T>>,
384 resolver: &InstrumentResolver<'_, T>,
385 ) -> Histogram<T>
386 where
387 T: Number,
388 {
389 let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
390 if let Err(err) = validation_result {
391 otel_error!(
392 name: "InstrumentCreationFailed",
393 meter_name = self.scope.name(),
394 instrument_name = builder.name.as_ref(),
395 message = "Measurements from this Histogram will be ignored.",
396 reason = format!("{}", err)
397 );
398 return Histogram::new(Arc::new(NoopSyncInstrument::new()));
399 }
400
401 if let Some(ref boundaries) = builder.boundaries {
402 let validation_result = validate_bucket_boundaries(boundaries);
403 if let Err(err) = validation_result {
404 otel_error!(
408 name: "InstrumentCreationFailed",
409 meter_name = self.scope.name(),
410 instrument_name = builder.name.as_ref(),
411 message = "Measurements from this Histogram will be ignored.",
412 reason = format!("{}", err)
413 );
414 return Histogram::new(Arc::new(NoopSyncInstrument::new()));
415 }
416 }
417
418 match resolver
419 .lookup(
420 InstrumentKind::Histogram,
421 builder.name.clone(),
422 builder.description,
423 builder.unit,
424 builder.boundaries,
425 )
426 .map(|i| Histogram::new(Arc::new(i)))
427 {
428 Ok(histogram) => histogram,
429 Err(err) => {
430 otel_error!(
431 name: "InstrumentCreationFailed",
432 meter_name = self.scope.name(),
433 instrument_name = builder.name.as_ref(),
434 message = "Measurements from this Histogram will be ignored.",
435 reason = format!("{}", err)
436 );
437 Histogram::new(Arc::new(NoopSyncInstrument::new()))
438 }
439 }
440 }
441}
442
443#[doc(hidden)]
444impl InstrumentProvider for SdkMeter {
445 fn u64_counter(&self, builder: InstrumentBuilder<'_, Counter<u64>>) -> Counter<u64> {
446 let resolver = InstrumentResolver::new(self, &self.u64_resolver);
447 self.create_counter(builder, &resolver)
448 }
449
450 fn f64_counter(&self, builder: InstrumentBuilder<'_, Counter<f64>>) -> Counter<f64> {
451 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
452 self.create_counter(builder, &resolver)
453 }
454
455 fn u64_observable_counter(
456 &self,
457 builder: AsyncInstrumentBuilder<'_, ObservableCounter<u64>, u64>,
458 ) -> ObservableCounter<u64> {
459 let resolver = InstrumentResolver::new(self, &self.u64_resolver);
460 self.create_observable_counter(builder, &resolver)
461 }
462
463 fn f64_observable_counter(
464 &self,
465 builder: AsyncInstrumentBuilder<'_, ObservableCounter<f64>, f64>,
466 ) -> ObservableCounter<f64> {
467 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
468 self.create_observable_counter(builder, &resolver)
469 }
470
471 fn i64_up_down_counter(
472 &self,
473 builder: InstrumentBuilder<'_, UpDownCounter<i64>>,
474 ) -> UpDownCounter<i64> {
475 let resolver = InstrumentResolver::new(self, &self.i64_resolver);
476 self.create_updown_counter(builder, &resolver)
477 }
478
479 fn f64_up_down_counter(
480 &self,
481 builder: InstrumentBuilder<'_, UpDownCounter<f64>>,
482 ) -> UpDownCounter<f64> {
483 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
484 self.create_updown_counter(builder, &resolver)
485 }
486
487 fn i64_observable_up_down_counter(
488 &self,
489 builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<i64>, i64>,
490 ) -> ObservableUpDownCounter<i64> {
491 let resolver = InstrumentResolver::new(self, &self.i64_resolver);
492 self.create_observable_updown_counter(builder, &resolver)
493 }
494
495 fn f64_observable_up_down_counter(
496 &self,
497 builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<f64>, f64>,
498 ) -> ObservableUpDownCounter<f64> {
499 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
500 self.create_observable_updown_counter(builder, &resolver)
501 }
502
503 fn u64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<u64>>) -> Gauge<u64> {
504 let resolver = InstrumentResolver::new(self, &self.u64_resolver);
505 self.create_gauge(builder, &resolver)
506 }
507
508 fn f64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<f64>>) -> Gauge<f64> {
509 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
510 self.create_gauge(builder, &resolver)
511 }
512
513 fn i64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<i64>>) -> Gauge<i64> {
514 let resolver = InstrumentResolver::new(self, &self.i64_resolver);
515 self.create_gauge(builder, &resolver)
516 }
517
518 fn u64_observable_gauge(
519 &self,
520 builder: AsyncInstrumentBuilder<'_, ObservableGauge<u64>, u64>,
521 ) -> ObservableGauge<u64> {
522 let resolver = InstrumentResolver::new(self, &self.u64_resolver);
523 self.create_observable_gauge(builder, &resolver)
524 }
525
526 fn i64_observable_gauge(
527 &self,
528 builder: AsyncInstrumentBuilder<'_, ObservableGauge<i64>, i64>,
529 ) -> ObservableGauge<i64> {
530 let resolver = InstrumentResolver::new(self, &self.i64_resolver);
531 self.create_observable_gauge(builder, &resolver)
532 }
533
534 fn f64_observable_gauge(
535 &self,
536 builder: AsyncInstrumentBuilder<'_, ObservableGauge<f64>, f64>,
537 ) -> ObservableGauge<f64> {
538 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
539 self.create_observable_gauge(builder, &resolver)
540 }
541
542 fn f64_histogram(&self, builder: HistogramBuilder<'_, Histogram<f64>>) -> Histogram<f64> {
543 let resolver = InstrumentResolver::new(self, &self.f64_resolver);
544 self.create_histogram(builder, &resolver)
545 }
546
547 fn u64_histogram(&self, builder: HistogramBuilder<'_, Histogram<u64>>) -> Histogram<u64> {
548 let resolver = InstrumentResolver::new(self, &self.u64_resolver);
549 self.create_histogram(builder, &resolver)
550 }
551}
552
553fn validate_instrument_config(name: &str, unit: &Option<Cow<'static, str>>) -> MetricResult<()> {
554 validate_instrument_name(name).and_then(|_| validate_instrument_unit(unit))
555}
556
557fn validate_bucket_boundaries(boundaries: &[f64]) -> MetricResult<()> {
558 for boundary in boundaries {
560 if boundary.is_nan() || boundary.is_infinite() {
561 return Err(MetricError::InvalidInstrumentConfiguration(
562 "Bucket boundaries must not contain NaN, +Inf, or -Inf",
563 ));
564 }
565 }
566
567 for i in 1..boundaries.len() {
569 if boundaries[i] <= boundaries[i - 1] {
570 return Err(MetricError::InvalidInstrumentConfiguration(
571 "Bucket boundaries must be sorted and non-duplicate",
572 ));
573 }
574 }
575
576 Ok(())
577}
578
579#[cfg(feature = "experimental_metrics_disable_name_validation")]
580fn validate_instrument_name(_name: &str) -> MetricResult<()> {
581 Ok(())
583}
584
585#[cfg(not(feature = "experimental_metrics_disable_name_validation"))]
586fn validate_instrument_name(name: &str) -> MetricResult<()> {
587 if name.is_empty() {
588 return Err(MetricError::InvalidInstrumentConfiguration(
589 INSTRUMENT_NAME_EMPTY,
590 ));
591 }
592 if name.len() > INSTRUMENT_NAME_MAX_LENGTH {
593 return Err(MetricError::InvalidInstrumentConfiguration(
594 INSTRUMENT_NAME_LENGTH,
595 ));
596 }
597
598 if name.starts_with(|c: char| !c.is_ascii_alphabetic()) {
599 return Err(MetricError::InvalidInstrumentConfiguration(
600 INSTRUMENT_NAME_FIRST_ALPHABETIC,
601 ));
602 }
603 if name.contains(|c: char| {
604 !c.is_ascii_alphanumeric() && !INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS.contains(&c)
605 }) {
606 return Err(MetricError::InvalidInstrumentConfiguration(
607 INSTRUMENT_NAME_INVALID_CHAR,
608 ));
609 }
610 Ok(())
611}
612
613fn validate_instrument_unit(unit: &Option<Cow<'static, str>>) -> MetricResult<()> {
614 if let Some(unit) = unit {
615 if unit.len() > INSTRUMENT_UNIT_NAME_MAX_LENGTH {
616 return Err(MetricError::InvalidInstrumentConfiguration(
617 INSTRUMENT_UNIT_LENGTH,
618 ));
619 }
620 if unit.contains(|c: char| !c.is_ascii()) {
621 return Err(MetricError::InvalidInstrumentConfiguration(
622 INSTRUMENT_UNIT_INVALID_CHAR,
623 ));
624 }
625 }
626 Ok(())
627}
628
629impl fmt::Debug for SdkMeter {
630 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631 f.debug_struct("Meter").field("scope", &self.scope).finish()
632 }
633}
634
635struct InstrumentResolver<'a, T> {
637 meter: &'a SdkMeter,
638 resolve: &'a Resolver<T>,
639}
640
641impl<'a, T> InstrumentResolver<'a, T>
642where
643 T: Number,
644{
645 fn new(meter: &'a SdkMeter, resolve: &'a Resolver<T>) -> Self {
646 InstrumentResolver { meter, resolve }
647 }
648
649 fn lookup(
651 &self,
652 kind: InstrumentKind,
653 name: Cow<'static, str>,
654 description: Option<Cow<'static, str>>,
655 unit: Option<Cow<'static, str>>,
656 boundaries: Option<Vec<f64>>,
657 ) -> MetricResult<ResolvedMeasures<T>> {
658 let aggregators = self.measures(kind, name, description, unit, boundaries)?;
659 Ok(ResolvedMeasures {
660 measures: aggregators,
661 })
662 }
663
664 fn measures(
665 &self,
666 kind: InstrumentKind,
667 name: Cow<'static, str>,
668 description: Option<Cow<'static, str>>,
669 unit: Option<Cow<'static, str>>,
670 boundaries: Option<Vec<f64>>,
671 ) -> MetricResult<Vec<Arc<dyn internal::Measure<T>>>> {
672 let inst = Instrument {
673 name,
674 description: description.unwrap_or_default(),
675 unit: unit.unwrap_or_default(),
676 kind: Some(kind),
677 scope: self.meter.scope.clone(),
678 };
679
680 self.resolve.measures(inst, boundaries)
681 }
682}
683
684#[allow(unused_imports)]
685#[cfg(test)]
686mod tests {
687 use std::borrow::Cow;
688
689 use crate::metrics::MetricError;
690
691 use super::{
692 validate_instrument_name, validate_instrument_unit, INSTRUMENT_NAME_EMPTY,
693 INSTRUMENT_NAME_FIRST_ALPHABETIC, INSTRUMENT_NAME_INVALID_CHAR, INSTRUMENT_NAME_LENGTH,
694 INSTRUMENT_UNIT_INVALID_CHAR, INSTRUMENT_UNIT_LENGTH,
695 };
696
697 #[test]
698 #[cfg(not(feature = "experimental_metrics_disable_name_validation"))]
699 fn instrument_name_validation() {
700 let instrument_name_test_cases = vec![
702 ("validateName", ""),
703 ("_startWithNoneAlphabet", INSTRUMENT_NAME_FIRST_ALPHABETIC),
704 ("utf8char锈", INSTRUMENT_NAME_INVALID_CHAR),
705 ("a".repeat(255).leak(), ""),
706 ("a".repeat(256).leak(), INSTRUMENT_NAME_LENGTH),
707 ("invalid name", INSTRUMENT_NAME_INVALID_CHAR),
708 ("allow/slash", ""),
709 ("allow_under_score", ""),
710 ("allow.dots.ok", ""),
711 ("", INSTRUMENT_NAME_EMPTY),
712 ("\\allow\\slash /sec", INSTRUMENT_NAME_FIRST_ALPHABETIC),
713 ("\\allow\\$$slash /sec", INSTRUMENT_NAME_FIRST_ALPHABETIC),
714 ("Total $ Count", INSTRUMENT_NAME_INVALID_CHAR),
715 (
716 "\\test\\UsagePercent(Total) > 80%",
717 INSTRUMENT_NAME_FIRST_ALPHABETIC,
718 ),
719 ("/not / allowed", INSTRUMENT_NAME_FIRST_ALPHABETIC),
720 ];
721 for (name, expected_error) in instrument_name_test_cases {
722 let assert = |result: Result<_, MetricError>| {
723 if expected_error.is_empty() {
724 assert!(result.is_ok());
725 } else {
726 assert!(matches!(
727 result.unwrap_err(),
728 MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
729 ));
730 }
731 };
732
733 assert(validate_instrument_name(name).map(|_| ()));
734 }
735 }
736
737 #[test]
738 #[cfg(feature = "experimental_metrics_disable_name_validation")]
739 fn instrument_name_validation_disabled() {
740 let instrument_name_test_cases = vec![
742 ("validateName", ""),
743 ("_startWithNoneAlphabet", ""),
744 ("utf8char锈", ""),
745 ("a".repeat(255).leak(), ""),
746 ("a".repeat(256).leak(), ""),
747 ("invalid name", ""),
748 ("allow/slash", ""),
749 ("allow_under_score", ""),
750 ("allow.dots.ok", ""),
751 ("", ""),
752 ("\\allow\\slash /sec", ""),
753 ("\\allow\\$$slash /sec", ""),
754 ("Total $ Count", ""),
755 ("\\test\\UsagePercent(Total) > 80%", ""),
756 ("/not / allowed", ""),
757 ];
758 for (name, expected_error) in instrument_name_test_cases {
759 let assert = |result: Result<_, MetricError>| {
760 if expected_error.is_empty() {
761 assert!(result.is_ok());
762 } else {
763 assert!(matches!(
764 result.unwrap_err(),
765 MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
766 ));
767 }
768 };
769
770 assert(validate_instrument_name(name).map(|_| ()));
771 }
772 }
773
774 #[test]
775 fn instrument_unit_validation() {
776 let instrument_unit_test_cases = vec![
778 (
779 "0123456789012345678901234567890123456789012345678901234567890123",
780 INSTRUMENT_UNIT_LENGTH,
781 ),
782 ("utf8char锈", INSTRUMENT_UNIT_INVALID_CHAR),
783 ("kb", ""),
784 ("Kb/sec", ""),
785 ("%", ""),
786 ("", ""),
787 ];
788
789 for (unit, expected_error) in instrument_unit_test_cases {
790 let assert = |result: Result<_, MetricError>| {
791 if expected_error.is_empty() {
792 assert!(result.is_ok());
793 } else {
794 assert!(matches!(
795 result.unwrap_err(),
796 MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
797 ));
798 }
799 };
800 let unit: Option<Cow<'static, str>> = Some(unit.into());
801
802 assert(validate_instrument_unit(&unit).map(|_| ()));
803 }
804 }
805}