Skip to main content

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