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