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 
5 use std::collections::HashMap;
6 use std::hash::Hash;
7 use std::marker::PhantomData;
8 
9 use super::{CommonMetricData, Instant};
10 
11 /// Extra keys for events.
12 ///
13 /// Extra keys need to be pre-defined and map to a string representation.
14 ///
15 /// For user-defined `EventMetric`s these will be defined as `enums`.
16 /// Each variant will correspond to an entry in the `ALLOWED_KEYS` list.
17 /// The Glean SDK requires the keys as strings for submission in pings,
18 /// whereas in code we want to provide users a type to work with
19 /// (e.g. to avoid typos or misuse of the API).
20 pub trait ExtraKeys: Hash + Eq + PartialEq + Copy {
21     /// List of allowed extra keys as strings.
22     const ALLOWED_KEYS: &'static [&'static str];
23 
24     /// The index of the extra key.
25     ///
26     /// It corresponds to its position in the associated `ALLOWED_KEYS` list.
27     ///
28     /// *Note*: An index of `-1` indicates an invalid / non-existing extra key.
29     /// Invalid / non-existing extra keys will be recorded as an error.
30     /// This cannot happen for generated code.
index(self) -> i3231     fn index(self) -> i32;
32 }
33 
34 /// Default of no extra keys for events.
35 ///
36 /// An enum with no values for convenient use as the default set of extra keys
37 /// that an `EventMetric` can accept.
38 ///
39 /// *Note*: There exist no values for this enum, it can never exist.
40 /// It its equivalent to the [`never / !` type](https://doc.rust-lang.org/std/primitive.never.html).
41 #[derive(Clone, Copy, Hash, Eq, PartialEq)]
42 pub enum NoExtraKeys {}
43 
44 impl ExtraKeys for NoExtraKeys {
45     const ALLOWED_KEYS: &'static [&'static str] = &[];
46 
index(self) -> i3247     fn index(self) -> i32 {
48         // This index will never be used.
49         -1
50     }
51 }
52 
53 /// An event metric.
54 ///
55 /// Events allow recording of e.g. individual occurences of user actions, say
56 /// every time a view was open and from where. Each time you record an event, it
57 /// records a timestamp, the event's name and a set of custom values.
58 #[derive(Clone, Debug)]
59 pub struct EventMetric<K> {
60     inner: glean_core::metrics::EventMetric,
61     extra_keys: PhantomData<K>,
62 }
63 
64 impl<K: ExtraKeys> EventMetric<K> {
65     /// Create a new event metric.
new(meta: CommonMetricData) -> Self66     pub fn new(meta: CommonMetricData) -> Self {
67         let allowed_extra_keys = K::ALLOWED_KEYS.iter().map(|s| s.to_string()).collect();
68         let inner = glean_core::metrics::EventMetric::new(meta, allowed_extra_keys);
69 
70         Self {
71             inner,
72             extra_keys: PhantomData,
73         }
74     }
75 
76     /// Record an event.
77     ///
78     /// Records an event under this metric's name and attaches a timestamp.
79     /// If given a map of extra values is added to the event log.
80     ///
81     /// ## Arguments
82     ///
83     /// * `extra` - An (optional) map of (key, value) pairs.
record<M: Into<Option<HashMap<K, String>>>>(&self, extra: M)84     pub fn record<M: Into<Option<HashMap<K, String>>>>(&self, extra: M) {
85         let now = Instant::now();
86 
87         // Translate from [ExtraKey -> String] to a [Int -> String] map
88         let extra = extra
89             .into()
90             .map(|h| h.into_iter().map(|(k, v)| (k.index(), v)).collect());
91 
92         crate::with_glean(|glean| self.inner.record(glean, now.as_millis(), extra))
93     }
94 
95     /// **Test-only API.**
96     ///
97     /// Get the currently stored events for this event metric as a JSON-encoded string.
98     /// This doesn't clear the stored value.
99     ///
100     /// ## Note
101     ///
102     /// This currently returns the value as a JSON encoded string.
103     /// `glean_core` doesn't expose the underlying recorded event type.
104     /// This will eventually change to a proper `RecordedEventData` type.
105     /// See [Bug 1635074](https://bugzilla.mozilla.org/show_bug.cgi?id=1635074).
106     ///
107     /// ## Arguments
108     ///
109     /// * `storage_name` - the storage name to look into.
110     ///
111     /// ## Return value
112     ///
113     /// Returns the stored value or `None` if nothing stored.
test_get_value(&self, storage_name: &str) -> Option<String>114     pub fn test_get_value(&self, storage_name: &str) -> Option<String> {
115         crate::with_glean(|glean| {
116             // Unfortunately we need to do this branch ourselves right now,
117             // as `None` is encoded into the JSON `null`.
118 
119             let inner = &self.inner;
120             if inner.test_has_value(glean, storage_name) {
121                 Some(inner.test_get_value_as_json_string(glean, storage_name))
122             } else {
123                 None
124             }
125         })
126     }
127 }
128