1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std::collections::HashMap;
use crate::common_metric_data::CommonMetricDataInternal;
use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
use crate::event_database::RecordedEvent;
use crate::metrics::MetricType;
use crate::util::truncate_string_at_boundary_with_error;
use crate::CommonMetricData;
use crate::Glean;
use chrono::Utc;
const MAX_LENGTH_EXTRA_KEY_VALUE: usize = 500;
/// An event metric.
///
/// Events allow recording of e.g. individual occurences of user actions, say
/// every time a view was open and from where. Each time you record an event, it
/// records a timestamp, the event's name and a set of custom values.
#[derive(Clone, Debug)]
pub struct EventMetric {
meta: CommonMetricDataInternal,
allowed_extra_keys: Vec<String>,
}
impl MetricType for EventMetric {
fn meta(&self) -> &CommonMetricDataInternal {
&self.meta
}
}
// IMPORTANT:
//
// When changing this implementation, make sure all the operations are
// also declared in the related trait in `../traits/`.
impl EventMetric {
/// Creates a new event metric.
pub fn new(meta: CommonMetricData, allowed_extra_keys: Vec<String>) -> Self {
Self {
meta: meta.into(),
allowed_extra_keys,
}
}
/// Records an event.
///
/// # Arguments
///
/// * `extra` - A [`HashMap`] of `(key, value)` pairs.
/// Keys must be one of the allowed extra keys.
/// If any key is not allowed, an error is reported and no event is recorded.
pub fn record(&self, extra: HashMap<String, String>) {
let timestamp = crate::get_timestamp_ms();
self.record_with_time(timestamp, extra);
}
/// Record a new event with a provided timestamp.
///
/// It's the caller's responsibility to ensure the timestamp comes from the same clock source.
///
/// # Arguments
///
/// * `timestamp` - The event timestamp, in milliseconds.
/// * `extra` - A [`HashMap`] of `(key, value)` pairs.
/// Keys must be one of the allowed extra keys.
/// If any key is not allowed, an error is reported and no event is recorded.
pub fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) {
let metric = self.clone();
// Precise timestamp based on wallclock. Will be used if `enable_event_timestamps` is true.
let now = Utc::now();
let precise_timestamp = now.timestamp_millis() as u64;
crate::launch_with_glean(move |glean| {
let sent = metric.record_sync(glean, timestamp, extra, precise_timestamp);
if sent {
let state = crate::global_state().lock().unwrap();
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
});
let id = self.meta().base_identifier();
crate::launch_with_glean(move |_| {
let event_listeners = crate::event_listeners().lock().unwrap();
event_listeners
.iter()
.for_each(|(_, listener)| listener.on_event_recorded(id.clone()));
});
}
/// Validate that extras are empty or all extra keys are allowed.
///
/// If at least one key is not allowed, record an error and fail.
fn validate_extra(
&self,
glean: &Glean,
extra: HashMap<String, String>,
) -> Result<Option<HashMap<String, String>>, ()> {
if extra.is_empty() {
return Ok(None);
}
let mut extra_strings = HashMap::new();
for (k, v) in extra.into_iter() {
if !self.allowed_extra_keys.contains(&k) {
let msg = format!("Invalid key index {}", k);
record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
return Err(());
}
let value = truncate_string_at_boundary_with_error(
glean,
&self.meta,
v,
MAX_LENGTH_EXTRA_KEY_VALUE,
);
extra_strings.insert(k, value);
}
Ok(Some(extra_strings))
}
/// Records an event.
///
/// ## Returns
///
/// `true` if a ping was submitted and should be uploaded.
/// `false` otherwise.
#[doc(hidden)]
pub fn record_sync(
&self,
glean: &Glean,
timestamp: u64,
extra: HashMap<String, String>,
precise_timestamp: u64,
) -> bool {
if !self.should_record(glean) {
return false;
}
let mut extra_strings = match self.validate_extra(glean, extra) {
Ok(extra) => extra,
Err(()) => return false,
};
if glean.with_timestamps() {
if extra_strings.is_none() {
extra_strings.replace(Default::default());
}
let map = extra_strings.get_or_insert(Default::default());
map.insert("glean_timestamp".to_string(), precise_timestamp.to_string());
}
glean
.event_storage()
.record(glean, &self.meta, timestamp, extra_strings)
}
/// **Test-only API (exported for FFI purposes).**
///
/// Get the vector of currently stored events for this event metric.
#[doc(hidden)]
pub fn get_value<'a, S: Into<Option<&'a str>>>(
&self,
glean: &Glean,
ping_name: S,
) -> Option<Vec<RecordedEvent>> {
let queried_ping_name = ping_name
.into()
.unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
let events = glean
.event_storage()
.test_get_value(&self.meta, queried_ping_name);
events.map(|mut evts| {
for ev in &mut evts {
let Some(extra) = &mut ev.extra else { continue };
extra.remove("glean_timestamp");
if extra.is_empty() {
ev.extra = None;
}
}
evts
})
}
/// **Test-only API (exported for FFI purposes).**
///
/// Get the vector of currently stored events for this event metric.
///
/// This doesn't clear the stored value.
///
/// # Arguments
///
/// * `ping_name` - the optional name of the ping to retrieve the metric
/// for. Defaults to the first value in `send_in_pings`.
pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
}
/// **Exported for test purposes.**
///
/// Gets the number of recorded errors for the given metric and error type.
///
/// # Arguments
///
/// * `error` - The type of error
///
/// # Returns
///
/// The number of errors reported.
pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| {
test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
})
}
}