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