glean_core/metrics/
event.rs1use std::collections::HashMap;
6
7use crate::common_metric_data::CommonMetricDataInternal;
8use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
9use crate::event_database::RecordedEvent;
10use crate::metrics::MetricType;
11use crate::util::truncate_string_at_boundary_with_error;
12use crate::CommonMetricData;
13use crate::Glean;
14
15use chrono::Utc;
16
17const MAX_LENGTH_EXTRA_KEY_VALUE: usize = 500;
18
19#[derive(Clone, Debug)]
25pub struct EventMetric {
26    meta: CommonMetricDataInternal,
27    allowed_extra_keys: Vec<String>,
28}
29
30impl MetricType for EventMetric {
31    fn meta(&self) -> &CommonMetricDataInternal {
32        &self.meta
33    }
34}
35
36impl EventMetric {
41    pub fn new(meta: CommonMetricData, allowed_extra_keys: Vec<String>) -> Self {
43        Self {
44            meta: meta.into(),
45            allowed_extra_keys,
46        }
47    }
48
49    pub fn record(&self, extra: HashMap<String, String>) {
57        let timestamp = crate::get_timestamp_ms();
58        self.record_with_time(timestamp, extra);
59    }
60
61    pub fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) {
72        let metric = self.clone();
73
74        let now = Utc::now();
76        let precise_timestamp = now.timestamp_millis() as u64;
77
78        crate::launch_with_glean(move |glean| {
79            let sent = metric.record_sync(glean, timestamp, extra, precise_timestamp);
80            if sent {
81                let state = crate::global_state().lock().unwrap();
82                if let Err(e) = state.callbacks.trigger_upload() {
83                    log::error!("Triggering upload failed. Error: {}", e);
84                }
85            }
86        });
87
88        let id = self.meta().base_identifier();
89        crate::launch_with_glean(move |_| {
90            let event_listeners = crate::event_listeners().lock().unwrap();
91            event_listeners
92                .iter()
93                .for_each(|(_, listener)| listener.on_event_recorded(id.clone()));
94        });
95    }
96
97    fn validate_extra(
101        &self,
102        glean: &Glean,
103        extra: HashMap<String, String>,
104    ) -> Result<Option<HashMap<String, String>>, ()> {
105        if extra.is_empty() {
106            return Ok(None);
107        }
108
109        let mut extra_strings = HashMap::new();
110        for (k, v) in extra.into_iter() {
111            if !self.allowed_extra_keys.contains(&k) {
112                let msg = format!("Invalid key index {}", k);
113                record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
114                return Err(());
115            }
116
117            let value = truncate_string_at_boundary_with_error(
118                glean,
119                &self.meta,
120                v,
121                MAX_LENGTH_EXTRA_KEY_VALUE,
122            );
123            extra_strings.insert(k, value);
124        }
125
126        Ok(Some(extra_strings))
127    }
128
129    #[doc(hidden)]
136    pub fn record_sync(
137        &self,
138        glean: &Glean,
139        timestamp: u64,
140        extra: HashMap<String, String>,
141        precise_timestamp: u64,
142    ) -> bool {
143        if !self.should_record(glean) {
144            return false;
145        }
146
147        let mut extra_strings = match self.validate_extra(glean, extra) {
148            Ok(extra) => extra,
149            Err(()) => return false,
150        };
151
152        if glean.with_timestamps() {
153            if extra_strings.is_none() {
154                extra_strings.replace(Default::default());
155            }
156            let map = extra_strings.get_or_insert(Default::default());
157            map.insert("glean_timestamp".to_string(), precise_timestamp.to_string());
158        }
159
160        glean
161            .event_storage()
162            .record(glean, &self.meta, timestamp, extra_strings)
163    }
164
165    #[doc(hidden)]
169    pub fn get_value<'a, S: Into<Option<&'a str>>>(
170        &self,
171        glean: &Glean,
172        ping_name: S,
173    ) -> Option<Vec<RecordedEvent>> {
174        let queried_ping_name = ping_name
175            .into()
176            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
177
178        let events = glean
179            .event_storage()
180            .test_get_value(&self.meta, queried_ping_name);
181
182        events.map(|mut evts| {
183            for ev in &mut evts {
184                let Some(extra) = &mut ev.extra else { continue };
185                extra.remove("glean_timestamp");
186                if extra.is_empty() {
187                    ev.extra = None;
188                }
189            }
190
191            evts
192        })
193    }
194
195    pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
206        crate::block_on_dispatcher();
207        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
208    }
209
210    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
222        crate::block_on_dispatcher();
223
224        crate::core::with_glean(|glean| {
225            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
226        })
227    }
228}