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::Glean;
13use crate::{CommonMetricData, TestGetValue};
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_num_recorded_errors(&self, error: ErrorType) -> i32 {
207 crate::block_on_dispatcher();
208
209 crate::core::with_glean(|glean| {
210 test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
211 })
212 }
213}
214
215impl TestGetValue<Vec<RecordedEvent>> for EventMetric {
216 fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
227 crate::block_on_dispatcher();
228 crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
229 }
230}