1use 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#[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 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 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 pub fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) {
70 self.inner.record_with_time(timestamp, extra);
71 }
72}
73
74impl<K> EventMetric<K> {
76 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}