glean_core/
lib.rs

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#![allow(clippy::doc_overindented_list_items)]
6#![allow(clippy::significant_drop_in_scrutinee)]
7#![allow(clippy::uninlined_format_args)]
8#![deny(rustdoc::broken_intra_doc_links)]
9#![deny(missing_docs)]
10
11//! Glean is a modern approach for recording and sending Telemetry data.
12//!
13//! It's in use at Mozilla.
14//!
15//! All documentation can be found online:
16//!
17//! ## [The Glean SDK Book](https://mozilla.github.io/glean)
18
19use std::borrow::Cow;
20use std::collections::HashMap;
21use std::path::Path;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex};
24use std::time::{Duration, UNIX_EPOCH};
25use std::{fmt, fs};
26
27use crossbeam_channel::unbounded;
28use log::LevelFilter;
29use malloc_size_of_derive::MallocSizeOf;
30use once_cell::sync::{Lazy, OnceCell};
31use uuid::Uuid;
32
33use metrics::RemoteSettingsConfig;
34
35mod common_metric_data;
36mod core;
37mod core_metrics;
38mod coverage;
39mod database;
40mod debug;
41mod dispatcher;
42mod error;
43mod error_recording;
44mod event_database;
45mod glean_metrics;
46mod histogram;
47mod internal_metrics;
48mod internal_pings;
49pub mod metrics;
50pub mod ping;
51mod scheduler;
52pub mod storage;
53mod system;
54#[doc(hidden)]
55pub mod thread;
56pub mod traits;
57pub mod upload;
58mod util;
59
60#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
61mod fd_logger;
62
63pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
64pub use crate::core::Glean;
65pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
66use crate::dispatcher::is_test_mode;
67pub use crate::error::{Error, ErrorKind, Result};
68pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
69pub use crate::histogram::HistogramType;
70use crate::internal_metrics::DataDirectoryInfoObject;
71pub use crate::metrics::labeled::{
72    AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
73    LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
74    LabeledTimingDistribution,
75};
76pub use crate::metrics::{
77    BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
78    DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
79    LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
80    MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
81    Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
82    TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
83    UrlMetric, UuidMetric,
84};
85pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
86
87const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
88const GLEAN_SCHEMA_VERSION: u32 = 1;
89const DEFAULT_MAX_EVENTS: u32 = 500;
90static KNOWN_CLIENT_ID: Lazy<Uuid> =
91    Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
92
93// The names of the pings directories.
94pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
95pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
96
97/// Set when `glean::initialize()` returns.
98/// This allows to detect calls that happen before `glean::initialize()` was called.
99/// Note: The initialization might still be in progress, as it runs in a separate thread.
100static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
101
102/// Keep track of the debug features before Glean is initialized.
103static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
104static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
105static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
106
107/// Keep track of pings registered before Glean is initialized.
108static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
109static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
110
111/// Keep track of attribution and distribution supplied before Glean is initialized.
112static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
113static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
114
115/// Global singleton of the handles of the glean.init threads.
116/// For joining. For tests.
117/// (Why a Vec? There might be more than one concurrent call to initialize.)
118static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
119    Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
120
121/// Configuration for Glean
122#[derive(Debug, Clone, MallocSizeOf)]
123pub struct InternalConfiguration {
124    /// Whether upload should be enabled.
125    pub upload_enabled: bool,
126    /// Path to a directory to store all data in.
127    pub data_path: String,
128    /// The application ID (will be sanitized during initialization).
129    pub application_id: String,
130    /// The name of the programming language used by the binding creating this instance of Glean.
131    pub language_binding_name: String,
132    /// The maximum number of events to store before sending a ping containing events.
133    pub max_events: Option<u32>,
134    /// Whether Glean should delay persistence of data from metrics with ping lifetime.
135    pub delay_ping_lifetime_io: bool,
136    /// The application's build identifier. If this is different from the one provided for a previous init,
137    /// and use_core_mps is `true`, we will trigger a "metrics" ping.
138    pub app_build: String,
139    /// Whether Glean should schedule "metrics" pings.
140    pub use_core_mps: bool,
141    /// Whether Glean should, on init, trim its event storage to only the registered pings.
142    pub trim_data_to_registered_pings: bool,
143    /// The internal logging level.
144    /// ignore
145    #[ignore_malloc_size_of = "external non-allocating type"]
146    pub log_level: Option<LevelFilter>,
147    /// The rate at which pings may be uploaded before they are throttled.
148    pub rate_limit: Option<PingRateLimit>,
149    /// Whether to add a wallclock timestamp to all events.
150    pub enable_event_timestamps: bool,
151    /// An experimentation identifier derived by the application to be sent with all pings, it should
152    /// be noted that this has an underlying StringMetric and so should conform to the limitations that
153    /// StringMetric places on length, etc.
154    pub experimentation_id: Option<String>,
155    /// Whether to enable internal pings. Default: true
156    pub enable_internal_pings: bool,
157    /// A ping schedule map.
158    /// Maps a ping name to a list of pings to schedule along with it.
159    /// Only used if the ping's own ping schedule list is empty.
160    pub ping_schedule: HashMap<String, Vec<String>>,
161
162    /// Write count threshold when to auto-flush. `0` disables it.
163    pub ping_lifetime_threshold: u64,
164    /// After what time to auto-flush. 0 disables it.
165    pub ping_lifetime_max_time: u64,
166}
167
168/// How to specify the rate at which pings may be uploaded before they are throttled.
169#[derive(Debug, Clone, MallocSizeOf)]
170pub struct PingRateLimit {
171    /// Length of time in seconds of a ping uploading interval.
172    pub seconds_per_interval: u64,
173    /// Number of pings that may be uploaded in a ping uploading interval.
174    pub pings_per_interval: u32,
175}
176
177/// Launches a new task on the global dispatch queue with a reference to the Glean singleton.
178fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
179    dispatcher::launch(|| core::with_glean(callback));
180}
181
182/// Launches a new task on the global dispatch queue with a mutable reference to the
183/// Glean singleton.
184fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
185    dispatcher::launch(|| core::with_glean_mut(callback));
186}
187
188/// Block on the dispatcher emptying.
189///
190/// This will panic if called before Glean is initialized.
191fn block_on_dispatcher() {
192    dispatcher::block_on_queue()
193}
194
195/// Returns a timestamp corresponding to "now" with millisecond precision, awake time only.
196pub fn get_awake_timestamp_ms() -> u64 {
197    const NANOS_PER_MILLI: u64 = 1_000_000;
198    zeitstempel::now_awake() / NANOS_PER_MILLI
199}
200
201/// Returns a timestamp corresponding to "now" with millisecond precision.
202pub fn get_timestamp_ms() -> u64 {
203    const NANOS_PER_MILLI: u64 = 1_000_000;
204    zeitstempel::now() / NANOS_PER_MILLI
205}
206
207/// State to keep track for the Rust Language bindings.
208///
209/// This is useful for setting Glean SDK-owned metrics when
210/// the state of the upload is toggled.
211struct State {
212    /// Client info metrics set by the application.
213    client_info: ClientInfoMetrics,
214
215    callbacks: Box<dyn OnGleanEvents>,
216}
217
218/// A global singleton storing additional state for Glean.
219///
220/// Requires a Mutex, because in tests we can actual reset this.
221static STATE: OnceCell<Mutex<State>> = OnceCell::new();
222
223/// Get a reference to the global state object.
224///
225/// Panics if no global state object was set.
226#[track_caller] // If this fails we're interested in the caller.
227fn global_state() -> &'static Mutex<State> {
228    STATE.get().unwrap()
229}
230
231/// Attempt to get a reference to the global state object.
232///
233/// If it hasn't been set yet, we return None.
234#[track_caller] // If this fails we're interested in the caller.
235fn maybe_global_state() -> Option<&'static Mutex<State>> {
236    STATE.get()
237}
238
239/// Set or replace the global bindings State object.
240fn setup_state(state: State) {
241    // The `OnceCell` type wrapping our state is thread-safe and can only be set once.
242    // Therefore even if our check for it being empty succeeds, setting it could fail if a
243    // concurrent thread is quicker in setting it.
244    // However this will not cause a bigger problem, as the second `set` operation will just fail.
245    // We can log it and move on.
246    //
247    // For all wrappers this is not a problem, as the State object is intialized exactly once on
248    // calling `initialize` on the global singleton and further operations check that it has been
249    // initialized.
250    if STATE.get().is_none() {
251        if STATE.set(Mutex::new(state)).is_err() {
252            log::error!(
253                "Global Glean state object is initialized already. This probably happened concurrently."
254            );
255        }
256    } else {
257        // We allow overriding the global State object to support test mode.
258        // In test mode the State object is fully destroyed and recreated.
259        // This all happens behind a mutex and is therefore also thread-safe.
260        let mut lock = STATE.get().unwrap().lock().unwrap();
261        *lock = state;
262    }
263}
264
265/// A global singleton that stores listener callbacks registered with Glean
266/// to receive event recording notifications.
267static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
268    OnceCell::new();
269
270fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
271    EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
272}
273
274fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
275    let mut lock = event_listeners().lock().unwrap();
276    lock.insert(tag, listener);
277}
278
279fn unregister_event_listener(tag: String) {
280    let mut lock = event_listeners().lock().unwrap();
281    lock.remove(&tag);
282}
283
284/// An error returned from callbacks.
285#[derive(Debug)]
286pub enum CallbackError {
287    /// An unexpected error occured.
288    UnexpectedError,
289}
290
291impl fmt::Display for CallbackError {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        write!(f, "Unexpected error")
294    }
295}
296
297impl std::error::Error for CallbackError {}
298
299impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
300    fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
301        CallbackError::UnexpectedError
302    }
303}
304
305/// A callback object used to trigger actions on the foreign-language side.
306///
307/// A callback object is stored in glean-core for the entire lifetime of the application.
308pub trait OnGleanEvents: Send {
309    /// Initialization finished.
310    ///
311    /// The language SDK can do additional things from within the same initializer thread,
312    /// e.g. starting to observe application events for foreground/background behavior.
313    /// The observer then needs to call the respective client activity API.
314    fn initialize_finished(&self);
315
316    /// Trigger the uploader whenever a ping was submitted.
317    ///
318    /// This should not block.
319    /// The uploader needs to asynchronously poll Glean for new pings to upload.
320    fn trigger_upload(&self) -> Result<(), CallbackError>;
321
322    /// Start the Metrics Ping Scheduler.
323    fn start_metrics_ping_scheduler(&self) -> bool;
324
325    /// Called when upload is disabled and uploads should be stopped
326    fn cancel_uploads(&self) -> Result<(), CallbackError>;
327
328    /// Called on shutdown, before glean-core is fully shutdown.
329    ///
330    /// * This MUST NOT put any new tasks on the dispatcher.
331    ///   * New tasks will be ignored.
332    /// * This SHOULD NOT block arbitrarily long.
333    ///   * Shutdown waits for a maximum of 30 seconds.
334    fn shutdown(&self) -> Result<(), CallbackError> {
335        // empty by default
336        Ok(())
337    }
338}
339
340/// A callback handler that receives the base identifier of recorded events
341/// The identifier is in the format: `<category>.<name>`
342pub trait GleanEventListener: Send {
343    /// Called when an event is recorded, indicating the id of the event
344    fn on_event_recorded(&self, id: String);
345}
346
347/// Initializes Glean.
348///
349/// # Arguments
350///
351/// * `cfg` - the [`InternalConfiguration`] options to initialize with.
352/// * `client_info` - the [`ClientInfoMetrics`] values used to set Glean
353///   core metrics.
354/// * `callbacks` - A callback object, stored for the entire application lifetime.
355pub fn glean_initialize(
356    cfg: InternalConfiguration,
357    client_info: ClientInfoMetrics,
358    callbacks: Box<dyn OnGleanEvents>,
359) {
360    initialize_inner(cfg, client_info, callbacks);
361}
362
363/// Shuts down Glean in an orderly fashion.
364pub fn glean_shutdown() {
365    shutdown();
366}
367
368/// Creates and initializes a new Glean object for use in a subprocess.
369///
370/// Importantly, this will not send any pings at startup, since that
371/// sort of management should only happen in the main process.
372pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
373    let glean = match Glean::new_for_subprocess(&cfg, true) {
374        Ok(glean) => glean,
375        Err(err) => {
376            log::error!("Failed to initialize Glean: {}", err);
377            return false;
378        }
379    };
380    if core::setup_glean(glean).is_err() {
381        return false;
382    }
383    log::info!("Glean initialized for subprocess");
384    true
385}
386
387fn initialize_inner(
388    cfg: InternalConfiguration,
389    client_info: ClientInfoMetrics,
390    callbacks: Box<dyn OnGleanEvents>,
391) {
392    if was_initialize_called() {
393        log::error!("Glean should not be initialized multiple times");
394        return;
395    }
396
397    let init_handle = thread::spawn("glean.init", move || {
398        let upload_enabled = cfg.upload_enabled;
399        let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
400
401        // Set the internal logging level.
402        if let Some(level) = cfg.log_level {
403            log::set_max_level(level)
404        }
405
406        let data_path_str = cfg.data_path.clone();
407        let data_path = Path::new(&data_path_str);
408        let internal_pings_enabled = cfg.enable_internal_pings;
409        let dir_info = if !is_test_mode() && internal_pings_enabled {
410            collect_directory_info(Path::new(&data_path))
411        } else {
412            None
413        };
414
415        let glean = match Glean::new(cfg) {
416            Ok(glean) => glean,
417            Err(err) => {
418                log::error!("Failed to initialize Glean: {}", err);
419                return;
420            }
421        };
422        if core::setup_glean(glean).is_err() {
423            return;
424        }
425
426        log::info!("Glean initialized");
427
428        core::with_glean(|glean| {
429            glean.health_metrics.init_count.add_sync(glean, 1);
430        });
431
432        setup_state(State {
433            client_info,
434            callbacks,
435        });
436
437        let mut is_first_run = false;
438        let mut dirty_flag = false;
439        let mut pings_submitted = false;
440        core::with_glean_mut(|glean| {
441            // The debug view tag might have been set before initialize,
442            // get the cached value and set it.
443            let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
444            if !debug_tag.is_empty() {
445                glean.set_debug_view_tag(&debug_tag);
446            }
447
448            // The log pings debug option might have been set before initialize,
449            // get the cached value and set it.
450            let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
451            if log_pigs {
452                glean.set_log_pings(log_pigs);
453            }
454
455            // The source tags might have been set before initialize,
456            // get the cached value and set them.
457            let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
458            if !source_tags.is_empty() {
459                glean.set_source_tags(source_tags.to_vec());
460            }
461
462            // Get the current value of the dirty flag so we know whether to
463            // send a dirty startup baseline ping below.  Immediately set it to
464            // `false` so that dirty startup pings won't be sent if Glean
465            // initialization does not complete successfully.
466            dirty_flag = glean.is_dirty_flag_set();
467            glean.set_dirty_flag(false);
468
469            // Perform registration of pings that were attempted to be
470            // registered before init.
471            let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
472            for ping in pings.iter() {
473                glean.register_ping_type(ping);
474            }
475            let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
476            for (ping, enabled) in pings.iter() {
477                glean.set_ping_enabled(ping, *enabled);
478            }
479
480            // The attribution and distribution might have been set before initialize,
481            // take the cached values and set them.
482            if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
483                glean.update_attribution(attribution);
484            }
485            if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
486                glean.update_distribution(distribution);
487            }
488
489            // If this is the first time ever the Glean SDK runs, make sure to set
490            // some initial core metrics in case we need to generate early pings.
491            // The next times we start, we would have them around already.
492            is_first_run = glean.is_first_run();
493            if is_first_run {
494                let state = global_state().lock().unwrap();
495                initialize_core_metrics(glean, &state.client_info);
496            }
497
498            // Deal with any pending events so we can start recording new ones
499            pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
500        });
501
502        {
503            let state = global_state().lock().unwrap();
504            // We need to kick off upload in these cases:
505            // 1. Pings were submitted through Glean and it is ready to upload those pings;
506            // 2. Upload is disabled, to upload a possible deletion-request ping.
507            if pings_submitted || !upload_enabled {
508                if let Err(e) = state.callbacks.trigger_upload() {
509                    log::error!("Triggering upload failed. Error: {}", e);
510                }
511            }
512        }
513
514        core::with_glean(|glean| {
515            // Start the MPS if its handled within Rust.
516            glean.start_metrics_ping_scheduler();
517        });
518
519        // The metrics ping scheduler might _synchronously_ submit a ping
520        // so that it runs before we clear application-lifetime metrics further below.
521        // For that it needs access to the `Glean` object.
522        // Thus we need to unlock that by leaving the context above,
523        // then re-lock it afterwards.
524        // That's safe because user-visible functions will be queued and thus not execute until
525        // we unblock later anyway.
526        {
527            let state = global_state().lock().unwrap();
528
529            // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
530            // ping startup check should be performed before any other ping, since it relies
531            // on being dispatched to the API context before any other metric.
532            if state.callbacks.start_metrics_ping_scheduler() {
533                if let Err(e) = state.callbacks.trigger_upload() {
534                    log::error!("Triggering upload failed. Error: {}", e);
535                }
536            }
537        }
538
539        core::with_glean_mut(|glean| {
540            let state = global_state().lock().unwrap();
541
542            // Check if the "dirty flag" is set. That means the product was probably
543            // force-closed. If that's the case, submit a 'baseline' ping with the
544            // reason "dirty_startup". We only do that from the second run.
545            if !is_first_run && dirty_flag {
546                // The `submit_ping_by_name_sync` function cannot be used, otherwise
547                // startup will cause a dead-lock, since that function requests a
548                // write lock on the `glean` object.
549                // Note that unwrapping below is safe: the function will return an
550                // `Ok` value for a known ping.
551                if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
552                    if let Err(e) = state.callbacks.trigger_upload() {
553                        log::error!("Triggering upload failed. Error: {}", e);
554                    }
555                }
556            }
557
558            // From the second time we run, after all startup pings are generated,
559            // make sure to clear `lifetime: application` metrics and set them again.
560            // Any new value will be sent in newly generated pings after startup.
561            if !is_first_run {
562                glean.clear_application_lifetime_metrics();
563                initialize_core_metrics(glean, &state.client_info);
564            }
565        });
566
567        // Signal Dispatcher that init is complete
568        // bug 1839433: It is important that this happens after any init tasks
569        // that shutdown() depends on. At time of writing that's only setting up
570        // the global Glean, but it is probably best to flush the preinit queue
571        // as late as possible in the glean.init thread.
572        match dispatcher::flush_init() {
573            Ok(task_count) if task_count > 0 => {
574                core::with_glean(|glean| {
575                    glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
576                });
577            }
578            Ok(_) => {}
579            Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
580        }
581
582        if !is_test_mode() && internal_pings_enabled {
583            // Now that Glean is initialized, we can capture the directory info from the pre_init phase and send it in
584            // a health ping with reason "pre_init".
585            record_dir_info_and_submit_health_ping(dir_info, "pre_init");
586
587            // Now capture a post_init snapshot of the state of Glean's data directories after initialization to send
588            // in a health ping with reason "post_init".
589            record_dir_info_and_submit_health_ping(collect_directory_info(data_path), "post_init");
590
591            let state = global_state().lock().unwrap();
592            if let Err(e) = state.callbacks.trigger_upload() {
593                log::error!("Triggering upload failed. Error: {}", e);
594            }
595        }
596        let state = global_state().lock().unwrap();
597        state.callbacks.initialize_finished();
598    })
599    .expect("Failed to spawn Glean's init thread");
600
601    // For test purposes, store the glean init thread's JoinHandle.
602    INIT_HANDLES.lock().unwrap().push(init_handle);
603
604    // Mark the initialization as called: this needs to happen outside of the
605    // dispatched block!
606    INITIALIZE_CALLED.store(true, Ordering::SeqCst);
607
608    // In test mode we wait for initialization to finish.
609    // This needs to run after we set `INITIALIZE_CALLED`, so it's similar to normal behavior.
610    if dispatcher::global::is_test_mode() {
611        join_init();
612    }
613}
614
615/// Return the heap usage of the `Glean` object and all descendant heap-allocated structures.
616///
617/// Value is in bytes.
618pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
619    use malloc_size_of::MallocSizeOf;
620    core::with_glean(|glean| glean.size_of(ops))
621}
622
623/// TEST ONLY FUNCTION
624/// Waits on all the glean.init threads' join handles.
625pub fn join_init() {
626    let mut handles = INIT_HANDLES.lock().unwrap();
627    for handle in handles.drain(..) {
628        handle.join().unwrap();
629    }
630}
631
632/// Call the `shutdown` callback.
633///
634/// This calls the shutdown in a separate thread and waits up to 30s for it to finish.
635/// If not finished in that time frame it continues.
636///
637/// Under normal operation that is fine, as the main process will end
638/// and thus the thread will get killed.
639fn uploader_shutdown() {
640    let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
641    let (tx, rx) = unbounded();
642
643    let handle = thread::spawn("glean.shutdown", move || {
644        let state = global_state().lock().unwrap();
645        if let Err(e) = state.callbacks.shutdown() {
646            log::error!("Shutdown callback failed: {e:?}");
647        }
648
649        // Best-effort sending. The other side might have timed out already.
650        let _ = tx.send(()).ok();
651    })
652    .expect("Unable to spawn thread to wait on shutdown");
653
654    // TODO: 30 seconds? What's a good default here? Should this be configurable?
655    // Reasoning:
656    //   * If we shut down early we might still be processing pending pings.
657    //     In this case we wait at most 3 times for 1s = 3s before we upload.
658    //   * If we're rate-limited the uploader sleeps for up to 60s.
659    //     Thus waiting 30s will rarely allow another upload.
660    //   * We don't know how long uploads take until we get data from bug 1814592.
661    let result = rx.recv_timeout(Duration::from_secs(30));
662
663    let stop_time = zeitstempel::now_awake();
664    core::with_glean(|glean| {
665        glean
666            .additional_metrics
667            .shutdown_wait
668            .set_stop_and_accumulate(glean, timer_id, stop_time);
669    });
670
671    if result.is_err() {
672        log::warn!("Waiting for upload failed. We're shutting down.");
673    } else {
674        let _ = handle.join().ok();
675    }
676}
677
678/// Shuts down Glean in an orderly fashion.
679pub fn shutdown() {
680    // Shutdown might have been called
681    // 1) Before init was called
682    //    * (data loss, oh well. Not enough time to do squat)
683    // 2) After init was called, but before it completed
684    //    * (we're willing to wait a little bit for init to complete)
685    // 3) After init completed
686    //    * (we can shut down immediately)
687
688    // Case 1: "Before init was called"
689    if !was_initialize_called() {
690        log::warn!("Shutdown called before Glean is initialized");
691        if let Err(e) = dispatcher::kill() {
692            log::error!("Can't kill dispatcher thread: {:?}", e);
693        }
694        return;
695    }
696
697    // Case 2: "After init was called, but before it completed"
698    if core::global_glean().is_none() {
699        log::warn!("Shutdown called before Glean is initialized. Waiting.");
700        // We can't join on the `glean.init` thread because there's no (easy) way
701        // to do that with a timeout. Instead, we wait for the preinit queue to
702        // empty, which is the last meaningful thing we do on that thread.
703
704        // TODO: Make the timeout configurable?
705        // We don't need the return value, as we're less interested in whether
706        // this times out than we are in whether there's a Global Glean at the end.
707        let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
708    }
709    // We can't shut down Glean if there's no Glean to shut down.
710    if core::global_glean().is_none() {
711        log::warn!("Waiting for Glean initialization timed out. Exiting.");
712        if let Err(e) = dispatcher::kill() {
713            log::error!("Can't kill dispatcher thread: {:?}", e);
714        }
715        return;
716    }
717
718    // Case 3: "After init completed"
719    crate::launch_with_glean_mut(|glean| {
720        glean.cancel_metrics_ping_scheduler();
721        glean.set_dirty_flag(false);
722    });
723
724    // We need to wait for above task to finish,
725    // but we also don't wait around forever.
726    //
727    // TODO: Make the timeout configurable?
728    // The default hang watchdog on Firefox waits 60s,
729    // Glean's `uploader_shutdown` further below waits up to 30s.
730    let timer_id = core::with_glean(|glean| {
731        glean
732            .additional_metrics
733            .shutdown_dispatcher_wait
734            .start_sync()
735    });
736    let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
737
738    // Always record the dispatcher wait, regardless of the timeout.
739    let stop_time = zeitstempel::now_awake();
740    core::with_glean(|glean| {
741        glean
742            .additional_metrics
743            .shutdown_dispatcher_wait
744            .set_stop_and_accumulate(glean, timer_id, stop_time);
745    });
746    if blocked.is_err() {
747        log::error!(
748            "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
749        );
750        return;
751    }
752
753    if let Err(e) = dispatcher::shutdown() {
754        log::error!("Can't shutdown dispatcher thread: {:?}", e);
755    }
756
757    uploader_shutdown();
758
759    // Be sure to call this _after_ draining the dispatcher
760    core::with_glean(|glean| {
761        if let Err(e) = glean.persist_ping_lifetime_data() {
762            log::info!("Can't persist ping lifetime data: {:?}", e);
763        }
764    });
765}
766
767/// Asks the database to persist ping-lifetime data to disk.
768///
769/// Probably expensive to call.
770/// Only has effect when Glean is configured with `delay_ping_lifetime_io: true`.
771/// If Glean hasn't been initialized this will dispatch and return Ok(()),
772/// otherwise it will block until the persist is done and return its Result.
773pub fn glean_persist_ping_lifetime_data() {
774    // This is async, we can't get the Error back to the caller.
775    crate::launch_with_glean(|glean| {
776        let _ = glean.persist_ping_lifetime_data();
777    });
778}
779
780fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
781    core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
782    core_metrics::internal_metrics::app_display_version
783        .set_sync(glean, &client_info.app_display_version[..]);
784    core_metrics::internal_metrics::app_build_date
785        .set_sync(glean, Some(client_info.app_build_date.clone()));
786    if let Some(app_channel) = client_info.channel.as_ref() {
787        core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
788    }
789
790    core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
791    core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
792
793    if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
794        core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
795    }
796    if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
797        core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
798    }
799    if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
800        core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
801    }
802    if let Some(device_model) = client_info.device_model.as_ref() {
803        core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
804    }
805    if let Some(locale) = client_info.locale.as_ref() {
806        core_metrics::internal_metrics::locale.set_sync(glean, locale);
807    }
808}
809
810/// Checks if [`glean_initialize`] was ever called.
811///
812/// # Returns
813///
814/// `true` if it was, `false` otherwise.
815fn was_initialize_called() -> bool {
816    INITIALIZE_CALLED.load(Ordering::SeqCst)
817}
818
819/// Initialize the logging system based on the target platform. This ensures
820/// that logging is shown when executing the Glean SDK unit tests.
821#[no_mangle]
822pub extern "C" fn glean_enable_logging() {
823    #[cfg(target_os = "android")]
824    {
825        let _ = std::panic::catch_unwind(|| {
826            let filter = android_logger::FilterBuilder::new()
827                .filter_module("glean_ffi", log::LevelFilter::Debug)
828                .filter_module("glean_core", log::LevelFilter::Debug)
829                .filter_module("glean", log::LevelFilter::Debug)
830                .filter_module("glean_core::ffi", log::LevelFilter::Info)
831                .build();
832            android_logger::init_once(
833                android_logger::Config::default()
834                    .with_max_level(log::LevelFilter::Debug)
835                    .with_filter(filter)
836                    .with_tag("libglean_ffi"),
837            );
838            log::trace!("Android logging should be hooked up!")
839        });
840    }
841
842    // On iOS enable logging with a level filter.
843    #[cfg(target_os = "ios")]
844    {
845        // Debug logging in debug mode.
846        // (Note: `debug_assertions` is the next best thing to determine if this is a debug build)
847        #[cfg(debug_assertions)]
848        let level = log::LevelFilter::Debug;
849        #[cfg(not(debug_assertions))]
850        let level = log::LevelFilter::Info;
851
852        let logger = oslog::OsLogger::new("org.mozilla.glean")
853            .level_filter(level)
854            // Filter UniFFI log messages
855            .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
856
857        match logger.init() {
858            Ok(_) => log::trace!("os_log should be hooked up!"),
859            // Please note that this is only expected to fail during unit tests,
860            // where the logger might have already been initialized by a previous
861            // test. So it's fine to print with the "logger".
862            Err(_) => log::warn!("os_log was already initialized"),
863        };
864    }
865
866    // When specifically requested make sure logging does something on non-Android platforms as well.
867    // Use the RUST_LOG environment variable to set the desired log level,
868    // e.g. setting RUST_LOG=debug sets the log level to debug.
869    #[cfg(all(
870        not(target_os = "android"),
871        not(target_os = "ios"),
872        feature = "enable_env_logger"
873    ))]
874    {
875        match env_logger::try_init() {
876            Ok(_) => log::trace!("stdout logging should be hooked up!"),
877            // Please note that this is only expected to fail during unit tests,
878            // where the logger might have already been initialized by a previous
879            // test. So it's fine to print with the "logger".
880            Err(_) => log::warn!("stdout logging was already initialized"),
881        };
882    }
883}
884
885/// **DEPRECATED** Sets whether upload is enabled or not.
886///
887/// **DEPRECATION NOTICE**:
888/// This API is deprecated. Use `set_collection_enabled` instead.
889pub fn glean_set_upload_enabled(enabled: bool) {
890    if !was_initialize_called() {
891        return;
892    }
893
894    crate::launch_with_glean_mut(move |glean| {
895        let state = global_state().lock().unwrap();
896        let original_enabled = glean.is_upload_enabled();
897
898        if !enabled {
899            // Stop the MPS if its handled within Rust.
900            glean.cancel_metrics_ping_scheduler();
901            // Stop wrapper-controlled uploader.
902            if let Err(e) = state.callbacks.cancel_uploads() {
903                log::error!("Canceling upload failed. Error: {}", e);
904            }
905        }
906
907        glean.set_upload_enabled(enabled);
908
909        if !original_enabled && enabled {
910            initialize_core_metrics(glean, &state.client_info);
911        }
912
913        if original_enabled && !enabled {
914            if let Err(e) = state.callbacks.trigger_upload() {
915                log::error!("Triggering upload failed. Error: {}", e);
916            }
917        }
918    })
919}
920
921/// Sets whether collection is enabled or not.
922///
923/// This replaces `set_upload_enabled`.
924pub fn glean_set_collection_enabled(enabled: bool) {
925    glean_set_upload_enabled(enabled)
926}
927
928/// Enable or disable a ping.
929///
930/// Disabling a ping causes all data for that ping to be removed from storage
931/// and all pending pings of that type to be deleted.
932pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
933    let ping = ping.clone();
934    if was_initialize_called() && core::global_glean().is_some() {
935        crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
936    } else {
937        let m = &PRE_INIT_PING_ENABLED;
938        let mut lock = m.lock().unwrap();
939        lock.push((ping, enabled));
940    }
941}
942
943/// Register a new [`PingType`].
944pub(crate) fn register_ping_type(ping: &PingType) {
945    // If this happens after Glean.initialize is called (and returns),
946    // we dispatch ping registration on the thread pool.
947    // Registering a ping should not block the application.
948    // Submission itself is also dispatched, so it will always come after the registration.
949    if was_initialize_called() && core::global_glean().is_some() {
950        let ping = ping.clone();
951        crate::launch_with_glean_mut(move |glean| {
952            glean.register_ping_type(&ping);
953        })
954    } else {
955        // We need to keep track of pings, so they get re-registered after a reset or
956        // if ping registration is attempted before Glean initializes.
957        // This state is kept across Glean resets, which should only ever happen in test mode.
958        // It's a set and keeping them around forever should not have much of an impact.
959        let m = &PRE_INIT_PING_REGISTRATION;
960        let mut lock = m.lock().unwrap();
961        lock.push(ping.clone());
962    }
963}
964
965/// Gets a list of currently registered ping names.
966///
967/// # Returns
968///
969/// The list of ping names that are currently registered.
970pub fn glean_get_registered_ping_names() -> Vec<String> {
971    block_on_dispatcher();
972    core::with_glean(|glean| {
973        glean
974            .get_registered_ping_names()
975            .iter()
976            .map(|ping| ping.to_string())
977            .collect()
978    })
979}
980
981/// Indicate that an experiment is running.  Glean will then add an
982/// experiment annotation to the environment which is sent with pings. This
983/// infomration is not persisted between runs.
984///
985/// See [`core::Glean::set_experiment_active`].
986pub fn glean_set_experiment_active(
987    experiment_id: String,
988    branch: String,
989    extra: HashMap<String, String>,
990) {
991    launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
992}
993
994/// Indicate that an experiment is no longer running.
995///
996/// See [`core::Glean::set_experiment_inactive`].
997pub fn glean_set_experiment_inactive(experiment_id: String) {
998    launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
999}
1000
1001/// TEST ONLY FUNCTION.
1002/// Returns the [`RecordedExperiment`] for the given `experiment_id`
1003/// or `None` if the id isn't found.
1004pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1005    block_on_dispatcher();
1006    core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1007}
1008
1009/// Set an experimentation identifier dynamically.
1010///
1011/// Note: it's probably a good idea to unenroll from any experiments when identifiers change.
1012pub fn glean_set_experimentation_id(experimentation_id: String) {
1013    launch_with_glean(move |glean| {
1014        glean
1015            .additional_metrics
1016            .experimentation_id
1017            .set(experimentation_id);
1018    });
1019}
1020
1021/// TEST ONLY FUNCTION.
1022/// Gets stored experimentation id annotation.
1023pub fn glean_test_get_experimentation_id() -> Option<String> {
1024    block_on_dispatcher();
1025    core::with_glean(|glean| glean.test_get_experimentation_id())
1026}
1027
1028/// Sets a remote configuration to override metrics' default enabled/disabled
1029/// state
1030///
1031/// See [`core::Glean::apply_server_knobs_config`].
1032pub fn glean_apply_server_knobs_config(json: String) {
1033    // An empty config means it is not set,
1034    // so we avoid logging an error about it.
1035    if json.is_empty() {
1036        return;
1037    }
1038
1039    match RemoteSettingsConfig::try_from(json) {
1040        Ok(cfg) => launch_with_glean(|glean| {
1041            glean.apply_server_knobs_config(cfg);
1042        }),
1043        Err(e) => {
1044            log::error!("Error setting metrics feature config: {:?}", e);
1045        }
1046    }
1047}
1048
1049/// Sets a debug view tag.
1050///
1051/// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the
1052/// value of the tag and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
1053///
1054/// # Arguments
1055///
1056/// * `tag` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
1057///
1058/// # Returns
1059///
1060/// This will return `false` in case `tag` is not a valid tag and `true` otherwise.
1061/// If called before Glean is initialized it will always return `true`.
1062pub fn glean_set_debug_view_tag(tag: String) -> bool {
1063    if was_initialize_called() && core::global_glean().is_some() {
1064        crate::launch_with_glean_mut(move |glean| {
1065            glean.set_debug_view_tag(&tag);
1066        });
1067        true
1068    } else {
1069        // Glean has not been initialized yet. Cache the provided tag value.
1070        let m = &PRE_INIT_DEBUG_VIEW_TAG;
1071        let mut lock = m.lock().unwrap();
1072        *lock = tag;
1073        // When setting the debug view tag before initialization,
1074        // we don't validate the tag, thus this function always returns true.
1075        true
1076    }
1077}
1078
1079/// Gets the currently set debug view tag.
1080///
1081/// # Returns
1082///
1083/// Return the value for the debug view tag or [`None`] if it hasn't been set.
1084pub fn glean_get_debug_view_tag() -> Option<String> {
1085    block_on_dispatcher();
1086    core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1087}
1088
1089/// Sets source tags.
1090///
1091/// Overrides any existing source tags.
1092/// Source tags will show in the destination datasets, after ingestion.
1093///
1094/// **Note** If one or more tags are invalid, all tags are ignored.
1095///
1096/// # Arguments
1097///
1098/// * `tags` - A vector of at most 5 valid HTTP header values. Individual
1099///   tags must match the regex: "[a-zA-Z0-9-]{1,20}".
1100pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1101    if was_initialize_called() && core::global_glean().is_some() {
1102        crate::launch_with_glean_mut(|glean| {
1103            glean.set_source_tags(tags);
1104        });
1105        true
1106    } else {
1107        // Glean has not been initialized yet. Cache the provided source tags.
1108        let m = &PRE_INIT_SOURCE_TAGS;
1109        let mut lock = m.lock().unwrap();
1110        *lock = tags;
1111        // When setting the source tags before initialization,
1112        // we don't validate the tags, thus this function always returns true.
1113        true
1114    }
1115}
1116
1117/// Sets the log pings debug option.
1118///
1119/// When the log pings debug option is `true`,
1120/// we log the payload of all succesfully assembled pings.
1121///
1122/// # Arguments
1123///
1124/// * `value` - The value of the log pings option
1125pub fn glean_set_log_pings(value: bool) {
1126    if was_initialize_called() && core::global_glean().is_some() {
1127        crate::launch_with_glean_mut(move |glean| {
1128            glean.set_log_pings(value);
1129        });
1130    } else {
1131        PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1132    }
1133}
1134
1135/// Gets the current log pings value.
1136///
1137/// # Returns
1138///
1139/// Return the value for the log pings debug option.
1140pub fn glean_get_log_pings() -> bool {
1141    block_on_dispatcher();
1142    core::with_glean(|glean| glean.log_pings())
1143}
1144
1145/// Performs the collection/cleanup operations required by becoming active.
1146///
1147/// This functions generates a baseline ping with reason `active`
1148/// and then sets the dirty bit.
1149/// This should be called whenever the consuming product becomes active (e.g.
1150/// getting to foreground).
1151pub fn glean_handle_client_active() {
1152    dispatcher::launch(|| {
1153        core::with_glean_mut(|glean| {
1154            glean.handle_client_active();
1155        });
1156
1157        // The above call may generate pings, so we need to trigger
1158        // the uploader. It's fine to trigger it if no ping was generated:
1159        // it will bail out.
1160        let state = global_state().lock().unwrap();
1161        if let Err(e) = state.callbacks.trigger_upload() {
1162            log::error!("Triggering upload failed. Error: {}", e);
1163        }
1164    });
1165
1166    // The previous block of code may send a ping containing the `duration` metric,
1167    // in `glean.handle_client_active`. We intentionally start recording a new
1168    // `duration` after that happens, so that the measurement gets reported when
1169    // calling `handle_client_inactive`.
1170    core_metrics::internal_metrics::baseline_duration.start();
1171}
1172
1173/// Performs the collection/cleanup operations required by becoming inactive.
1174///
1175/// This functions generates a baseline and an events ping with reason
1176/// `inactive` and then clears the dirty bit.
1177/// This should be called whenever the consuming product becomes inactive (e.g.
1178/// getting to background).
1179pub fn glean_handle_client_inactive() {
1180    // This needs to be called before the `handle_client_inactive` api: it stops
1181    // measuring the duration of the previous activity time, before any ping is sent
1182    // by the next call.
1183    core_metrics::internal_metrics::baseline_duration.stop();
1184
1185    dispatcher::launch(|| {
1186        core::with_glean_mut(|glean| {
1187            glean.handle_client_inactive();
1188        });
1189
1190        // The above call may generate pings, so we need to trigger
1191        // the uploader. It's fine to trigger it if no ping was generated:
1192        // it will bail out.
1193        let state = global_state().lock().unwrap();
1194        if let Err(e) = state.callbacks.trigger_upload() {
1195            log::error!("Triggering upload failed. Error: {}", e);
1196        }
1197    })
1198}
1199
1200/// Collect and submit a ping for eventual upload by name.
1201pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1202    dispatcher::launch(|| {
1203        let sent =
1204            core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1205
1206        if sent {
1207            let state = global_state().lock().unwrap();
1208            if let Err(e) = state.callbacks.trigger_upload() {
1209                log::error!("Triggering upload failed. Error: {}", e);
1210            }
1211        }
1212    })
1213}
1214
1215/// Collect and submit a ping (by its name) for eventual upload, synchronously.
1216///
1217/// Note: This does not trigger the uploader. The caller is responsible to do this.
1218pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1219    if !was_initialize_called() {
1220        return false;
1221    }
1222
1223    core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1224        .unwrap_or(false)
1225}
1226
1227/// EXPERIMENTAL: Register a listener object to recieve notifications of event recordings.
1228///
1229/// # Arguments
1230///
1231/// * `tag` - A string identifier used to later unregister the listener
1232/// * `listener` - Implements the `GleanEventListener` trait
1233pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1234    register_event_listener(tag, listener);
1235}
1236
1237/// Unregister an event listener from recieving notifications.
1238///
1239/// Does not panic if the listener doesn't exist.
1240///
1241/// # Arguments
1242///
1243/// * `tag` - The tag used when registering the listener to be unregistered
1244pub fn glean_unregister_event_listener(tag: String) {
1245    unregister_event_listener(tag);
1246}
1247
1248/// **TEST-ONLY Method**
1249///
1250/// Set test mode
1251pub fn glean_set_test_mode(enabled: bool) {
1252    dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1253}
1254
1255/// **TEST-ONLY Method**
1256///
1257/// Destroy the underlying database.
1258pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1259    if was_initialize_called() {
1260        // Just because initialize was called doesn't mean it's done.
1261        join_init();
1262
1263        dispatcher::reset_dispatcher();
1264
1265        // Only useful if Glean initialization finished successfully
1266        // and set up the storage.
1267        let has_storage = core::with_opt_glean(|glean| {
1268            // We need to flush the ping lifetime data before a full shutdown.
1269            glean
1270                .storage_opt()
1271                .map(|storage| storage.persist_ping_lifetime_data())
1272                .is_some()
1273        })
1274        .unwrap_or(false);
1275        if has_storage {
1276            uploader_shutdown();
1277        }
1278
1279        if core::global_glean().is_some() {
1280            core::with_glean_mut(|glean| {
1281                if clear_stores {
1282                    glean.test_clear_all_stores()
1283                }
1284                glean.destroy_db()
1285            });
1286        }
1287
1288        // Allow us to go through initialization again.
1289        INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1290    } else if clear_stores {
1291        if let Some(data_path) = data_path {
1292            let _ = std::fs::remove_dir_all(data_path).ok();
1293        } else {
1294            log::warn!("Asked to clear stores before initialization, but no data path given.");
1295        }
1296    }
1297}
1298
1299/// Get the next upload task
1300pub fn glean_get_upload_task() -> PingUploadTask {
1301    core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1302}
1303
1304/// Processes the response from an attempt to upload a ping.
1305pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1306    core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1307}
1308
1309/// **TEST-ONLY Method**
1310///
1311/// Set the dirty flag
1312pub fn glean_set_dirty_flag(new_value: bool) {
1313    core::with_glean(|glean| glean.set_dirty_flag(new_value))
1314}
1315
1316/// Updates attribution fields with new values.
1317/// AttributionMetrics fields with `None` values will not overwrite older values.
1318pub fn glean_update_attribution(attribution: AttributionMetrics) {
1319    if was_initialize_called() && core::global_glean().is_some() {
1320        core::with_glean(|glean| glean.update_attribution(attribution));
1321    } else {
1322        PRE_INIT_ATTRIBUTION
1323            .lock()
1324            .unwrap()
1325            .get_or_insert(Default::default())
1326            .update(attribution);
1327    }
1328}
1329
1330/// **TEST-ONLY Method**
1331///
1332/// Returns the current attribution metrics.
1333/// Panics if called before init.
1334pub fn glean_test_get_attribution() -> AttributionMetrics {
1335    join_init();
1336    core::with_glean(|glean| glean.test_get_attribution())
1337}
1338
1339/// Updates distribution fields with new values.
1340/// DistributionMetrics fields with `None` values will not overwrite older values.
1341pub fn glean_update_distribution(distribution: DistributionMetrics) {
1342    if was_initialize_called() && core::global_glean().is_some() {
1343        core::with_glean(|glean| glean.update_distribution(distribution));
1344    } else {
1345        PRE_INIT_DISTRIBUTION
1346            .lock()
1347            .unwrap()
1348            .get_or_insert(Default::default())
1349            .update(distribution);
1350    }
1351}
1352
1353/// **TEST-ONLY Method**
1354///
1355/// Returns the current distribution metrics.
1356/// Panics if called before init.
1357pub fn glean_test_get_distribution() -> DistributionMetrics {
1358    join_init();
1359    core::with_glean(|glean| glean.test_get_distribution())
1360}
1361
1362#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1363static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1364
1365/// Initialize the logging system to send JSON messages to a file descriptor
1366/// (Unix) or file handle (Windows).
1367///
1368/// Not available on Android and iOS.
1369///
1370/// `fd` is a writable file descriptor (on Unix) or file handle (on Windows).
1371///
1372/// # Safety
1373///
1374/// `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1375/// This function is marked safe,
1376/// because we can't call unsafe functions from generated UniFFI code.
1377#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1378pub fn glean_enable_logging_to_fd(fd: u64) {
1379    // SAFETY:
1380    // This functions is unsafe.
1381    // Due to UniFFI restrictions we cannot mark it as such.
1382    //
1383    // `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1384    unsafe {
1385        // Set up logging to a file descriptor/handle. For this usage, the
1386        // language binding should setup a pipe and pass in the descriptor to
1387        // the writing side of the pipe as the `fd` parameter. Log messages are
1388        // written as JSON to the file descriptor.
1389        let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1390        // Set the level so everything goes through to the language
1391        // binding side where it will be filtered by the language
1392        // binding's logging system.
1393        if log::set_logger(logger).is_ok() {
1394            log::set_max_level(log::LevelFilter::Debug);
1395        }
1396    }
1397}
1398
1399/// Collects information about the data directories used by FOG.
1400fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1401    // List of child directories to check
1402    let subdirs = ["db", "events", "pending_pings"];
1403    let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1404        DataDirectoryInfoObject::with_capacity(subdirs.len());
1405
1406    for subdir in subdirs.iter() {
1407        let dir_path = path.join(subdir);
1408
1409        // Initialize a DataDirectoryInfoObjectItem for each directory
1410        let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1411            dir_name: Some(subdir.to_string()),
1412            dir_exists: None,
1413            dir_created: None,
1414            dir_modified: None,
1415            file_count: None,
1416            files: Vec::new(),
1417            error_message: None,
1418        };
1419
1420        // Check if the directory exists
1421        if dir_path.is_dir() {
1422            directory_info.dir_exists = Some(true);
1423
1424            // Get directory metadata
1425            match fs::metadata(&dir_path) {
1426                Ok(metadata) => {
1427                    if let Ok(created) = metadata.created() {
1428                        directory_info.dir_created = Some(
1429                            created
1430                                .duration_since(UNIX_EPOCH)
1431                                .unwrap_or(Duration::ZERO)
1432                                .as_secs() as i64,
1433                        );
1434                    }
1435                    if let Ok(modified) = metadata.modified() {
1436                        directory_info.dir_modified = Some(
1437                            modified
1438                                .duration_since(UNIX_EPOCH)
1439                                .unwrap_or(Duration::ZERO)
1440                                .as_secs() as i64,
1441                        );
1442                    }
1443                }
1444                Err(error) => {
1445                    let msg = format!("Unable to get metadata: {}", error.kind());
1446                    directory_info.error_message = Some(msg.clone());
1447                    log::warn!("{}", msg);
1448                    continue;
1449                }
1450            }
1451
1452            // Read the directory's contents
1453            let mut file_count = 0;
1454            let entries = match fs::read_dir(&dir_path) {
1455                Ok(entries) => entries,
1456                Err(error) => {
1457                    let msg = format!("Unable to read subdir: {}", error.kind());
1458                    directory_info.error_message = Some(msg.clone());
1459                    log::warn!("{}", msg);
1460                    continue;
1461                }
1462            };
1463            for entry in entries {
1464                directory_info.files.push(
1465                    crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1466                        file_name: None,
1467                        file_created: None,
1468                        file_modified: None,
1469                        file_size: None,
1470                        error_message: None,
1471                    },
1472                );
1473                // Safely get and unwrap the file_info we just pushed so we can populate it
1474                let file_info = directory_info.files.last_mut().unwrap();
1475                let entry = match entry {
1476                    Ok(entry) => entry,
1477                    Err(error) => {
1478                        let msg = format!("Unable to read file: {}", error.kind());
1479                        file_info.error_message = Some(msg.clone());
1480                        log::warn!("{}", msg);
1481                        continue;
1482                    }
1483                };
1484                let file_name = match entry.file_name().into_string() {
1485                    Ok(file_name) => file_name,
1486                    _ => {
1487                        let msg = "Unable to convert file name to string".to_string();
1488                        file_info.error_message = Some(msg.clone());
1489                        log::warn!("{}", msg);
1490                        continue;
1491                    }
1492                };
1493                let metadata = match entry.metadata() {
1494                    Ok(metadata) => metadata,
1495                    Err(error) => {
1496                        let msg = format!("Unable to read file metadata: {}", error.kind());
1497                        file_info.file_name = Some(file_name);
1498                        file_info.error_message = Some(msg.clone());
1499                        log::warn!("{}", msg);
1500                        continue;
1501                    }
1502                };
1503
1504                // Check if the entry is a file
1505                if metadata.is_file() {
1506                    file_count += 1;
1507
1508                    // Collect file details
1509                    file_info.file_name = Some(file_name);
1510                    file_info.file_created = Some(
1511                        metadata
1512                            .created()
1513                            .unwrap_or(UNIX_EPOCH)
1514                            .duration_since(UNIX_EPOCH)
1515                            .unwrap_or(Duration::ZERO)
1516                            .as_secs() as i64,
1517                    );
1518                    file_info.file_modified = Some(
1519                        metadata
1520                            .modified()
1521                            .unwrap_or(UNIX_EPOCH)
1522                            .duration_since(UNIX_EPOCH)
1523                            .unwrap_or(Duration::ZERO)
1524                            .as_secs() as i64,
1525                    );
1526                    file_info.file_size = Some(metadata.len() as i64);
1527                } else {
1528                    let msg = format!("Skipping non-file entry: {}", file_name.clone());
1529                    file_info.file_name = Some(file_name);
1530                    file_info.error_message = Some(msg.clone());
1531                    log::warn!("{}", msg);
1532                }
1533            }
1534
1535            directory_info.file_count = Some(file_count as i64);
1536        } else {
1537            directory_info.dir_exists = Some(false);
1538        }
1539
1540        // Add the directory info to the final collection
1541        directories_info.push(directory_info);
1542    }
1543
1544    if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1545        Some(directories_info_json)
1546    } else {
1547        log::error!("Failed to serialize data directory info");
1548        None
1549    }
1550}
1551
1552fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1553    core::with_glean(|glean| {
1554        glean
1555            .health_metrics
1556            .data_directory_info
1557            .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1558        glean.internal_pings.health.submit_sync(glean, Some(reason));
1559    });
1560}
1561
1562/// Unused function. Not used on Android or iOS.
1563#[cfg(any(target_os = "android", target_os = "ios"))]
1564pub fn glean_enable_logging_to_fd(_fd: u64) {
1565    // intentionally left empty
1566}
1567
1568#[allow(missing_docs)]
1569// uniffi-generated code should not be checked.
1570#[allow(clippy::all)]
1571mod ffi {
1572    use super::*;
1573    uniffi::include_scaffolding!("glean");
1574
1575    type CowString = Cow<'static, str>;
1576
1577    uniffi::custom_type!(CowString, String, {
1578        remote,
1579        lower: |s| s.into_owned(),
1580        try_lift: |s| Ok(Cow::from(s))
1581    });
1582
1583    type JsonValue = serde_json::Value;
1584
1585    uniffi::custom_type!(JsonValue, String, {
1586        remote,
1587        lower: |s| serde_json::to_string(&s).unwrap(),
1588        try_lift: |s| Ok(serde_json::from_str(&s)?)
1589    });
1590}
1591pub use ffi::*;
1592
1593// Split unit tests to a separate file, to reduce the file of this one.
1594#[cfg(test)]
1595#[path = "lib_unit_tests.rs"]
1596mod tests;