// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. //! Helper macros for implementing the FFI API for metric types. /// Get a metric object by ID from the corresponding map, then /// execute the provided closure with it. /// /// # Arguments /// /// * `$map` - The name of the hash map within `metrics::__glean_metric_maps` /// (or `factory::__jog_metric_maps`) /// as generated by glean_parser. /// * `$id` - The ID of the metric to get. /// * `$m` - The identifier to use for the retrieved metric. /// The expression `$f` can use this identifier. /// * `$f` - The expression to execute with the retrieved metric `$m`. macro_rules! with_metric { (BOOLEAN_MAP, $id:ident, $m:ident, $f:expr) => { maybe_labeled_with_metric!(BOOLEAN_MAP, $id, $m, $f) }; (COUNTER_MAP, $id:ident, $m:ident, $f:expr) => { maybe_labeled_with_metric!(COUNTER_MAP, $id, $m, $f) }; (STRING_MAP, $id:ident, $m:ident, $f:expr) => { maybe_labeled_with_metric!(STRING_MAP, $id, $m, $f) }; ($map:ident, $id:ident, $m:ident, $f:expr) => { just_with_metric!($map, $id, $m, $f) }; } /// Get a dynamically-registered metric object by id from the corresponding map, /// then execute the provided closure with it. /// /// Assumes `$id` is for a dynamic non-submetric metric. /// Will panic if it isn't. /// /// # Arguments /// /// * `$map` - The name of the hash map within `factory::__jog_metric_maps` /// as generated by glean_parser. /// * `$id` - The ID of the metric to get. /// * `$m` - The identifier to use for the retrieved metric. /// The expression `$f` can use this identifier. /// * `$f` - The expression to execute with the retrieved metric `$m`. macro_rules! just_with_jog_metric { ($map:ident, $id:ident, $m:ident, $f:expr) => {{ let map = $crate::factory::__jog_metric_maps::$map .read() .expect("Read lock for dynamic metric map was poisoned"); match map.get(&$id.into()) { Some($m) => $f, None => panic!("No (dynamic) metric for id {}", $id), } }}; } /// Get a metric object by id from the corresponding map, then /// execute the provided closure with it. /// /// Ignores the possibility that the $id might be for a labeled submetric. /// /// # Arguments /// /// * `$map` - The name of the hash map within `metrics::__glean_metric_maps` /// (or `factory::__jog_metric_maps`) /// as generated by glean_parser. /// * `$id` - The ID of the metric to get. /// * `$m` - The identifier to use for the retrieved metric. /// The expression `$f` can use this identifier. /// * `$f` - The expression to execute with the retrieved metric `$m`. macro_rules! just_with_metric { ($map:ident, $id:ident, $m:ident, $f:expr) => { if $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT) > 0 { just_with_jog_metric!($map, $id, $m, $f) } else { match $crate::metrics::__glean_metric_maps::$map.get(&$id.into()) { Some($m) => $f, None => panic!("No metric for id {}", $id), } } }; } /// Get a metric object by id from the corresponding map, then /// execute the provided closure with it. /// /// Requires that the provided $map be of a type that can be labeled, since it /// assumes the presence of a same-named map in /// `metrics::_glean_metrics_map::submetric_maps`. /// /// # Arguments /// /// * `$map` - The name of the hash map within `metrics::__glean_metric_maps` /// and `metrics::__glean_metric_maps::submetric_maps` as generated /// by glean_parser. /// * `$id` - The ID of the metric to get. /// * `$m` - The identifier to use for the retrieved metric. /// The expression `$f` can use this identifier. /// * `$f` - The expression to execute with the retrieved metric `$m`. macro_rules! maybe_labeled_with_metric { ($map:ident, $id:ident, $m:ident, $f:expr) => { if $id & (1 << $crate::metrics::__glean_metric_maps::submetric_maps::SUBMETRIC_BIT) > 0 { let map = $crate::metrics::__glean_metric_maps::submetric_maps::$map .read() .expect("Read lock for labeled metric map was poisoned"); match map.get(&$id.into()) { Some($m) => $f, None => panic!("No submetric for id {}", $id), } } else { just_with_metric!($map, $id, $m, $f) } }; } /// Test whether a value is stored for the given metric. /// /// # Arguments /// /// * `$metric` - The metric to test. /// * `$storage` - the storage name to look into. macro_rules! test_has { ($metric:ident, $storage:ident) => {{ let storage = if $storage.is_empty() { None } else { Some($storage.to_utf8()) }; $metric.test_get_value(storage.as_deref()).is_some() }}; } /// Get the currently stored value for the given metric. /// /// # Arguments /// /// * `$metric` - The metric to test. /// * `$storage` - the storage name to look into. macro_rules! test_get { ($metric:ident, $storage:ident) => {{ let storage = if $storage.is_empty() { None } else { Some($storage.to_utf8()) }; $metric.test_get_value(storage.as_deref()).unwrap() }}; } /// Check the provided metric in the provided storage for errors. /// On finding one, return an error string. /// /// # Arguments /// /// * `$metric` - The metric to test. macro_rules! test_get_errors { ($metric:path) => {{ let error_types = [ glean::ErrorType::InvalidValue, glean::ErrorType::InvalidLabel, glean::ErrorType::InvalidState, glean::ErrorType::InvalidOverflow, ]; let mut error_str = None; for &error_type in error_types.iter() { let num_errors = $metric.test_get_num_recorded_errors(error_type); if num_errors > 0 { error_str = Some(format!( "Metric had {} error(s) of type {}!", num_errors, error_type.as_str() )); break; } } error_str }}; } /// Get the submetric id for a given labeled metric and label. /// /// # Arguments /// /// * `$id` - The id of the labeled metric. /// * `$label` - The (string) label of the submetric. /// * `$labeled_map` - The name of the labeled metric's map for retrieval (JOG only). /// * `$labeled_get` - The name of the labeled metric's get fn for retrieval. /// * `$submetric_map`- The name of the submetrics' map for storage. /// * `$metric_type` - The submetric's type (needed for an internal closure). macro_rules! labeled_submetric_get { ($id:ident, $label:ident, $labeled_map:ident, $labeled_get:ident, $submetric_map:ident, $metric_type:ty) => {{ let tuple = ($id, $label.to_utf8().into()); { let map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS .read() .expect("read lock of submetric ids was poisoned"); if let Some(submetric_id) = map.get(&tuple) { return *submetric_id; } } // Gotta actually create a new submetric with a new id. let submetric_id = $crate::metrics::__glean_metric_maps::submetric_maps::NEXT_LABELED_SUBMETRIC_ID .fetch_add(1, Ordering::SeqCst); { if $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT) > 0 { just_with_jog_metric!($labeled_map, $id, metric, { let submetric = metric.get(&tuple.1); let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map .write() .expect("write lock of submetric map was poisoned"); map.insert(submetric_id.into(), submetric); }); } else { let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map .write() .expect("write lock of submetric map was poisoned"); map.insert( submetric_id.into(), $crate::metrics::__glean_metric_maps::$labeled_get($id, &tuple.1), ); } } let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS .write() .expect("write lock of submetric ids was poisoned"); map.insert(tuple, submetric_id); submetric_id }}; } /// Get the submetric id for a given labeled metric and label enum. /// /// # Arguments /// /// * `$id` - The id of the labeled metric. /// * `$label` - The (enum) label of the submetric. /// * `$labeled_get` - The name of the labeled metric's get fn for retrieval. /// * `$submetric_map`- The name of the submetrics' map for storage. /// * `$metric_type` - The submetric's type (needed for an internal closure). macro_rules! labeled_submetric_enum_get { ($id:ident, $label_enum:ident, $labeled_get:ident, $submetric_map:ident, $metric_type:ty) => {{ let tuple = ($id, $label_enum.into()); // First: Have we seen this enum before? If so, give out the same submetric id. { let map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_ENUMS_TO_IDS .read() .expect("read lock of enum submetric ids was poisoned"); if let Some(submetric_id) = map.get(&tuple) { return *submetric_id; } } // Alas, this is the first time we've needed to handle this metric with this enum. // Gotta actually create a new submetric with a new id. let submetric_id = $crate::metrics::__glean_metric_maps::submetric_maps::NEXT_LABELED_SUBMETRIC_ID .fetch_add(1, Ordering::SeqCst); { // What if the dynamic bit is set? // JOG only supports JS, and enum_get isn't (yet) supported in JS. assert_eq!( 0, $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT), "No enum_get support for JOG" ); let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map .write() .expect("write lock of submetric map was poisoned"); map.insert( submetric_id.into(), $crate::metrics::__glean_metric_maps::$labeled_get($id, tuple.1), ); } // And now ensure we store the submetric so we need not create it on subsequent calls. let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_ENUMS_TO_IDS .write() .expect("write lock of submetric ids was poisoned"); map.insert(tuple, submetric_id); submetric_id }}; }