stats/
macros.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under both the MIT license found in the
5 * LICENSE-MIT file in the root directory of this source tree and the Apache
6 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
7 * of this source tree.
8 */
9
10// This module defines only macros which don't show up on module level
11// documentation anyway so hide it.
12#![doc(hidden)]
13
14#[doc(hidden)]
15pub mod common_macro_prelude {
16    pub use std::sync::Arc;
17    pub use std::time::Duration;
18
19    pub use once_cell::sync::Lazy;
20    pub use perthread::PerThread;
21    pub use perthread::ThreadMap;
22    pub use stats_traits::dynamic_stat_types::DynamicStat;
23    pub use stats_traits::dynamic_stat_types::DynamicStatSync;
24    pub use stats_traits::field_stat_types::FieldStat;
25    pub use stats_traits::field_stat_types::FieldStatThreadLocal;
26    pub use stats_traits::stat_types::BoxCounter;
27    pub use stats_traits::stat_types::BoxHistogram;
28    pub use stats_traits::stat_types::BoxLocalCounter;
29    pub use stats_traits::stat_types::BoxLocalHistogram;
30    pub use stats_traits::stat_types::BoxLocalTimeseries;
31    pub use stats_traits::stat_types::BoxSingletonCounter;
32    pub use stats_traits::stat_types::BoxTimeseries;
33    pub use stats_traits::stats_manager::AggregationType::*;
34    pub use stats_traits::stats_manager::BoxStatsManager;
35    pub use stats_traits::stats_manager::BucketConfig;
36    pub use stats_traits::stats_manager::StatsManager;
37
38    pub use crate::create_singleton_counter;
39    pub use crate::create_stats_manager;
40    pub use crate::thread_local_aggregator::create_map;
41}
42
43/// The macro to define STATS module that contains static variables, one per
44/// counter you want to export. This is the main and recommended way to interact
45/// with statistics provided by this crate. If non empty prefix is passed then
46/// the exported counter name will be "{prefix}.{name}"
47///
48/// Examples:
49/// ```
50/// use stats::prelude::*;
51/// use fbinit::FacebookInit;
52///
53/// define_stats! {
54///     prefix = "my.test.counters";
55///     manual_c: singleton_counter(),
56///     test_c: counter(),
57///     test_c2: counter("test_c.two"),
58///     test_t: timeseries(Sum, Average),
59///     test_t2: timeseries("test_t.two"; Sum, Average),
60///     test_h: histogram(1, 0, 1000, Sum; P 99; P 50),
61///     dtest_c: dynamic_counter("test_c.{}", (job: u64)),
62///     dtest_t: dynamic_timeseries("test_t.{}", (region: &'static str); Rate, Sum),
63///     dtest_t2: dynamic_timeseries("test_t.two.{}.{}", (job: u64, region: &'static str); Count),
64///     dtest_h: dynamic_histogram("test_h.{}", (region: &'static str); 1, 0, 1000, Sum; P 99),
65///     test_qs: quantile_stat("test_qs"; Count, Sum, Average; P 95, P 99; Duration::from_secs(60)),
66///     test_qs_two: quantile_stat(Count, Sum, Average; P 95; Duration::from_secs(60)),
67///     test_dynqs: dynamic_quantile_stat("test_dynqs_{}", (num: i32); Count, Sum, Average; P 95, P 99; Duration::from_secs(60)),
68/// }
69///
70/// #[allow(non_snake_case)]
71/// mod ALT_STATS {
72///     use stats::define_stats;
73///     define_stats! {
74///         test_t: timeseries(Sum, Average),
75///         test_t2: timeseries("test.two"; Sum, Average),
76///     }
77///     pub use self::STATS::*;
78/// }
79///
80/// # #[allow(clippy::needless_doctest_main)]
81/// #[fbinit::main]
82/// fn main(fb: FacebookInit) {
83///     STATS::manual_c.set_value(fb, 1);
84///     STATS::test_c.increment_value(1);
85///     STATS::test_c2.increment_value(100);
86///     STATS::test_t.add_value(1);
87///     STATS::test_t2.add_value_aggregated(79, 10);  // Add 79 and note it came from 10 samples
88///     STATS::test_h.add_value(1);
89///     STATS::test_h.add_repeated_value(1, 44);  // 44 times repeat adding 1
90///     STATS::dtest_c.increment_value(7, (1000,));
91///     STATS::dtest_t.add_value(77, ("lla",));
92///     STATS::dtest_t2.add_value_aggregated(81, 12, (7, "lla"));
93///     STATS::dtest_h.add_value(2, ("frc",));
94///
95///     ALT_STATS::test_t.add_value(1);
96///     ALT_STATS::test_t2.add_value(1);
97/// }
98/// ```
99///
100/// # Reference
101///
102/// ## `singleton_counter`, `counter`
103///
104/// **DEPRECATED:** Use `timeseries` instead.
105///
106/// Raw counter types. These take an optional key parameter - if it is not
107/// specified, then it's derived from the stat field name.
108///
109/// ## `timeseries`
110/// The general syntax for `timeseries` is:
111/// ```text
112/// timeseries(<optional key>; <list of aggregations>; [optional intervals])
113/// ```
114///
115/// If "optional key" is omitted then key is derived from the stat key name;
116/// otherwise it's a string literal.
117///
118/// "List of aggregations" is a `,`-separated list of
119/// [`AggregationType`](stats_traits::stats_manager::AggregationType) enum
120/// values.
121///
122/// "Optional intervals" is a `,`-separated list of [`Duration`s](std::time::Duration). It
123/// specifies over what time periods the aggregations aggregate. If not
124/// specified, it typically defaults to 60 seconds.
125///
126/// This maps to a call to
127/// [`StatsManager::create_timeseries`](stats_traits::stats_manager::StatsManager::create_histogram).
128///
129/// ## `histogram`
130///
131/// **DEPRECATED:** Use `quantile_stat` instead.
132///
133/// The general syntax for `histogram` is:
134/// ```text
135/// histogram(<optional key>; bucket-width, min, max, <list of aggregations>; <P XX percentiles>)
136/// ```
137/// If "optional key" is omitted then key is derived from the stat key name;
138/// otherwise it's a string literal.
139///
140/// `bucket-width` specifies what range of values are accumulated into each
141/// bucket; the larger it is the fewer buckets there are.
142///
143/// `min` and `max` specify the min and max of the expected range of samples.
144/// Samples outside this range will be aggregated into out-of-range buckets,
145/// which means they're not lost entirely but they lead to inaccurate stats.
146///
147/// Together the min, max and bucket width parameters determine how many buckets
148/// are created.
149///
150/// "List of aggregations" is a `,`-separated list of
151/// [`AggregationType`](stats_traits::stats_manager::AggregationType) enum
152/// values.
153///
154/// Percentiles are specified as `P NN` in a `;`-separated list, such as `P 20;
155/// P 50; P 90; P 99`...
156///
157/// This maps to a call to
158/// [`StatsManager::create_histogram`](stats_traits::stats_manager::StatsManager::create_histogram).
159///
160/// ## `quantile_stat`
161/// The general syntax for `quantile_stat` is:
162/// ```text
163/// quantile_stat(<optional key>; <list of aggregations>; <P XX percentiles>; <list of intervals>)
164/// ```
165///
166/// "List of aggregations" is a `,`-separated list of
167/// [`AggregationType`](stats_traits::stats_manager::AggregationType) enum
168/// values.
169///
170/// Percentiles are specified as `P NN` or `P NN.N` in a `,`-separated list, such as `P 20,
171/// P 50, P 90, P 99`...
172/// You can also use floating point values as well such as `P 95.0, P 99.0, P 99.99`...
173/// Please note that you provide "percentiles" instead of rates such as 0.95 like C++ API
174///
175/// "List of intervals" is a `,`-separated list of [`Duration`s](std::time::Duration). It
176/// specifies over what time periods the aggregations aggregate.
177///
178/// `quantile_stat` measures the same statistics as `histogram`, but it
179/// doesn't require buckets to be defined ahead of time. See [this workplace post](https://fb.workplace.com/notes/marc-celani/a-new-approach-to-quantile-estimation-in-c-services/212892662822481)
180/// for more.
181///
182///  This maps to a call to
183/// [`StatsManager::create_quantile_stat`](stats_traits::stats_manager::StatsManager::create_quantile_stat).
184///
185/// ## `dynamic_counter`, `dynamic_timeseries`, `dynamic_histogram`, `dynamic_quantile_stat`
186///
187/// These are equivalent to the corresponding `counter`/`timeseries`/`histogram`/`quantile_stat`
188/// above, except that they allow the key to have a dynamic component. The key
189/// is no longer optional, but is instead specified with `<format-string>,
190/// (variable:type, ...)`. The format string is standard
191/// [`format!`](std::format).
192#[macro_export]
193macro_rules! define_stats {
194    // Fill the optional prefix with empty string, all matching is repeated here to avoid the
195    // recursion limit reached error in case the macro is misused.
196    ($( $name:ident: $stat_type:tt($( $params:tt )*), )*) =>
197        (define_stats!(prefix = ""; $( $name: $stat_type($( $params )*), )*););
198
199    (prefix = $prefix:expr;
200     $( $name:ident: $stat_type:tt($( $params:tt )*), )*) => (
201        #[allow(non_snake_case, non_upper_case_globals, unused_imports)]
202        pub(crate) mod STATS {
203            use $crate::macros::common_macro_prelude::*;
204
205            static STATS_MAP: Lazy<Arc<ThreadMap<BoxStatsManager>>> = Lazy::new(|| create_map());
206            static STATS_MANAGER: Lazy<BoxStatsManager> = Lazy::new(|| create_stats_manager());
207
208            thread_local! {
209                static TL_STATS: PerThread<BoxStatsManager> =
210                    STATS_MAP.register(create_stats_manager());
211            }
212
213            $( $crate::__define_stat!($prefix; $name: $stat_type($( $params )*)); )*
214        }
215    );
216}
217
218#[doc(hidden)]
219#[macro_export]
220macro_rules! __define_key_generator {
221    ($name:ident($prefix:expr, $key:expr; $( $placeholder:ident: $type:ty ),+)) => (
222        fn $name(&($( ref $placeholder, )+): &($( $type, )+)) -> String {
223            let key = format!($key, $( $placeholder ),+);
224            if $prefix.is_empty() {
225                key
226            } else {
227                [$prefix, &key].join(".")
228            }
229        }
230    );
231}
232
233#[doc(hidden)]
234#[macro_export]
235macro_rules! __define_stat {
236    ($prefix:expr; $name:ident: singleton_counter()) => (
237        $crate::__define_stat!($prefix; $name: singleton_counter(stringify!($name)));
238    );
239
240    ($prefix:expr; $name:ident: singleton_counter($key:expr)) => (
241        pub static $name: Lazy<BoxSingletonCounter> = Lazy::new(|| create_singleton_counter($crate::__create_stat_key!($prefix, $key).to_string()));
242    );
243
244    ($prefix:expr; $name:ident: counter()) => (
245        $crate::__define_stat!($prefix; $name: counter(stringify!($name)));
246    );
247
248    ($prefix:expr; $name:ident: counter($key:expr)) => (
249        thread_local! {
250            pub static $name: BoxLocalCounter = TL_STATS.with(|stats| {
251                stats.create_counter(&$crate::__create_stat_key!($prefix, $key))
252            });
253        }
254    );
255
256    // There are 4 inputs we use to produce a timeseries: the the prefix, the name (used in
257    // STATS::name), the key (used in ODS or to query the key), the export types (SUM, RATE, etc.),
258    // and the intervals (e.g. 60, 600). The key defaults to the name, and the intervals default to
259    // whatever default Folly uses (which happens to be 60, 600, 3600);
260    ($prefix:expr; $name:ident: timeseries($( $aggregation_type:expr ),*)) => (
261        $crate::__define_stat!($prefix; $name: timeseries(stringify!($name); $( $aggregation_type ),*));
262    );
263    ($prefix:expr; $name:ident: timeseries($key:expr; $( $aggregation_type:expr ),*)) => (
264        $crate::__define_stat!($prefix; $name: timeseries($key; $( $aggregation_type ),* ; ));
265    );
266    ($prefix:expr; $name:ident: timeseries($key:expr; $( $aggregation_type:expr ),* ; $( $interval: expr ),*)) => (
267        thread_local! {
268            pub static $name: BoxLocalTimeseries = TL_STATS.with(|stats| {
269                stats.create_timeseries(
270                    &$crate::__create_stat_key!($prefix, $key),
271                    &[$( $aggregation_type ),*],
272                    &[$( $interval ),*]
273                )
274            });
275        }
276    );
277
278    ($prefix:expr;
279     $name:ident: histogram($bucket_width:expr,
280                            $min:expr,
281                            $max:expr
282                            $(, $aggregation_type:expr )*
283                            $(; P $percentile:expr )*)) => (
284        $crate::__define_stat!($prefix;
285                      $name: histogram(stringify!($name);
286                                       $bucket_width,
287                                       $min,
288                                       $max
289                                       $(, $aggregation_type )*
290                                       $(; P $percentile )*));
291    );
292
293    ($prefix:expr;
294     $name:ident: histogram($key:expr;
295                            $bucket_width:expr,
296                            $min:expr,
297                            $max:expr
298                            $(, $aggregation_type:expr )*
299                            $(; P $percentile:expr )*)) => (
300        thread_local! {
301            pub static $name: BoxLocalHistogram = TL_STATS.with(|stats| {
302                stats.create_histogram(
303                    &$crate::__create_stat_key!($prefix, $key),
304                    &[$( $aggregation_type ),*],
305                    BucketConfig {
306                        width: $bucket_width,
307                        min: $min,
308                        max: $max,
309                    },
310                    &[$( $percentile ),*])
311            });
312        }
313    );
314
315    ($prefix:expr;
316        $name:ident: quantile_stat(
317            $( $aggregation_type:expr ),*
318            ; $( P $percentile:expr ),*
319            ; $( $interval:expr ),*
320        )) => (
321            $crate::__define_stat!($prefix;
322                 $name: quantile_stat(stringify!($name)
323                                     ; $( $aggregation_type ),*
324                                     ; $( P $percentile ),*
325                                     ; $( $interval ),*));
326       );
327
328    ($prefix:expr;
329        $name:ident: quantile_stat($key:expr
330            ; $( $aggregation_type:expr ),*
331            ; $( P $percentile:expr ),*
332            ; $( $interval:expr ),*
333        )) => (
334                pub static $name: Lazy<BoxHistogram> = Lazy::new(|| {
335                    STATS_MANAGER.create_quantile_stat(
336                        &$crate::__create_stat_key!($prefix, $key),
337                        &[$( $aggregation_type ),*],
338                        &[$( $percentile as f32 ),*],
339                        &[$( $interval ),*],
340                    )
341                });
342
343       );
344
345    ($prefix:expr;
346     $name:ident: dynamic_singleton_counter($key:expr, ($( $placeholder:ident: $type:ty ),+))) => (
347        thread_local! {
348            pub static $name: DynamicStat<($( $type, )+), BoxSingletonCounter> = {
349                $crate::__define_key_generator!(
350                    __key_generator($prefix, $key; $( $placeholder: $type ),+)
351                );
352
353                fn __stat_generator(key: &str) -> BoxSingletonCounter {
354                    create_singleton_counter(key.to_string())
355                }
356
357                DynamicStat::new(__key_generator, __stat_generator)
358            }
359        }
360    );
361
362    ($prefix:expr;
363     $name:ident: dynamic_counter($key:expr, ($( $placeholder:ident: $type:ty ),+))) => (
364        thread_local! {
365            pub static $name: DynamicStat<($( $type, )+), BoxLocalCounter> = {
366                $crate::__define_key_generator!(
367                    __key_generator($prefix, $key; $( $placeholder: $type ),+)
368                );
369
370                fn __stat_generator(key: &str) -> BoxLocalCounter {
371                    TL_STATS.with(|stats| {
372                        stats.create_counter(key)
373                    })
374                }
375
376                DynamicStat::new(__key_generator, __stat_generator)
377            }
378        }
379    );
380
381    ($prefix:expr;
382     $name:ident: dynamic_timeseries($key:expr, ($( $placeholder:ident: $type:ty ),+);
383                                     $( $aggregation_type:expr ),*)) => (
384        $crate::__define_stat!(
385            $prefix;
386            $name: dynamic_timeseries(
387                $key,
388                ($( $placeholder: $type ),+);
389                $( $aggregation_type ),* ;
390            )
391        );
392    );
393
394    ($prefix:expr;
395     $name:ident: dynamic_timeseries($key:expr, ($( $placeholder:ident: $type:ty ),+);
396                                     $( $aggregation_type:expr ),* ; $( $interval:expr ),*)) => (
397        thread_local! {
398            pub static $name: DynamicStat<($( $type, )+), BoxLocalTimeseries> = {
399                $crate::__define_key_generator!(
400                    __key_generator($prefix, $key; $( $placeholder: $type ),+)
401                );
402
403                fn __stat_generator(key: &str) -> BoxLocalTimeseries {
404                    TL_STATS.with(|stats| {
405                        stats.create_timeseries(key, &[$( $aggregation_type ),*], &[$( $interval ),*])
406                    })
407                }
408
409                DynamicStat::new(__key_generator, __stat_generator)
410            };
411        }
412    );
413
414    ($prefix:expr;
415     $name:ident: dynamic_histogram($key:expr, ($( $placeholder:ident: $type:ty ),+);
416                                    $bucket_width:expr,
417                                    $min:expr,
418                                    $max:expr
419                                    $(, $aggregation_type:expr )*
420                                    $(; P $percentile:expr )*)) => (
421        thread_local! {
422            pub static $name: DynamicStat<($( $type, )+), BoxLocalHistogram> = {
423                $crate::__define_key_generator!(
424                    __key_generator($prefix, $key; $( $placeholder: $type ),+)
425                );
426
427                fn __stat_generator(key: &str) -> BoxLocalHistogram {
428                    TL_STATS.with(|stats| {
429                        stats.create_histogram(key,
430                                               &[$( $aggregation_type ),*],
431                                               BucketConfig {
432                                                   width: $bucket_width,
433                                                   min: $min,
434                                                   max: $max,
435                                               },
436                                               &[$( $percentile ),*])
437                    })
438                }
439
440                DynamicStat::new(__key_generator, __stat_generator)
441            };
442        }
443    );
444
445    ($prefix:expr;
446     $name:ident: dynamic_quantile_stat($key:expr, ($( $placeholder:ident: $type:ty ),+) ;
447                                        $( $aggregation_type:expr ),* ;
448                                        $( P $percentile:expr ),* ;
449                                        $( $interval:expr ),*)) => (
450                pub static $name: Lazy<DynamicStatSync<($( $type, )+), BoxHistogram>> = Lazy::new(|| {
451                    $crate::__define_key_generator!(
452                        __key_generator($prefix, $key; $( $placeholder: $type ),+)
453                    );
454
455                    fn __stat_generator(key: &str) -> BoxHistogram {
456                        STATS_MANAGER.create_quantile_stat(
457                            key,
458                            &[$( $aggregation_type ),*],
459                            &[$( $percentile as f32 ),*],
460                            &[$( $interval ),*],
461                        )
462                    }
463                    DynamicStatSync::new(__key_generator, __stat_generator)
464                });
465       );
466}
467
468#[doc(hidden)]
469#[macro_export]
470macro_rules! __create_stat_key {
471    ($prefix:expr, $key:expr) => {{
472        use std::borrow::Cow;
473        if $prefix.is_empty() {
474            Cow::Borrowed($key)
475        } else {
476            Cow::Owned(format!("{}.{}", $prefix, $key))
477        }
478    }};
479}
480
481/// Define a group of stats with dynamic names all parameterized by the same set of parameters.
482/// The intention is that when setting up a structure for some entity with associated stats, then
483/// the type produced by this macro can be included in that structure, and initialized with the
484/// appropriate name(s). This is more efficient than using single static "dynamic_" versions of
485/// the counters.
486///
487/// ```
488/// use stats::prelude::*;
489///
490/// define_stats_struct! {
491///    // struct name, key prefix template, key template params
492///    MyThingStat("things.{}.{}", mything_name: String, mything_idx: usize),
493///    cache_miss: counter() // default name from the field
494/// }
495///
496/// struct MyThing {
497///    stats: MyThingStat,
498/// }
499///
500/// impl MyThing {
501///    fn new(somename: String, someidx: usize) -> Self {
502///        MyThing {
503///           stats: MyThingStat::new(somename, someidx),
504///           //...
505///        }
506///    }
507/// }
508/// #
509/// # fn main() {}
510/// ```
511#[macro_export]
512macro_rules! define_stats_struct {
513    // Handle trailing comma
514    ($name:ident ($key:expr, $($pr_name:ident: $pr_type:ty),*) ,
515        $( $stat_name:ident: $stat_type:tt($( $params:tt )*) , )+) => {
516        define_stats_struct!($name ( $key, $($pr_name: $pr_type),*),
517            $($stat_name: $stat_type($($params)*)),* );
518    };
519
520    // Handle no params
521    ($name:ident ($key:expr) ,
522        $( $stat_name:ident: $stat_type:tt($( $params:tt )*) ),*) => {
523        define_stats_struct!($name ( $key, ),
524            $($stat_name: $stat_type($($params)*)),* );
525    };
526    ($name:ident ($key:expr) ,
527        $( $stat_name:ident: $stat_type:tt($( $params:tt )*) , )+) => {
528        define_stats_struct!($name ( $key, ),
529            $($stat_name: $stat_type($($params)*)),* );
530    };
531
532    // Define struct and its methods.
533    ($name:ident ($key:expr, $($pr_name:ident: $pr_type:ty),*) ,
534        $( $stat_name:ident: $stat_type:tt($( $params:tt )*) ),*) => {
535        #[allow(missing_docs)]
536        pub struct $name {
537            $(pub $stat_name: $crate::__struct_field_type!($stat_type), )*
538        }
539        impl $name {
540            #[allow(unused_imports, missing_docs, non_upper_case_globals)]
541            pub fn new($($pr_name: $pr_type),*) -> $name {
542                use $crate::macros::common_macro_prelude::*;
543
544
545                static STATS_MAP: Lazy<Arc<ThreadMap<BoxStatsManager>>> = Lazy::new(|| create_map());
546                static STATS_MANAGER: Lazy<BoxStatsManager> = Lazy::new(|| create_stats_manager());
547
548                thread_local! {
549                    static TL_STATS: PerThread<BoxStatsManager> =
550                        STATS_MAP.register(create_stats_manager());
551                }
552
553                $(
554                    $crate::__struct_thread_local_init! { $stat_name, $stat_type, $($params)* }
555                )*
556
557                let __prefix = format!($key, $($pr_name),*);
558
559                $name {
560                    $($stat_name: $crate::__struct_field_init!(__prefix, $stat_name, $stat_type, $($params)*)),*
561                }
562            }
563        }
564        impl std::fmt::Debug for $name {
565            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566                write!(fmt, "<{}>", stringify!($name))
567            }
568        }
569    }
570}
571
572#[macro_export]
573#[doc(hidden)]
574macro_rules! __struct_field_type {
575    (singleton_counter) => {
576        $crate::macros::common_macro_prelude::BoxSingletonCounter
577    };
578    (counter) => {
579        $crate::macros::common_macro_prelude::BoxCounter
580    };
581    (timeseries) => {
582        $crate::macros::common_macro_prelude::BoxTimeseries
583    };
584    (histogram) => {
585        $crate::macros::common_macro_prelude::BoxHistogram
586    };
587    (quantile_stat) => {
588        $crate::macros::common_macro_prelude::BoxHistogram
589    };
590}
591
592#[macro_export]
593#[doc(hidden)]
594macro_rules! __struct_thread_local_init {
595    ($name:ident, singleton_counter, ) => {
596        $crate::__struct_thread_local_init!($name, singleton_counter, stringify!($name))
597    };
598    ($name:ident, singleton_counter, $key:expr) => {};
599
600    ($name:ident, counter, ) => {
601        $crate::__struct_thread_local_init! { $name, counter, stringify!($name)}
602    };
603    ($name:ident, counter, $key:expr) => {
604        $crate::__struct_thread_local_init! { $name, counter, $key ; }
605    };
606    ($name:ident, counter, $key:expr ; ) => {
607        thread_local! {
608            static $name: FieldStatThreadLocal<BoxLocalCounter> = {
609
610                fn __stat_generator(key: &str) -> BoxLocalCounter {
611                    TL_STATS.with(|stats| {
612                        stats.create_counter(key)
613                    })
614                }
615
616                FieldStatThreadLocal::new(__stat_generator)
617            };
618        }
619    };
620
621    ($name:ident, timeseries, $( $aggregation_type:expr ),+) => {
622        $crate::__struct_thread_local_init! { $name, timeseries, stringify!($name) ; $($aggregation_type),*}
623    };
624    ($name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ) => {
625        $crate::__struct_thread_local_init! { $name, timeseries, $key ; $($aggregation_type),* ;}
626    };
627    ($name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ; $( $interval:expr ),* ) => {
628        thread_local! {
629
630            static $name: FieldStatThreadLocal<BoxLocalTimeseries> = {
631
632                fn __stat_generator(key: &str) -> BoxLocalTimeseries {
633                    TL_STATS.with(|stats| {
634                        stats.create_timeseries(key, &[$( $aggregation_type ),*], &[$( $interval ),*])
635                    })
636                }
637
638                FieldStatThreadLocal::new(__stat_generator)
639            };
640        }
641    };
642
643    ($name:ident, histogram,
644        $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)*
645        $(; P $percentile:expr )*) => {
646        $crate::__struct_thread_local_init! { $name, histogram,
647            stringify!($name) ; $bucket_width, $min, $max $(, $aggregation_type)*
648            $(; P $percentile)* }
649    };
650    ($name:ident, histogram, $key:expr ;
651        $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)*
652        $(; P $percentile:expr )*) => {
653
654        thread_local! {
655            static $name: FieldStatThreadLocal<BoxLocalHistogram> = {
656
657                fn __stat_generator(key: &str) -> BoxLocalHistogram {
658                    TL_STATS.with(|stats| {
659                        stats.create_histogram(key,
660                                               &[$( $aggregation_type ),*],
661                                               BucketConfig {
662                                                   width: $bucket_width,
663                                                   min: $min,
664                                                   max: $max,
665                                               },
666                                               &[$( $percentile ),*])
667                    })
668                }
669
670                FieldStatThreadLocal::new(__stat_generator)
671            };
672        }
673    };
674    ($name:ident, quantile_stat,
675        $( $aggregation_type:expr ),*
676        ; $( P $percentile:expr ),*
677        ; $( $interval:expr ),*) => ();
678    ($name:ident, quantile_stat, $key:expr
679        ; $( $aggregation_type:expr ),*
680        ; $( P $percentile:expr ),*
681        ; $( $interval:expr ),*) => ();
682}
683
684#[macro_export]
685#[doc(hidden)]
686macro_rules! __struct_field_init {
687    ($prefix:expr, $name:ident, singleton_counter, ) => {
688        $crate::__struct_field_init!($prefix, $name, singleton_counter, stringify!($name))
689    };
690    ($prefix:expr, $name:ident, singleton_counter, $key:expr) => {{ create_singleton_counter(format!("{}.{}", $prefix, $key)) }};
691
692    ($prefix:expr, $name:ident, counter, ) => {
693        $crate::__struct_field_init!($prefix, $name, counter, stringify!($name) ;)
694    };
695    ($prefix:expr, $name:ident, counter, $key:expr) => {
696        $crate::__struct_field_init!($prefix, $name, counter, $key ;)
697    };
698    ($prefix:expr, $name:ident, counter, $key:expr ; $(params:tt)*) => {{ Box::new(FieldStat::new(&$name, format!("{}.{}", $prefix, $key))) }};
699
700
701    ($prefix:expr, $name:ident, timeseries, $( $aggregation_type:expr ),+) => {
702        $crate::__struct_field_init!($prefix, $name, timeseries, stringify!($name) ; $($aggregation_type),*)
703    };
704    ($prefix:expr, $name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ) => {
705        $crate::__struct_field_init!($prefix, $name, timeseries, $key ; $($aggregation_type),* ;)
706    };
707    ($prefix:expr, $name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ; $( $interval:expr ),* ) => {{
708        Box::new(FieldStat::new(&$name, format!("{}.{}", $prefix, $key)))
709    }};
710
711    ($prefix:expr, $name:ident, histogram,
712        $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)*
713        $(; P $percentile:expr )*) => {
714        $crate::__struct_field_init!($prefix, $name, histogram,
715            stringify!($name) ; $bucket_width, $min, $max $(, $aggregation_type)*
716            $(; P $percentile)*)
717    };
718    ($prefix:expr, $name:ident, histogram, $key:expr ;
719        $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)*
720        $(; P $percentile:expr )*) => {{ Box::new(FieldStat::new(&$name, format!("{}.{}", $prefix, $key))) }};
721    ($prefix:expr, $name:ident, quantile_stat,
722        $( $aggregation_type:expr ),*
723        ; $( P $percentile:expr ),*
724        ; $( $interval:expr ),*) => {
725            $crate::__struct_field_init!($prefix, $name, quantile_stat,
726                stringify!($name)
727                ; $( $aggregation_type ),*
728                ; $( P $percentile ),*
729                ; $( $interval ),*)
730    };
731    ($prefix:expr, $name:ident, quantile_stat,
732        $key:expr
733        ; $( $aggregation_type:expr ),*
734        ; $( P $percentile:expr ),*
735        ; $( $interval:expr ),*) => {{
736            STATS_MANAGER.create_quantile_stat(
737                &$crate::__create_stat_key!($prefix, $key),
738                &[$( $aggregation_type ),*],
739                &[$( $percentile as f32 ),*],
740                &[$( $interval ),*],
741            )
742
743    }};
744}