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