diff options
Diffstat (limited to 'third_party/rust/glean/src/private')
-rw-r--r-- | third_party/rust/glean/src/private/event.rs | 221 | ||||
-rw-r--r-- | third_party/rust/glean/src/private/mod.rs | 35 | ||||
-rw-r--r-- | third_party/rust/glean/src/private/ping.rs | 88 |
3 files changed, 344 insertions, 0 deletions
diff --git a/third_party/rust/glean/src/private/event.rs b/third_party/rust/glean/src/private/event.rs new file mode 100644 index 0000000000..8b1abca84f --- /dev/null +++ b/third_party/rust/glean/src/private/event.rs @@ -0,0 +1,221 @@ +// 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 inherent::inherent; +use std::{collections::HashMap, marker::PhantomData}; + +use glean_core::traits; + +use crate::{ErrorType, RecordedEvent}; + +// We need to wrap the glean-core type: otherwise if we try to implement +// the trait for the metric in `glean_core::metrics` we hit error[E0117]: +// only traits defined in the current crate can be implemented for arbitrary +// types. + +/// Developer-facing API for recording event metrics. +/// +/// Instances of this class type are automatically generated by the parsers +/// at build time, allowing developers to record values that were previously +/// registered in the metrics.yaml file. +#[derive(Clone)] +pub struct EventMetric<K> { + pub(crate) inner: glean_core::metrics::EventMetric, + extra_keys: PhantomData<K>, +} + +impl<K: traits::ExtraKeys> EventMetric<K> { + /// The public constructor used by automatically generated metrics. + pub fn new(meta: glean_core::CommonMetricData) -> Self { + let allowed_extra_keys = K::ALLOWED_KEYS.iter().map(|s| s.to_string()).collect(); + let inner = glean_core::metrics::EventMetric::new(meta, allowed_extra_keys); + Self { + inner, + extra_keys: PhantomData, + } + } + + /// The public constructor used by runtime-defined metrics. + pub fn with_runtime_extra_keys( + meta: glean_core::CommonMetricData, + allowed_extra_keys: Vec<String>, + ) -> Self { + let inner = glean_core::metrics::EventMetric::new(meta, allowed_extra_keys); + Self { + inner, + extra_keys: PhantomData, + } + } + + /// Record a new event with a provided timestamp. + /// + /// It's the caller's responsibility to ensure the timestamp comes from the same clock source. + /// Use [`glean::get_timestamp_ms`](crate::get_timestamp_ms) to get a valid timestamp. + pub fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) { + self.inner.record_with_time(timestamp, extra); + } +} + +#[inherent] +impl<K: traits::ExtraKeys> traits::Event for EventMetric<K> { + type Extra = K; + + pub fn record<M: Into<Option<<Self as traits::Event>::Extra>>>(&self, extra: M) { + let extra = extra + .into() + .map(|e| e.into_ffi_extra()) + .unwrap_or_else(HashMap::new); + self.inner.record(extra); + } + + pub fn test_get_value<'a, S: Into<Option<&'a str>>>( + &self, + ping_name: S, + ) -> Option<Vec<RecordedEvent>> { + let ping_name = ping_name.into().map(|s| s.to_string()); + self.inner.test_get_value(ping_name) + } + + pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 { + self.inner.test_get_num_recorded_errors(error) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::common_test::{lock_test, new_glean}; + use crate::CommonMetricData; + + #[test] + fn no_extra_keys() { + let _lock = lock_test(); + let _t = new_glean(None, true); + + let metric: EventMetric<traits::NoExtraKeys> = EventMetric::new(CommonMetricData { + name: "event".into(), + category: "test".into(), + send_in_pings: vec!["test1".into()], + ..Default::default() + }); + + metric.record(None); + metric.record(None); + + let data = metric.test_get_value(None).expect("no event recorded"); + assert_eq!(2, data.len()); + assert!(data[0].timestamp <= data[1].timestamp); + } + + #[test] + fn with_extra_keys() { + let _lock = lock_test(); + let _t = new_glean(None, true); + + #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)] + struct SomeExtra { + key1: Option<String>, + key2: Option<String>, + } + + impl glean_core::traits::ExtraKeys for SomeExtra { + const ALLOWED_KEYS: &'static [&'static str] = &["key1", "key2"]; + + fn into_ffi_extra(self) -> HashMap<String, String> { + let mut map = HashMap::new(); + self.key1.and_then(|key1| map.insert("key1".into(), key1)); + self.key2.and_then(|key2| map.insert("key2".into(), key2)); + map + } + } + + let metric: EventMetric<SomeExtra> = EventMetric::new(CommonMetricData { + name: "event".into(), + category: "test".into(), + send_in_pings: vec!["test1".into()], + ..Default::default() + }); + + let map1 = SomeExtra { + key1: Some("1".into()), + ..Default::default() + }; + metric.record(map1); + + let map2 = SomeExtra { + key1: Some("1".into()), + key2: Some("2".into()), + }; + metric.record(map2); + + metric.record(None); + + let data = metric.test_get_value(None).expect("no event recorded"); + assert_eq!(3, data.len()); + assert!(data[0].timestamp <= data[1].timestamp); + assert!(data[1].timestamp <= data[2].timestamp); + + let mut map = HashMap::new(); + map.insert("key1".into(), "1".into()); + assert_eq!(Some(map), data[0].extra); + + let mut map = HashMap::new(); + map.insert("key1".into(), "1".into()); + map.insert("key2".into(), "2".into()); + assert_eq!(Some(map), data[1].extra); + + assert_eq!(None, data[2].extra); + } + + #[test] + fn with_runtime_extra_keys() { + let _lock = lock_test(); + let _t = new_glean(None, true); + + #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)] + struct RuntimeExtra {} + + impl glean_core::traits::ExtraKeys for RuntimeExtra { + const ALLOWED_KEYS: &'static [&'static str] = &[]; + + fn into_ffi_extra(self) -> HashMap<String, String> { + HashMap::new() + } + } + + let metric: EventMetric<RuntimeExtra> = EventMetric::with_runtime_extra_keys( + CommonMetricData { + name: "event".into(), + category: "test".into(), + send_in_pings: vec!["test1".into()], + ..Default::default() + }, + vec!["key1".into(), "key2".into()], + ); + + let map1 = HashMap::from([("key1".into(), "1".into())]); + metric.record_with_time(0, map1); + + let map2 = HashMap::from([("key1".into(), "1".into()), ("key2".into(), "2".into())]); + metric.record_with_time(1, map2); + + metric.record_with_time(2, HashMap::new()); + + let data = metric.test_get_value(None).expect("no event recorded"); + assert_eq!(3, data.len()); + assert!(data[0].timestamp <= data[1].timestamp); + assert!(data[1].timestamp <= data[2].timestamp); + + let mut map = HashMap::new(); + map.insert("key1".into(), "1".into()); + assert_eq!(Some(map), data[0].extra); + + let mut map = HashMap::new(); + map.insert("key1".into(), "1".into()); + map.insert("key2".into(), "2".into()); + assert_eq!(Some(map), data[1].extra); + + assert_eq!(None, data[2].extra); + } +} diff --git a/third_party/rust/glean/src/private/mod.rs b/third_party/rust/glean/src/private/mod.rs new file mode 100644 index 0000000000..8a5c304193 --- /dev/null +++ b/third_party/rust/glean/src/private/mod.rs @@ -0,0 +1,35 @@ +// 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/. + +//! The different metric types supported by the Glean SDK to handle data. + +mod event; +mod ping; + +pub use event::EventMetric; +pub use glean_core::BooleanMetric; +pub use glean_core::CounterMetric; +pub use glean_core::CustomDistributionMetric; +pub use glean_core::DenominatorMetric; +pub use glean_core::MemoryDistributionMetric; +pub use glean_core::NumeratorMetric; +pub use glean_core::QuantityMetric; +pub use glean_core::RateMetric; +pub use glean_core::RecordedExperiment; +pub use glean_core::StringListMetric; +pub use glean_core::StringMetric; +pub use glean_core::TextMetric; +pub use glean_core::TimespanMetric; +pub use glean_core::TimingDistributionMetric; +pub use glean_core::UrlMetric; +pub use glean_core::UuidMetric; +pub use glean_core::{AllowLabeled, LabeledMetric}; +pub use glean_core::{Datetime, DatetimeMetric}; +pub use ping::PingType; + +// Re-export types that are used by the glean_parser-generated code. +#[doc(hidden)] +pub mod __export { + pub use once_cell::sync::Lazy; +} diff --git a/third_party/rust/glean/src/private/ping.rs b/third_party/rust/glean/src/private/ping.rs new file mode 100644 index 0000000000..c9c68a10a2 --- /dev/null +++ b/third_party/rust/glean/src/private/ping.rs @@ -0,0 +1,88 @@ +// 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::sync::{Arc, Mutex}; + +type BoxedCallback = Box<dyn FnOnce(Option<&str>) + Send + 'static>; + +/// A ping is a bundle of related metrics, gathered in a payload to be transmitted. +/// +/// The ping payload will be encoded in JSON format and contains shared information data. +#[derive(Clone)] +pub struct PingType { + pub(crate) inner: glean_core::metrics::PingType, + + /// **Test-only API** + /// + /// A function to be called right before a ping is submitted. + test_callback: Arc<Mutex<Option<BoxedCallback>>>, +} + +impl PingType { + /// Creates a new ping type. + /// + /// # Arguments + /// + /// * `name` - The name of the ping. + /// * `include_client_id` - Whether to include the client ID in the assembled ping when. + /// * `send_if_empty` - Whether the ping should be sent empty or not. + /// * `reason_codes` - The valid reason codes for this ping. + pub fn new<A: Into<String>>( + name: A, + include_client_id: bool, + send_if_empty: bool, + precise_timestamps: bool, + reason_codes: Vec<String>, + ) -> Self { + let inner = glean_core::metrics::PingType::new( + name.into(), + include_client_id, + send_if_empty, + precise_timestamps, + reason_codes, + ); + + Self { + inner, + test_callback: Arc::new(Default::default()), + } + } + + /// Submits the ping for eventual uploading. + /// + /// The ping content is assembled as soon as possible, but upload is not + /// guaranteed to happen immediately, as that depends on the upload policies. + /// + /// If the ping currently contains no content, it will not be sent, + /// unless it is configured to be sent if empty. + /// + /// # Arguments + /// + /// * `reason` - the reason the ping was triggered. Included in the + /// `ping_info.reason` part of the payload. + pub fn submit(&self, reason: Option<&str>) { + let mut cb = self.test_callback.lock().unwrap(); + let cb = cb.take(); + if let Some(cb) = cb { + cb(reason) + } + + self.inner.submit(reason.map(|s| s.to_string())) + } + + /// **Test-only API** + /// + /// Attach a callback to be called right before a new ping is submitted. + /// The provided function is called exactly once before submitting a ping. + /// + /// Note: The callback will be called on any call to submit. + /// A ping might not be sent afterwards, e.g. if the ping is otherwise empty (and + /// `send_if_empty` is `false`). + pub fn test_before_next_submit(&self, cb: impl FnOnce(Option<&str>) + Send + 'static) { + let mut test_callback = self.test_callback.lock().unwrap(); + + let cb = Box::new(cb); + *test_callback = Some(cb); + } +} |