emit/
metric.rs

1/*!
2The [`Metric`] type.
3*/
4
5use core::ops::ControlFlow;
6
7use emit_core::{
8    and::And,
9    emitter::Emitter,
10    event::{Event, ToEvent},
11    extent::{Extent, ToExtent},
12    or::Or,
13    path::Path,
14    props::{ErasedProps, Props},
15    str::{Str, ToStr},
16    template::{self, Template},
17    timestamp::Timestamp,
18    value::{ToValue, Value},
19    well_known::{KEY_EVT_KIND, KEY_METRIC_AGG, KEY_METRIC_NAME, KEY_METRIC_VALUE},
20};
21
22use crate::kind::Kind;
23
24pub use self::{sampler::Sampler, source::Source};
25
26/**
27A diagnostic event that represents a metric sample.
28
29Metrics are an extension of [`Event`]s that explicitly take the well-known properties that signal an event as being a metric sample. See the [`crate::metric`] module for details.
30
31A `Metric` can be converted into an [`Event`] through its [`ToEvent`] implemenation, or passed directly to an [`Emitter`] to emit it.
32*/
33pub struct Metric<'a, P> {
34    mdl: Path<'a>,
35    name: Str<'a>,
36    agg: Str<'a>,
37    extent: Option<Extent>,
38    tpl: Option<Template<'a>>,
39    value: Value<'a>,
40    props: P,
41}
42
43impl<'a, P> Metric<'a, P> {
44    /**
45    Create a new metric from its properties.
46
47    Each metric consists of:
48
49    - `mdl`: The module that owns the underlying data source.
50    - `extent`: The [`Extent`] that the sample covers.
51    - `name`: The name of the underlying data source.
52    - `agg`: The aggregation applied to the underlying data source to produce the sample. See the [`crate::metric`] module for details.
53    - `value`: The value of the sample itself.
54    - `props`: Additional [`Props`] to associate with the sample.
55    */
56    pub fn new(
57        mdl: impl Into<Path<'a>>,
58        name: impl Into<Str<'a>>,
59        agg: impl Into<Str<'a>>,
60        extent: impl ToExtent,
61        value: impl Into<Value<'a>>,
62        props: P,
63    ) -> Self {
64        Metric {
65            mdl: mdl.into(),
66            extent: extent.to_extent(),
67            tpl: None,
68            name: name.into(),
69            agg: agg.into(),
70            value: value.into(),
71            props,
72        }
73    }
74
75    /**
76    Get the module that owns the underlying data source.
77    */
78    pub fn mdl(&self) -> &Path<'a> {
79        &self.mdl
80    }
81
82    /**
83    Set the module of the underlying data source to a new value.
84    */
85    pub fn with_mdl(mut self, mdl: impl Into<Path<'a>>) -> Self {
86        self.mdl = mdl.into();
87        self
88    }
89
90    /**
91    Get the name of the underlying data source.
92    */
93    pub fn name(&self) -> &Str<'a> {
94        &self.name
95    }
96
97    /**
98    Set the name of the underlying data source to a new value.
99    */
100    pub fn with_name(mut self, name: impl Into<Str<'a>>) -> Self {
101        self.name = name.into();
102        self
103    }
104
105    /**
106    Get the aggregation applied to the underlying data source to produce the sample.
107
108    The value of the aggregation should be one of the [`crate::well_known`] aggregation types.
109    */
110    pub fn agg(&self) -> &Str<'a> {
111        &self.agg
112    }
113
114    /**
115    Set the aggregation to a new value.
116
117    The value of the aggregation should be one of the [`crate::well_known`] aggregation types.
118    */
119    pub fn with_agg(mut self, agg: impl Into<Str<'a>>) -> Self {
120        self.agg = agg.into();
121        self
122    }
123
124    /**
125    Get the value of the sample itself.
126    */
127    pub fn value(&self) -> &Value<'a> {
128        &self.value
129    }
130
131    /**
132    Set the sample to a new value.
133    */
134    pub fn with_value(mut self, value: impl Into<Value<'a>>) -> Self {
135        self.value = value.into();
136        self
137    }
138
139    /**
140    Get the extent for which the sample was generated.
141    */
142    pub fn extent(&self) -> Option<&Extent> {
143        self.extent.as_ref()
144    }
145
146    /**
147    Set the extent of the sample to a new value.
148    */
149    pub fn with_extent(mut self, extent: impl ToExtent) -> Self {
150        self.extent = extent.to_extent();
151        self
152    }
153
154    /**
155    Get the extent of the metric as a point in time.
156
157    If the metric has an extent then this method will return `Some`, with the result of [`Extent::as_point`]. If the metric doesn't have an extent then this method will return `None`.
158    */
159    pub fn ts(&self) -> Option<&Timestamp> {
160        self.extent.as_ref().map(|extent| extent.as_point())
161    }
162
163    /**
164    Get the start point of the extent of the metric.
165
166    If the metric has an extent, and that extent covers a timespan then this method will return `Some`. Otherwise this method will return `None`.
167    */
168    pub fn ts_start(&self) -> Option<&Timestamp> {
169        self.extent
170            .as_ref()
171            .and_then(|extent| extent.as_range())
172            .map(|span| &span.start)
173    }
174
175    /**
176    Get the template that will be used to render the metric.
177    */
178    pub fn tpl(&self) -> &Template<'a> {
179        self.tpl.as_ref().unwrap_or(&TEMPLATE)
180    }
181
182    /**
183    Set the template of the metric.
184    */
185    pub fn with_tpl(mut self, tpl: impl Into<Template<'a>>) -> Self {
186        self.tpl = Some(tpl.into());
187        self
188    }
189
190    /**
191    Get the additional properties associated with the sample.
192    */
193    pub fn props(&self) -> &P {
194        &self.props
195    }
196
197    /**
198    Set the additional properties associated with the sample to a new value.
199    */
200    pub fn with_props<U>(self, props: U) -> Metric<'a, U> {
201        Metric {
202            mdl: self.mdl,
203            extent: self.extent,
204            tpl: self.tpl,
205            name: self.name,
206            agg: self.agg,
207            value: self.value,
208            props,
209        }
210    }
211
212    /**
213    Map the properties of the metric.
214    */
215    pub fn map_props<U>(self, map: impl FnOnce(P) -> U) -> Metric<'a, U> {
216        Metric {
217            mdl: self.mdl,
218            extent: self.extent,
219            tpl: self.tpl,
220            name: self.name,
221            agg: self.agg,
222            value: self.value,
223            props: map(self.props),
224        }
225    }
226}
227
228impl<'a, P: Props> ToEvent for Metric<'a, P> {
229    type Props<'b>
230        = &'b Self
231    where
232        Self: 'b;
233
234    fn to_event<'b>(&'b self) -> Event<'b, Self::Props<'b>> {
235        Event::new(
236            self.mdl.by_ref(),
237            self.tpl().by_ref(),
238            self.extent.clone(),
239            self,
240        )
241    }
242}
243
244impl<'a, P: Props> Metric<'a, P> {
245    /**
246    Get a new metric sample, borrowing data from this one.
247    */
248    pub fn by_ref<'b>(&'b self) -> Metric<'b, &'b P> {
249        Metric {
250            mdl: self.mdl.by_ref(),
251            extent: self.extent.clone(),
252            tpl: self.tpl.as_ref().map(|tpl| tpl.by_ref()),
253            name: self.name.by_ref(),
254            agg: self.agg.by_ref(),
255            value: self.value.by_ref(),
256            props: &self.props,
257        }
258    }
259
260    /**
261    Get a type-erased metric sample, borrowing data from this one.
262    */
263    pub fn erase<'b>(&'b self) -> Metric<'b, &'b dyn ErasedProps> {
264        Metric {
265            mdl: self.mdl.by_ref(),
266            extent: self.extent.clone(),
267            tpl: self.tpl.as_ref().map(|tpl| tpl.by_ref()),
268            name: self.name.by_ref(),
269            agg: self.agg.by_ref(),
270            value: self.value.by_ref(),
271            props: &self.props,
272        }
273    }
274}
275
276impl<'a, P> ToExtent for Metric<'a, P> {
277    fn to_extent(&self) -> Option<Extent> {
278        self.extent.clone()
279    }
280}
281
282impl<'a, P: Props> Props for Metric<'a, P> {
283    fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
284        &'kv self,
285        mut for_each: F,
286    ) -> ControlFlow<()> {
287        for_each(KEY_EVT_KIND.to_str(), Kind::Metric.to_value())?;
288        for_each(KEY_METRIC_NAME.to_str(), self.name.to_value())?;
289        for_each(KEY_METRIC_AGG.to_str(), self.agg.to_value())?;
290        for_each(KEY_METRIC_VALUE.to_str(), self.value.by_ref())?;
291
292        self.props.for_each(for_each)
293    }
294}
295
296// "{metric_agg} of {metric_name} is {metric_value}"
297const TEMPLATE_PARTS: &'static [template::Part<'static>] = &[
298    template::Part::hole("metric_agg"),
299    template::Part::text(" of "),
300    template::Part::hole("metric_name"),
301    template::Part::text(" is "),
302    template::Part::hole("metric_value"),
303];
304
305static TEMPLATE: Template<'static> = Template::new(TEMPLATE_PARTS);
306
307pub mod source {
308    /*!
309    The [`Source`] type.
310
311    [`Source`]s produce [`Metric`]s on-demand. They can be sampled directly, or combined with a [`crate::metric::Reporter`] and sampled together.
312    */
313
314    use self::sampler::ErasedSampler;
315
316    use super::*;
317
318    /**
319    A source of [`Metric`]s.
320    */
321    pub trait Source {
322        /**
323        Produce a current sample for all metrics in the source.
324        */
325        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S);
326
327        /**
328        Chain this source to `other`, sampling metrics from both.
329        */
330        fn and_sample<U>(self, other: U) -> And<Self, U>
331        where
332            Self: Sized,
333        {
334            And::new(self, other)
335        }
336    }
337
338    impl<'a, T: Source + ?Sized> Source for &'a T {
339        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
340            (**self).sample_metrics(sampler)
341        }
342    }
343
344    impl<T: Source> Source for Option<T> {
345        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
346            if let Some(source) = self {
347                source.sample_metrics(sampler);
348            }
349        }
350    }
351
352    #[cfg(feature = "alloc")]
353    impl<'a, T: Source + ?Sized + 'a> Source for alloc::boxed::Box<T> {
354        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
355            (**self).sample_metrics(sampler)
356        }
357    }
358
359    #[cfg(feature = "alloc")]
360    impl<'a, T: Source + ?Sized + 'a> Source for alloc::sync::Arc<T> {
361        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
362            (**self).sample_metrics(sampler)
363        }
364    }
365
366    impl<T: Source, U: Source> Source for And<T, U> {
367        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
368            self.left().sample_metrics(&sampler);
369            self.right().sample_metrics(&sampler);
370        }
371    }
372
373    impl<T: Source, U: Source> Source for Or<T, U> {
374        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
375            self.left().sample_metrics(&sampler);
376            self.right().sample_metrics(&sampler);
377        }
378    }
379
380    /**
381    A [`Source`] from a function.
382
383    This type can be created directly, or via [`from_fn`].
384    */
385    pub struct FromFn<F = fn(&mut dyn ErasedSampler)>(F);
386
387    /**
388    Create a [`Source`] from a function.
389    */
390    pub const fn from_fn<F: Fn(&mut dyn ErasedSampler)>(source: F) -> FromFn<F> {
391        FromFn::new(source)
392    }
393
394    impl<F> FromFn<F> {
395        /**
396        Wrap the given source function.
397        */
398        pub const fn new(source: F) -> Self {
399            FromFn(source)
400        }
401    }
402
403    impl<F: Fn(&mut dyn ErasedSampler)> Source for FromFn<F> {
404        fn sample_metrics<S: sampler::Sampler>(&self, mut sampler: S) {
405            (self.0)(&mut sampler)
406        }
407    }
408
409    mod internal {
410        use super::*;
411
412        pub trait DispatchSource {
413            fn dispatch_sample_metrics(&self, sampler: &dyn sampler::ErasedSampler);
414        }
415
416        pub trait SealedSource {
417            fn erase_source(&self) -> crate::internal::Erased<&dyn DispatchSource>;
418        }
419    }
420
421    /**
422    An object-safe [`Source`].
423
424    A `dyn ErasedSource` can be treated as `impl Source`.
425    */
426    pub trait ErasedSource: internal::SealedSource {}
427
428    impl<T: Source> ErasedSource for T {}
429
430    impl<T: Source> internal::SealedSource for T {
431        fn erase_source(&self) -> crate::internal::Erased<&dyn internal::DispatchSource> {
432            crate::internal::Erased(self)
433        }
434    }
435
436    impl<T: Source> internal::DispatchSource for T {
437        fn dispatch_sample_metrics(&self, sampler: &dyn sampler::ErasedSampler) {
438            self.sample_metrics(sampler)
439        }
440    }
441
442    impl<'a> Source for dyn ErasedSource + 'a {
443        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
444            self.erase_source().0.dispatch_sample_metrics(&sampler)
445        }
446    }
447
448    impl<'a> Source for dyn ErasedSource + Send + Sync + 'a {
449        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
450            (self as &(dyn ErasedSource + 'a)).sample_metrics(sampler)
451        }
452    }
453
454    #[cfg(test)]
455    mod tests {
456        use super::*;
457        use std::cell::Cell;
458
459        #[test]
460        fn source_sample_emit() {
461            struct MySource;
462
463            impl Source for MySource {
464                fn sample_metrics<S: Sampler>(&self, sampler: S) {
465                    sampler.metric(Metric::new(
466                        Path::new_raw("test"),
467                        "metric 1",
468                        "count",
469                        crate::Empty,
470                        42,
471                        crate::Empty,
472                    ));
473
474                    sampler.metric(Metric::new(
475                        Path::new_raw("test"),
476                        "metric 2",
477                        "count",
478                        crate::Empty,
479                        42,
480                        crate::Empty,
481                    ));
482                }
483            }
484
485            let calls = Cell::new(0);
486
487            MySource.sample_metrics(sampler::from_fn(|_| {
488                calls.set(calls.get() + 1);
489            }));
490
491            assert_eq!(2, calls.get());
492        }
493
494        #[test]
495        fn and_sample() {
496            let calls = Cell::new(0);
497
498            from_fn(|sampler| {
499                sampler.metric(Metric::new(
500                    Path::new_raw("test"),
501                    "metric 1",
502                    "count",
503                    crate::Empty,
504                    42,
505                    crate::Empty,
506                ));
507            })
508            .and_sample(from_fn(|sampler| {
509                sampler.metric(Metric::new(
510                    Path::new_raw("test"),
511                    "metric 2",
512                    "count",
513                    crate::Empty,
514                    42,
515                    crate::Empty,
516                ));
517            }))
518            .sample_metrics(sampler::from_fn(|_| {
519                calls.set(calls.get() + 1);
520            }));
521
522            assert_eq!(2, calls.get());
523        }
524
525        #[test]
526        fn from_fn_source() {
527            let calls = Cell::new(0);
528
529            from_fn(|sampler| {
530                sampler.metric(Metric::new(
531                    Path::new_raw("test"),
532                    "metric 1",
533                    "count",
534                    crate::Empty,
535                    42,
536                    crate::Empty,
537                ));
538
539                sampler.metric(Metric::new(
540                    Path::new_raw("test"),
541                    "metric 2",
542                    "count",
543                    crate::Empty,
544                    42,
545                    crate::Empty,
546                ));
547            })
548            .sample_metrics(sampler::from_fn(|_| {
549                calls.set(calls.get() + 1);
550            }));
551
552            assert_eq!(2, calls.get());
553        }
554
555        #[test]
556        fn erased_source() {
557            let source = from_fn(|sampler| {
558                sampler.metric(Metric::new(
559                    Path::new_raw("test"),
560                    "metric 1",
561                    "count",
562                    crate::Empty,
563                    42,
564                    crate::Empty,
565                ));
566
567                sampler.metric(Metric::new(
568                    Path::new_raw("test"),
569                    "metric 2",
570                    "count",
571                    crate::Empty,
572                    42,
573                    crate::Empty,
574                ));
575            });
576
577            let source = &source as &dyn ErasedSource;
578
579            let calls = Cell::new(0);
580
581            source.sample_metrics(sampler::from_fn(|_| {
582                calls.set(calls.get() + 1);
583            }));
584
585            assert_eq!(2, calls.get());
586        }
587    }
588}
589
590#[cfg(feature = "alloc")]
591mod alloc_support {
592    use super::*;
593
594    use alloc::{boxed::Box, vec::Vec};
595    use core::ops::Range;
596
597    use crate::{
598        clock::{Clock, ErasedClock},
599        metric::source::{ErasedSource, Source},
600    };
601
602    /**
603    A set of [`Source`]s that are all sampled together.
604
605    The reporter can be sampled like any other source through its own [`Source`] implementation.
606
607    # Normalization
608
609    The reporter will attempt to normalize the extents of any metrics sampled from its sources. Normalization will:
610
611    1. Take the current timestamp, `now`, when sampling metrics.
612    2. If the metric sample has no extent, or has a point extent, it will be replaced with `now`.
613    3. If the metric sample has a range extent, the end will be set to `now` and the start will be `now` minus the original length. If this would produce an invalid range then the original is kept.
614
615    When the `std` Cargo feature is enabled this will be done automatically. In other cases, normalization won't happen unless it's configured by [`Reporter::normalize_with_clock`].
616
617    Normalization can be disabled by calling [`Reporter::without_normalization`].
618    */
619    pub struct Reporter {
620        sources: Vec<Box<dyn ErasedSource + Send + Sync>>,
621        clock: ReporterClock,
622    }
623
624    impl Reporter {
625        /**
626        Create a new empty reporter.
627
628        When the `std` Cargo feature is enabled, the reporter will normalize timestamps on reported samples using the system clock.
629        When the `std` Cargo feature is not enabled, the reporter will not attempt to normalize timestamps.
630        */
631        pub const fn new() -> Self {
632            Reporter {
633                sources: Vec::new(),
634                clock: {
635                    #[cfg(feature = "std")]
636                    {
637                        ReporterClock::System
638                    }
639                    #[cfg(not(feature = "std"))]
640                    {
641                        ReporterClock::Other(None)
642                    }
643                },
644            }
645        }
646
647        /**
648        Set the clock the reporter will use to unify timestamps on sampled metrics.
649        */
650        pub fn normalize_with_clock(
651            &mut self,
652            clock: impl Clock + Send + Sync + 'static,
653        ) -> &mut Self {
654            self.clock = ReporterClock::Other(Some(Box::new(clock)));
655
656            self
657        }
658
659        /**
660        Disable the clock, preventing the reporter from normalizing timestamps on sampled metrics.
661        */
662        pub fn without_normalization(&mut self) -> &mut Self {
663            self.clock = ReporterClock::Other(None);
664
665            self
666        }
667
668        /**
669        Add a [`Source`] to the reporter.
670        */
671        pub fn add_source(&mut self, source: impl Source + Send + Sync + 'static) -> &mut Self {
672            self.sources.push(Box::new(source));
673
674            self
675        }
676
677        /**
678        Produce a current sample for all metrics.
679        */
680        pub fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
681            let sampler = TimeNormalizer::new(self.clock.now(), sampler);
682
683            for source in &self.sources {
684                source.sample_metrics(&sampler);
685            }
686        }
687
688        /**
689        Produce a current sample for all metrics, emitting them as diagnostic events to the given [`Emitter`].
690        */
691        pub fn emit_metrics<E: Emitter>(&self, emitter: E) {
692            self.sample_metrics(sampler::from_emitter(emitter))
693        }
694    }
695
696    impl Source for Reporter {
697        fn sample_metrics<S: sampler::Sampler>(&self, sampler: S) {
698            self.sample_metrics(sampler)
699        }
700    }
701
702    struct TimeNormalizer<S> {
703        now: Option<Timestamp>,
704        inner: S,
705    }
706
707    impl<S> TimeNormalizer<S> {
708        fn new(now: Option<Timestamp>, sampler: S) -> TimeNormalizer<S> {
709            TimeNormalizer {
710                now,
711                inner: sampler,
712            }
713        }
714    }
715
716    impl<S: Sampler> Sampler for TimeNormalizer<S> {
717        fn metric<P: Props>(&self, metric: Metric<P>) {
718            if let Some(now) = self.now {
719                let extent = metric.extent();
720
721                let extent = if let Some(range) = extent.and_then(|extent| extent.as_range()) {
722                    // If the extent is a range then attempt to normalize it
723                    normalize_range(now, range)
724                        .map(Extent::range)
725                        // If normalizing the range fails then use the original range
726                        .unwrap_or_else(|| Extent::range(range.clone()))
727                } else {
728                    // If the extent is missing or a point then use the value of now
729                    Extent::point(now)
730                };
731
732                self.inner.metric(metric.with_extent(extent))
733            } else {
734                self.inner.metric(metric)
735            }
736        }
737    }
738
739    fn normalize_range(now: Timestamp, range: &Range<Timestamp>) -> Option<Range<Timestamp>> {
740        // Normalize a range by assigning its end bound to now
741        // and its start bound to now - length
742        let len = range.end.duration_since(range.start)?;
743        let start = now.checked_sub(len)?;
744
745        Some(start..now)
746    }
747
748    enum ReporterClock {
749        #[cfg(feature = "std")]
750        System,
751        Other(Option<Box<dyn ErasedClock + Send + Sync>>),
752    }
753
754    impl Clock for ReporterClock {
755        fn now(&self) -> Option<Timestamp> {
756            match self {
757                #[cfg(feature = "std")]
758                ReporterClock::System => crate::platform::system_clock::SystemClock::new().now(),
759                ReporterClock::Other(clock) => clock.now(),
760            }
761        }
762    }
763
764    #[cfg(test)]
765    mod tests {
766        use super::*;
767        use std::time::Duration;
768
769        #[test]
770        fn reporter_is_send_sync() {
771            fn check<T: Send + Sync>() {}
772
773            check::<Reporter>();
774        }
775
776        #[test]
777        #[cfg(not(miri))]
778        fn reporter_sample() {
779            use std::cell::Cell;
780
781            let mut reporter = Reporter::new();
782
783            reporter
784                .add_source(source::from_fn(|sampler| {
785                    sampler.metric(Metric::new(
786                        Path::new_raw("test"),
787                        "metric 1",
788                        "count",
789                        crate::Empty,
790                        42,
791                        crate::Empty,
792                    ));
793                }))
794                .add_source(source::from_fn(|sampler| {
795                    sampler.metric(Metric::new(
796                        Path::new_raw("test"),
797                        "metric 2",
798                        "count",
799                        crate::Empty,
800                        42,
801                        crate::Empty,
802                    ));
803                }));
804
805            let calls = Cell::new(0);
806
807            reporter.sample_metrics(sampler::from_fn(|_| {
808                calls.set(calls.get() + 1);
809            }));
810
811            assert_eq!(2, calls.get());
812        }
813
814        struct TestClock(Option<Timestamp>);
815
816        impl Clock for TestClock {
817            fn now(&self) -> Option<Timestamp> {
818                self.0
819            }
820        }
821
822        #[test]
823        #[cfg(all(feature = "std", not(miri)))]
824        fn reporter_normalize_std() {
825            let mut reporter = Reporter::new();
826
827            reporter.add_source(source::from_fn(|sampler| {
828                sampler.metric(Metric::new(
829                    Path::new_raw("test"),
830                    "metric 1",
831                    "count",
832                    crate::Empty,
833                    42,
834                    crate::Empty,
835                ));
836            }));
837
838            reporter.sample_metrics(sampler::from_fn(|metric| {
839                assert!(metric.extent().is_some());
840            }));
841        }
842
843        #[test]
844        fn reporter_normalize_empty_extent() {
845            let mut reporter = Reporter::new();
846
847            reporter.normalize_with_clock(TestClock(Some(Timestamp::MIN)));
848
849            reporter.add_source(source::from_fn(|sampler| {
850                sampler.metric(Metric::new(
851                    Path::new_raw("test"),
852                    "metric 1",
853                    "count",
854                    crate::Empty,
855                    42,
856                    crate::Empty,
857                ));
858            }));
859
860            reporter.sample_metrics(sampler::from_fn(|metric| {
861                assert_eq!(Timestamp::MIN, metric.extent().unwrap().as_point());
862            }));
863        }
864
865        #[test]
866        fn reporter_normalize_point_extent() {
867            let mut reporter = Reporter::new();
868
869            reporter.normalize_with_clock(TestClock(Some(
870                Timestamp::from_unix(Duration::from_secs(37)).unwrap(),
871            )));
872
873            reporter.add_source(source::from_fn(|sampler| {
874                sampler.metric(Metric::new(
875                    Path::new_raw("test"),
876                    "metric 1",
877                    "count",
878                    Timestamp::from_unix(Duration::from_secs(100)).unwrap(),
879                    42,
880                    crate::Empty,
881                ));
882            }));
883
884            reporter.sample_metrics(sampler::from_fn(|metric| {
885                assert_eq!(
886                    Timestamp::from_unix(Duration::from_secs(37)).unwrap(),
887                    metric.extent().unwrap().as_point()
888                );
889            }));
890        }
891
892        #[test]
893        fn reporter_normalize_range_extent() {
894            let mut reporter = Reporter::new();
895
896            reporter.normalize_with_clock(TestClock(Some(
897                Timestamp::from_unix(Duration::from_secs(350)).unwrap(),
898            )));
899
900            reporter.add_source(source::from_fn(|sampler| {
901                sampler.metric(Metric::new(
902                    Path::new_raw("test"),
903                    "metric 1",
904                    "count",
905                    Timestamp::from_unix(Duration::from_secs(100)).unwrap()
906                        ..Timestamp::from_unix(Duration::from_secs(200)).unwrap(),
907                    42,
908                    crate::Empty,
909                ));
910            }));
911
912            reporter.sample_metrics(sampler::from_fn(|metric| {
913                assert_eq!(
914                    Timestamp::from_unix(Duration::from_secs(250)).unwrap()
915                        ..Timestamp::from_unix(Duration::from_secs(350)).unwrap(),
916                    metric.extent().unwrap().as_range().unwrap().clone()
917                );
918            }));
919        }
920    }
921}
922
923#[cfg(feature = "alloc")]
924pub use self::alloc_support::*;
925
926pub mod sampler {
927    /*!
928    The [`Sampler`] type.
929
930    A [`Sampler`] is a visitor for a [`Source`] that receives [`Metric`]s when the source is sampled.
931    */
932
933    use emit_core::empty::Empty;
934
935    use super::*;
936
937    /**
938    A receiver of [`Metric`]s as produced by a [`Source`].
939    */
940    pub trait Sampler {
941        /**
942        Receive a metric sample.
943        */
944        fn metric<P: Props>(&self, metric: Metric<P>);
945    }
946
947    impl<'a, T: Sampler + ?Sized> Sampler for &'a T {
948        fn metric<P: Props>(&self, metric: Metric<P>) {
949            (**self).metric(metric)
950        }
951    }
952
953    impl Sampler for Empty {
954        fn metric<P: Props>(&self, _: Metric<P>) {}
955    }
956
957    /**
958    A [`Sampler`] from an [`Emitter`].
959
960    On completion, a [`Metric`] will be emitted as an event using [`Metric::to_event`].
961
962    This type can be created directly, or via [`from_emitter`].
963    */
964    pub struct FromEmitter<E>(E);
965
966    impl<E: Emitter> Sampler for FromEmitter<E> {
967        fn metric<P: Props>(&self, metric: Metric<P>) {
968            self.0.emit(metric)
969        }
970    }
971
972    impl<E> FromEmitter<E> {
973        /**
974        Wrap the given emitter.
975        */
976        pub const fn new(emitter: E) -> Self {
977            FromEmitter(emitter)
978        }
979    }
980
981    /**
982    A [`Sampler`] from an [`Emitter`].
983
984    On completion, a [`Metric`] will be emitted as an event using [`Metric::to_event`].
985    */
986    pub const fn from_emitter<E: Emitter>(emitter: E) -> FromEmitter<E> {
987        FromEmitter(emitter)
988    }
989
990    /**
991    A [`Sampler`] from a function.
992
993    This type can be created directly, or via [`from_fn`].
994    */
995    pub struct FromFn<F = fn(Metric<&dyn ErasedProps>)>(F);
996
997    /**
998    Create a [`Sampler`] from a function.
999    */
1000    pub const fn from_fn<F: Fn(Metric<&dyn ErasedProps>)>(f: F) -> FromFn<F> {
1001        FromFn(f)
1002    }
1003
1004    impl<F> FromFn<F> {
1005        /**
1006        Wrap the given sampler function.
1007        */
1008        pub const fn new(sampler: F) -> FromFn<F> {
1009            FromFn(sampler)
1010        }
1011    }
1012
1013    impl<F: Fn(Metric<&dyn ErasedProps>)> Sampler for FromFn<F> {
1014        fn metric<P: Props>(&self, metric: Metric<P>) {
1015            (self.0)(metric.erase())
1016        }
1017    }
1018
1019    mod internal {
1020        use super::*;
1021
1022        pub trait DispatchSampler {
1023            fn dispatch_metric(&self, metric: Metric<&dyn ErasedProps>);
1024        }
1025
1026        pub trait SealedSampler {
1027            fn erase_sampler(&self) -> crate::internal::Erased<&dyn DispatchSampler>;
1028        }
1029    }
1030
1031    /**
1032    An object-safe [`Sampler`].
1033
1034    A `dyn ErasedSampler` can be treated as `impl Sampler`.
1035    */
1036    pub trait ErasedSampler: internal::SealedSampler {}
1037
1038    impl<T: Sampler> ErasedSampler for T {}
1039
1040    impl<T: Sampler> internal::SealedSampler for T {
1041        fn erase_sampler(&self) -> crate::internal::Erased<&dyn internal::DispatchSampler> {
1042            crate::internal::Erased(self)
1043        }
1044    }
1045
1046    impl<T: Sampler> internal::DispatchSampler for T {
1047        fn dispatch_metric(&self, metric: Metric<&dyn ErasedProps>) {
1048            self.metric(metric)
1049        }
1050    }
1051
1052    impl<'a> Sampler for dyn ErasedSampler + 'a {
1053        fn metric<P: Props>(&self, metric: Metric<P>) {
1054            self.erase_sampler().0.dispatch_metric(metric.erase())
1055        }
1056    }
1057
1058    impl<'a> Sampler for dyn ErasedSampler + Send + Sync + 'a {
1059        fn metric<P: Props>(&self, metric: Metric<P>) {
1060            (self as &(dyn ErasedSampler + 'a)).metric(metric)
1061        }
1062    }
1063
1064    #[cfg(test)]
1065    mod tests {
1066        use super::*;
1067        use std::cell::Cell;
1068
1069        #[test]
1070        fn from_fn_sampler() {
1071            let called = Cell::new(false);
1072
1073            let sampler = from_fn(|metric| {
1074                assert_eq!("test", metric.name());
1075
1076                called.set(true);
1077            });
1078
1079            sampler.metric(Metric::new(
1080                Path::new_raw("test"),
1081                "test",
1082                "count",
1083                Empty,
1084                1,
1085                Empty,
1086            ));
1087
1088            assert!(called.get());
1089        }
1090
1091        #[test]
1092        fn erased_sampler() {
1093            let called = Cell::new(false);
1094
1095            let sampler = from_fn(|metric| {
1096                assert_eq!("test", metric.name());
1097
1098                called.set(true);
1099            });
1100
1101            let sampler = &sampler as &dyn ErasedSampler;
1102
1103            sampler.metric(Metric::new(
1104                Path::new_raw("test"),
1105                "test",
1106                "count",
1107                Empty,
1108                1,
1109                Empty,
1110            ));
1111
1112            assert!(called.get());
1113        }
1114    }
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119    use super::*;
1120    use std::time::Duration;
1121
1122    use crate::Timestamp;
1123
1124    #[test]
1125    fn metric_new() {
1126        let metric = Metric::new(
1127            Path::new_raw("test"),
1128            "my metric",
1129            "count",
1130            Timestamp::from_unix(Duration::from_secs(1)),
1131            42,
1132            ("metric_prop", true),
1133        );
1134
1135        assert_eq!("test", metric.mdl());
1136        assert_eq!(
1137            Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
1138            metric.extent().unwrap().as_point()
1139        );
1140        assert_eq!("my metric", metric.name());
1141        assert_eq!("count", metric.agg());
1142        assert_eq!(42, metric.value().by_ref().cast::<i32>().unwrap());
1143        assert_eq!(true, metric.props().pull::<bool, _>("metric_prop").unwrap());
1144    }
1145
1146    #[test]
1147    fn metric_to_event() {
1148        let metric = Metric::new(
1149            Path::new_raw("test"),
1150            "my metric",
1151            "count",
1152            Timestamp::from_unix(Duration::from_secs(1)),
1153            42,
1154            ("metric_prop", true),
1155        );
1156
1157        let evt = metric.to_event();
1158
1159        assert_eq!("test", evt.mdl());
1160        assert_eq!(
1161            Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
1162            evt.extent().unwrap().as_point()
1163        );
1164        assert_eq!("count of my metric is 42", evt.msg().to_string());
1165        assert_eq!("count", evt.props().pull::<Str, _>(KEY_METRIC_AGG).unwrap());
1166        assert_eq!(42, evt.props().pull::<i32, _>(KEY_METRIC_VALUE).unwrap());
1167        assert_eq!(
1168            "my metric",
1169            evt.props().pull::<Str, _>(KEY_METRIC_NAME).unwrap()
1170        );
1171        assert_eq!(true, evt.props().pull::<bool, _>("metric_prop").unwrap());
1172        assert_eq!(
1173            Kind::Metric,
1174            evt.props().pull::<Kind, _>(KEY_EVT_KIND).unwrap()
1175        );
1176    }
1177
1178    #[test]
1179    fn metric_to_event_uses_tpl() {
1180        assert_eq!(
1181            "test",
1182            Metric::new(
1183                Path::new_raw("test"),
1184                "my metric",
1185                "count",
1186                Timestamp::from_unix(Duration::from_secs(1)),
1187                42,
1188                ("metric_prop", true),
1189            )
1190            .with_tpl(Template::literal("test"))
1191            .to_event()
1192            .msg()
1193            .to_string(),
1194        );
1195    }
1196
1197    #[test]
1198    fn metric_to_extent() {
1199        for (case, expected) in [
1200            (
1201                Some(Timestamp::from_unix(Duration::from_secs(1)).unwrap()),
1202                Some(Extent::point(
1203                    Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
1204                )),
1205            ),
1206            (None, None),
1207        ] {
1208            let metric = Metric::new(
1209                Path::new_raw("test"),
1210                "my metric",
1211                "count",
1212                case,
1213                42,
1214                ("metric_prop", true),
1215            );
1216
1217            let extent = metric.to_extent();
1218
1219            assert_eq!(
1220                expected.map(|extent| extent.as_range().cloned()),
1221                extent.map(|extent| extent.as_range().cloned())
1222            );
1223        }
1224    }
1225}