// 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/. use inherent::inherent; use std::{collections::HashMap, marker::PhantomData, sync::Arc}; use glean_core::metrics::MetricType; use glean_core::traits; use crate::{dispatcher, ErrorType, RecordedEvent}; pub use glean_core::traits::NoExtraKeys; // We need to wrap the glean-core type: otherwise if we try to implement // the trait for the metric in `glean_core::metrics` we hit error[E0117]: // only traits defined in the current crate can be implemented for arbitrary // types. /// This implements the developer facing API for recording event metrics. /// /// Instances of this class type are automatically generated by the parsers /// at build time, allowing developers to record values that were previously /// registered in the metrics.yaml file. #[derive(Clone)] pub struct EventMetric { pub(crate) inner: Arc, extra_keys: PhantomData, } impl EventMetric { /// The public constructor used by automatically generated metrics. pub fn new(meta: glean_core::CommonMetricData) -> Self { let allowed_extra_keys = K::ALLOWED_KEYS.iter().map(|s| s.to_string()).collect(); let inner = Arc::new(glean_core::metrics::EventMetric::new( meta, allowed_extra_keys, )); Self { inner, extra_keys: PhantomData, } } } #[inherent(pub)] impl traits::Event for EventMetric { type Extra = K; fn record::Extra, String>>>>(&self, extra: M) { const NANOS_PER_MILLI: u64 = 1_000_000; let now = time::precise_time_ns() / NANOS_PER_MILLI; // Translate from [ExtraKey -> String] to a [Int -> String] map let extra = extra .into() .map(|h| h.into_iter().map(|(k, v)| (k.index(), v)).collect()); let metric = Arc::clone(&self.inner); dispatcher::launch(move || crate::with_glean(|glean| metric.record(glean, now, extra))); } fn test_get_value<'a, S: Into>>( &self, ping_name: S, ) -> Option> { crate::block_on_dispatcher(); let queried_ping_name = ping_name .into() .unwrap_or_else(|| &self.inner.meta().send_in_pings[0]); crate::with_glean(|glean| self.inner.test_get_value(glean, queried_ping_name)) } fn test_get_num_recorded_errors<'a, S: Into>>( &self, error: ErrorType, ping_name: S, ) -> i32 { crate::block_on_dispatcher(); crate::with_glean_mut(|glean| { glean_core::test_get_num_recorded_errors( &glean, self.inner.meta(), error, ping_name.into(), ) .unwrap_or(0) }) } } #[cfg(test)] mod test { use super::*; use crate::common_test::{lock_test, new_glean}; use crate::CommonMetricData; #[test] fn no_extra_keys() { let _lock = lock_test(); let _t = new_glean(None, true); let metric: EventMetric = EventMetric::new(CommonMetricData { name: "event".into(), category: "test".into(), send_in_pings: vec!["test1".into()], ..Default::default() }); metric.record(None); metric.record(None); let data = metric.test_get_value(None).expect("no event recorded"); assert_eq!(2, data.len()); assert!(data[0].timestamp <= data[1].timestamp); } #[test] fn with_extra_keys() { let _lock = lock_test(); let _t = new_glean(None, true); #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] enum SomeExtra { Key1, Key2, } impl glean_core::traits::ExtraKeys for SomeExtra { const ALLOWED_KEYS: &'static [&'static str] = &["key1", "key2"]; fn index(self) -> i32 { self as i32 } } let metric: EventMetric = EventMetric::new(CommonMetricData { name: "event".into(), category: "test".into(), send_in_pings: vec!["test1".into()], ..Default::default() }); let mut map1 = HashMap::new(); map1.insert(SomeExtra::Key1, "1".into()); metric.record(map1); let mut map2 = HashMap::new(); map2.insert(SomeExtra::Key1, "1".into()); map2.insert(SomeExtra::Key2, "2".into()); metric.record(map2); metric.record(None); let data = metric.test_get_value(None).expect("no event recorded"); assert_eq!(3, data.len()); assert!(data[0].timestamp <= data[1].timestamp); assert!(data[1].timestamp <= data[2].timestamp); let mut map = HashMap::new(); map.insert("key1".into(), "1".into()); assert_eq!(Some(map), data[0].extra); let mut map = HashMap::new(); map.insert("key1".into(), "1".into()); map.insert("key2".into(), "2".into()); assert_eq!(Some(map), data[1].extra); assert_eq!(None, data[2].extra); } }