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