summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/glean/src
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/glean/src')
-rw-r--r--third_party/rust/glean/src/common_test.rs57
-rw-r--r--third_party/rust/glean/src/configuration.rs31
-rw-r--r--third_party/rust/glean/src/core_metrics.rs115
-rw-r--r--third_party/rust/glean/src/dispatcher/global.rs191
-rw-r--r--third_party/rust/glean/src/dispatcher/mod.rs554
-rw-r--r--third_party/rust/glean/src/glean_metrics.rs10
-rw-r--r--third_party/rust/glean/src/lib.rs661
-rw-r--r--third_party/rust/glean/src/net/http_uploader.rs24
-rw-r--r--third_party/rust/glean/src/net/mod.rs114
-rw-r--r--third_party/rust/glean/src/pings.rs57
-rw-r--r--third_party/rust/glean/src/private/boolean.rs49
-rw-r--r--third_party/rust/glean/src/private/counter.rs62
-rw-r--r--third_party/rust/glean/src/private/custom_distribution.rs81
-rw-r--r--third_party/rust/glean/src/private/datetime.rs103
-rw-r--r--third_party/rust/glean/src/private/event.rs173
-rw-r--r--third_party/rust/glean/src/private/labeled.rs355
-rw-r--r--third_party/rust/glean/src/private/memory_distribution.rs67
-rw-r--r--third_party/rust/glean/src/private/mod.rs37
-rw-r--r--third_party/rust/glean/src/private/ping.rs48
-rw-r--r--third_party/rust/glean/src/private/quantity.rs63
-rw-r--r--third_party/rust/glean/src/private/recorded_experiment_data.rs15
-rw-r--r--third_party/rust/glean/src/private/string.rs72
-rw-r--r--third_party/rust/glean/src/private/string_list.rs108
-rw-r--r--third_party/rust/glean/src/private/timespan.rs163
-rw-r--r--third_party/rust/glean/src/private/timing_distribution.rs99
-rw-r--r--third_party/rust/glean/src/private/uuid.rs69
-rw-r--r--third_party/rust/glean/src/system.rs55
-rw-r--r--third_party/rust/glean/src/test.rs797
28 files changed, 4230 insertions, 0 deletions
diff --git a/third_party/rust/glean/src/common_test.rs b/third_party/rust/glean/src/common_test.rs
new file mode 100644
index 0000000000..5a0ccb43ef
--- /dev/null
+++ b/third_party/rust/glean/src/common_test.rs
@@ -0,0 +1,57 @@
+// 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 crate::ClientInfoMetrics;
+use crate::Configuration;
+use std::sync::{Mutex, MutexGuard};
+
+use once_cell::sync::Lazy;
+
+pub(crate) const GLOBAL_APPLICATION_ID: &str = "org.mozilla.rlb.test";
+
+// Because Glean uses a global-singleton, we need to run the tests one-by-one to
+// avoid different tests stomping over each other.
+// This is only an issue because we're resetting Glean, this cannot happen in normal
+// use of the RLB.
+//
+// We use a global lock to force synchronization of all tests, even if run multi-threaded.
+// This allows us to run without `--test-threads 1`.`
+pub(crate) fn lock_test() -> MutexGuard<'static, ()> {
+ static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+
+ // This is going to be called from all the tests: make sure
+ // to enable logging.
+ env_logger::try_init().ok();
+
+ let lock = GLOBAL_LOCK.lock().unwrap();
+
+ lock
+}
+
+// Create a new instance of Glean with a temporary directory.
+// We need to keep the `TempDir` alive, so that it's not deleted before we stop using it.
+pub(crate) fn new_glean(
+ configuration: Option<Configuration>,
+ clear_stores: bool,
+) -> tempfile::TempDir {
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = match configuration {
+ Some(c) => c,
+ None => Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ },
+ };
+
+ crate::test_reset_glean(cfg, ClientInfoMetrics::unknown(), clear_stores);
+ dir
+}
diff --git a/third_party/rust/glean/src/configuration.rs b/third_party/rust/glean/src/configuration.rs
new file mode 100644
index 0000000000..b13b2bf975
--- /dev/null
+++ b/third_party/rust/glean/src/configuration.rs
@@ -0,0 +1,31 @@
+// 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 crate::net::PingUploader;
+
+/// The default server pings are sent to.
+pub(crate) const DEFAULT_GLEAN_ENDPOINT: &str = "https://incoming.telemetry.mozilla.org";
+
+/// The Glean configuration.
+///
+/// Optional values will be filled in with default values.
+#[derive(Debug)]
+pub struct Configuration {
+ /// Whether upload should be enabled.
+ pub upload_enabled: bool,
+ /// Path to a directory to store all data in.
+ pub data_path: String,
+ /// The application ID (will be sanitized during initialization).
+ pub application_id: String,
+ /// The maximum number of events to store before sending a ping containing events.
+ pub max_events: Option<usize>,
+ /// Whether Glean should delay persistence of data from metrics with ping lifetime.
+ pub delay_ping_lifetime_io: bool,
+ /// The release channel the application is on, if known.
+ pub channel: Option<String>,
+ /// The server pings are sent to.
+ pub server_endpoint: Option<String>,
+ /// The instance of the uploader used to send pings.
+ pub uploader: Option<Box<dyn PingUploader + 'static>>,
+}
diff --git a/third_party/rust/glean/src/core_metrics.rs b/third_party/rust/glean/src/core_metrics.rs
new file mode 100644
index 0000000000..36e57c523a
--- /dev/null
+++ b/third_party/rust/glean/src/core_metrics.rs
@@ -0,0 +1,115 @@
+// 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 crate::private::StringMetric;
+use crate::{CommonMetricData, Lifetime};
+
+use once_cell::sync::Lazy;
+
+/// Metrics included in every ping as `client_info`.
+#[derive(Debug)]
+pub struct ClientInfoMetrics {
+ /// The build identifier generated by the CI system (e.g. "1234/A").
+ pub app_build: String,
+ /// The user visible version string (e.g. "1.0.3").
+ pub app_display_version: String,
+}
+
+impl ClientInfoMetrics {
+ /// Creates the client info with dummy values for all.
+ pub fn unknown() -> Self {
+ ClientInfoMetrics {
+ app_build: "unknown".to_string(),
+ app_display_version: "unknown".to_string(),
+ }
+ }
+}
+
+pub mod internal_metrics {
+ use super::*;
+
+ #[allow(non_upper_case_globals)]
+ pub static app_build: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "app_build".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static app_display_version: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "app_display_version".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static app_channel: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "app_channel".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static os_version: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "os_version".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static architecture: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "architecture".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static device_manufacturer: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "device_manufacturer".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static device_model: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(CommonMetricData {
+ name: "device_model".into(),
+ category: "".into(),
+ send_in_pings: vec!["glean_client_info".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+}
diff --git a/third_party/rust/glean/src/dispatcher/global.rs b/third_party/rust/glean/src/dispatcher/global.rs
new file mode 100644
index 0000000000..c35428d6f6
--- /dev/null
+++ b/third_party/rust/glean/src/dispatcher/global.rs
@@ -0,0 +1,191 @@
+// 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 once_cell::sync::Lazy;
+use std::sync::RwLock;
+
+use super::{DispatchError, DispatchGuard, Dispatcher};
+
+const GLOBAL_DISPATCHER_LIMIT: usize = 100;
+static GLOBAL_DISPATCHER: Lazy<RwLock<Option<Dispatcher>>> =
+ Lazy::new(|| RwLock::new(Some(Dispatcher::new(GLOBAL_DISPATCHER_LIMIT))));
+
+/// Get a dispatcher for the global queue.
+///
+/// A dispatcher is cheap to create, so we create one on every access instead of caching it.
+/// This avoids troubles for tests where the global dispatcher _can_ change.
+fn guard() -> DispatchGuard {
+ GLOBAL_DISPATCHER
+ .read()
+ .unwrap()
+ .as_ref()
+ .map(|dispatcher| dispatcher.guard())
+ .unwrap()
+}
+
+/// Launches a new task on the global dispatch queue.
+///
+/// The new task will be enqueued immediately.
+/// If the pre-init queue was already flushed,
+/// the background thread will process tasks in the queue (see [`flush_init`]).
+///
+/// This will not block.
+///
+/// [`flush_init`]: fn.flush_init.html
+pub fn launch(task: impl FnOnce() + Send + 'static) {
+ match guard().launch(task) {
+ Ok(_) => {}
+ Err(DispatchError::QueueFull) => {
+ log::info!("Exceeded maximum queue size, discarding task");
+ // TODO: Record this as an error.
+ }
+ Err(_) => {
+ log::info!("Failed to launch a task on the queue. Discarding task.");
+ }
+ }
+}
+
+/// Block until all tasks prior to this call are processed.
+pub fn block_on_queue() {
+ guard().block_on_queue();
+}
+
+/// Starts processing queued tasks in the global dispatch queue.
+///
+/// This function blocks until queued tasks prior to this call are finished.
+/// Once the initial queue is empty the dispatcher will wait for new tasks to be launched.
+pub fn flush_init() -> Result<(), DispatchError> {
+ guard().flush_init()
+}
+
+fn join_dispatcher_thread() -> Result<(), DispatchError> {
+ // After we issue the shutdown command, make sure to wait for the
+ // worker thread to join.
+ let mut lock = GLOBAL_DISPATCHER.write().unwrap();
+ let dispatcher = lock.as_mut().expect("Global dispatcher has gone missing");
+
+ if let Some(worker) = dispatcher.worker.take() {
+ return worker.join().map_err(|_| DispatchError::WorkerPanic);
+ }
+
+ Ok(())
+}
+
+/// Kill the blocked dispatcher without processing the queue.
+///
+/// This will immediately shutdown the worker thread
+/// and no other tasks will be processed.
+/// This only has an effect when the queue is still blocked.
+pub fn kill() -> Result<(), DispatchError> {
+ guard().kill()?;
+ join_dispatcher_thread()
+}
+
+/// Shuts down the dispatch queue.
+///
+/// This will initiate a shutdown of the worker thread
+/// and no new tasks will be processed after this.
+pub fn shutdown() -> Result<(), DispatchError> {
+ guard().shutdown()?;
+ join_dispatcher_thread()
+}
+
+/// TEST ONLY FUNCTION.
+/// Resets the Glean state and triggers init again.
+pub(crate) fn reset_dispatcher() {
+ // We don't care about shutdown errors, since they will
+ // definitely happen if this
+ let _ = shutdown();
+
+ // Now that the dispatcher is shut down, replace it.
+ // For that we
+ // 1. Create a new
+ // 2. Replace the global one
+ // 3. Only then return (and thus release the lock)
+ let mut lock = GLOBAL_DISPATCHER.write().unwrap();
+ let new_dispatcher = Some(Dispatcher::new(GLOBAL_DISPATCHER_LIMIT));
+ *lock = new_dispatcher;
+}
+
+#[cfg(test)]
+mod test {
+ use std::sync::{Arc, Mutex};
+
+ use super::*;
+
+ #[test]
+ #[ignore] // We can't reset the queue at the moment, so filling it up breaks other tests.
+ fn global_fills_up_in_order_and_works() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let result = Arc::new(Mutex::new(vec![]));
+
+ for i in 1..=GLOBAL_DISPATCHER_LIMIT {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(i);
+ });
+ }
+
+ {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(150);
+ });
+ }
+
+ flush_init().unwrap();
+
+ {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(200);
+ });
+ }
+
+ block_on_queue();
+
+ let mut expected = (1..=GLOBAL_DISPATCHER_LIMIT).collect::<Vec<_>>();
+ expected.push(200);
+ assert_eq!(&*result.lock().unwrap(), &expected);
+ }
+
+ #[test]
+ #[ignore] // We can't reset the queue at the moment, so flushing it breaks other tests.
+ fn global_nested_calls() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let result = Arc::new(Mutex::new(vec![]));
+
+ {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(1);
+ });
+ }
+
+ flush_init().unwrap();
+
+ {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(21);
+
+ {
+ let result = Arc::clone(&result);
+ launch(move || {
+ result.lock().unwrap().push(3);
+ });
+ }
+
+ result.lock().unwrap().push(22);
+ });
+ }
+
+ block_on_queue();
+
+ let expected = vec![1, 21, 22, 3];
+ assert_eq!(&*result.lock().unwrap(), &expected);
+ }
+}
diff --git a/third_party/rust/glean/src/dispatcher/mod.rs b/third_party/rust/glean/src/dispatcher/mod.rs
new file mode 100644
index 0000000000..e8dd8d2d6c
--- /dev/null
+++ b/third_party/rust/glean/src/dispatcher/mod.rs
@@ -0,0 +1,554 @@
+// 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/.
+
+//! A global dispatcher queue.
+//!
+//! # Example - Global Dispatch queue
+//!
+//! The global dispatch queue is pre-configured with a maximum queue size of 100 tasks.
+//!
+//! ```rust,ignore
+//! // Ensure the dispatcher queue is being worked on.
+//! dispatcher::flush_init();
+//!
+//! dispatcher::launch(|| {
+//! println!("Executing expensive task");
+//! // Run your expensive task in a separate thread.
+//! });
+//!
+//! dispatcher::launch(|| {
+//! println!("A second task that's executed sequentially, but off the main thread.");
+//! });
+//! ```
+
+// TODO: remove this once bug 1672440 is merged and the code below
+// will actually be used somewhere.
+#![allow(dead_code)]
+
+use std::{
+ mem,
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ },
+ thread::{self, JoinHandle},
+};
+
+use crossbeam_channel::{bounded, unbounded, SendError, Sender, TrySendError};
+use thiserror::Error;
+
+pub use global::*;
+
+mod global;
+
+/// Command received while blocked from further work.
+enum Blocked {
+ /// Shutdown immediately without processing the queue.
+ Shutdown,
+ /// Unblock and continue with work as normal.
+ Continue,
+}
+
+/// The command a worker should execute.
+enum Command {
+ /// A task is a user-defined function to run.
+ Task(Box<dyn FnOnce() + Send>),
+
+ /// Swap the channel
+ Swap(Sender<()>),
+
+ /// Signal the worker to finish work and shut down.
+ Shutdown,
+}
+
+/// The error returned from operations on the dispatcher
+#[derive(Error, Debug, PartialEq)]
+pub enum DispatchError {
+ /// The worker panicked while running a task
+ #[error("The worker panicked while running a task")]
+ WorkerPanic,
+
+ /// Maximum queue size reached
+ #[error("Maximum queue size reached")]
+ QueueFull,
+
+ /// Pre-init buffer was already flushed
+ #[error("Pre-init buffer was already flushed")]
+ AlreadyFlushed,
+
+ /// Failed to send command to worker thread
+ #[error("Failed to send command to worker thread")]
+ SendError,
+
+ /// Failed to receive from channel
+ #[error("Failed to receive from channel")]
+ RecvError(#[from] crossbeam_channel::RecvError),
+}
+
+impl From<TrySendError<Command>> for DispatchError {
+ fn from(err: TrySendError<Command>) -> Self {
+ match err {
+ TrySendError::Full(_) => DispatchError::QueueFull,
+ _ => DispatchError::SendError,
+ }
+ }
+}
+
+impl<T> From<SendError<T>> for DispatchError {
+ fn from(_: SendError<T>) -> Self {
+ DispatchError::SendError
+ }
+}
+
+/// A clonable guard for a dispatch queue.
+#[derive(Clone)]
+struct DispatchGuard {
+ /// Whether to queue on the preinit buffer or on the unbounded queue
+ queue_preinit: Arc<AtomicBool>,
+
+ /// Used to unblock the worker thread initially.
+ block_sender: Sender<Blocked>,
+
+ /// Sender for the preinit queue.
+ preinit_sender: Sender<Command>,
+
+ /// Sender for the unbounded queue.
+ sender: Sender<Command>,
+}
+
+impl DispatchGuard {
+ pub fn launch(&self, task: impl FnOnce() + Send + 'static) -> Result<(), DispatchError> {
+ let task = Command::Task(Box::new(task));
+ self.send(task)
+ }
+
+ pub fn shutdown(&mut self) -> Result<(), DispatchError> {
+ // Need to flush in order for the thread to actually process anything,
+ // including the shutdown command.
+ self.flush_init().ok();
+ self.send(Command::Shutdown)
+ }
+
+ fn send(&self, task: Command) -> Result<(), DispatchError> {
+ if self.queue_preinit.load(Ordering::SeqCst) {
+ match self.preinit_sender.try_send(task) {
+ Ok(()) => Ok(()),
+ Err(TrySendError::Full(_)) => Err(DispatchError::QueueFull),
+ Err(TrySendError::Disconnected(_)) => Err(DispatchError::SendError),
+ }
+ } else {
+ self.sender.send(task)?;
+ Ok(())
+ }
+ }
+
+ fn block_on_queue(&self) {
+ let (tx, rx) = crossbeam_channel::bounded(0);
+ self.launch(move || {
+ tx.send(())
+ .expect("(worker) Can't send message on single-use channel")
+ })
+ .expect("Failed to launch the blocking task");
+ rx.recv()
+ .expect("Failed to receive message on single-use channel");
+ }
+
+ fn kill(&mut self) -> Result<(), DispatchError> {
+ // We immediately stop queueing in the pre-init buffer.
+ let old_val = self.queue_preinit.swap(false, Ordering::SeqCst);
+ if !old_val {
+ return Err(DispatchError::AlreadyFlushed);
+ }
+
+ // Unblock the worker thread exactly once.
+ self.block_sender.send(Blocked::Shutdown)?;
+ Ok(())
+ }
+
+ fn flush_init(&mut self) -> Result<(), DispatchError> {
+ // We immediately stop queueing in the pre-init buffer.
+ let old_val = self.queue_preinit.swap(false, Ordering::SeqCst);
+ if !old_val {
+ return Err(DispatchError::AlreadyFlushed);
+ }
+
+ // Unblock the worker thread exactly once.
+ self.block_sender.send(Blocked::Continue)?;
+
+ // Single-use channel to communicate with the worker thread.
+ let (swap_sender, swap_receiver) = bounded(0);
+
+ // Send final command and block until it is sent.
+ self.preinit_sender
+ .send(Command::Swap(swap_sender))
+ .map_err(|_| DispatchError::SendError)?;
+
+ // Now wait for the worker thread to do the swap and inform us.
+ // This blocks until all tasks in the preinit buffer have been processed.
+ swap_receiver.recv()?;
+ Ok(())
+ }
+}
+
+/// A dispatcher.
+///
+/// Run expensive processing tasks sequentially off the main thread.
+/// Tasks are processed in a single separate thread in the order they are submitted.
+/// The dispatch queue will enqueue tasks while not flushed, up to the maximum queue size.
+/// Processing will start after flushing once, processing already enqueued tasks first, then
+/// waiting for further tasks to be enqueued.
+pub struct Dispatcher {
+ /// Guard used for communication with the worker thread.
+ guard: DispatchGuard,
+
+ /// Handle to the worker thread, allows to wait for it to finish.
+ worker: Option<JoinHandle<()>>,
+}
+
+impl Dispatcher {
+ /// Creates a new dispatcher with a maximum queue size.
+ ///
+ /// Launched tasks won't run until [`flush_init`] is called.
+ ///
+ /// [`flush_init`]: #method.flush_init
+ pub fn new(max_queue_size: usize) -> Self {
+ let (block_sender, block_receiver) = bounded(1);
+ let (preinit_sender, preinit_receiver) = bounded(max_queue_size);
+ let (sender, mut unbounded_receiver) = unbounded();
+
+ let queue_preinit = Arc::new(AtomicBool::new(true));
+
+ let worker = thread::Builder::new()
+ .name("glean.dispatcher".into())
+ .spawn(move || {
+ match block_receiver.recv() {
+ Err(_) => {
+ // The other side was disconnected.
+ // There's nothing the worker thread can do.
+ log::error!("The task producer was disconnected. Worker thread will exit.");
+ return;
+ }
+ Ok(Blocked::Shutdown) => {
+ // The other side wants us to stop immediately
+ return;
+ }
+ Ok(Blocked::Continue) => {
+ // Queue is unblocked, processing continues as normal.
+ }
+ }
+
+ let mut receiver = preinit_receiver;
+ loop {
+ use Command::*;
+
+ match receiver.recv() {
+ Ok(Shutdown) => {
+ break;
+ }
+
+ Ok(Task(f)) => {
+ (f)();
+ }
+
+ Ok(Swap(swap_done)) => {
+ // A swap should only occur exactly once.
+ // This is upheld by `flush_init`, which errors out if the preinit buffer
+ // was already flushed.
+
+ // We swap the channels we listen on for new tasks.
+ // The next iteration will continue with the unbounded queue.
+ mem::swap(&mut receiver, &mut unbounded_receiver);
+
+ // The swap command MUST be the last one received on the preinit buffer,
+ // so by the time we run this we know all preinit tasks were processed.
+ // We can notify the other side.
+ swap_done
+ .send(())
+ .expect("The caller of `flush_init` has gone missing");
+ }
+
+ // Other side was disconnected.
+ Err(_) => {
+ log::error!(
+ "The task producer was disconnected. Worker thread will exit."
+ );
+ return;
+ }
+ }
+ }
+ })
+ .expect("Failed to spawn Glean's dispatcher thread");
+
+ let guard = DispatchGuard {
+ queue_preinit,
+ block_sender,
+ preinit_sender,
+ sender,
+ };
+
+ Dispatcher {
+ guard,
+ worker: Some(worker),
+ }
+ }
+
+ fn guard(&self) -> DispatchGuard {
+ self.guard.clone()
+ }
+
+ fn block_on_queue(&self) {
+ self.guard().block_on_queue()
+ }
+
+ /// Waits for the worker thread to finish and finishes the dispatch queue.
+ ///
+ /// You need to call `shutdown` to initiate a shutdown of the queue.
+ fn join(mut self) -> Result<(), DispatchError> {
+ if let Some(worker) = self.worker.take() {
+ worker.join().map_err(|_| DispatchError::WorkerPanic)?;
+ }
+ Ok(())
+ }
+
+ /// Flushes the pre-init buffer.
+ ///
+ /// This function blocks until tasks queued prior to this call are finished.
+ /// Once the initial queue is empty the dispatcher will wait for new tasks to be launched.
+ ///
+ /// Returns an error if called multiple times.
+ pub fn flush_init(&mut self) -> Result<(), DispatchError> {
+ self.guard().flush_init()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
+ use std::sync::{Arc, Mutex};
+ use std::{thread, time::Duration};
+
+ fn enable_test_logging() {
+ // When testing we want all logs to go to stdout/stderr by default,
+ // without requiring each individual test to activate it.
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+
+ #[test]
+ fn tasks_run_off_the_main_thread() {
+ enable_test_logging();
+
+ let main_thread_id = thread::current().id();
+ let thread_canary = Arc::new(AtomicBool::new(false));
+
+ let mut dispatcher = Dispatcher::new(100);
+
+ // Force the Dispatcher out of the pre-init queue mode.
+ dispatcher
+ .flush_init()
+ .expect("Failed to get out of preinit queue mode");
+
+ let canary_clone = thread_canary.clone();
+ dispatcher
+ .guard()
+ .launch(move || {
+ assert!(thread::current().id() != main_thread_id);
+ // Use the canary bool to make sure this is getting called before
+ // the test completes.
+ assert_eq!(false, canary_clone.load(Ordering::SeqCst));
+ canary_clone.store(true, Ordering::SeqCst);
+ })
+ .expect("Failed to dispatch the test task");
+
+ dispatcher.block_on_queue();
+ assert_eq!(true, thread_canary.load(Ordering::SeqCst));
+ assert_eq!(main_thread_id, thread::current().id());
+ }
+
+ #[test]
+ fn launch_correctly_adds_tasks_to_preinit_queue() {
+ enable_test_logging();
+
+ let main_thread_id = thread::current().id();
+ let thread_canary = Arc::new(AtomicU8::new(0));
+
+ let mut dispatcher = Dispatcher::new(100);
+
+ // Add 3 tasks to queue each one increasing thread_canary by 1 to
+ // signal that the tasks ran.
+ for _ in 0..3 {
+ let canary_clone = thread_canary.clone();
+ dispatcher
+ .guard()
+ .launch(move || {
+ // Make sure the task is flushed off-the-main thread.
+ assert!(thread::current().id() != main_thread_id);
+ canary_clone.fetch_add(1, Ordering::SeqCst);
+ })
+ .expect("Failed to dispatch the test task");
+ }
+
+ // Ensure that no task ran.
+ assert_eq!(0, thread_canary.load(Ordering::SeqCst));
+
+ // Flush the queue and wait for the tasks to complete.
+ dispatcher
+ .flush_init()
+ .expect("Failed to get out of preinit queue mode");
+ // Validate that we have the expected canary value.
+ assert_eq!(3, thread_canary.load(Ordering::SeqCst));
+ }
+
+ #[test]
+ fn preinit_tasks_are_processed_after_flush() {
+ enable_test_logging();
+
+ let mut dispatcher = Dispatcher::new(10);
+
+ let result = Arc::new(Mutex::new(vec![]));
+ for i in 1..=5 {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ result.lock().unwrap().push(i);
+ })
+ .unwrap();
+ }
+
+ result.lock().unwrap().push(0);
+ dispatcher.flush_init().unwrap();
+ for i in 6..=10 {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ result.lock().unwrap().push(i);
+ })
+ .unwrap();
+ }
+
+ dispatcher.block_on_queue();
+
+ // This additionally checks that tasks were executed in order.
+ assert_eq!(
+ &*result.lock().unwrap(),
+ &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ );
+ }
+
+ #[test]
+ fn tasks_after_shutdown_are_not_processed() {
+ enable_test_logging();
+
+ let mut dispatcher = Dispatcher::new(10);
+
+ let result = Arc::new(Mutex::new(vec![]));
+
+ dispatcher.flush_init().unwrap();
+
+ dispatcher.guard().shutdown().unwrap();
+ {
+ let result = Arc::clone(&result);
+ // This might fail because the shutdown is quick enough,
+ // or it might succeed and still send the task.
+ // In any case that task should not be executed.
+ let _ = dispatcher.guard().launch(move || {
+ result.lock().unwrap().push(0);
+ });
+ }
+
+ dispatcher.join().unwrap();
+
+ assert!(result.lock().unwrap().is_empty());
+ }
+
+ #[test]
+ fn preinit_buffer_fills_up() {
+ enable_test_logging();
+
+ let mut dispatcher = Dispatcher::new(5);
+
+ let result = Arc::new(Mutex::new(vec![]));
+
+ for i in 1..=5 {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ result.lock().unwrap().push(i);
+ })
+ .unwrap();
+ }
+
+ {
+ let result = Arc::clone(&result);
+ let err = dispatcher.guard().launch(move || {
+ result.lock().unwrap().push(10);
+ });
+ assert_eq!(Err(DispatchError::QueueFull), err);
+ }
+
+ dispatcher.flush_init().unwrap();
+
+ {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ result.lock().unwrap().push(20);
+ })
+ .unwrap();
+ }
+
+ dispatcher.block_on_queue();
+
+ assert_eq!(&*result.lock().unwrap(), &[1, 2, 3, 4, 5, 20]);
+ }
+
+ #[test]
+ fn normal_queue_is_unbounded() {
+ enable_test_logging();
+
+ // Note: We can't actually test that it's fully unbounded,
+ // but we can quickly queue more slow tasks than the pre-init buffer holds
+ // and then guarantuee they all run.
+
+ let mut dispatcher = Dispatcher::new(5);
+
+ let result = Arc::new(Mutex::new(vec![]));
+
+ for i in 1..=5 {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ result.lock().unwrap().push(i);
+ })
+ .unwrap();
+ }
+
+ dispatcher.flush_init().unwrap();
+
+ // Queue more than 5 tasks,
+ // Each one is slow to process, so we should be faster in queueing
+ // them up than they are processed.
+ for i in 6..=20 {
+ let result = Arc::clone(&result);
+ dispatcher
+ .guard()
+ .launch(move || {
+ thread::sleep(Duration::from_millis(50));
+ result.lock().unwrap().push(i);
+ })
+ .unwrap();
+ }
+
+ dispatcher.guard().shutdown().unwrap();
+ dispatcher.join().unwrap();
+
+ let expected = (1..=20).collect::<Vec<_>>();
+ assert_eq!(&*result.lock().unwrap(), &expected);
+ }
+}
diff --git a/third_party/rust/glean/src/glean_metrics.rs b/third_party/rust/glean/src/glean_metrics.rs
new file mode 100644
index 0000000000..782f04d610
--- /dev/null
+++ b/third_party/rust/glean/src/glean_metrics.rs
@@ -0,0 +1,10 @@
+// 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/.
+
+// ** IMPORTANT **
+//
+// This file is required in order to include the ones generated by
+// 'glean-parser' from the SDK registry files.
+
+include!(concat!("pings.rs"));
diff --git a/third_party/rust/glean/src/lib.rs b/third_party/rust/glean/src/lib.rs
new file mode 100644
index 0000000000..42fd6944fc
--- /dev/null
+++ b/third_party/rust/glean/src/lib.rs
@@ -0,0 +1,661 @@
+// 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/.
+
+#![deny(broken_intra_doc_links)]
+#![deny(missing_docs)]
+
+//! Glean is a modern approach for recording and sending Telemetry data.
+//!
+//! It's in use at Mozilla.
+//!
+//! All documentation can be found online:
+//!
+//! ## [The Glean SDK Book](https://mozilla.github.io/glean)
+//!
+//! ## Example
+//!
+//! Initialize Glean, register a ping and then send it.
+//!
+//! ```rust,no_run
+//! # use glean::{Configuration, ClientInfoMetrics, Error, private::*};
+//! let cfg = Configuration {
+//! data_path: "/tmp/data".into(),
+//! application_id: "org.mozilla.glean_core.example".into(),
+//! upload_enabled: true,
+//! max_events: None,
+//! delay_ping_lifetime_io: false,
+//! channel: None,
+//! server_endpoint: None,
+//! uploader: None,
+//! };
+//! glean::initialize(cfg, ClientInfoMetrics::unknown());
+//!
+//! let prototype_ping = PingType::new("prototype", true, true, vec!());
+//!
+//! glean::register_ping_type(&prototype_ping);
+//!
+//! prototype_ping.submit(None);
+//! ```
+
+use once_cell::sync::OnceCell;
+use std::collections::HashMap;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Mutex;
+
+pub use configuration::Configuration;
+use configuration::DEFAULT_GLEAN_ENDPOINT;
+pub use core_metrics::ClientInfoMetrics;
+pub use glean_core::{
+ global_glean,
+ metrics::{DistributionData, MemoryUnit, RecordedEvent, TimeUnit},
+ setup_glean, CommonMetricData, Error, ErrorType, Glean, HistogramType, Lifetime, Result,
+};
+use private::RecordedExperimentData;
+
+mod configuration;
+mod core_metrics;
+mod dispatcher;
+mod glean_metrics;
+pub mod net;
+pub mod private;
+mod system;
+
+#[cfg(test)]
+mod common_test;
+
+const LANGUAGE_BINDING_NAME: &str = "Rust";
+
+/// State to keep track for the Rust Language bindings.
+///
+/// This is useful for setting Glean SDK-owned metrics when
+/// the state of the upload is toggled.
+#[derive(Debug)]
+struct RustBindingsState {
+ /// The channel the application is being distributed on.
+ channel: Option<String>,
+
+ /// Client info metrics set by the application.
+ client_info: ClientInfoMetrics,
+
+ /// An instance of the upload manager
+ upload_manager: net::UploadManager,
+}
+
+/// Set when `glean::initialize()` returns.
+/// This allows to detect calls that happen before `glean::initialize()` was called.
+/// Note: The initialization might still be in progress, as it runs in a separate thread.
+static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
+
+/// Keep track of the debug features before Glean is initialized.
+static PRE_INIT_DEBUG_VIEW_TAG: OnceCell<Mutex<String>> = OnceCell::new();
+static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
+static PRE_INIT_SOURCE_TAGS: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
+
+/// Keep track of pings registered before Glean is initialized.
+static PRE_INIT_PING_REGISTRATION: OnceCell<Mutex<Vec<private::PingType>>> = OnceCell::new();
+
+/// A global singleton storing additional state for Glean.
+///
+/// Requires a Mutex, because in tests we can actual reset this.
+static STATE: OnceCell<Mutex<RustBindingsState>> = OnceCell::new();
+
+/// Get a reference to the global state object.
+///
+/// Panics if no global state object was set.
+fn global_state() -> &'static Mutex<RustBindingsState> {
+ STATE.get().unwrap()
+}
+
+/// Set or replace the global bindings State object.
+fn setup_state(state: RustBindingsState) {
+ // The `OnceCell` type wrapping our state is thread-safe and can only be set once.
+ // Therefore even if our check for it being empty succeeds, setting it could fail if a
+ // concurrent thread is quicker in setting it.
+ // However this will not cause a bigger problem, as the second `set` operation will just fail.
+ // We can log it and move on.
+ //
+ // For all wrappers this is not a problem, as the State object is intialized exactly once on
+ // calling `initialize` on the global singleton and further operations check that it has been
+ // initialized.
+ if STATE.get().is_none() {
+ if STATE.set(Mutex::new(state)).is_err() {
+ log::error!(
+ "Global Glean state object is initialized already. This probably happened concurrently."
+ );
+ }
+ } else {
+ // We allow overriding the global State object to support test mode.
+ // In test mode the State object is fully destroyed and recreated.
+ // This all happens behind a mutex and is therefore also thread-safe.
+ let mut lock = STATE.get().unwrap().lock().unwrap();
+ *lock = state;
+ }
+}
+
+fn with_glean<F, R>(f: F) -> R
+where
+ F: FnOnce(&Glean) -> R,
+{
+ let glean = global_glean().expect("Global Glean object not initialized");
+ let lock = glean.lock().unwrap();
+ f(&lock)
+}
+
+fn with_glean_mut<F, R>(f: F) -> R
+where
+ F: FnOnce(&mut Glean) -> R,
+{
+ let glean = global_glean().expect("Global Glean object not initialized");
+ let mut lock = glean.lock().unwrap();
+ f(&mut lock)
+}
+
+/// Creates and initializes a new Glean object.
+///
+/// See [`glean_core::Glean::new`] for more information.
+///
+/// # Arguments
+///
+/// * `cfg` - the [`Configuration`] options to initialize with.
+/// * `client_info` - the [`ClientInfoMetrics`] values used to set Glean
+/// core metrics.
+pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) {
+ if was_initialize_called() {
+ log::error!("Glean should not be initialized multiple times");
+ return;
+ }
+
+ std::thread::Builder::new()
+ .name("glean.init".into())
+ .spawn(move || {
+ let core_cfg = glean_core::Configuration {
+ upload_enabled: cfg.upload_enabled,
+ data_path: cfg.data_path.clone(),
+ application_id: cfg.application_id.clone(),
+ language_binding_name: LANGUAGE_BINDING_NAME.into(),
+ max_events: cfg.max_events,
+ delay_ping_lifetime_io: cfg.delay_ping_lifetime_io,
+ };
+
+ let glean = match Glean::new(core_cfg) {
+ Ok(glean) => glean,
+ Err(err) => {
+ log::error!("Failed to initialize Glean: {}", err);
+ return;
+ }
+ };
+
+ // glean-core already takes care of logging errors: other bindings
+ // simply do early returns, as we're doing.
+ if glean_core::setup_glean(glean).is_err() {
+ return;
+ }
+
+ log::info!("Glean initialized");
+
+ // Initialize the ping uploader.
+ let upload_manager = net::UploadManager::new(
+ cfg.server_endpoint
+ .unwrap_or_else(|| DEFAULT_GLEAN_ENDPOINT.to_string()),
+ cfg.uploader
+ .unwrap_or_else(|| Box::new(net::HttpUploader) as Box<dyn net::PingUploader>),
+ );
+
+ // Now make this the global object available to others.
+ setup_state(RustBindingsState {
+ channel: cfg.channel,
+ client_info,
+ upload_manager,
+ });
+
+ let upload_enabled = cfg.upload_enabled;
+
+ with_glean_mut(|glean| {
+ let state = global_state().lock().unwrap();
+
+ // The debug view tag might have been set before initialize,
+ // get the cached value and set it.
+ if let Some(tag) = PRE_INIT_DEBUG_VIEW_TAG.get() {
+ let lock = tag.try_lock();
+ if let Ok(ref debug_tag) = lock {
+ glean.set_debug_view_tag(debug_tag);
+ }
+ }
+ // The log pings debug option might have been set before initialize,
+ // get the cached value and set it.
+ let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
+ if log_pigs {
+ glean.set_log_pings(log_pigs);
+ }
+ // The source tags might have been set before initialize,
+ // get the cached value and set them.
+ if let Some(tags) = PRE_INIT_SOURCE_TAGS.get() {
+ let lock = tags.try_lock();
+ if let Ok(ref source_tags) = lock {
+ glean.set_source_tags(source_tags.to_vec());
+ }
+ }
+
+ // Get the current value of the dirty flag so we know whether to
+ // send a dirty startup baseline ping below. Immediately set it to
+ // `false` so that dirty startup pings won't be sent if Glean
+ // initialization does not complete successfully.
+ // TODO Bug 1672956 will decide where to set this flag again.
+ let dirty_flag = glean.is_dirty_flag_set();
+ glean.set_dirty_flag(false);
+
+ // Register builtin pings.
+ // Unfortunately we need to manually list them here to guarantee
+ // they are registered synchronously before we need them.
+ // We don't need to handle the deletion-request ping. It's never touched
+ // from the language implementation.
+ glean.register_ping_type(&glean_metrics::pings::baseline.ping_type);
+ glean.register_ping_type(&glean_metrics::pings::metrics.ping_type);
+ glean.register_ping_type(&glean_metrics::pings::events.ping_type);
+
+ // Perform registration of pings that were attempted to be
+ // registered before init.
+ if let Some(tags) = PRE_INIT_PING_REGISTRATION.get() {
+ let lock = tags.try_lock();
+ if let Ok(pings) = lock {
+ for ping in &*pings {
+ glean.register_ping_type(&ping.ping_type);
+ }
+ }
+ }
+
+ // If this is the first time ever the Glean SDK runs, make sure to set
+ // some initial core metrics in case we need to generate early pings.
+ // The next times we start, we would have them around already.
+ let is_first_run = glean.is_first_run();
+ if is_first_run {
+ initialize_core_metrics(&glean, &state.client_info, state.channel.clone());
+ }
+
+ // Deal with any pending events so we can start recording new ones
+ let pings_submitted = glean.on_ready_to_submit_pings();
+
+ // We need to kick off upload in these cases:
+ // 1. Pings were submitted through Glean and it is ready to upload those pings;
+ // 2. Upload is disabled, to upload a possible deletion-request ping.
+ if pings_submitted || !upload_enabled {
+ state.upload_manager.trigger_upload();
+ }
+
+ // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
+ // ping startup check should be performed before any other ping, since it relies
+ // on being dispatched to the API context before any other metric.
+ // TODO: start the metrics ping scheduler, will happen in bug 1672951.
+
+ // Check if the "dirty flag" is set. That means the product was probably
+ // force-closed. If that's the case, submit a 'baseline' ping with the
+ // reason "dirty_startup". We only do that from the second run.
+ if !is_first_run && dirty_flag {
+ // TODO: bug 1672956 - submit_ping_by_name_sync("baseline", "dirty_startup");
+ }
+
+ // From the second time we run, after all startup pings are generated,
+ // make sure to clear `lifetime: application` metrics and set them again.
+ // Any new value will be sent in newly generated pings after startup.
+ if !is_first_run {
+ glean.clear_application_lifetime_metrics();
+ initialize_core_metrics(&glean, &state.client_info, state.channel.clone());
+ }
+ });
+
+ // Signal Dispatcher that init is complete
+ if let Err(err) = dispatcher::flush_init() {
+ log::error!("Unable to flush the preinit queue: {}", err);
+ }
+ })
+ .expect("Failed to spawn Glean's init thread");
+
+ // Mark the initialization as called: this needs to happen outside of the
+ // dispatched block!
+ INITIALIZE_CALLED.store(true, Ordering::SeqCst);
+}
+
+/// Shuts down Glean.
+///
+/// This currently only attempts to shut down the
+/// internal dispatcher.
+pub fn shutdown() {
+ if global_glean().is_none() {
+ log::warn!("Shutdown called before Glean is initialized");
+ if let Err(e) = dispatcher::kill() {
+ log::error!("Can't kill dispatcher thread: {:?}", e);
+ }
+
+ return;
+ }
+
+ if let Err(e) = dispatcher::shutdown() {
+ log::error!("Can't shutdown dispatcher thread: {:?}", e);
+ }
+}
+
+/// Block on the dispatcher emptying.
+///
+/// This will panic if called before Glean is initialized.
+fn block_on_dispatcher() {
+ assert!(
+ was_initialize_called(),
+ "initialize was never called. Can't block on the dispatcher queue."
+ );
+ dispatcher::block_on_queue()
+}
+
+/// Checks if [`initialize`] was ever called.
+///
+/// # Returns
+///
+/// `true` if it was, `false` otherwise.
+fn was_initialize_called() -> bool {
+ INITIALIZE_CALLED.load(Ordering::SeqCst)
+}
+
+fn initialize_core_metrics(
+ glean: &Glean,
+ client_info: &ClientInfoMetrics,
+ channel: Option<String>,
+) {
+ core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
+ core_metrics::internal_metrics::app_display_version
+ .set_sync(glean, &client_info.app_display_version[..]);
+ if let Some(app_channel) = channel {
+ core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
+ }
+ core_metrics::internal_metrics::os_version.set_sync(glean, "unknown".to_string());
+ core_metrics::internal_metrics::architecture.set_sync(glean, system::ARCH.to_string());
+ core_metrics::internal_metrics::device_manufacturer.set_sync(glean, "unknown".to_string());
+ core_metrics::internal_metrics::device_model.set_sync(glean, "unknown".to_string());
+}
+
+/// Sets whether upload is enabled or not.
+///
+/// See [`glean_core::Glean::set_upload_enabled`].
+pub fn set_upload_enabled(enabled: bool) {
+ if !was_initialize_called() {
+ let msg =
+ "Changing upload enabled before Glean is initialized is not supported.\n \
+ Pass the correct state into `Glean.initialize()`.\n \
+ See documentation at https://mozilla.github.io/glean/book/user/general-api.html#initializing-the-glean-sdk";
+ log::error!("{}", msg);
+ return;
+ }
+
+ // Changing upload enabled always happens asynchronous.
+ // That way it follows what a user expect when calling it inbetween other calls:
+ // it executes in the right order.
+ //
+ // Because the dispatch queue is halted until Glean is fully initialized
+ // we can safely enqueue here and it will execute after initialization.
+ dispatcher::launch(move || {
+ with_glean_mut(|glean| {
+ let state = global_state().lock().unwrap();
+ let old_enabled = glean.is_upload_enabled();
+ glean.set_upload_enabled(enabled);
+
+ // TODO: Cancel upload and any outstanding metrics ping scheduler
+ // task. Will happen on bug 1672951.
+
+ if !old_enabled && enabled {
+ // If uploading is being re-enabled, we have to restore the
+ // application-lifetime metrics.
+ initialize_core_metrics(&glean, &state.client_info, state.channel.clone());
+ }
+
+ if old_enabled && !enabled {
+ // If uploading is disabled, we need to send the deletion-request ping:
+ // note that glean-core takes care of generating it.
+ state.upload_manager.trigger_upload();
+ }
+ });
+ });
+}
+
+/// Register a new [`PingType`](private::PingType).
+pub fn register_ping_type(ping: &private::PingType) {
+ // If this happens after Glean.initialize is called (and returns),
+ // we dispatch ping registration on the thread pool.
+ // Registering a ping should not block the application.
+ // Submission itself is also dispatched, so it will always come after the registration.
+ if was_initialize_called() {
+ let ping = ping.clone();
+ dispatcher::launch(move || {
+ with_glean_mut(|glean| {
+ glean.register_ping_type(&ping.ping_type);
+ })
+ })
+ } else {
+ // We need to keep track of pings, so they get re-registered after a reset or
+ // if ping registration is attempted before Glean initializes.
+ // This state is kept across Glean resets, which should only ever happen in test mode.
+ // It's a set and keeping them around forever should not have much of an impact.
+ let m = PRE_INIT_PING_REGISTRATION.get_or_init(Default::default);
+ let mut lock = m.lock().unwrap();
+ lock.push(ping.clone());
+ }
+}
+
+/// Collects and submits a ping for eventual uploading.
+///
+/// See [`glean_core::Glean.submit_ping`].
+pub(crate) fn submit_ping(ping: &private::PingType, reason: Option<&str>) {
+ submit_ping_by_name(&ping.name, reason)
+}
+
+/// Collects and submits a ping for eventual uploading by name.
+///
+/// Note that this needs to be public in order for RLB consumers to
+/// use Glean debugging facilities.
+///
+/// See [`glean_core::Glean.submit_ping_by_name`].
+pub fn submit_ping_by_name(ping: &str, reason: Option<&str>) {
+ let ping = ping.to_string();
+ let reason = reason.map(|s| s.to_string());
+ dispatcher::launch(move || {
+ submit_ping_by_name_sync(&ping, reason.as_deref());
+ })
+}
+
+/// Collect and submit a ping (by its name) for eventual upload, synchronously.
+///
+/// The ping will be looked up in the known instances of [`private::PingType`]. If the
+/// ping isn't known, an error is logged and the ping isn't queued for 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 assembled and
+/// queued for sending, unless explicitly specified otherwise in the registry
+/// file.
+///
+/// # Arguments
+///
+/// * `ping_name` - the name of the ping to submit.
+/// * `reason` - the reason the ping is being submitted.
+pub(crate) fn submit_ping_by_name_sync(ping: &str, reason: Option<&str>) {
+ if !was_initialize_called() {
+ log::error!("Glean must be initialized before submitting pings.");
+ return;
+ }
+
+ let submitted_ping = with_glean(|glean| {
+ if !glean.is_upload_enabled() {
+ log::info!("Glean disabled: not submitting any pings.");
+ // This won't actually return from `submit_ping_by_name`, but
+ // returning `false` here skips spinning up the uploader below,
+ // which is basically the same.
+ return Some(false);
+ }
+
+ glean.submit_ping_by_name(&ping, reason.as_deref()).ok()
+ });
+
+ if let Some(true) = submitted_ping {
+ let state = global_state().lock().unwrap();
+ state.upload_manager.trigger_upload();
+ }
+}
+
+/// Indicate that an experiment is running. Glean will then add an
+/// experiment annotation to the environment which is sent with pings. This
+/// infomration is not persisted between runs.
+///
+/// See [`glean_core::Glean::set_experiment_active`].
+pub fn set_experiment_active(
+ experiment_id: String,
+ branch: String,
+ extra: Option<HashMap<String, String>>,
+) {
+ dispatcher::launch(move || {
+ with_glean(|glean| {
+ glean.set_experiment_active(
+ experiment_id.to_owned(),
+ branch.to_owned(),
+ extra.to_owned(),
+ )
+ });
+ })
+}
+
+/// Indicate that an experiment is no longer running.
+///
+/// See [`glean_core::Glean::set_experiment_inactive`].
+pub fn set_experiment_inactive(experiment_id: String) {
+ dispatcher::launch(move || {
+ with_glean(|glean| glean.set_experiment_inactive(experiment_id.to_owned()))
+ })
+}
+
+/// TEST ONLY FUNCTION.
+/// Checks if an experiment is currently active.
+#[allow(dead_code)]
+pub(crate) fn test_is_experiment_active(experiment_id: String) -> bool {
+ block_on_dispatcher();
+ with_glean(|glean| glean.test_is_experiment_active(experiment_id.to_owned()))
+}
+
+/// TEST ONLY FUNCTION.
+/// Returns the [`RecordedExperimentData`] for the given `experiment_id` or panics if
+/// the id isn't found.
+#[allow(dead_code)]
+pub(crate) fn test_get_experiment_data(experiment_id: String) -> RecordedExperimentData {
+ block_on_dispatcher();
+ with_glean(|glean| {
+ let json_data = glean
+ .test_get_experiment_data_as_json(experiment_id.to_owned())
+ .unwrap_or_else(|| panic!("No experiment found for id: {}", experiment_id));
+ serde_json::from_str::<RecordedExperimentData>(&json_data).unwrap()
+ })
+}
+
+/// Destroy the global Glean state.
+pub(crate) fn destroy_glean(clear_stores: bool) {
+ // Destroy the existing glean instance from glean-core.
+ if was_initialize_called() {
+ // We need to check if the Glean object (from glean-core) is
+ // initialized, otherwise this will crash on the first test
+ // due to bug 1675215 (this check can be removed once that
+ // bug is fixed).
+ if global_glean().is_some() {
+ with_glean_mut(|glean| {
+ if clear_stores {
+ glean.test_clear_all_stores()
+ }
+ glean.destroy_db()
+ });
+ }
+ // Allow us to go through initialization again.
+ INITIALIZE_CALLED.store(false, Ordering::SeqCst);
+ // Reset the dispatcher.
+ dispatcher::reset_dispatcher();
+ }
+}
+
+/// TEST ONLY FUNCTION.
+/// Resets the Glean state and triggers init again.
+pub fn test_reset_glean(cfg: Configuration, client_info: ClientInfoMetrics, clear_stores: bool) {
+ destroy_glean(clear_stores);
+
+ // Always log pings for tests
+ //Glean.setLogPings(true)
+ initialize(cfg, client_info);
+}
+
+/// Sets a debug view tag.
+///
+/// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the
+/// value of the tag and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
+///
+/// # Arguments
+///
+/// * `tag` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
+///
+/// # Returns
+///
+/// This will return `false` in case `tag` is not a valid tag and `true` otherwise.
+/// If called before Glean is initialized it will always return `true`.
+pub fn set_debug_view_tag(tag: &str) -> bool {
+ if was_initialize_called() {
+ with_glean_mut(|glean| glean.set_debug_view_tag(tag))
+ } else {
+ // Glean has not been initialized yet. Cache the provided tag value.
+ let m = PRE_INIT_DEBUG_VIEW_TAG.get_or_init(Default::default);
+ let mut lock = m.lock().unwrap();
+ *lock = tag.to_string();
+ // When setting the debug view tag before initialization,
+ // we don't validate the tag, thus this function always returns true.
+ true
+ }
+}
+
+/// Sets the log pings debug option.
+///
+/// When the log pings debug option is `true`,
+/// we log the payload of all succesfully assembled pings.
+///
+/// # Arguments
+///
+/// * `value` - The value of the log pings option
+pub fn set_log_pings(value: bool) {
+ if was_initialize_called() {
+ with_glean_mut(|glean| glean.set_log_pings(value));
+ } else {
+ PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
+ }
+}
+
+/// Sets source tags.
+///
+/// Overrides any existing source tags.
+/// Source tags will show in the destination datasets, after ingestion.
+///
+/// # Arguments
+///
+/// * `tags` - A vector of at most 5 valid HTTP header values. Individual
+/// tags must match the regex: "[a-zA-Z0-9-]{1,20}".
+///
+/// # Returns
+///
+/// This will return `false` in case `value` contains invalid tags and `true`
+/// otherwise or if the tag is set before Glean is initialized.
+pub fn set_source_tags(tags: Vec<String>) -> bool {
+ if was_initialize_called() {
+ with_glean_mut(|glean| glean.set_source_tags(tags))
+ } else {
+ // Glean has not been initialized yet. Cache the provided source tags.
+ let m = PRE_INIT_SOURCE_TAGS.get_or_init(Default::default);
+ let mut lock = m.lock().unwrap();
+ *lock = tags;
+ // When setting the source tags before initialization,
+ // we don't validate the tags, thus this function always returns true.
+ true
+ }
+}
+
+#[cfg(test)]
+mod test;
diff --git a/third_party/rust/glean/src/net/http_uploader.rs b/third_party/rust/glean/src/net/http_uploader.rs
new file mode 100644
index 0000000000..076e005704
--- /dev/null
+++ b/third_party/rust/glean/src/net/http_uploader.rs
@@ -0,0 +1,24 @@
+// 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 crate::net::{PingUploader, UploadResult};
+
+/// A simple mechanism to upload pings over HTTPS.
+#[derive(Debug)]
+pub struct HttpUploader;
+
+impl PingUploader for HttpUploader {
+ /// Uploads a ping to a server.
+ ///
+ /// # Arguments
+ ///
+ /// * `url` - the URL path to upload the data to.
+ /// * `body` - the serialized text data to send.
+ /// * `headers` - a vector of tuples containing the headers to send with
+ /// the request, i.e. (Name, Value).
+ fn upload(&self, url: String, _body: Vec<u8>, _headers: Vec<(String, String)>) -> UploadResult {
+ log::debug!("TODO bug 1675468: submitting to {:?}", url);
+ UploadResult::HttpStatus(200)
+ }
+}
diff --git a/third_party/rust/glean/src/net/mod.rs b/third_party/rust/glean/src/net/mod.rs
new file mode 100644
index 0000000000..3ec71baf00
--- /dev/null
+++ b/third_party/rust/glean/src/net/mod.rs
@@ -0,0 +1,114 @@
+// 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/.
+
+//! Handling the Glean upload logic.
+//!
+//! This doesn't perform the actual upload but rather handles
+//! retries, upload limitations and error tracking.
+
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
+use std::thread;
+use std::time::Duration;
+
+use crate::with_glean;
+use glean_core::upload::PingUploadTask;
+pub use glean_core::upload::{PingRequest, UploadResult};
+
+pub use http_uploader::*;
+
+mod http_uploader;
+
+/// A description of a component used to upload pings.
+pub trait PingUploader: std::fmt::Debug + Send + Sync {
+ /// Uploads a ping to a server.
+ ///
+ /// # Arguments
+ ///
+ /// * `url` - the URL path to upload the data to.
+ /// * `body` - the serialized text data to send.
+ /// * `headers` - a vector of tuples containing the headers to send with
+ /// the request, i.e. (Name, Value).
+ fn upload(&self, url: String, body: Vec<u8>, headers: Vec<(String, String)>) -> UploadResult;
+}
+
+/// The logic for uploading pings: this leaves the actual upload mechanism as
+/// a detail of the user-provided object implementing [`PingUploader`].
+#[derive(Debug)]
+pub(crate) struct UploadManager {
+ inner: Arc<Inner>,
+}
+
+#[derive(Debug)]
+struct Inner {
+ server_endpoint: String,
+ uploader: Box<dyn PingUploader + 'static>,
+ thread_running: AtomicBool,
+}
+
+impl UploadManager {
+ /// Create a new instance of the upload manager.
+ ///
+ /// # Arguments
+ ///
+ /// * `server_endpoint` - the server pings are sent to.
+ /// * `new_uploader` - the instance of the uploader used to send pings.
+ pub(crate) fn new(
+ server_endpoint: String,
+ new_uploader: Box<dyn PingUploader + 'static>,
+ ) -> Self {
+ Self {
+ inner: Arc::new(Inner {
+ server_endpoint,
+ uploader: new_uploader,
+ thread_running: AtomicBool::new(false),
+ }),
+ }
+ }
+
+ /// Signals Glean to upload pings at the next best opportunity.
+ pub(crate) fn trigger_upload(&self) {
+ if self.inner.thread_running.load(Ordering::SeqCst) {
+ log::debug!("The upload task is already running.");
+ return;
+ }
+
+ let inner = Arc::clone(&self.inner);
+
+ thread::Builder::new()
+ .name("glean.upload".into())
+ .spawn(move || {
+ // Mark the uploader as running.
+ inner.thread_running.store(true, Ordering::SeqCst);
+
+ loop {
+ let incoming_task = with_glean(|glean| glean.get_upload_task());
+
+ match incoming_task {
+ PingUploadTask::Upload(request) => {
+ let doc_id = request.document_id.clone();
+ let upload_url = format!("{}{}", inner.server_endpoint, request.path);
+ let headers: Vec<(String, String)> =
+ request.headers.into_iter().collect();
+ let result = inner.uploader.upload(upload_url, request.body, headers);
+ // Process the upload response.
+ with_glean(|glean| glean.process_ping_upload_response(&doc_id, result));
+ }
+ PingUploadTask::Wait(time) => {
+ thread::sleep(Duration::from_millis(time));
+ }
+ PingUploadTask::Done => {
+ // Nothing to do here, break out of the loop and clear the
+ // running flag.
+ inner.thread_running.store(false, Ordering::SeqCst);
+ return;
+ }
+ }
+ }
+ })
+ .expect("Failed to spawn Glean's uploader thread");
+ }
+}
diff --git a/third_party/rust/glean/src/pings.rs b/third_party/rust/glean/src/pings.rs
new file mode 100644
index 0000000000..68e2071bff
--- /dev/null
+++ b/third_party/rust/glean/src/pings.rs
@@ -0,0 +1,57 @@
+// 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/.
+
+// ** IMPORTANT **
+//
+// This file is *temporary*, it will be generated by 'glean-parser'
+// from the SDK registry files in the long run.
+
+pub mod pings {
+ use crate::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static baseline: Lazy<PingType> = Lazy::new(|| {
+ PingType::new(
+ "baseline",
+ true,
+ true,
+ vec![
+ "background".to_string(),
+ "dirty_startup".to_string(),
+ "foreground".to_string()
+ ]
+ )
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static metrics: Lazy<PingType> = Lazy::new(|| {
+ PingType::new(
+ "metrics",
+ true,
+ false,
+ vec![
+ "overdue".to_string(),
+ "reschedule".to_string(),
+ "today".to_string(),
+ "tomorrow".to_string(),
+ "upgrade".to_string()
+ ]
+ )
+ });
+
+ #[allow(non_upper_case_globals)]
+ pub static events: Lazy<PingType> = Lazy::new(|| {
+ PingType::new(
+ "metrics",
+ true,
+ false,
+ vec![
+ "background".to_string(),
+ "max_capacity".to_string(),
+ "startup".to_string()
+ ]
+ )
+ });
+}
diff --git a/third_party/rust/glean/src/private/boolean.rs b/third_party/rust/glean/src/private/boolean.rs
new file mode 100644
index 0000000000..c9bc87535d
--- /dev/null
+++ b/third_party/rust/glean/src/private/boolean.rs
@@ -0,0 +1,49 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording boolean 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 BooleanMetric(pub(crate) Arc<glean_core::metrics::BooleanMetric>);
+
+impl BooleanMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::BooleanMetric::new(meta)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Boolean for BooleanMetric {
+ fn set(&self, value: bool) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<bool> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = match ping_name.into() {
+ Some(name) => name,
+ None => self.0.meta().send_in_pings.first().unwrap(),
+ };
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+}
diff --git a/third_party/rust/glean/src/private/counter.rs b/third_party/rust/glean/src/private/counter.rs
new file mode 100644
index 0000000000..61e4cc3242
--- /dev/null
+++ b/third_party/rust/glean/src/private/counter.rs
@@ -0,0 +1,62 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording counter 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 CounterMetric(pub(crate) Arc<glean_core::metrics::CounterMetric>);
+
+impl CounterMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::CounterMetric::new(meta)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Counter for CounterMetric {
+ fn add(&self, amount: i32) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.add(glean, amount)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/private/custom_distribution.rs b/third_party/rust/glean/src/private/custom_distribution.rs
new file mode 100644
index 0000000000..790850c8d7
--- /dev/null
+++ b/third_party/rust/glean/src/private/custom_distribution.rs
@@ -0,0 +1,81 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::{DistributionData, MetricType};
+use glean_core::{CommonMetricData, ErrorType, HistogramType};
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer-facing API for recording custom distribution 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 CustomDistributionMetric(pub(crate) Arc<glean_core::metrics::CustomDistributionMetric>);
+
+impl CustomDistributionMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(
+ meta: CommonMetricData,
+ range_min: u64,
+ range_max: u64,
+ bucket_count: u64,
+ histogram_type: HistogramType,
+ ) -> Self {
+ Self(Arc::new(
+ glean_core::metrics::CustomDistributionMetric::new(
+ meta,
+ range_min,
+ range_max,
+ bucket_count,
+ histogram_type,
+ ),
+ ))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::CustomDistribution for CustomDistributionMetric {
+ fn accumulate_samples_signed(&self, samples: Vec<i64>) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || {
+ crate::with_glean(|glean| metric.accumulate_samples_signed(glean, samples))
+ });
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/private/datetime.rs b/third_party/rust/glean/src/private/datetime.rs
new file mode 100644
index 0000000000..fcb6376022
--- /dev/null
+++ b/third_party/rust/glean/src/private/datetime.rs
@@ -0,0 +1,103 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+pub use glean_core::metrics::{Datetime, TimeUnit};
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording Datetime 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 DatetimeMetric(pub(crate) Arc<glean_core::metrics::DatetimeMetric>);
+
+impl DatetimeMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData, time_unit: TimeUnit) -> Self {
+ Self(Arc::new(glean_core::metrics::DatetimeMetric::new(
+ meta, time_unit,
+ )))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Datetime for DatetimeMetric {
+ fn set(&self, value: Option<Datetime>) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<Datetime> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::common_test::{lock_test, new_glean};
+ use crate::CommonMetricData;
+ use chrono::prelude::*;
+
+ #[test]
+ fn datetime_convenient_api() {
+ let _lock = lock_test();
+ let _t = new_glean(None, true);
+
+ let metric: DatetimeMetric = DatetimeMetric::new(
+ CommonMetricData {
+ name: "datetime".into(),
+ category: "test".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ TimeUnit::Day,
+ );
+
+ // Record a date: it will get truncated to Day resolution.
+ let sample_date = FixedOffset::east(0).ymd(2018, 2, 25).and_hms(11, 5, 0);
+ metric.set(Some(sample_date));
+
+ // Check that the value has the correct resolution.
+ let date = metric.test_get_value(None).unwrap();
+ assert_eq!(date, FixedOffset::east(0).ymd(2018, 2, 25).and_hms(0, 0, 0));
+
+ // Ensure no error was recorded.
+ assert_eq!(
+ metric.test_get_num_recorded_errors(ErrorType::InvalidValue, None),
+ 0
+ )
+ }
+}
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..7bc6fe1017
--- /dev/null
+++ b/third_party/rust/glean/src/private/event.rs
@@ -0,0 +1,173 @@
+// 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, sync::Arc};
+
+use glean_core::metrics::MetricType;
+use glean_core::traits;
+
+use crate::{dispatcher, ErrorType, RecordedEvent};
+
+pub use glean_core::traits::NoExtraKeys;
+
+// 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.
+
+/// This implements the 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: Arc<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 = Arc::new(glean_core::metrics::EventMetric::new(
+ meta,
+ allowed_extra_keys,
+ ));
+ Self {
+ inner,
+ extra_keys: PhantomData,
+ }
+ }
+}
+
+#[inherent(pub)]
+impl<K: traits::ExtraKeys> traits::Event for EventMetric<K> {
+ type Extra = K;
+
+ fn record<M: Into<Option<HashMap<<Self as traits::Event>::Extra, String>>>>(&self, extra: M) {
+ const NANOS_PER_MILLI: u64 = 1_000_000;
+ let now = time::precise_time_ns() / NANOS_PER_MILLI;
+
+ // Translate from [ExtraKey -> String] to a [Int -> String] map
+ let extra = extra
+ .into()
+ .map(|h| h.into_iter().map(|(k, v)| (k.index(), v)).collect());
+ let metric = Arc::clone(&self.inner);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.record(glean, now, extra)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<Vec<RecordedEvent>> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.inner.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.inner.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(
+ &glean,
+ self.inner.meta(),
+ error,
+ ping_name.into(),
+ )
+ .unwrap_or(0)
+ })
+ }
+}
+
+#[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<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(Debug, Clone, Copy, Hash, Eq, PartialEq)]
+ enum SomeExtra {
+ Key1,
+ Key2,
+ }
+
+ impl glean_core::traits::ExtraKeys for SomeExtra {
+ const ALLOWED_KEYS: &'static [&'static str] = &["key1", "key2"];
+
+ fn index(self) -> i32 {
+ self as i32
+ }
+ }
+
+ let metric: EventMetric<SomeExtra> = EventMetric::new(CommonMetricData {
+ name: "event".into(),
+ category: "test".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ });
+
+ let mut map1 = HashMap::new();
+ map1.insert(SomeExtra::Key1, "1".into());
+ metric.record(map1);
+
+ let mut map2 = HashMap::new();
+ map2.insert(SomeExtra::Key1, "1".into());
+ map2.insert(SomeExtra::Key2, "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);
+ }
+}
diff --git a/third_party/rust/glean/src/private/labeled.rs b/third_party/rust/glean/src/private/labeled.rs
new file mode 100644
index 0000000000..af4f1411a3
--- /dev/null
+++ b/third_party/rust/glean/src/private/labeled.rs
@@ -0,0 +1,355 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+/// Sealed traits protect against downstream implementations.
+///
+/// We wrap it in a private module that is inaccessible outside of this module.
+mod private {
+ use crate::{
+ private::BooleanMetric, private::CounterMetric, private::StringMetric, CommonMetricData,
+ };
+ use std::sync::Arc;
+
+ /// The sealed labeled trait.
+ ///
+ /// This also allows us to hide methods, that are only used internally
+ /// and should not be visible to users of the object implementing the
+ /// `Labeled<T>` trait.
+ pub trait Sealed {
+ /// The `glean_core` metric type representing the labeled metric.
+ type Inner: glean_core::metrics::MetricType + Clone;
+
+ /// Create a new metric object implementing this trait from the inner type.
+ fn from_inner(metric: Self::Inner) -> Self;
+
+ /// Create a new `glean_core` metric from the metadata.
+ fn new_inner(meta: crate::CommonMetricData) -> Self::Inner;
+ }
+
+ // `LabeledMetric<BooleanMetric>` is possible.
+ //
+ // See [Labeled Booleans](https://mozilla.github.io/glean/book/user/metrics/labeled_booleans.html).
+ impl Sealed for BooleanMetric {
+ type Inner = glean_core::metrics::BooleanMetric;
+
+ fn from_inner(metric: Self::Inner) -> Self {
+ BooleanMetric(Arc::new(metric))
+ }
+
+ fn new_inner(meta: CommonMetricData) -> Self::Inner {
+ glean_core::metrics::BooleanMetric::new(meta)
+ }
+ }
+
+ // `LabeledMetric<StringMetric>` is possible.
+ //
+ // See [Labeled Strings](https://mozilla.github.io/glean/book/user/metrics/labeled_strings.html).
+ impl Sealed for StringMetric {
+ type Inner = glean_core::metrics::StringMetric;
+
+ fn from_inner(metric: Self::Inner) -> Self {
+ StringMetric(Arc::new(metric))
+ }
+
+ fn new_inner(meta: CommonMetricData) -> Self::Inner {
+ glean_core::metrics::StringMetric::new(meta)
+ }
+ }
+
+ // `LabeledMetric<CounterMetric>` is possible.
+ //
+ // See [Labeled Counters](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html).
+ impl Sealed for CounterMetric {
+ type Inner = glean_core::metrics::CounterMetric;
+
+ fn from_inner(metric: Self::Inner) -> Self {
+ CounterMetric(Arc::new(metric))
+ }
+
+ fn new_inner(meta: CommonMetricData) -> Self::Inner {
+ glean_core::metrics::CounterMetric::new(meta)
+ }
+ }
+}
+
+/// Marker trait for metrics that can be nested inside a labeled metric.
+///
+/// This trait is sealed and cannot be implemented for types outside this crate.
+pub trait AllowLabeled: private::Sealed {}
+
+// Implement the trait for everything we marked as allowed.
+impl<T> AllowLabeled for T where T: private::Sealed {}
+
+// 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.
+
+/// This implements the specific facing API for recording labeled metrics.
+///
+/// Instances of this type are automatically generated by the parser
+/// at build time, allowing developers to record values that were previously
+/// registered in the metrics.yaml file.
+/// Unlike most metric types, [`LabeledMetric`] does not have its own corresponding
+/// storage, but records metrics for the underlying metric type `T` in the storage
+/// for that type.
+#[derive(Clone)]
+pub struct LabeledMetric<T: AllowLabeled>(
+ pub(crate) Arc<glean_core::metrics::LabeledMetric<T::Inner>>,
+);
+
+impl<T> LabeledMetric<T>
+where
+ T: AllowLabeled,
+{
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData, labels: Option<Vec<String>>) -> Self {
+ let submetric = T::new_inner(meta);
+ let core = glean_core::metrics::LabeledMetric::new(submetric, labels);
+ Self(Arc::new(core))
+ }
+}
+
+#[inherent(pub)]
+impl<T> glean_core::traits::Labeled<T> for LabeledMetric<T>
+where
+ T: AllowLabeled + Clone,
+{
+ fn get(&self, label: &str) -> T {
+ let inner = self.0.get(label);
+ T::from_inner(inner)
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(
+ &glean,
+ self.0.get_submetric().meta(),
+ error,
+ ping_name.into(),
+ )
+ .unwrap_or(0)
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::ErrorType;
+ use crate::common_test::{lock_test, new_glean};
+ use crate::destroy_glean;
+ use crate::private::{BooleanMetric, CounterMetric, LabeledMetric, StringMetric};
+ use crate::CommonMetricData;
+
+ #[test]
+ fn test_labeled_counter_type() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<CounterMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_counter".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("label1").add(1);
+ metric.get("label2").add(2);
+ assert_eq!(1, metric.get("label1").test_get_value("test1").unwrap());
+ assert_eq!(2, metric.get("label2").test_get_value("test1").unwrap());
+ }
+
+ #[test]
+ fn test_other_label_with_predefined_labels() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<CounterMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_counter".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ Some(vec!["foo".into(), "bar".into(), "baz".into()]),
+ );
+
+ metric.get("foo").add(1);
+ metric.get("foo").add(2);
+ metric.get("bar").add(1);
+ metric.get("not_there").add(1);
+ metric.get("also_not_there").add(1);
+ metric.get("not_me").add(1);
+
+ assert_eq!(3, metric.get("foo").test_get_value(None).unwrap());
+ assert_eq!(1, metric.get("bar").test_get_value(None).unwrap());
+ assert!(metric.get("baz").test_get_value(None).is_none());
+ // The rest all lands in the __other__ bucket.
+ assert_eq!(3, metric.get("__other__").test_get_value(None).unwrap());
+ }
+
+ #[test]
+ fn test_other_label_without_predefined_labels() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<CounterMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_counter".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ // Record in 20 labels: it will go over the maximum number of supported
+ // dynamic labels.
+ for i in 0..=20 {
+ metric.get(format!("label_{}", i).as_str()).add(1);
+ }
+ // Record in a label once again.
+ metric.get("label_0").add(1);
+
+ assert_eq!(2, metric.get("label_0").test_get_value(None).unwrap());
+ for i in 1..15 {
+ assert_eq!(
+ 1,
+ metric
+ .get(format!("label_{}", i).as_str())
+ .test_get_value(None)
+ .unwrap()
+ );
+ }
+ assert_eq!(5, metric.get("__other__").test_get_value(None).unwrap());
+ }
+
+ #[test]
+ fn test_other_label_without_predefined_labels_before_glean_init() {
+ let _lock = lock_test();
+
+ // We explicitly want Glean to not be initialized.
+ destroy_glean(true);
+
+ let metric: LabeledMetric<CounterMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_counter".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ // Record in 20 labels: it will go over the maximum number of supported
+ // dynamic labels.
+ for i in 0..=20 {
+ metric.get(format!("label_{}", i).as_str()).add(1);
+ }
+ // Record in a label once again.
+ metric.get("label_0").add(1);
+
+ // Initialize Glean.
+ let _t = new_glean(None, false);
+
+ assert_eq!(2, metric.get("label_0").test_get_value(None).unwrap());
+ for i in 1..15 {
+ assert_eq!(
+ 1,
+ metric
+ .get(format!("label_{}", i).as_str())
+ .test_get_value(None)
+ .unwrap()
+ );
+ }
+ assert_eq!(5, metric.get("__other__").test_get_value(None).unwrap());
+ }
+
+ #[test]
+ fn test_labeled_string_type() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<StringMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_string".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("label1").set("foo");
+ metric.get("label2").set("bar");
+ assert_eq!("foo", metric.get("label1").test_get_value("test1").unwrap());
+ assert_eq!("bar", metric.get("label2").test_get_value("test1").unwrap());
+ }
+
+ #[test]
+ fn test_labeled_boolean_type() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<BooleanMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_boolean".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("label1").set(false);
+ metric.get("label2").set(true);
+ assert!(!metric.get("label1").test_get_value("test1").unwrap());
+ assert!(metric.get("label2").test_get_value("test1").unwrap());
+ }
+
+ #[test]
+ fn test_invalid_labels_record_errors() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ let metric: LabeledMetric<BooleanMetric> = LabeledMetric::new(
+ CommonMetricData {
+ name: "labeled_boolean".into(),
+ category: "labeled".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ None,
+ );
+
+ let invalid_label = "!#I'm invalid#--_";
+ metric.get(invalid_label).set(true);
+ assert_eq!(true, metric.get("__other__").test_get_value(None).unwrap());
+ assert_eq!(
+ 1,
+ metric.test_get_num_recorded_errors(ErrorType::InvalidLabel, None)
+ );
+ }
+}
diff --git a/third_party/rust/glean/src/private/memory_distribution.rs b/third_party/rust/glean/src/private/memory_distribution.rs
new file mode 100644
index 0000000000..584d86c9d1
--- /dev/null
+++ b/third_party/rust/glean/src/private/memory_distribution.rs
@@ -0,0 +1,67 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::{DistributionData, MemoryUnit, MetricType};
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer-facing API for recording memory distribution 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 MemoryDistributionMetric(pub(crate) Arc<glean_core::metrics::MemoryDistributionMetric>);
+
+impl MemoryDistributionMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData, memory_unit: MemoryUnit) -> Self {
+ Self(Arc::new(
+ glean_core::metrics::MemoryDistributionMetric::new(meta, memory_unit),
+ ))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::MemoryDistribution for MemoryDistributionMetric {
+ fn accumulate(&self, sample: u64) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.accumulate(glean, sample)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
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..c4b692072b
--- /dev/null
+++ b/third_party/rust/glean/src/private/mod.rs
@@ -0,0 +1,37 @@
+// 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 boolean;
+mod counter;
+mod custom_distribution;
+mod datetime;
+mod event;
+mod labeled;
+mod memory_distribution;
+mod ping;
+mod quantity;
+mod recorded_experiment_data;
+mod string;
+mod string_list;
+mod timespan;
+mod timing_distribution;
+mod uuid;
+
+pub use self::uuid::UuidMetric;
+pub use boolean::BooleanMetric;
+pub use counter::CounterMetric;
+pub use custom_distribution::CustomDistributionMetric;
+pub use datetime::{Datetime, DatetimeMetric};
+pub use event::EventMetric;
+pub use labeled::{AllowLabeled, LabeledMetric};
+pub use memory_distribution::MemoryDistributionMetric;
+pub use ping::PingType;
+pub use quantity::QuantityMetric;
+pub use recorded_experiment_data::RecordedExperimentData;
+pub use string::StringMetric;
+pub use string_list::StringListMetric;
+pub use timespan::TimespanMetric;
+pub use timing_distribution::TimingDistributionMetric;
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..c9452b9d35
--- /dev/null
+++ b/third_party/rust/glean/src/private/ping.rs
@@ -0,0 +1,48 @@
+// 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;
+
+/// A Glean ping.
+#[derive(Clone, Debug)]
+pub struct PingType {
+ pub(crate) name: String,
+ pub(crate) ping_type: glean_core::metrics::PingType,
+}
+
+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,
+ reason_codes: Vec<String>,
+ ) -> Self {
+ let name = name.into();
+ let ping_type = glean_core::metrics::PingType::new(
+ name.clone(),
+ include_client_id,
+ send_if_empty,
+ reason_codes,
+ );
+
+ let me = Self { name, ping_type };
+ crate::register_ping_type(&me);
+ me
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Ping for PingType {
+ fn submit(&self, reason: Option<&str>) {
+ crate::submit_ping(self, reason)
+ }
+}
diff --git a/third_party/rust/glean/src/private/quantity.rs b/third_party/rust/glean/src/private/quantity.rs
new file mode 100644
index 0000000000..716ce5147c
--- /dev/null
+++ b/third_party/rust/glean/src/private/quantity.rs
@@ -0,0 +1,63 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording Quantity 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 QuantityMetric(pub(crate) Arc<glean_core::metrics::QuantityMetric>);
+
+impl QuantityMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::QuantityMetric::new(meta)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Quantity for QuantityMetric {
+ fn set(&self, value: i64) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i64> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ #[allow(dead_code)] // Remove after mozilla/glean#1328
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/private/recorded_experiment_data.rs b/third_party/rust/glean/src/private/recorded_experiment_data.rs
new file mode 100644
index 0000000000..0550b536a7
--- /dev/null
+++ b/third_party/rust/glean/src/private/recorded_experiment_data.rs
@@ -0,0 +1,15 @@
+// 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 crate::HashMap;
+use serde::Deserialize;
+
+/// Deserialized experiment data.
+#[derive(Clone, Deserialize, Debug)]
+pub struct RecordedExperimentData {
+ /// The experiment's branch as set through [`set_experiment_active`](crate::set_experiment_active).
+ pub branch: String,
+ /// Any extra data associated with this experiment through [`set_experiment_active`](crate::set_experiment_active).
+ pub extra: Option<HashMap<String, String>>,
+}
diff --git a/third_party/rust/glean/src/private/string.rs b/third_party/rust/glean/src/private/string.rs
new file mode 100644
index 0000000000..0f11a016a9
--- /dev/null
+++ b/third_party/rust/glean/src/private/string.rs
@@ -0,0 +1,72 @@
+// 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 glean_core::Glean;
+use inherent::inherent;
+use std::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording string 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 StringMetric(pub(crate) Arc<glean_core::metrics::StringMetric>);
+
+impl StringMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::StringMetric::new(meta)))
+ }
+
+ /// Internal only, synchronous API for setting a string value.
+ pub(crate) fn set_sync<S: Into<std::string::String>>(&self, glean: &Glean, value: S) {
+ self.0.set(glean, value);
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::String for StringMetric {
+ fn set<S: Into<std::string::String>>(&self, value: S) {
+ let metric = Arc::clone(&self.0);
+ let new_value = value.into();
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, new_value)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<std::string::String> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/private/string_list.rs b/third_party/rust/glean/src/private/string_list.rs
new file mode 100644
index 0000000000..e37bec4fa6
--- /dev/null
+++ b/third_party/rust/glean/src/private/string_list.rs
@@ -0,0 +1,108 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer-facing API for recording string list 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 StringListMetric(pub(crate) Arc<glean_core::metrics::StringListMetric>);
+
+impl StringListMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::StringListMetric::new(meta)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::StringList for StringListMetric {
+ fn add<S: Into<String>>(&self, value: S) {
+ let metric = Arc::clone(&self.0);
+ let new_value = value.into();
+ dispatcher::launch(move || crate::with_glean(|glean| metric.add(glean, new_value)));
+ }
+
+ fn set(&self, value: Vec<String>) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value)));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<Vec<String>> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::common_test::{lock_test, new_glean};
+ use crate::{CommonMetricData, ErrorType};
+
+ #[test]
+ fn string_list_metric_docs() {
+ let _lock = lock_test();
+ let _t = new_glean(None, true);
+
+ let engine_metric: StringListMetric = StringListMetric::new(CommonMetricData {
+ name: "event".into(),
+ category: "test".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ });
+
+ let engines: Vec<String> = vec!["Google".to_string(), "DuckDuckGo".to_string()];
+
+ // Add them one at a time
+ engines.iter().for_each(|x| engine_metric.add(x));
+
+ // Set them in one go
+ engine_metric.set(engines);
+
+ assert!(engine_metric.test_get_value(None).is_some());
+
+ assert_eq!(
+ vec!["Google".to_string(), "DuckDuckGo".to_string()],
+ engine_metric.test_get_value(None).unwrap()
+ );
+
+ assert_eq!(
+ 0,
+ engine_metric.test_get_num_recorded_errors(ErrorType::InvalidValue, None)
+ );
+ }
+}
diff --git a/third_party/rust/glean/src/private/timespan.rs b/third_party/rust/glean/src/private/timespan.rs
new file mode 100644
index 0000000000..111d3b2d4a
--- /dev/null
+++ b/third_party/rust/glean/src/private/timespan.rs
@@ -0,0 +1,163 @@
+// 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, RwLock};
+
+use inherent::inherent;
+
+use glean_core::metrics::{MetricType, TimeUnit};
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording timespan 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 TimespanMetric(pub(crate) Arc<RwLock<glean_core::metrics::TimespanMetric>>);
+
+impl TimespanMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData, time_unit: TimeUnit) -> Self {
+ let timespan = glean_core::metrics::TimespanMetric::new(meta, time_unit);
+ Self(Arc::new(RwLock::new(timespan)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Timespan for TimespanMetric {
+ fn start(&self) {
+ let start_time = time::precise_time_ns();
+
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || {
+ crate::with_glean(|glean| {
+ let mut lock = metric
+ .write()
+ .expect("Lock poisoned for timespan metric on start.");
+ lock.set_start(glean, start_time)
+ })
+ });
+ }
+
+ fn stop(&self) {
+ let stop_time = time::precise_time_ns();
+
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || {
+ crate::with_glean(|glean| {
+ let mut lock = metric
+ .write()
+ .expect("Lock poisoned for timespan metric on stop.");
+ lock.set_stop(glean, stop_time)
+ })
+ });
+ }
+
+ fn cancel(&self) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || {
+ let mut lock = metric
+ .write()
+ .expect("Lock poisoned for timespan metric on cancel.");
+ lock.cancel()
+ });
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<u64> {
+ crate::block_on_dispatcher();
+
+ crate::with_glean(|glean| {
+ // Note: The order of operations is important here to avoid potential deadlocks because
+ // of `lock-order-inversion`.
+ // `with_glean` takes a lock on the global Glean object,
+ // then we take a lock on the metric itself here.
+ //
+ // Other parts do it in the same order, see for example `start`.
+ let metric = self
+ .0
+ .read()
+ .expect("Lock poisoned for timespan metric on test_get_value.");
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &metric.meta().send_in_pings[0]);
+ metric.test_get_value(glean, queried_ping_name)
+ })
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ let metric = self
+ .0
+ .read()
+ .expect("Lock poisoned for timespan metric on test_get_value.");
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, metric.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::{thread, time::Duration};
+
+ use super::*;
+ use crate::common_test::{lock_test, new_glean};
+ use crate::CommonMetricData;
+
+ #[test]
+ fn timespan_convenient_api() {
+ let _lock = lock_test();
+ let _t = new_glean(None, true);
+
+ let metric: TimespanMetric = TimespanMetric::new(
+ CommonMetricData {
+ name: "timespan".into(),
+ category: "test".into(),
+ send_in_pings: vec!["test1".into()],
+ ..Default::default()
+ },
+ TimeUnit::Millisecond,
+ );
+
+ // Canceling doesn't store data.
+ metric.start();
+ metric.cancel();
+ assert!(metric.test_get_value(None).is_none());
+
+ // Starting and stopping measures time.
+ metric.start();
+ thread::sleep(Duration::from_millis(10));
+ metric.stop();
+ assert!(10 <= metric.test_get_value(None).unwrap());
+
+ // No errors
+ assert_eq!(
+ metric.test_get_num_recorded_errors(ErrorType::InvalidState, None),
+ 0
+ );
+
+ // Stopping without starting is an error
+ metric.stop();
+ assert_eq!(
+ metric.test_get_num_recorded_errors(ErrorType::InvalidState, None),
+ 1
+ )
+ }
+}
diff --git a/third_party/rust/glean/src/private/timing_distribution.rs b/third_party/rust/glean/src/private/timing_distribution.rs
new file mode 100644
index 0000000000..5e1a9f930f
--- /dev/null
+++ b/third_party/rust/glean/src/private/timing_distribution.rs
@@ -0,0 +1,99 @@
+// 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::sync::{Arc, RwLock};
+
+use glean_core::metrics::{DistributionData, MetricType, TimeUnit, TimerId};
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer-facing API for recording timing distribution 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 TimingDistributionMetric(
+ pub(crate) Arc<RwLock<glean_core::metrics::TimingDistributionMetric>>,
+);
+
+impl TimingDistributionMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData, time_unit: TimeUnit) -> Self {
+ Self(Arc::new(RwLock::new(
+ glean_core::metrics::TimingDistributionMetric::new(meta, time_unit),
+ )))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::TimingDistribution for TimingDistributionMetric {
+ fn start(&self) -> TimerId {
+ let start_time = time::precise_time_ns();
+ self.0.write().unwrap().set_start(start_time)
+ }
+
+ fn stop_and_accumulate(&self, id: TimerId) {
+ let stop_time = time::precise_time_ns();
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || {
+ crate::with_glean(|glean| {
+ metric
+ .write()
+ .unwrap()
+ .set_stop_and_accumulate(glean, id, stop_time)
+ })
+ });
+ }
+
+ fn cancel(&self, id: TimerId) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || metric.write().unwrap().cancel(id));
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ crate::block_on_dispatcher();
+
+ crate::with_glean(|glean| {
+ // The order of taking these locks matter. Glean must be first.
+ let inner = self
+ .0
+ .read()
+ .expect("Lock poisoned for timing distribution metric on test_get_value.");
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &inner.meta().send_in_pings[0]);
+
+ inner.test_get_value(glean, queried_ping_name)
+ })
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(
+ &glean,
+ self.0.read().unwrap().meta(),
+ error,
+ ping_name.into(),
+ )
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/private/uuid.rs b/third_party/rust/glean/src/private/uuid.rs
new file mode 100644
index 0000000000..fc82b67b8c
--- /dev/null
+++ b/third_party/rust/glean/src/private/uuid.rs
@@ -0,0 +1,69 @@
+// 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::sync::Arc;
+
+use glean_core::metrics::MetricType;
+use glean_core::ErrorType;
+
+use crate::dispatcher;
+
+// 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.
+
+/// This implements the developer facing API for recording UUID 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 UuidMetric(pub(crate) Arc<glean_core::metrics::UuidMetric>);
+
+impl UuidMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(meta: glean_core::CommonMetricData) -> Self {
+ Self(Arc::new(glean_core::metrics::UuidMetric::new(meta)))
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::traits::Uuid for UuidMetric {
+ fn set(&self, value: uuid::Uuid) {
+ let metric = Arc::clone(&self.0);
+ dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value)));
+ }
+
+ fn generate_and_set(&self) -> uuid::Uuid {
+ // TODO: We can use glean-core's generate_and_set after bug 1673017.
+ let uuid = uuid::Uuid::new_v4();
+ self.set(uuid);
+ uuid
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<uuid::Uuid> {
+ crate::block_on_dispatcher();
+
+ let queried_ping_name = ping_name
+ .into()
+ .unwrap_or_else(|| &self.0.meta().send_in_pings[0]);
+
+ crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name))
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ crate::block_on_dispatcher();
+
+ crate::with_glean_mut(|glean| {
+ glean_core::test_get_num_recorded_errors(&glean, self.0.meta(), error, ping_name.into())
+ .unwrap_or(0)
+ })
+ }
+}
diff --git a/third_party/rust/glean/src/system.rs b/third_party/rust/glean/src/system.rs
new file mode 100644
index 0000000000..5bb7d3c34a
--- /dev/null
+++ b/third_party/rust/glean/src/system.rs
@@ -0,0 +1,55 @@
+// Copyright (c) 2017 The Rust Project Developers
+// Licensed under the MIT License.
+// Original license:
+// https://github.com/RustSec/platforms-crate/blob/ebbd3403243067ba3096f31684557285e352b639/LICENSE-MIT
+//
+// Permission is hereby granted, free of charge, to any
+// person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the
+// Software without restriction, including without
+// limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice
+// shall be included in all copies or substantial portions
+// of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+//! Detect and expose `target_arch` as a constant
+
+#[cfg(target_arch = "aarch64")]
+/// `target_arch` when building this crate: `aarch64`
+pub const ARCH: &str = "aarch64";
+
+#[cfg(target_arch = "arm")]
+/// `target_arch` when building this crate: `arm`
+pub const ARCH: &str = "arm";
+
+#[cfg(target_arch = "x86")]
+/// `target_arch` when building this crate: `x86`
+pub const ARCH: &str = "x86";
+
+#[cfg(target_arch = "x86_64")]
+/// `target_arch` when building this crate: `x86_64`
+pub const ARCH: &str = "x86_64";
+
+#[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "x86",
+ target_arch = "x86_64"
+)))]
+/// `target_arch` when building this crate: unknown!
+pub const ARCH: &str = "unknown";
diff --git a/third_party/rust/glean/src/test.rs b/third_party/rust/glean/src/test.rs
new file mode 100644
index 0000000000..789177706a
--- /dev/null
+++ b/third_party/rust/glean/src/test.rs
@@ -0,0 +1,797 @@
+// 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 crate::private::PingType;
+use crate::private::{BooleanMetric, CounterMetric};
+use std::path::PathBuf;
+
+use super::*;
+use crate::common_test::{lock_test, new_glean, GLOBAL_APPLICATION_ID};
+
+#[test]
+fn send_a_ping() {
+ let _lock = lock_test();
+
+ let (s, r) = crossbeam_channel::bounded::<String>(1);
+
+ // Define a fake uploader that reports back the submission URL
+ // using a crossbeam channel.
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<String>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ url: String,
+ _body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(url).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ };
+
+ let _t = new_glean(Some(cfg), true);
+ crate::block_on_dispatcher();
+
+ // Define a new ping and submit it.
+ const PING_NAME: &str = "test-ping";
+ let custom_ping = private::PingType::new(PING_NAME, true, true, vec![]);
+ custom_ping.submit(None);
+
+ // Wait for the ping to arrive.
+ let url = r.recv().unwrap();
+ assert_eq!(url.contains(PING_NAME), true);
+}
+
+#[test]
+fn disabling_upload_disables_metrics_recording() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+ crate::block_on_dispatcher();
+
+ let metric = BooleanMetric::new(CommonMetricData {
+ name: "bool_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["store1".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ dynamic_label: None,
+ });
+
+ crate::set_upload_enabled(false);
+
+ assert!(metric.test_get_value("store1").is_none())
+}
+
+#[test]
+fn test_experiments_recording() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, true);
+
+ set_experiment_active("experiment_test".to_string(), "branch_a".to_string(), None);
+ let mut extra = HashMap::new();
+ extra.insert("test_key".to_string(), "value".to_string());
+ set_experiment_active(
+ "experiment_api".to_string(),
+ "branch_b".to_string(),
+ Some(extra),
+ );
+ assert!(test_is_experiment_active("experiment_test".to_string()));
+ assert!(test_is_experiment_active("experiment_api".to_string()));
+ set_experiment_inactive("experiment_test".to_string());
+ assert!(!test_is_experiment_active("experiment_test".to_string()));
+ assert!(test_is_experiment_active("experiment_api".to_string()));
+ let stored_data = test_get_experiment_data("experiment_api".to_string());
+ assert_eq!("branch_b", stored_data.branch);
+ assert_eq!("value", stored_data.extra.unwrap()["test_key"]);
+}
+
+#[test]
+fn test_experiments_recording_before_glean_inits() {
+ let _lock = lock_test();
+
+ // Destroy the existing glean instance from glean-core so that we
+ // can test the pre-init queueing of the experiment api commands.
+ // This is doing the exact same thing that `reset_glean` is doing
+ // but without calling `initialize`.
+ if was_initialize_called() {
+ // We need to check if the Glean object (from glean-core) is
+ // initialized, otherwise this will crash on the first test
+ // due to bug 1675215 (this check can be removed once that
+ // bug is fixed).
+ if global_glean().is_some() {
+ with_glean_mut(|glean| {
+ glean.test_clear_all_stores();
+ glean.destroy_db();
+ });
+ }
+ // Allow us to go through initialization again.
+ INITIALIZE_CALLED.store(false, Ordering::SeqCst);
+ // Reset the dispatcher.
+ dispatcher::reset_dispatcher();
+ }
+
+ set_experiment_active(
+ "experiment_set_preinit".to_string(),
+ "branch_a".to_string(),
+ None,
+ );
+ set_experiment_active(
+ "experiment_preinit_disabled".to_string(),
+ "branch_a".to_string(),
+ None,
+ );
+ set_experiment_inactive("experiment_preinit_disabled".to_string());
+
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ },
+ ClientInfoMetrics::unknown(),
+ false,
+ );
+ crate::block_on_dispatcher();
+
+ assert!(test_is_experiment_active(
+ "experiment_set_preinit".to_string()
+ ));
+ assert!(!test_is_experiment_active(
+ "experiment_preinit_disabled".to_string()
+ ));
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1673645.
+fn test_sending_of_foreground_background_pings() {
+ todo!()
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1672958.
+fn test_sending_of_startup_baseline_ping() {
+ todo!()
+}
+
+#[test]
+fn initialize_must_not_crash_if_data_dir_is_messed_up() {
+ let _lock = lock_test();
+
+ let dir = tempfile::tempdir().unwrap();
+ let tmpdirname = dir.path().display().to_string();
+ // Create a file in the temporary dir and use that as the
+ // name of the Glean data dir.
+ let file_path = PathBuf::from(tmpdirname).join("notadir");
+ std::fs::write(file_path.clone(), "test").expect("The test Glean dir file must be created");
+
+ let cfg = Configuration {
+ data_path: file_path.to_string_lossy().to_string(),
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ };
+
+ test_reset_glean(cfg, ClientInfoMetrics::unknown(), false);
+ // TODO(bug 1675215): ensure initialize runs through dispatcher.
+ // Glean init is async and, for this test, it bails out early due to
+ // an caused by not being able to create the data dir: we can do nothing
+ // but wait. Tests in other bindings use the dispatcher's test mode, which
+ // runs tasks sequentially on the main thread, so no sleep is required,
+ // because we're guaranteed that, once we reach this point, the full
+ // init potentially ran.
+ std::thread::sleep(std::time::Duration::from_secs(3));
+}
+
+#[test]
+fn queued_recorded_metrics_correctly_record_during_init() {
+ let _lock = lock_test();
+
+ destroy_glean(true);
+
+ let metric = CounterMetric::new(CommonMetricData {
+ name: "counter_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["store1".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ dynamic_label: None,
+ });
+
+ // This will queue 3 tasks that will add to the metric value once Glean is initialized
+ for _ in 0..3 {
+ metric.add(1);
+ }
+
+ // TODO: To be fixed in bug 1677150.
+ // Ensure that no value has been stored yet since the tasks have only been queued
+ // and not executed yet
+
+ // Calling `new_glean` here will cause Glean to be initialized and should cause the queued
+ // tasks recording metrics to execute
+ let _t = new_glean(None, false);
+
+ // Verify that the callback was executed by testing for the correct value
+ assert!(metric.test_get_value(None).is_some(), "Value must exist");
+ assert_eq!(3, metric.test_get_value(None).unwrap(), "Value must match");
+}
+
+#[test]
+fn initializing_twice_is_a_noop() {
+ let _lock = lock_test();
+
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname.clone(),
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ },
+ ClientInfoMetrics::unknown(),
+ true,
+ );
+
+ crate::block_on_dispatcher();
+
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ },
+ ClientInfoMetrics::unknown(),
+ false,
+ );
+
+ // TODO(bug 1675215): ensure initialize runs through dispatcher.
+ // Glean init is async and, for this test, it bails out early due to
+ // being initialized: we can do nothing but wait. Tests in other bindings use
+ // the dispatcher's test mode, which runs tasks sequentially on the main
+ // thread, so no sleep is required. Bug 1675215 might fix this, as well.
+ std::thread::sleep(std::time::Duration::from_secs(3));
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1673668.
+fn dont_handle_events_when_uninitialized() {
+ todo!()
+}
+
+#[test]
+fn the_app_channel_must_be_correctly_set_if_requested() {
+ let _lock = lock_test();
+
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ // No appChannel must be set if nothing was provided through the config
+ // options.
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: None,
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ },
+ ClientInfoMetrics::unknown(),
+ true,
+ );
+ assert!(core_metrics::internal_metrics::app_channel
+ .test_get_value(None)
+ .is_none());
+
+ // The appChannel must be correctly reported if a channel value
+ // was provided.
+ let _t = new_glean(None, true);
+ assert_eq!(
+ "testing",
+ core_metrics::internal_metrics::app_channel
+ .test_get_value(None)
+ .unwrap()
+ );
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1673672.
+fn ping_collection_must_happen_after_concurrently_scheduled_metrics_recordings() {
+ todo!()
+}
+
+#[test]
+fn basic_metrics_should_be_cleared_when_disabling_uploading() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, false);
+
+ let metric = private::StringMetric::new(CommonMetricData {
+ name: "string_metric".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["default".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ });
+
+ assert!(metric.test_get_value(None).is_none());
+
+ metric.set("TEST VALUE");
+ assert!(metric.test_get_value(None).is_some());
+
+ set_upload_enabled(false);
+ assert!(metric.test_get_value(None).is_none());
+ metric.set("TEST VALUE");
+ assert!(metric.test_get_value(None).is_none());
+
+ set_upload_enabled(true);
+ assert!(metric.test_get_value(None).is_none());
+ metric.set("TEST VALUE");
+ assert_eq!("TEST VALUE", metric.test_get_value(None).unwrap());
+}
+
+#[test]
+fn core_metrics_should_be_cleared_and_restored_when_disabling_and_enabling_uploading() {
+ let _lock = lock_test();
+
+ let _t = new_glean(None, false);
+
+ assert!(core_metrics::internal_metrics::os_version
+ .test_get_value(None)
+ .is_some());
+
+ set_upload_enabled(false);
+ assert!(core_metrics::internal_metrics::os_version
+ .test_get_value(None)
+ .is_none());
+
+ set_upload_enabled(true);
+ assert!(core_metrics::internal_metrics::os_version
+ .test_get_value(None)
+ .is_some());
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1686736.
+fn overflowing_the_task_queue_records_telemetry() {
+ todo!()
+}
+
+#[test]
+fn sending_deletion_ping_if_disabled_outside_of_run() {
+ let _lock = lock_test();
+
+ let (s, r) = crossbeam_channel::bounded::<String>(1);
+
+ // Define a fake uploader that reports back the submission URL
+ // using a crossbeam channel.
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<String>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ url: String,
+ _body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(url).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname.clone(),
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ };
+
+ let _t = new_glean(Some(cfg), true);
+
+ crate::block_on_dispatcher();
+
+ // Now reset Glean and disable upload: it should still send a deletion request
+ // ping even though we're just starting.
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: false,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ },
+ ClientInfoMetrics::unknown(),
+ false,
+ );
+
+ // Wait for the ping to arrive.
+ let url = r.recv().unwrap();
+ assert_eq!(url.contains("deletion-request"), true);
+}
+
+#[test]
+fn no_sending_of_deletion_ping_if_unchanged_outside_of_run() {
+ let _lock = lock_test();
+
+ let (s, r) = crossbeam_channel::bounded::<String>(1);
+
+ // Define a fake uploader that reports back the submission URL
+ // using a crossbeam channel.
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<String>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ url: String,
+ _body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(url).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname.clone(),
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: None,
+ };
+
+ let _t = new_glean(Some(cfg), true);
+
+ crate::block_on_dispatcher();
+
+ // Now reset Glean and keep upload enabled: no deletion-request
+ // should be sent.
+ test_reset_glean(
+ Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ },
+ ClientInfoMetrics::unknown(),
+ false,
+ );
+
+ crate::block_on_dispatcher();
+
+ assert_eq!(0, r.len());
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1672956.
+fn test_sending_of_startup_baseline_ping_with_application_lifetime_metric() {
+ todo!()
+}
+
+#[test]
+#[ignore] // TODO: To be done in bug 1672956.
+fn test_dirty_flag_is_reset_to_false() {
+ todo!()
+}
+
+#[test]
+fn setting_debug_view_tag_before_initialization_should_not_crash() {
+ let _lock = lock_test();
+
+ destroy_glean(true);
+ assert!(!was_initialize_called());
+
+ // Define a fake uploader that reports back the submission headers
+ // using a crossbeam channel.
+ let (s, r) = crossbeam_channel::bounded::<Vec<(String, String)>>(1);
+
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<Vec<(String, String)>>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ _url: String,
+ _body: Vec<u8>,
+ headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(headers).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Attempt to set a debug view tag before Glean is initialized.
+ set_debug_view_tag("valid-tag");
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ };
+
+ let _t = new_glean(Some(cfg), true);
+ crate::block_on_dispatcher();
+
+ // Submit a baseline ping.
+ submit_ping_by_name("baseline", Some("background"));
+
+ // Wait for the ping to arrive.
+ let headers = r.recv().unwrap();
+ assert_eq!(
+ "valid-tag",
+ headers.iter().find(|&kv| kv.0 == "X-Debug-ID").unwrap().1
+ );
+}
+
+#[test]
+fn setting_source_tags_before_initialization_should_not_crash() {
+ let _lock = lock_test();
+
+ destroy_glean(true);
+ assert!(!was_initialize_called());
+
+ // Define a fake uploader that reports back the submission headers
+ // using a crossbeam channel.
+ let (s, r) = crossbeam_channel::bounded::<Vec<(String, String)>>(1);
+
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<Vec<(String, String)>>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ _url: String,
+ _body: Vec<u8>,
+ headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(headers).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Attempt to set source tags before Glean is initialized.
+ set_source_tags(vec!["valid-tag1".to_string(), "valid-tag2".to_string()]);
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ };
+
+ let _t = new_glean(Some(cfg), true);
+ crate::block_on_dispatcher();
+
+ // Submit a baseline ping.
+ submit_ping_by_name("baseline", Some("background"));
+
+ // Wait for the ping to arrive.
+ let headers = r.recv().unwrap();
+ assert_eq!(
+ "valid-tag1,valid-tag2",
+ headers
+ .iter()
+ .find(|&kv| kv.0 == "X-Source-Tags")
+ .unwrap()
+ .1
+ );
+}
+
+#[test]
+fn flipping_upload_enabled_respects_order_of_events() {
+ // NOTES(janerik):
+ // I'm reasonably sure this test is excercising the right code paths
+ // and from the log output it does the right thing:
+ //
+ // * It fully initializes with the assumption uploadEnabled=true
+ // * It then disables upload
+ // * Then it submits the custom ping, which rightfully is ignored because uploadEnabled=false.
+ //
+ // The test passes.
+ let _lock = lock_test();
+
+ let (s, r) = crossbeam_channel::bounded::<String>(1);
+
+ // Define a fake uploader that reports back the submission URL
+ // using a crossbeam channel.
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<String>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ url: String,
+ _body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(url).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ };
+
+ // We create a ping and a metric before we initialize Glean
+ let sample_ping = PingType::new("sample-ping-1", true, false, vec![]);
+ let metric = private::StringMetric::new(CommonMetricData {
+ name: "string_metric".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["sample-ping-1".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ });
+
+ let _t = new_glean(Some(cfg), true);
+
+ // Glean might still be initializing. Disable upload.
+ set_upload_enabled(false);
+
+ // Set data and try to submit a custom ping.
+ metric.set("some-test-value");
+ sample_ping.submit(None);
+
+ // Wait for the ping to arrive.
+ let url = r.recv().unwrap();
+ assert_eq!(url.contains("deletion-request"), true);
+}
+
+#[test]
+fn registering_pings_before_init_must_work() {
+ let _lock = lock_test();
+
+ destroy_glean(true);
+ assert!(!was_initialize_called());
+
+ // Define a fake uploader that reports back the submission headers
+ // using a crossbeam channel.
+ let (s, r) = crossbeam_channel::bounded::<String>(1);
+
+ #[derive(Debug)]
+ pub struct FakeUploader {
+ sender: crossbeam_channel::Sender<String>,
+ }
+ impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ url: String,
+ _body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ self.sender.send(url).unwrap();
+ net::UploadResult::HttpStatus(200)
+ }
+ }
+
+ // Create a custom ping and attempt its registration.
+ let sample_ping = PingType::new("pre-register", true, true, vec![]);
+
+ // Create a custom configuration to use a fake uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+
+ let cfg = Configuration {
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some("testing".into()),
+ server_endpoint: Some("invalid-test-host".into()),
+ uploader: Some(Box::new(FakeUploader { sender: s })),
+ };
+
+ let _t = new_glean(Some(cfg), true);
+ crate::block_on_dispatcher();
+
+ // Submit a baseline ping.
+ sample_ping.submit(None);
+
+ // Wait for the ping to arrive.
+ let url = r.recv().unwrap();
+ assert!(url.contains("pre-register"));
+}