summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean/src/private
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/glean/src/private')
-rw-r--r--third_party/rust/glean/src/private/event.rs221
-rw-r--r--third_party/rust/glean/src/private/mod.rs35
-rw-r--r--third_party/rust/glean/src/private/ping.rs88
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);
+ }
+}