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