shed/stats/src/macros.rs (273 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under both the MIT license found in the * LICENSE-MIT file in the root directory of this source tree and the Apache * License, Version 2.0 found in the LICENSE-APACHE file in the root directory * of this source tree. */ // This module defines only macros which don't show up on module level // documentation anyway so hide it. #![doc(hidden)] #[doc(hidden)] pub mod common_macro_prelude { pub use lazy_static::lazy_static; pub use perthread::{PerThread, ThreadMap}; pub use stats_traits::{ dynamic_stat_types::DynamicStat, stat_types::{BoxCounter, BoxHistogram, BoxSingletonCounter, BoxTimeseries}, stats_manager::{AggregationType::*, BoxStatsManager, BucketConfig, StatsManager}, }; pub use std::sync::Arc; pub use std::time::Duration; pub use crate::create_singleton_counter; pub use crate::create_stats_manager; pub use crate::thread_local_aggregator::create_map; } /// The macro to define STATS module that contains static variables, one per counter you want to /// export. This is the main and recomended way to interact with statistics provided by this crate. /// If non empty prefix is passed then the exported counter name will be "{prefix}.{name}" /// /// Examples: /// ``` /// use stats::prelude::*; /// use fbinit::FacebookInit; /// /// define_stats! { /// prefix = "my.test.counters"; /// manual_c: singleton_counter(), /// test_c: counter(), /// test_c2: counter("test_c.two"), /// test_t: timeseries(Sum, Average), /// test_t2: timeseries("test_t.two"; Sum, Average), /// test_h: histogram(1, 0, 1000, Sum; P 99; P 50), /// dtest_c: dynamic_counter("test_c.{}", (job: u64)), /// dtest_t: dynamic_timeseries("test_t.{}", (region: &'static str); Rate, Sum), /// dtest_t2: dynamic_timeseries("test_t.two.{}.{}", (job: u64, region: &'static str); Count), /// dtest_h: dynamic_histogram("test_h.{}", (region: &'static str); 1, 0, 1000, Sum; P 99), /// } /// /// #[allow(non_snake_case)] /// mod ALT_STATS { /// use stats::define_stats; /// define_stats! { /// test_t: timeseries(Sum, Average), /// test_t2: timeseries("test.two"; Sum, Average), /// } /// pub use self::STATS::*; /// } /// /// #[fbinit::main] /// fn main(fb: FacebookInit) { /// STATS::manual_c.set_value(fb, 1); /// STATS::test_c.increment_value(1); /// STATS::test_c2.increment_value(100); /// STATS::test_t.add_value(1); /// STATS::test_t2.add_value_aggregated(79, 10); // Add 79 and note it came from 10 samples /// STATS::test_h.add_value(1); /// STATS::test_h.add_repeated_value(1, 44); // 44 times repeat adding 1 /// STATS::dtest_c.increment_value(7, (1000,)); /// STATS::dtest_t.add_value(77, ("lla",)); /// STATS::dtest_t2.add_value_aggregated(81, 12, (7, "lla")); /// STATS::dtest_h.add_value(2, ("frc",)); /// /// ALT_STATS::test_t.add_value(1); /// ALT_STATS::test_t2.add_value(1); /// } /// ``` #[macro_export] macro_rules! define_stats { // Fill the optional prefix with empty string, all matching is repeated here to avoid the // recursion limit reached error in case the macro is misused. ($( $name:ident: $stat_type:tt($( $params:tt )*), )*) => (define_stats!(prefix = ""; $( $name: $stat_type($( $params )*), )*);); (prefix = $prefix:expr; $( $name:ident: $stat_type:tt($( $params:tt )*), )*) => ( #[allow(non_snake_case, non_upper_case_globals, unused_imports)] pub(crate) mod STATS { use $crate::macros::common_macro_prelude::*; lazy_static! { static ref STATS_MAP: Arc<ThreadMap<BoxStatsManager>> = create_map(); } thread_local! { static TL_STATS: PerThread<BoxStatsManager> = STATS_MAP.register(create_stats_manager()); } $( $crate::__define_stat!($prefix; $name: $stat_type($( $params )*)); )* } ); } #[doc(hidden)] #[macro_export] macro_rules! __define_key_generator { ($name:ident($prefix:expr, $key:expr; $( $placeholder:ident: $type:ty ),+)) => ( fn $name(&($( ref $placeholder, )+): &($( $type, )+)) -> String { let key = format!($key, $( $placeholder ),+); if $prefix.is_empty() { key } else { [$prefix, &key].join(".") } } ); } #[doc(hidden)] #[macro_export] macro_rules! __define_stat { ($prefix:expr; $name:ident: singleton_counter()) => ( $crate::__define_stat!($prefix; $name: singleton_counter(stringify!($name))); ); ($prefix:expr; $name:ident: singleton_counter($key:expr)) => ( lazy_static! { pub static ref $name: BoxSingletonCounter = create_singleton_counter($crate::__create_stat_key!($prefix, $key).to_string()); } ); ($prefix:expr; $name:ident: counter()) => ( $crate::__define_stat!($prefix; $name: counter(stringify!($name))); ); ($prefix:expr; $name:ident: counter($key:expr)) => ( thread_local! { pub static $name: BoxCounter = TL_STATS.with(|stats| { stats.create_counter(&$crate::__create_stat_key!($prefix, $key)) }); } ); // There are 4 inputs we use to produce a timeseries: the the prefix, the name (used in // STATS::name), the key (used in ODS or to query the key), the export types (SUM, RATE, etc.), // and the intervals (e.g. 60, 600). The key defaults to the name, and the intervals default to // whatever default Folly uses (which happens to be 60, 600, 3600); ($prefix:expr; $name:ident: timeseries($( $aggregation_type:expr ),*)) => ( $crate::__define_stat!($prefix; $name: timeseries(stringify!($name); $( $aggregation_type ),*)); ); ($prefix:expr; $name:ident: timeseries($key:expr; $( $aggregation_type:expr ),*)) => ( $crate::__define_stat!($prefix; $name: timeseries($key; $( $aggregation_type ),* ; )); ); ($prefix:expr; $name:ident: timeseries($key:expr; $( $aggregation_type:expr ),* ; $( $interval: expr ),*)) => ( thread_local! { pub static $name: BoxTimeseries = TL_STATS.with(|stats| { stats.create_timeseries( &$crate::__create_stat_key!($prefix, $key), &[$( $aggregation_type ),*], &[$( $interval ),*] ) }); } ); ($prefix:expr; $name:ident: histogram($bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr )* $(; P $percentile:expr )*)) => ( $crate::__define_stat!($prefix; $name: histogram(stringify!($name); $bucket_width, $min, $max $(, $aggregation_type )* $(; P $percentile )*)); ); ($prefix:expr; $name:ident: histogram($key:expr; $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr )* $(; P $percentile:expr )*)) => ( thread_local! { pub static $name: BoxHistogram = TL_STATS.with(|stats| { stats.create_histogram( &$crate::__create_stat_key!($prefix, $key), &[$( $aggregation_type ),*], BucketConfig { width: $bucket_width, min: $min, max: $max, }, &[$( $percentile ),*]) }); } ); ($prefix:expr; $name:ident: dynamic_singleton_counter($key:expr, ($( $placeholder:ident: $type:ty ),+))) => ( thread_local! { pub static $name: DynamicStat<($( $type, )+), BoxSingletonCounter> = { $crate::__define_key_generator!( __key_generator($prefix, $key; $( $placeholder: $type ),+) ); fn __stat_generator(key: &str) -> BoxSingletonCounter { create_singleton_counter(key.to_string()) } DynamicStat::new(__key_generator, __stat_generator) } } ); ($prefix:expr; $name:ident: dynamic_counter($key:expr, ($( $placeholder:ident: $type:ty ),+))) => ( thread_local! { pub static $name: DynamicStat<($( $type, )+), BoxCounter> = { $crate::__define_key_generator!( __key_generator($prefix, $key; $( $placeholder: $type ),+) ); fn __stat_generator(key: &str) -> BoxCounter { TL_STATS.with(|stats| { stats.create_counter(key) }) } DynamicStat::new(__key_generator, __stat_generator) } } ); ($prefix:expr; $name:ident: dynamic_timeseries($key:expr, ($( $placeholder:ident: $type:ty ),+); $( $aggregation_type:expr ),*)) => ( $crate::__define_stat!( $prefix; $name: dynamic_timeseries( $key, ($( $placeholder: $type ),+); $( $aggregation_type ),* ; ) ); ); ($prefix:expr; $name:ident: dynamic_timeseries($key:expr, ($( $placeholder:ident: $type:ty ),+); $( $aggregation_type:expr ),* ; $( $interval:expr ),*)) => ( thread_local! { pub static $name: DynamicStat<($( $type, )+), BoxTimeseries> = { $crate::__define_key_generator!( __key_generator($prefix, $key; $( $placeholder: $type ),+) ); fn __stat_generator(key: &str) -> BoxTimeseries { TL_STATS.with(|stats| { stats.create_timeseries(key, &[$( $aggregation_type ),*], &[$( $interval ),*]) }) } DynamicStat::new(__key_generator, __stat_generator) }; } ); ($prefix:expr; $name:ident: dynamic_histogram($key:expr, ($( $placeholder:ident: $type:ty ),+); $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr )* $(; P $percentile:expr )*)) => ( thread_local! { pub static $name: DynamicStat<($( $type, )+), BoxHistogram> = { $crate::__define_key_generator!( __key_generator($prefix, $key; $( $placeholder: $type ),+) ); fn __stat_generator(key: &str) -> BoxHistogram { TL_STATS.with(|stats| { stats.create_histogram(key, &[$( $aggregation_type ),*], BucketConfig { width: $bucket_width, min: $min, max: $max, }, &[$( $percentile ),*]) }) } DynamicStat::new(__key_generator, __stat_generator) }; } ); } #[doc(hidden)] #[macro_export] macro_rules! __create_stat_key { ($prefix:expr, $key:expr) => {{ use std::borrow::Cow; if $prefix.is_empty() { Cow::Borrowed($key) } else { Cow::Owned(format!("{}.{}", $prefix, $key)) } }}; } /// Define a group of stats with dynamic names all parameterized by the same set of parameters. /// The intention is that when setting up a structure for some entity with associated stats, then /// the type produced by this macro can be included in that structure, and initialized with the /// appropriate name(s). This is more efficient than using single static "dynamic_" versions of /// the counters. /// /// ``` /// use stats::prelude::*; /// /// define_stats_struct! { /// // struct name, key prefix template, key template params /// MyThingStat("things.{}.{}", mything_name: String, mything_idx: usize), /// cache_miss: counter() // default name from the field /// } /// /// struct MyThing { /// stats: MyThingStat, /// } /// /// impl MyThing { /// fn new(somename: String, someidx: usize) -> Self { /// MyThing { /// stats: MyThingStat::new(somename, someidx), /// //... /// } /// } /// } /// # /// # fn main() {} /// ``` #[macro_export] macro_rules! define_stats_struct { // Handle trailing comma ($name:ident ($key:expr, $($pr_name:ident: $pr_type:ty),*) , $( $stat_name:ident: $stat_type:tt($( $params:tt )*) , )+) => { define_stats_struct!($name ( $key, $($pr_name: $pr_type),*), $($stat_name: $stat_type($($params)*)),* ); }; // Handle no params ($name:ident ($key:expr) , $( $stat_name:ident: $stat_type:tt($( $params:tt )*) ),*) => { define_stats_struct!($name ( $key, ), $($stat_name: $stat_type($($params)*)),* ); }; ($name:ident ($key:expr) , $( $stat_name:ident: $stat_type:tt($( $params:tt )*) , )+) => { define_stats_struct!($name ( $key, ), $($stat_name: $stat_type($($params)*)),* ); }; // Define struct and its methods. ($name:ident ($key:expr, $($pr_name:ident: $pr_type:ty),*) , $( $stat_name:ident: $stat_type:tt($( $params:tt )*) ),*) => { #[allow(missing_docs)] pub struct $name { $(pub $stat_name: $crate::__struct_field_type!($stat_type), )* } impl $name { #[allow(unused_imports, missing_docs)] pub fn new($($pr_name: $pr_type),*) -> $name { use $crate::macros::common_macro_prelude::*; lazy_static! { static ref STATS_MAP: Arc<ThreadMap<BoxStatsManager>> = create_map(); } thread_local! { static TL_STATS: PerThread<BoxStatsManager> = STATS_MAP.register(create_stats_manager()); } let prefix = format!($key, $($pr_name),*); $name { $($stat_name: $crate::__struct_field_init!(prefix, $stat_name, $stat_type, $($params)*)),* } } } impl std::fmt::Debug for $name { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(fmt, "<{}>", stringify!($name)) } } } } #[macro_export] #[doc(hidden)] macro_rules! __struct_field_type { (singleton_counter) => { $crate::macros::common_macro_prelude::BoxSingletonCounter }; (counter) => { $crate::macros::common_macro_prelude::BoxCounter }; (timeseries) => { $crate::macros::common_macro_prelude::BoxTimeseries }; (histogram) => { $crate::macros::common_macro_prelude::BoxHistogram }; } #[macro_export] #[doc(hidden)] macro_rules! __struct_field_init { ($prefix:expr, $name:ident, singleton_counter, ) => { $crate::__struct_field_init! ($prefix, $name, singleton_counter, stringify!($name)) }; ($prefix:expr, $name:ident, singleton_counter, $key:expr) => { $crate::__struct_field_init! ($prefix, $name, singleton_counter, $key ; ) }; ($prefix:expr, $name:ident, singleton_counter, $key:expr ; ) => {{ let key = format!("{}.{}", $prefix, $key); create_singleton_counter(key) }}; ($prefix:expr, $name:ident, counter, ) => { $crate::__struct_field_init! ($prefix, $name, counter, stringify!($name)) }; ($prefix:expr, $name:ident, counter, $key:expr) => { $crate::__struct_field_init! ($prefix, $name, counter, $key ; ) }; ($prefix:expr, $name:ident, counter, $key:expr ; ) => {{ let key = format!("{}.{}", $prefix, $key); TL_STATS.with(|stats| { stats.create_counter(&key) }) }}; ($prefix:expr, $name:ident, timeseries, $( $aggregation_type:expr ),+) => { $crate::__struct_field_init! ($prefix, $name, timeseries, stringify!($name) ; $($aggregation_type),*) }; ($prefix:expr, $name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ) => {{ $crate::__struct_field_init! ($prefix, $name, timeseries, $key ; $($aggregation_type),* ;) }}; ($prefix:expr, $name:ident, timeseries, $key:expr ; $( $aggregation_type:expr ),* ; $( $interval:expr ),* ) => {{ let key = format!("{}.{}", $prefix, $key); TL_STATS.with(|stats| { stats.create_timeseries(&key, &[$( $aggregation_type ),*], &[$( $interval),*]) }) }}; ($prefix:expr, $name:ident, histogram, $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)* $(; P $percentile:expr )*) => { $crate::__struct_field_init! ($prefix, $name, histogram, stringify!($name) ; $bucket_width, $min, $max $(, $aggregation_type)* $(; P $percentile)* ) }; ($prefix:expr, $name:ident, histogram, $key:expr ; $bucket_width:expr, $min:expr, $max:expr $(, $aggregation_type:expr)* $(; P $percentile:expr )*) => {{ let key = format!("{}.{}", $prefix, $key); TL_STATS.with(|stats| { stats.create_histogram( &key, &[$( $aggregation_type ),*], BucketConfig { width: $bucket_width, min: $min, max: $max, }, &[$( $percentile ),*]) }) }}; }