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