Skip to main content

glean/private/
event.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use inherent::inherent;
6use std::{collections::HashMap, marker::PhantomData};
7
8use malloc_size_of::MallocSizeOf;
9
10use glean_core::{metrics::MetricIdentifier, traits, TestGetValue};
11
12use crate::{ErrorType, RecordedEvent};
13
14// We need to wrap the glean-core type: otherwise if we try to implement
15// the trait for the metric in `glean_core::metrics` we hit error[E0117]:
16// only traits defined in the current crate can be implemented for arbitrary
17// types.
18
19/// Developer-facing API for recording event metrics.
20///
21/// Instances of this class type are automatically generated by the parsers
22/// at build time, allowing developers to record values that were previously
23/// registered in the metrics.yaml file.
24#[derive(Clone)]
25pub struct EventMetric<K> {
26    pub(crate) inner: glean_core::metrics::EventMetric,
27    extra_keys: PhantomData<K>,
28}
29
30impl<K> MallocSizeOf for EventMetric<K> {
31    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
32        self.inner.size_of(ops)
33    }
34}
35
36impl<'a, K> MetricIdentifier<'a> for EventMetric<K> {
37    fn get_identifiers(&'a self) -> (&'a str, &'a str, Option<&'a str>) {
38        self.inner.get_identifiers()
39    }
40}
41
42impl<K: traits::ExtraKeys> EventMetric<K> {
43    /// The public constructor used by automatically generated metrics.
44    pub fn new(meta: glean_core::CommonMetricData) -> Self {
45        let allowed_extra_keys = K::ALLOWED_KEYS.iter().map(|s| s.to_string()).collect();
46        let inner = glean_core::metrics::EventMetric::new(meta, allowed_extra_keys);
47        Self {
48            inner,
49            extra_keys: PhantomData,
50        }
51    }
52
53    /// The public constructor used by runtime-defined metrics.
54    pub fn with_runtime_extra_keys(
55        meta: glean_core::CommonMetricData,
56        allowed_extra_keys: Vec<String>,
57    ) -> Self {
58        let inner = glean_core::metrics::EventMetric::new(meta, allowed_extra_keys);
59        Self {
60            inner,
61            extra_keys: PhantomData,
62        }
63    }
64
65    /// Record a new event with a provided timestamp.
66    ///
67    /// It's the caller's responsibility to ensure the timestamp comes from the same clock source.
68    /// Use [`glean::get_timestamp_ms`](crate::get_timestamp_ms) to get a valid timestamp.
69    pub fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) {
70        self.inner.record_with_time(timestamp, extra);
71    }
72}
73
74// Separately implemented so it doesn't require `K: ExtraKeys`.
75impl<K> EventMetric<K> {
76    /// **Exported for test purposes.**
77    ///
78    /// Gets the number of recorded errors for the given metric and error type.
79    ///
80    /// # Arguments
81    ///
82    /// * `error` - The type of error
83    ///
84    /// # Returns
85    ///
86    /// The number of errors reported.
87    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
88        self.inner.test_get_num_recorded_errors(error)
89    }
90}
91
92#[inherent]
93impl<K> TestGetValue for EventMetric<K> {
94    type Output = Vec<RecordedEvent>;
95
96    pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
97        self.inner.test_get_value(ping_name)
98    }
99}
100
101#[inherent]
102impl<K: traits::ExtraKeys> traits::Event for EventMetric<K> {
103    type Extra = K;
104
105    pub fn record<M: Into<Option<<Self as traits::Event>::Extra>>>(&self, extra: M) {
106        let extra = extra
107            .into()
108            .map(|e| e.into_ffi_extra())
109            .unwrap_or_else(HashMap::new);
110        self.inner.record(extra);
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117    use crate::common_test::{lock_test, new_glean};
118    use crate::CommonMetricData;
119
120    #[test]
121    fn no_extra_keys() {
122        let _lock = lock_test();
123        let _t = new_glean(None, true);
124
125        let metric: EventMetric<traits::NoExtraKeys> = EventMetric::new(CommonMetricData {
126            name: "event".into(),
127            category: "test".into(),
128            send_in_pings: vec!["store1".into()],
129            ..Default::default()
130        });
131
132        metric.record(None);
133        metric.record(None);
134
135        let data = metric.test_get_value(None).expect("no event recorded");
136        assert_eq!(2, data.len());
137        assert!(data[0].timestamp <= data[1].timestamp);
138    }
139
140    #[test]
141    fn with_extra_keys() {
142        let _lock = lock_test();
143        let _t = new_glean(None, true);
144
145        #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)]
146        struct SomeExtra {
147            key1: Option<String>,
148            key2: Option<String>,
149        }
150
151        impl glean_core::traits::ExtraKeys for SomeExtra {
152            const ALLOWED_KEYS: &'static [&'static str] = &["key1", "key2"];
153
154            fn into_ffi_extra(self) -> HashMap<String, String> {
155                let mut map = HashMap::new();
156                self.key1.and_then(|key1| map.insert("key1".into(), key1));
157                self.key2.and_then(|key2| map.insert("key2".into(), key2));
158                map
159            }
160        }
161
162        let metric: EventMetric<SomeExtra> = EventMetric::new(CommonMetricData {
163            name: "event".into(),
164            category: "test".into(),
165            send_in_pings: vec!["store1".into()],
166            ..Default::default()
167        });
168
169        let map1 = SomeExtra {
170            key1: Some("1".into()),
171            ..Default::default()
172        };
173        metric.record(map1);
174
175        let map2 = SomeExtra {
176            key1: Some("1".into()),
177            key2: Some("2".into()),
178        };
179        metric.record(map2);
180
181        metric.record(None);
182
183        let data = metric.test_get_value(None).expect("no event recorded");
184        assert_eq!(3, data.len());
185        assert!(data[0].timestamp <= data[1].timestamp);
186        assert!(data[1].timestamp <= data[2].timestamp);
187
188        let mut map = HashMap::new();
189        map.insert("key1".into(), "1".into());
190        assert_eq!(Some(map), data[0].extra);
191
192        let mut map = HashMap::new();
193        map.insert("key1".into(), "1".into());
194        map.insert("key2".into(), "2".into());
195        assert_eq!(Some(map), data[1].extra);
196
197        assert_eq!(None, data[2].extra);
198    }
199
200    #[test]
201    fn with_runtime_extra_keys() {
202        let _lock = lock_test();
203        let _t = new_glean(None, true);
204
205        #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)]
206        struct RuntimeExtra {}
207
208        impl glean_core::traits::ExtraKeys for RuntimeExtra {
209            const ALLOWED_KEYS: &'static [&'static str] = &[];
210
211            fn into_ffi_extra(self) -> HashMap<String, String> {
212                HashMap::new()
213            }
214        }
215
216        let metric: EventMetric<RuntimeExtra> = EventMetric::with_runtime_extra_keys(
217            CommonMetricData {
218                name: "event".into(),
219                category: "test".into(),
220                send_in_pings: vec!["store1".into()],
221                ..Default::default()
222            },
223            vec!["key1".into(), "key2".into()],
224        );
225
226        let map1 = HashMap::from([("key1".into(), "1".into())]);
227        metric.record_with_time(0, map1);
228
229        let map2 = HashMap::from([("key1".into(), "1".into()), ("key2".into(), "2".into())]);
230        metric.record_with_time(1, map2);
231
232        metric.record_with_time(2, HashMap::new());
233
234        let data = metric.test_get_value(None).expect("no event recorded");
235        assert_eq!(3, data.len());
236        assert!(data[0].timestamp <= data[1].timestamp);
237        assert!(data[1].timestamp <= data[2].timestamp);
238
239        let mut map = HashMap::new();
240        map.insert("key1".into(), "1".into());
241        assert_eq!(Some(map), data[0].extra);
242
243        let mut map = HashMap::new();
244        map.insert("key1".into(), "1".into());
245        map.insert("key2".into(), "2".into());
246        assert_eq!(Some(map), data[1].extra);
247
248        assert_eq!(None, data[2].extra);
249    }
250}