1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #![deny(rustdoc::broken_intra_doc_links)]
6 #![deny(missing_docs)]
7 
8 //! Glean is a modern approach for recording and sending Telemetry data.
9 //!
10 //! It's in use at Mozilla.
11 //!
12 //! All documentation can be found online:
13 //!
14 //! ## [The Glean SDK Book](https://mozilla.github.io/glean)
15 //!
16 //! ## Example
17 //!
18 //! Initialize Glean, register a ping and then send it.
19 //!
20 //! ```rust,no_run
21 //! # use glean::{Configuration, ClientInfoMetrics, Error, private::*};
22 //! let cfg = Configuration {
23 //!     data_path: "/tmp/data".into(),
24 //!     application_id: "org.mozilla.glean_core.example".into(),
25 //!     upload_enabled: true,
26 //!     max_events: None,
27 //!     delay_ping_lifetime_io: false,
28 //!     channel: None,
29 //!     server_endpoint: None,
30 //!     uploader: None,
31 //!     use_core_mps: false,
32 //! };
33 //! glean::initialize(cfg, ClientInfoMetrics::unknown());
34 //!
35 //! let prototype_ping = PingType::new("prototype", true, true, vec!());
36 //!
37 //! glean::register_ping_type(&prototype_ping);
38 //!
39 //! prototype_ping.submit(None);
40 //! ```
41 
42 use once_cell::sync::{Lazy, OnceCell};
43 use std::collections::HashMap;
44 use std::sync::atomic::{AtomicBool, Ordering};
45 use std::sync::{Arc, Mutex};
46 
47 pub use configuration::Configuration;
48 use configuration::DEFAULT_GLEAN_ENDPOINT;
49 pub use core_metrics::ClientInfoMetrics;
50 use glean_core::global_glean;
51 pub use glean_core::{
52     metrics::{Datetime, DistributionData, MemoryUnit, RecordedEvent, TimeUnit, TimerId},
53     traits, CommonMetricData, Error, ErrorType, Glean, HistogramType, Lifetime, Result,
54 };
55 pub use private::RecordedExperimentData;
56 
57 mod configuration;
58 mod core_metrics;
59 mod dispatcher;
60 mod glean_metrics;
61 pub mod net;
62 pub mod private;
63 mod system;
64 
65 #[cfg(test)]
66 mod common_test;
67 
68 const LANGUAGE_BINDING_NAME: &str = "Rust";
69 
70 /// State to keep track for the Rust Language bindings.
71 ///
72 /// This is useful for setting Glean SDK-owned metrics when
73 /// the state of the upload is toggled.
74 #[derive(Debug)]
75 struct RustBindingsState {
76     /// The channel the application is being distributed on.
77     channel: Option<String>,
78 
79     /// Client info metrics set by the application.
80     client_info: ClientInfoMetrics,
81 
82     /// An instance of the upload manager
83     upload_manager: net::UploadManager,
84 }
85 
86 /// Set when `glean::initialize()` returns.
87 /// This allows to detect calls that happen before `glean::initialize()` was called.
88 /// Note: The initialization might still be in progress, as it runs in a separate thread.
89 static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
90 
91 /// Keep track of the debug features before Glean is initialized.
92 static PRE_INIT_DEBUG_VIEW_TAG: OnceCell<Mutex<String>> = OnceCell::new();
93 static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
94 static PRE_INIT_SOURCE_TAGS: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
95 
96 /// Keep track of pings registered before Glean is initialized.
97 static PRE_INIT_PING_REGISTRATION: OnceCell<Mutex<Vec<private::PingType>>> = OnceCell::new();
98 
99 /// A global singleton storing additional state for Glean.
100 ///
101 /// Requires a Mutex, because in tests we can actual reset this.
102 static STATE: OnceCell<Mutex<RustBindingsState>> = OnceCell::new();
103 
104 /// Global singleton of the handles of the glean.init threads.
105 /// For joining. For tests.
106 /// (Why a Vec? There might be more than one concurrent call to initialize.)
107 static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
108     Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
109 
110 /// Get a reference to the global state object.
111 ///
112 /// Panics if no global state object was set.
global_state() -> &'static Mutex<RustBindingsState>113 fn global_state() -> &'static Mutex<RustBindingsState> {
114     STATE.get().unwrap()
115 }
116 
117 /// Set or replace the global bindings State object.
setup_state(state: RustBindingsState)118 fn setup_state(state: RustBindingsState) {
119     // The `OnceCell` type wrapping our state is thread-safe and can only be set once.
120     // Therefore even if our check for it being empty succeeds, setting it could fail if a
121     // concurrent thread is quicker in setting it.
122     // However this will not cause a bigger problem, as the second `set` operation will just fail.
123     // We can log it and move on.
124     //
125     // For all wrappers this is not a problem, as the State object is intialized exactly once on
126     // calling `initialize` on the global singleton and further operations check that it has been
127     // initialized.
128     if STATE.get().is_none() {
129         if STATE.set(Mutex::new(state)).is_err() {
130             log::error!(
131                 "Global Glean state object is initialized already. This probably happened concurrently."
132             );
133         }
134     } else {
135         // We allow overriding the global State object to support test mode.
136         // In test mode the State object is fully destroyed and recreated.
137         // This all happens behind a mutex and is therefore also thread-safe.
138         let mut lock = STATE.get().unwrap().lock().unwrap();
139         *lock = state;
140     }
141 }
142 
with_glean<F, R>(f: F) -> R where F: FnOnce(&Glean) -> R,143 fn with_glean<F, R>(f: F) -> R
144 where
145     F: FnOnce(&Glean) -> R,
146 {
147     let glean = global_glean().expect("Global Glean object not initialized");
148     let lock = glean.lock().unwrap();
149     f(&lock)
150 }
151 
with_glean_mut<F, R>(f: F) -> R where F: FnOnce(&mut Glean) -> R,152 fn with_glean_mut<F, R>(f: F) -> R
153 where
154     F: FnOnce(&mut Glean) -> R,
155 {
156     let glean = global_glean().expect("Global Glean object not initialized");
157     let mut lock = glean.lock().unwrap();
158     f(&mut lock)
159 }
160 
161 /// Launches a new task on the global dispatch queue with a reference to the Glean singleton.
launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static)162 fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
163     dispatcher::launch(|| crate::with_glean(callback));
164 }
165 
166 /// Launches a new task on the global dispatch queue with a mutable reference to the
167 /// Glean singleton.
launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static)168 fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
169     dispatcher::launch(|| crate::with_glean_mut(callback));
170 }
171 
172 /// Creates and initializes a new Glean object.
173 ///
174 /// See [`glean_core::Glean::new`] for more information.
175 ///
176 /// # Arguments
177 ///
178 /// * `cfg` - the [`Configuration`] options to initialize with.
179 /// * `client_info` - the [`ClientInfoMetrics`] values used to set Glean
180 ///   core metrics.
initialize(cfg: Configuration, client_info: ClientInfoMetrics)181 pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) {
182     if was_initialize_called() {
183         log::error!("Glean should not be initialized multiple times");
184         return;
185     }
186 
187     let init_handle = std::thread::Builder::new()
188         .name("glean.init".into())
189         .spawn(move || {
190             let core_cfg = glean_core::Configuration {
191                 upload_enabled: cfg.upload_enabled,
192                 data_path: cfg.data_path,
193                 application_id: cfg.application_id.clone(),
194                 language_binding_name: LANGUAGE_BINDING_NAME.into(),
195                 max_events: cfg.max_events,
196                 delay_ping_lifetime_io: cfg.delay_ping_lifetime_io,
197                 app_build: client_info.app_build.clone(),
198                 use_core_mps: cfg.use_core_mps,
199             };
200 
201             let glean = match Glean::new(core_cfg) {
202                 Ok(glean) => glean,
203                 Err(err) => {
204                     log::error!("Failed to initialize Glean: {}", err);
205                     return;
206                 }
207             };
208 
209             // glean-core already takes care of logging errors: other bindings
210             // simply do early returns, as we're doing.
211             if glean_core::setup_glean(glean).is_err() {
212                 return;
213             }
214 
215             log::info!("Glean initialized");
216 
217             // Initialize the ping uploader.
218             let upload_manager = net::UploadManager::new(
219                 cfg.server_endpoint
220                     .unwrap_or_else(|| DEFAULT_GLEAN_ENDPOINT.to_string()),
221                 cfg.uploader
222                     .unwrap_or_else(|| Box::new(net::HttpUploader) as Box<dyn net::PingUploader>),
223             );
224 
225             // Now make this the global object available to others.
226             setup_state(RustBindingsState {
227                 channel: cfg.channel,
228                 client_info,
229                 upload_manager,
230             });
231 
232             let upload_enabled = cfg.upload_enabled;
233 
234             with_glean_mut(|glean| {
235                 let state = global_state().lock().unwrap();
236 
237                 // The debug view tag might have been set before initialize,
238                 // get the cached value and set it.
239                 if let Some(tag) = PRE_INIT_DEBUG_VIEW_TAG.get() {
240                     let lock = tag.try_lock();
241                     if let Ok(ref debug_tag) = lock {
242                         glean.set_debug_view_tag(debug_tag);
243                     }
244                 }
245                 // The log pings debug option might have been set before initialize,
246                 // get the cached value and set it.
247                 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
248                 if log_pigs {
249                     glean.set_log_pings(log_pigs);
250                 }
251                 // The source tags might have been set before initialize,
252                 // get the cached value and set them.
253                 if let Some(tags) = PRE_INIT_SOURCE_TAGS.get() {
254                     let lock = tags.try_lock();
255                     if let Ok(ref source_tags) = lock {
256                         glean.set_source_tags(source_tags.to_vec());
257                     }
258                 }
259 
260                 // Get the current value of the dirty flag so we know whether to
261                 // send a dirty startup baseline ping below.  Immediately set it to
262                 // `false` so that dirty startup pings won't be sent if Glean
263                 // initialization does not complete successfully.
264                 // TODO Bug 1672956 will decide where to set this flag again.
265                 let dirty_flag = glean.is_dirty_flag_set();
266                 glean.set_dirty_flag(false);
267 
268                 // Register builtin pings.
269                 // Unfortunately we need to manually list them here to guarantee
270                 // they are registered synchronously before we need them.
271                 // We don't need to handle the deletion-request ping. It's never touched
272                 // from the language implementation.
273                 //
274                 // Note: this will actually double-register them.
275                 // On instantiation they will launch a task to register themselves.
276                 // That task could fail to run if the dispatcher queue is full.
277                 // We still register them here synchronously.
278                 glean.register_ping_type(&glean_metrics::pings::baseline.ping_type);
279                 glean.register_ping_type(&glean_metrics::pings::metrics.ping_type);
280                 glean.register_ping_type(&glean_metrics::pings::events.ping_type);
281 
282                 // Perform registration of pings that were attempted to be
283                 // registered before init.
284                 if let Some(tags) = PRE_INIT_PING_REGISTRATION.get() {
285                     let lock = tags.try_lock();
286                     if let Ok(pings) = lock {
287                         for ping in &*pings {
288                             glean.register_ping_type(&ping.ping_type);
289                         }
290                     }
291                 }
292 
293                 // If this is the first time ever the Glean SDK runs, make sure to set
294                 // some initial core metrics in case we need to generate early pings.
295                 // The next times we start, we would have them around already.
296                 let is_first_run = glean.is_first_run();
297                 if is_first_run {
298                     initialize_core_metrics(glean, &state.client_info, state.channel.clone());
299                 }
300 
301                 // Deal with any pending events so we can start recording new ones
302                 let pings_submitted = glean.on_ready_to_submit_pings();
303 
304                 // We need to kick off upload in these cases:
305                 // 1. Pings were submitted through Glean and it is ready to upload those pings;
306                 // 2. Upload is disabled, to upload a possible deletion-request ping.
307                 if pings_submitted || !upload_enabled {
308                     state.upload_manager.trigger_upload();
309                 }
310 
311                 // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
312                 // ping startup check should be performed before any other ping, since it relies
313                 // on being dispatched to the API context before any other metric.
314                 glean.start_metrics_ping_scheduler();
315 
316                 // Check if the "dirty flag" is set. That means the product was probably
317                 // force-closed. If that's the case, submit a 'baseline' ping with the
318                 // reason "dirty_startup". We only do that from the second run.
319                 if !is_first_run && dirty_flag {
320                     // The `submit_ping_by_name_sync` function cannot be used, otherwise
321                     // startup will cause a dead-lock, since that function requests a
322                     // write lock on the `glean` object.
323                     // Note that unwrapping below is safe: the function will return an
324                     // `Ok` value for a known ping.
325                     if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
326                         state.upload_manager.trigger_upload();
327                     }
328                 }
329 
330                 // From the second time we run, after all startup pings are generated,
331                 // make sure to clear `lifetime: application` metrics and set them again.
332                 // Any new value will be sent in newly generated pings after startup.
333                 if !is_first_run {
334                     glean.clear_application_lifetime_metrics();
335                     initialize_core_metrics(glean, &state.client_info, state.channel.clone());
336                 }
337             });
338 
339             // Signal Dispatcher that init is complete
340             match dispatcher::flush_init() {
341                 Ok(task_count) if task_count > 0 => {
342                     with_glean(|glean| {
343                         glean_metrics::error::preinit_tasks_overflow
344                             .add_sync(glean, task_count as i32);
345                     });
346                 }
347                 Ok(_) => {}
348                 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
349             }
350         })
351         .expect("Failed to spawn Glean's init thread");
352 
353     // For test purposes, store the glean init thread's JoinHandle.
354     INIT_HANDLES.lock().unwrap().push(init_handle);
355 
356     // Mark the initialization as called: this needs to happen outside of the
357     // dispatched block!
358     INITIALIZE_CALLED.store(true, Ordering::SeqCst);
359 }
360 
361 /// TEST ONLY FUNCTION
362 /// Waits on all the glean.init threads' join handles.
join_init()363 pub fn join_init() {
364     let mut handles = INIT_HANDLES.lock().unwrap();
365     for handle in handles.drain(..) {
366         handle.join().unwrap();
367     }
368 }
369 
370 /// Shuts down Glean in an orderly fashion.
shutdown()371 pub fn shutdown() {
372     if global_glean().is_none() {
373         log::warn!("Shutdown called before Glean is initialized");
374         if let Err(e) = dispatcher::kill() {
375             log::error!("Can't kill dispatcher thread: {:?}", e);
376         }
377 
378         return;
379     }
380 
381     crate::launch_with_glean_mut(|glean| {
382         glean.cancel_metrics_ping_scheduler();
383         glean.set_dirty_flag(false);
384     });
385 
386     if let Err(e) = dispatcher::shutdown() {
387         log::error!("Can't shutdown dispatcher thread: {:?}", e);
388     }
389 
390     // Be sure to call this _after_ draining the dispatcher
391     crate::with_glean(|glean| {
392         if let Err(e) = glean.persist_ping_lifetime_data() {
393             log::error!("Can't persist ping lifetime data: {:?}", e);
394         }
395     });
396 }
397 
398 /// Unblock the global dispatcher to start processing queued tasks.
399 ///
400 /// This should _only_ be called if it is guaranteed that `initialize` will never be called.
401 ///
402 /// **Note**: Exported as a FFI function to be used by other language bindings (e.g. Kotlin/Swift)
403 /// to unblock the RLB-internal dispatcher.
404 /// This allows the usage of both the RLB and other language bindings (e.g. Kotlin/Swift)
405 /// within the same application.
406 #[no_mangle]
407 #[inline(never)]
rlb_flush_dispatcher()408 pub extern "C" fn rlb_flush_dispatcher() {
409     log::trace!("FLushing RLB dispatcher through the FFI");
410 
411     let was_initialized = was_initialize_called();
412 
413     // Panic in debug mode
414     debug_assert!(!was_initialized);
415 
416     // In release do a check and bail out
417     if was_initialized {
418         log::error!(
419             "Tried to flush the dispatcher from outside, but Glean was initialized in the RLB."
420         );
421         return;
422     }
423 
424     if let Err(err) = dispatcher::flush_init() {
425         log::error!("Unable to flush the preinit queue: {}", err);
426     }
427 }
428 
429 /// Block on the dispatcher emptying.
430 ///
431 /// This will panic if called before Glean is initialized.
block_on_dispatcher()432 fn block_on_dispatcher() {
433     assert!(
434         was_initialize_called(),
435         "initialize was never called. Can't block on the dispatcher queue."
436     );
437     dispatcher::block_on_queue().unwrap();
438 }
439 
440 /// Checks if [`initialize`] was ever called.
441 ///
442 /// # Returns
443 ///
444 /// `true` if it was, `false` otherwise.
was_initialize_called() -> bool445 fn was_initialize_called() -> bool {
446     INITIALIZE_CALLED.load(Ordering::SeqCst)
447 }
448 
initialize_core_metrics( glean: &Glean, client_info: &ClientInfoMetrics, channel: Option<String>, )449 fn initialize_core_metrics(
450     glean: &Glean,
451     client_info: &ClientInfoMetrics,
452     channel: Option<String>,
453 ) {
454     core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
455     core_metrics::internal_metrics::app_display_version
456         .set_sync(glean, &client_info.app_display_version[..]);
457     if let Some(app_channel) = channel {
458         core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
459     }
460     core_metrics::internal_metrics::os_version.set_sync(glean, system::get_os_version());
461     core_metrics::internal_metrics::architecture.set_sync(glean, system::ARCH.to_string());
462 }
463 
464 /// Sets whether upload is enabled or not.
465 ///
466 /// See [`glean_core::Glean::set_upload_enabled`].
set_upload_enabled(enabled: bool)467 pub fn set_upload_enabled(enabled: bool) {
468     if !was_initialize_called() {
469         let msg =
470             "Changing upload enabled before Glean is initialized is not supported.\n \
471             Pass the correct state into `Glean.initialize()`.\n \
472             See documentation at https://mozilla.github.io/glean/book/user/general-api.html#initializing-the-glean-sdk";
473         log::error!("{}", msg);
474         return;
475     }
476 
477     // Changing upload enabled always happens asynchronous.
478     // That way it follows what a user expect when calling it inbetween other calls:
479     // it executes in the right order.
480     //
481     // Because the dispatch queue is halted until Glean is fully initialized
482     // we can safely enqueue here and it will execute after initialization.
483     crate::launch_with_glean_mut(move |glean| {
484         let state = global_state().lock().unwrap();
485         let old_enabled = glean.is_upload_enabled();
486         glean.set_upload_enabled(enabled);
487 
488         if !old_enabled && enabled {
489             glean.start_metrics_ping_scheduler();
490             // If uploading is being re-enabled, we have to restore the
491             // application-lifetime metrics.
492             initialize_core_metrics(glean, &state.client_info, state.channel.clone());
493         }
494 
495         if old_enabled && !enabled {
496             glean.cancel_metrics_ping_scheduler();
497             // If uploading is disabled, we need to send the deletion-request ping:
498             // note that glean-core takes care of generating it.
499             state.upload_manager.trigger_upload();
500         }
501     });
502 }
503 
504 /// Register a new [`PingType`](private::PingType).
register_ping_type(ping: &private::PingType)505 pub fn register_ping_type(ping: &private::PingType) {
506     // If this happens after Glean.initialize is called (and returns),
507     // we dispatch ping registration on the thread pool.
508     // Registering a ping should not block the application.
509     // Submission itself is also dispatched, so it will always come after the registration.
510     if was_initialize_called() {
511         let ping = ping.clone();
512         crate::launch_with_glean_mut(move |glean| {
513             glean.register_ping_type(&ping.ping_type);
514         })
515     } else {
516         // We need to keep track of pings, so they get re-registered after a reset or
517         // if ping registration is attempted before Glean initializes.
518         // This state is kept across Glean resets, which should only ever happen in test mode.
519         // It's a set and keeping them around forever should not have much of an impact.
520         let m = PRE_INIT_PING_REGISTRATION.get_or_init(Default::default);
521         let mut lock = m.lock().unwrap();
522         lock.push(ping.clone());
523     }
524 }
525 
526 /// Collects and submits a ping for eventual uploading.
527 ///
528 /// See [`glean_core::Glean.submit_ping`].
submit_ping(ping: &private::PingType, reason: Option<&str>)529 pub(crate) fn submit_ping(ping: &private::PingType, reason: Option<&str>) {
530     submit_ping_by_name(&ping.name, reason)
531 }
532 
533 /// Collects and submits a ping for eventual uploading by name.
534 ///
535 /// Note that this needs to be public in order for RLB consumers to
536 /// use Glean debugging facilities.
537 ///
538 /// See [`glean_core::Glean.submit_ping_by_name`].
submit_ping_by_name(ping: &str, reason: Option<&str>)539 pub fn submit_ping_by_name(ping: &str, reason: Option<&str>) {
540     let ping = ping.to_string();
541     let reason = reason.map(|s| s.to_string());
542     dispatcher::launch(move || {
543         submit_ping_by_name_sync(&ping, reason.as_deref());
544     })
545 }
546 
547 /// Collect and submit a ping (by its name) for eventual upload, synchronously.
548 ///
549 /// The ping will be looked up in the known instances of [`private::PingType`]. If the
550 /// ping isn't known, an error is logged and the ping isn't queued for uploading.
551 ///
552 /// The ping content is assembled as soon as possible, but upload is not
553 /// guaranteed to happen immediately, as that depends on the upload
554 /// policies.
555 ///
556 /// If the ping currently contains no content, it will not be assembled and
557 /// queued for sending, unless explicitly specified otherwise in the registry
558 /// file.
559 ///
560 /// # Arguments
561 ///
562 /// * `ping_name` - the name of the ping to submit.
563 /// * `reason` - the reason the ping is being submitted.
submit_ping_by_name_sync(ping: &str, reason: Option<&str>)564 pub(crate) fn submit_ping_by_name_sync(ping: &str, reason: Option<&str>) {
565     if !was_initialize_called() {
566         log::error!("Glean must be initialized before submitting pings.");
567         return;
568     }
569 
570     let submitted_ping = with_glean(|glean| {
571         if !glean.is_upload_enabled() {
572             log::info!("Glean disabled: not submitting any pings.");
573             // This won't actually return from `submit_ping_by_name`, but
574             // returning `false` here skips spinning up the uploader below,
575             // which is basically the same.
576             return false;
577         }
578 
579         glean.submit_ping_by_name(ping, reason)
580     });
581 
582     if submitted_ping {
583         let state = global_state().lock().unwrap();
584         state.upload_manager.trigger_upload();
585     }
586 }
587 
588 /// Indicate that an experiment is running.  Glean will then add an
589 /// experiment annotation to the environment which is sent with pings. This
590 /// infomration is not persisted between runs.
591 ///
592 /// See [`glean_core::Glean::set_experiment_active`].
set_experiment_active( experiment_id: String, branch: String, extra: Option<HashMap<String, String>>, )593 pub fn set_experiment_active(
594     experiment_id: String,
595     branch: String,
596     extra: Option<HashMap<String, String>>,
597 ) {
598     crate::launch_with_glean(move |glean| {
599         glean.set_experiment_active(experiment_id.to_owned(), branch.to_owned(), extra)
600     })
601 }
602 
603 /// Indicate that an experiment is no longer running.
604 ///
605 /// See [`glean_core::Glean::set_experiment_inactive`].
set_experiment_inactive(experiment_id: String)606 pub fn set_experiment_inactive(experiment_id: String) {
607     crate::launch_with_glean(move |glean| glean.set_experiment_inactive(experiment_id))
608 }
609 
610 /// Performs the collection/cleanup operations required by becoming active.
611 ///
612 /// This functions generates a baseline ping with reason `active`
613 /// and then sets the dirty bit.
614 /// This should be called whenever the consuming product becomes active (e.g.
615 /// getting to foreground).
handle_client_active()616 pub fn handle_client_active() {
617     crate::launch_with_glean_mut(|glean| {
618         glean.handle_client_active();
619 
620         // The above call may generate pings, so we need to trigger
621         // the uploader. It's fine to trigger it if no ping was generated:
622         // it will bail out.
623         let state = global_state().lock().unwrap();
624         state.upload_manager.trigger_upload();
625     });
626 
627     // The previous block of code may send a ping containing the `duration` metric,
628     // in `glean.handle_client_active`. We intentionally start recording a new
629     // `duration` after that happens, so that the measurement gets reported when
630     // calling `handle_client_inactive`.
631     core_metrics::internal_metrics::baseline_duration.start();
632 }
633 
634 /// Performs the collection/cleanup operations required by becoming inactive.
635 ///
636 /// This functions generates a baseline and an events ping with reason
637 /// `inactive` and then clears the dirty bit.
638 /// This should be called whenever the consuming product becomes inactive (e.g.
639 /// getting to background).
handle_client_inactive()640 pub fn handle_client_inactive() {
641     // This needs to be called before the `handle_client_inactive` api: it stops
642     // measuring the duration of the previous activity time, before any ping is sent
643     // by the next call.
644     core_metrics::internal_metrics::baseline_duration.stop();
645 
646     crate::launch_with_glean_mut(|glean| {
647         glean.handle_client_inactive();
648 
649         // The above call may generate pings, so we need to trigger
650         // the uploader. It's fine to trigger it if no ping was generated:
651         // it will bail out.
652         let state = global_state().lock().unwrap();
653         state.upload_manager.trigger_upload();
654     })
655 }
656 
657 /// TEST ONLY FUNCTION.
658 /// Checks if an experiment is currently active.
test_is_experiment_active(experiment_id: String) -> bool659 pub fn test_is_experiment_active(experiment_id: String) -> bool {
660     block_on_dispatcher();
661     with_glean(|glean| glean.test_is_experiment_active(experiment_id.to_owned()))
662 }
663 
664 /// TEST ONLY FUNCTION.
665 /// Returns the [`RecordedExperimentData`] for the given `experiment_id` or panics if
666 /// the id isn't found.
test_get_experiment_data(experiment_id: String) -> RecordedExperimentData667 pub fn test_get_experiment_data(experiment_id: String) -> RecordedExperimentData {
668     block_on_dispatcher();
669     with_glean(|glean| {
670         let json_data = glean
671             .test_get_experiment_data_as_json(experiment_id.to_owned())
672             .unwrap_or_else(|| panic!("No experiment found for id: {}", experiment_id));
673         serde_json::from_str::<RecordedExperimentData>(&json_data).unwrap()
674     })
675 }
676 
677 /// Destroy the global Glean state.
destroy_glean(clear_stores: bool)678 pub(crate) fn destroy_glean(clear_stores: bool) {
679     // Destroy the existing glean instance from glean-core.
680     if was_initialize_called() {
681         // Just because initialize was called doesn't mean it's done.
682         join_init();
683 
684         // Reset the dispatcher first (it might still run tasks against the database)
685         dispatcher::reset_dispatcher();
686 
687         // Wait for any background uploader thread to finish.
688         // This needs to be done before the check below,
689         // as the uploader will also try to acquire a lock on the global Glean.
690         //
691         // Note: requires the block here, so we drop the lock again.
692         {
693             let state = global_state().lock().unwrap();
694             state.upload_manager.test_wait_for_upload();
695         }
696 
697         // We need to check if the Glean object (from glean-core) is
698         // initialized, otherwise this will crash on the first test
699         // due to bug 1675215 (this check can be removed once that
700         // bug is fixed).
701         if global_glean().is_some() {
702             with_glean_mut(|glean| {
703                 if clear_stores {
704                     glean.test_clear_all_stores()
705                 }
706                 glean.destroy_db()
707             });
708         }
709         // Allow us to go through initialization again.
710         INITIALIZE_CALLED.store(false, Ordering::SeqCst);
711 
712         // If Glean initialization previously didn't finish,
713         // then the global state might not have been reset
714         // and thus needs to be cleared here.
715         let state = global_state().lock().unwrap();
716         state.upload_manager.test_clear_upload_thread();
717     }
718 }
719 
720 /// TEST ONLY FUNCTION.
721 /// Resets the Glean state and triggers init again.
test_reset_glean(cfg: Configuration, client_info: ClientInfoMetrics, clear_stores: bool)722 pub fn test_reset_glean(cfg: Configuration, client_info: ClientInfoMetrics, clear_stores: bool) {
723     destroy_glean(clear_stores);
724 
725     initialize(cfg, client_info);
726     join_init();
727 }
728 
729 /// Sets a debug view tag.
730 ///
731 /// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the
732 /// value of the tag and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
733 ///
734 /// # Arguments
735 ///
736 /// * `tag` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
737 ///
738 /// # Returns
739 ///
740 /// This will return `false` in case `tag` is not a valid tag and `true` otherwise.
741 /// If called before Glean is initialized it will always return `true`.
set_debug_view_tag(tag: &str) -> bool742 pub fn set_debug_view_tag(tag: &str) -> bool {
743     if was_initialize_called() {
744         with_glean_mut(|glean| glean.set_debug_view_tag(tag))
745     } else {
746         // Glean has not been initialized yet. Cache the provided tag value.
747         let m = PRE_INIT_DEBUG_VIEW_TAG.get_or_init(Default::default);
748         let mut lock = m.lock().unwrap();
749         *lock = tag.to_string();
750         // When setting the debug view tag before initialization,
751         // we don't validate the tag, thus this function always returns true.
752         true
753     }
754 }
755 
756 /// Sets the log pings debug option.
757 ///
758 /// When the log pings debug option is `true`,
759 /// we log the payload of all succesfully assembled pings.
760 ///
761 /// # Arguments
762 ///
763 /// * `value` - The value of the log pings option
set_log_pings(value: bool)764 pub fn set_log_pings(value: bool) {
765     if was_initialize_called() {
766         with_glean_mut(|glean| glean.set_log_pings(value));
767     } else {
768         PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
769     }
770 }
771 
772 /// Sets source tags.
773 ///
774 /// Overrides any existing source tags.
775 /// Source tags will show in the destination datasets, after ingestion.
776 ///
777 /// **Note** If one or more tags are invalid, all tags are ignored.
778 ///
779 /// # Arguments
780 ///
781 /// * `tags` - A vector of at most 5 valid HTTP header values. Individual
782 ///   tags must match the regex: "[a-zA-Z0-9-]{1,20}".
set_source_tags(tags: Vec<String>)783 pub fn set_source_tags(tags: Vec<String>) {
784     if was_initialize_called() {
785         crate::launch_with_glean_mut(|glean| {
786             glean.set_source_tags(tags);
787         });
788     } else {
789         // Glean has not been initialized yet. Cache the provided source tags.
790         let m = PRE_INIT_SOURCE_TAGS.get_or_init(Default::default);
791         let mut lock = m.lock().unwrap();
792         *lock = tags;
793     }
794 }
795 
796 /// Returns a timestamp corresponding to "now" with millisecond precision.
get_timestamp_ms() -> u64797 pub fn get_timestamp_ms() -> u64 {
798     glean_core::get_timestamp_ms()
799 }
800 
801 /// Dispatches a request to the database to persist ping-lifetime data to disk.
persist_ping_lifetime_data()802 pub fn persist_ping_lifetime_data() {
803     crate::launch_with_glean(|glean| {
804         // This is async, we can't get the Error back to the caller.
805         let _ = glean.persist_ping_lifetime_data();
806     });
807 }
808 
809 #[cfg(test)]
810 mod test;
811