1#![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
11use 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(crate) mod session;
56pub mod storage;
57mod system;
58#[doc(hidden)]
59pub mod thread;
60pub mod traits;
61pub mod upload;
62mod util;
63
64#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
65mod fd_logger;
66
67pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
68pub use crate::core::Glean;
69pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
70use crate::dispatcher::is_test_mode;
71pub use crate::error::{Error, ErrorKind, Result};
72pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
73pub use crate::histogram::HistogramType;
74use crate::internal_metrics::DataDirectoryInfoObject;
75pub use crate::metrics::labeled::{
76 AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
77 LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
78 LabeledTimingDistribution,
79};
80pub use crate::metrics::{
81 BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
82 DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
83 LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
84 MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
85 Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
86 TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
87 UrlMetric, UuidMetric,
88};
89pub use crate::session::{SessionManager, SessionMetadata, SessionMode};
90pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
91
92const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
93const GLEAN_SCHEMA_VERSION: u32 = 1;
94const DEFAULT_MAX_EVENTS: u32 = 500;
95static KNOWN_CLIENT_ID: Lazy<Uuid> =
96 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
97
98pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
100pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
101
102static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
106
107static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
109static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
110static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
111
112static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
114static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
115
116static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
118static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
119static PRE_INIT_ATTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
120static PRE_INIT_DISTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
121
122static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
126 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
127
128#[derive(Debug, Clone, MallocSizeOf)]
130pub struct InternalConfiguration {
131 pub upload_enabled: bool,
133 pub data_path: String,
135 pub application_id: String,
137 pub language_binding_name: String,
139 pub max_events: Option<u32>,
141 pub delay_ping_lifetime_io: bool,
143 pub app_build: String,
146 pub use_core_mps: bool,
148 pub trim_data_to_registered_pings: bool,
150 #[ignore_malloc_size_of = "external non-allocating type"]
153 pub log_level: Option<LevelFilter>,
154 pub rate_limit: Option<PingRateLimit>,
156 pub enable_event_timestamps: bool,
158 pub experimentation_id: Option<String>,
162 pub enable_internal_pings: bool,
164 pub ping_schedule: HashMap<String, Vec<String>>,
168
169 pub ping_lifetime_threshold: u64,
171 pub ping_lifetime_max_time: u64,
173 pub max_pending_pings_count: Option<u64>,
175 pub max_pending_pings_directory_size: Option<u64>,
177 pub session_mode: session::SessionMode,
179 pub session_sample_rate: f64,
181 pub session_inactivity_timeout_ms: u64,
184}
185
186#[derive(Debug, Clone, MallocSizeOf)]
188pub struct PingRateLimit {
189 pub seconds_per_interval: u64,
191 pub pings_per_interval: u32,
193}
194
195fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
197 dispatcher::launch(|| core::with_glean(callback));
198}
199
200fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
203 dispatcher::launch(|| core::with_glean_mut(callback));
204}
205
206fn block_on_dispatcher() {
210 dispatcher::block_on_queue()
211}
212
213pub fn get_awake_timestamp_ms() -> u64 {
215 const NANOS_PER_MILLI: u64 = 1_000_000;
216 zeitstempel::now_awake() / NANOS_PER_MILLI
217}
218
219pub fn get_timestamp_ms() -> u64 {
221 const NANOS_PER_MILLI: u64 = 1_000_000;
222 zeitstempel::now() / NANOS_PER_MILLI
223}
224
225struct State {
230 client_info: ClientInfoMetrics,
232
233 callbacks: Box<dyn OnGleanEvents>,
234}
235
236static STATE: OnceCell<Mutex<State>> = OnceCell::new();
240
241#[track_caller] fn global_state() -> &'static Mutex<State> {
246 STATE.get().unwrap()
247}
248
249#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
254 STATE.get()
255}
256
257fn setup_state(state: State) {
259 if STATE.get().is_none() {
269 if STATE.set(Mutex::new(state)).is_err() {
270 log::error!(
271 "Global Glean state object is initialized already. This probably happened concurrently."
272 );
273 }
274 } else {
275 let mut lock = STATE.get().unwrap().lock().unwrap();
279 *lock = state;
280 }
281}
282
283static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
286 OnceCell::new();
287
288fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
289 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
290}
291
292fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
293 let mut lock = event_listeners().lock().unwrap();
294 lock.insert(tag, listener);
295}
296
297fn unregister_event_listener(tag: String) {
298 let mut lock = event_listeners().lock().unwrap();
299 lock.remove(&tag);
300}
301
302#[derive(Debug)]
304pub enum CallbackError {
305 UnexpectedError,
307}
308
309impl fmt::Display for CallbackError {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 write!(f, "Unexpected error")
312 }
313}
314
315impl std::error::Error for CallbackError {}
316
317impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
318 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
319 CallbackError::UnexpectedError
320 }
321}
322
323pub trait OnGleanEvents: Send {
327 fn initialize_finished(&self);
333
334 fn trigger_upload(&self) -> Result<(), CallbackError>;
339
340 fn start_metrics_ping_scheduler(&self) -> bool;
342
343 fn cancel_uploads(&self) -> Result<(), CallbackError>;
345
346 fn shutdown(&self) -> Result<(), CallbackError> {
353 Ok(())
355 }
356}
357
358pub trait GleanEventListener: Send {
361 fn on_event_recorded(&self, id: String);
363}
364
365pub fn glean_initialize(
374 cfg: InternalConfiguration,
375 client_info: ClientInfoMetrics,
376 callbacks: Box<dyn OnGleanEvents>,
377) {
378 initialize_inner(cfg, client_info, callbacks);
379}
380
381pub fn glean_shutdown() {
383 shutdown();
384}
385
386pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
391 let glean = match Glean::new_for_subprocess(&cfg, true) {
392 Ok(glean) => glean,
393 Err(err) => {
394 log::error!("Failed to initialize Glean: {}", err);
395 return false;
396 }
397 };
398 if core::setup_glean(glean).is_err() {
399 return false;
400 }
401 log::info!("Glean initialized for subprocess");
402 true
403}
404
405fn initialize_inner(
406 cfg: InternalConfiguration,
407 client_info: ClientInfoMetrics,
408 callbacks: Box<dyn OnGleanEvents>,
409) {
410 if was_initialize_called() {
411 log::error!("Glean should not be initialized multiple times");
412 return;
413 }
414
415 let init_handle = thread::spawn("glean.init", move || {
416 let upload_enabled = cfg.upload_enabled;
417 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
418
419 if let Some(level) = cfg.log_level {
421 log::set_max_level(level)
422 }
423
424 let data_path_str = cfg.data_path.clone();
425 let data_path = Path::new(&data_path_str);
426 let internal_pings_enabled = cfg.enable_internal_pings;
427 let dir_info = if !is_test_mode() && internal_pings_enabled {
428 collect_directory_info(Path::new(&data_path))
429 } else {
430 None
431 };
432
433 let glean = match Glean::new(cfg) {
434 Ok(glean) => glean,
435 Err(err) => {
436 log::error!("Failed to initialize Glean: {}", err);
437 return;
438 }
439 };
440 if core::setup_glean(glean).is_err() {
441 return;
442 }
443
444 log::info!("Glean initialized");
445
446 core::with_glean(|glean| {
447 glean.health_metrics.init_count.add_sync(glean, 1);
448 });
449
450 setup_state(State {
451 client_info,
452 callbacks,
453 });
454
455 let mut is_first_run = false;
456 let mut dirty_flag = false;
457 let mut pings_submitted = false;
458 core::with_glean_mut(|glean| {
459 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
462 if !debug_tag.is_empty() {
463 glean.set_debug_view_tag(&debug_tag);
464 }
465
466 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
469 if log_pigs {
470 glean.set_log_pings(log_pigs);
471 }
472
473 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
476 if !source_tags.is_empty() {
477 glean.set_source_tags(source_tags.to_vec());
478 }
479
480 dirty_flag = glean.is_dirty_flag_set();
485 glean.set_dirty_flag(false);
486
487 if dirty_flag {
491 glean.recover_session_on_dirty_flag();
492 }
493
494 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
497 for ping in pings.iter() {
498 glean.register_ping_type(ping);
499 }
500 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
501 for (ping, enabled) in pings.iter() {
502 glean.set_ping_enabled(ping, *enabled);
503 }
504
505 let clear_attribution = PRE_INIT_ATTRIBUTION_CLEARED.load(Ordering::SeqCst);
508 if clear_attribution {
509 glean.clear_attribution();
510 }
511 let clear_distribution = PRE_INIT_DISTRIBUTION_CLEARED.load(Ordering::SeqCst);
512 if clear_distribution {
513 glean.clear_distribution();
514 }
515 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
516 glean.update_attribution(attribution);
517 }
518 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
519 glean.update_distribution(distribution);
520 }
521
522 is_first_run = glean.is_first_run();
526 if is_first_run {
527 let state = global_state().lock().unwrap();
528 initialize_core_metrics(glean, &state.client_info);
529 }
530
531 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
533 });
534
535 {
536 let state = global_state().lock().unwrap();
537 if pings_submitted || !upload_enabled {
541 if let Err(e) = state.callbacks.trigger_upload() {
542 log::error!("Triggering upload failed. Error: {}", e);
543 }
544 }
545 }
546
547 core::with_glean(|glean| {
548 glean.start_metrics_ping_scheduler();
550 });
551
552 {
560 let state = global_state().lock().unwrap();
561
562 if state.callbacks.start_metrics_ping_scheduler() {
566 if let Err(e) = state.callbacks.trigger_upload() {
567 log::error!("Triggering upload failed. Error: {}", e);
568 }
569 }
570 }
571
572 core::with_glean_mut(|glean| {
573 let state = global_state().lock().unwrap();
574
575 if !is_first_run && dirty_flag {
579 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
585 if let Err(e) = state.callbacks.trigger_upload() {
586 log::error!("Triggering upload failed. Error: {}", e);
587 }
588 }
589 }
590
591 if !is_first_run {
595 glean.clear_application_lifetime_metrics();
596 initialize_core_metrics(glean, &state.client_info);
597 }
598 });
599
600 match dispatcher::flush_init() {
606 Ok(task_count) if task_count > 0 => {
607 core::with_glean(|glean| {
608 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
609 });
610 }
611 Ok(_) => {}
612 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
613 }
614
615 if !is_test_mode() && internal_pings_enabled {
616 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
619
620 let state = global_state().lock().unwrap();
621 if let Err(e) = state.callbacks.trigger_upload() {
622 log::error!("Triggering upload failed. Error: {}", e);
623 }
624 }
625 let state = global_state().lock().unwrap();
626 state.callbacks.initialize_finished();
627 })
628 .expect("Failed to spawn Glean's init thread");
629
630 INIT_HANDLES.lock().unwrap().push(init_handle);
632
633 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
636
637 if dispatcher::global::is_test_mode() {
640 join_init();
641 }
642}
643
644pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
648 use malloc_size_of::MallocSizeOf;
649 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
650}
651
652pub fn join_init() {
655 let mut handles = INIT_HANDLES.lock().unwrap();
656 for handle in handles.drain(..) {
657 handle.join().unwrap();
658 }
659}
660
661fn uploader_shutdown() {
669 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
670 let (tx, rx) = unbounded();
671
672 let handle = thread::spawn("glean.shutdown", move || {
673 let state = global_state().lock().unwrap();
674 if let Err(e) = state.callbacks.shutdown() {
675 log::error!("Shutdown callback failed: {e:?}");
676 }
677
678 let _ = tx.send(()).ok();
680 })
681 .expect("Unable to spawn thread to wait on shutdown");
682
683 let result = rx.recv_timeout(Duration::from_secs(30));
691
692 let stop_time = zeitstempel::now_awake();
693 core::with_glean(|glean| {
694 glean
695 .additional_metrics
696 .shutdown_wait
697 .set_stop_and_accumulate(glean, timer_id, stop_time);
698 });
699
700 if result.is_err() {
701 log::warn!("Waiting for upload failed. We're shutting down.");
702 } else {
703 let _ = handle.join().ok();
704 }
705}
706
707pub fn shutdown() {
709 if !was_initialize_called() {
719 log::warn!("Shutdown called before Glean is initialized");
720 if let Err(e) = dispatcher::kill() {
721 log::error!("Can't kill dispatcher thread: {:?}", e);
722 }
723 return;
724 }
725
726 if core::global_glean().is_none() {
728 log::warn!("Shutdown called before Glean is initialized. Waiting.");
729 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
737 }
738 if core::global_glean().is_none() {
740 log::warn!("Waiting for Glean initialization timed out. Exiting.");
741 if let Err(e) = dispatcher::kill() {
742 log::error!("Can't kill dispatcher thread: {:?}", e);
743 }
744 return;
745 }
746
747 crate::launch_with_glean_mut(|glean| {
749 glean.cancel_metrics_ping_scheduler();
750 glean.set_dirty_flag(false);
751 });
752
753 let timer_id = core::with_glean(|glean| {
760 glean
761 .additional_metrics
762 .shutdown_dispatcher_wait
763 .start_sync()
764 });
765 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
766
767 let stop_time = zeitstempel::now_awake();
769 core::with_glean(|glean| {
770 glean
771 .additional_metrics
772 .shutdown_dispatcher_wait
773 .set_stop_and_accumulate(glean, timer_id, stop_time);
774 });
775 if blocked.is_err() {
776 log::error!(
777 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
778 );
779 return;
780 }
781
782 if let Err(e) = dispatcher::shutdown() {
783 log::error!("Can't shutdown dispatcher thread: {:?}", e);
784 }
785
786 uploader_shutdown();
787
788 core::with_glean(|glean| {
790 if let Err(e) = glean.persist_ping_lifetime_data() {
791 log::info!("Can't persist ping lifetime data: {:?}", e);
792 }
793 });
794}
795
796pub fn glean_persist_ping_lifetime_data() {
803 crate::launch_with_glean(|glean| {
805 let _ = glean.persist_ping_lifetime_data();
806 });
807}
808
809fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
810 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
811 core_metrics::internal_metrics::app_display_version
812 .set_sync(glean, &client_info.app_display_version[..]);
813 core_metrics::internal_metrics::app_build_date
814 .set_sync(glean, Some(client_info.app_build_date.clone()));
815 if let Some(app_channel) = client_info.channel.as_ref() {
816 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
817 }
818
819 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
820 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
821
822 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
823 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
824 }
825 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
826 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
827 }
828 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
829 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
830 }
831 if let Some(device_model) = client_info.device_model.as_ref() {
832 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
833 }
834 if let Some(locale) = client_info.locale.as_ref() {
835 core_metrics::internal_metrics::locale.set_sync(glean, locale);
836 }
837}
838
839fn was_initialize_called() -> bool {
845 INITIALIZE_CALLED.load(Ordering::SeqCst)
846}
847
848#[no_mangle]
851pub extern "C" fn glean_enable_logging() {
852 #[cfg(target_os = "android")]
853 {
854 let _ = std::panic::catch_unwind(|| {
855 let filter = android_logger::FilterBuilder::new()
856 .filter_module("glean_ffi", log::LevelFilter::Debug)
857 .filter_module("glean_core", log::LevelFilter::Debug)
858 .filter_module("glean", log::LevelFilter::Debug)
859 .filter_module("glean_core::ffi", log::LevelFilter::Info)
860 .build();
861 android_logger::init_once(
862 android_logger::Config::default()
863 .with_max_level(log::LevelFilter::Debug)
864 .with_filter(filter)
865 .with_tag("libglean_ffi"),
866 );
867 log::trace!("Android logging should be hooked up!")
868 });
869 }
870
871 #[cfg(target_os = "ios")]
873 {
874 #[cfg(debug_assertions)]
877 let level = log::LevelFilter::Debug;
878 #[cfg(not(debug_assertions))]
879 let level = log::LevelFilter::Info;
880
881 let logger = oslog::OsLogger::new("org.mozilla.glean")
882 .level_filter(level)
883 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
885
886 match logger.init() {
887 Ok(_) => log::trace!("os_log should be hooked up!"),
888 Err(_) => log::warn!("os_log was already initialized"),
892 };
893 }
894
895 #[cfg(all(
899 not(target_os = "android"),
900 not(target_os = "ios"),
901 feature = "enable_env_logger"
902 ))]
903 {
904 match env_logger::try_init() {
905 Ok(_) => log::trace!("stdout logging should be hooked up!"),
906 Err(_) => log::warn!("stdout logging was already initialized"),
910 };
911 }
912}
913
914pub fn glean_set_upload_enabled(enabled: bool) {
919 if !was_initialize_called() {
920 return;
921 }
922
923 crate::launch_with_glean_mut(move |glean| {
924 let state = global_state().lock().unwrap();
925 let original_enabled = glean.is_upload_enabled();
926
927 if !enabled {
928 glean.cancel_metrics_ping_scheduler();
930 if let Err(e) = state.callbacks.cancel_uploads() {
932 log::error!("Canceling upload failed. Error: {}", e);
933 }
934 }
935
936 glean.set_upload_enabled(enabled);
937
938 if !original_enabled && enabled {
939 initialize_core_metrics(glean, &state.client_info);
940 }
941
942 if original_enabled && !enabled {
943 if let Err(e) = state.callbacks.trigger_upload() {
944 log::error!("Triggering upload failed. Error: {}", e);
945 }
946 }
947 })
948}
949
950pub fn glean_set_collection_enabled(enabled: bool) {
954 glean_set_upload_enabled(enabled)
955}
956
957pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
962 let ping = ping.clone();
963 if was_initialize_called() && core::global_glean().is_some() {
964 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
965 } else {
966 let m = &PRE_INIT_PING_ENABLED;
967 let mut lock = m.lock().unwrap();
968 lock.push((ping, enabled));
969 }
970}
971
972pub(crate) fn register_ping_type(ping: &PingType) {
974 if was_initialize_called() && core::global_glean().is_some() {
979 let ping = ping.clone();
980 crate::launch_with_glean_mut(move |glean| {
981 glean.register_ping_type(&ping);
982 })
983 } else {
984 let m = &PRE_INIT_PING_REGISTRATION;
989 let mut lock = m.lock().unwrap();
990 lock.push(ping.clone());
991 }
992}
993
994pub fn glean_get_registered_ping_names() -> Vec<String> {
1000 block_on_dispatcher();
1001 core::with_glean(|glean| {
1002 glean
1003 .get_registered_ping_names()
1004 .iter()
1005 .map(|ping| ping.to_string())
1006 .collect()
1007 })
1008}
1009
1010pub fn glean_set_experiment_active(
1016 experiment_id: String,
1017 branch: String,
1018 extra: HashMap<String, String>,
1019) {
1020 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1021}
1022
1023pub fn glean_set_experiment_inactive(experiment_id: String) {
1027 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1028}
1029
1030pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1034 block_on_dispatcher();
1035 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1036}
1037
1038pub fn glean_set_experimentation_id(experimentation_id: String) {
1042 launch_with_glean(move |glean| {
1043 glean
1044 .additional_metrics
1045 .experimentation_id
1046 .set(experimentation_id);
1047 });
1048}
1049
1050pub fn glean_test_get_experimentation_id() -> Option<String> {
1053 block_on_dispatcher();
1054 core::with_glean(|glean| glean.test_get_experimentation_id())
1055}
1056
1057pub fn glean_apply_server_knobs_config(json: String) {
1062 if json.is_empty() {
1065 return;
1066 }
1067
1068 match RemoteSettingsConfig::try_from(json) {
1069 Ok(cfg) => launch_with_glean(|glean| {
1070 glean.apply_server_knobs_config(cfg);
1071 }),
1072 Err(e) => {
1073 log::error!("Error setting metrics feature config: {:?}", e);
1074 }
1075 }
1076}
1077
1078pub fn glean_set_debug_view_tag(tag: String) -> bool {
1092 if was_initialize_called() && core::global_glean().is_some() {
1093 crate::launch_with_glean_mut(move |glean| {
1094 glean.set_debug_view_tag(&tag);
1095 });
1096 true
1097 } else {
1098 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1100 let mut lock = m.lock().unwrap();
1101 *lock = tag;
1102 true
1105 }
1106}
1107
1108pub fn glean_get_debug_view_tag() -> Option<String> {
1114 block_on_dispatcher();
1115 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1116}
1117
1118pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1130 if was_initialize_called() && core::global_glean().is_some() {
1131 crate::launch_with_glean_mut(|glean| {
1132 glean.set_source_tags(tags);
1133 });
1134 true
1135 } else {
1136 let m = &PRE_INIT_SOURCE_TAGS;
1138 let mut lock = m.lock().unwrap();
1139 *lock = tags;
1140 true
1143 }
1144}
1145
1146pub fn glean_set_log_pings(value: bool) {
1155 if was_initialize_called() && core::global_glean().is_some() {
1156 crate::launch_with_glean_mut(move |glean| {
1157 glean.set_log_pings(value);
1158 });
1159 } else {
1160 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1161 }
1162}
1163
1164pub fn glean_get_log_pings() -> bool {
1170 block_on_dispatcher();
1171 core::with_glean(|glean| glean.log_pings())
1172}
1173
1174pub fn glean_handle_client_active() {
1181 dispatcher::launch(|| {
1182 core::with_glean_mut(|glean| {
1183 glean.handle_client_active();
1184 });
1185
1186 let state = global_state().lock().unwrap();
1190 if let Err(e) = state.callbacks.trigger_upload() {
1191 log::error!("Triggering upload failed. Error: {}", e);
1192 }
1193 });
1194
1195 core_metrics::internal_metrics::baseline_duration.start();
1200}
1201
1202pub fn glean_handle_client_inactive() {
1209 core_metrics::internal_metrics::baseline_duration.stop();
1213
1214 dispatcher::launch(|| {
1215 core::with_glean_mut(|glean| {
1216 glean.handle_client_inactive();
1217 });
1218
1219 let state = global_state().lock().unwrap();
1223 if let Err(e) = state.callbacks.trigger_upload() {
1224 log::error!("Triggering upload failed. Error: {}", e);
1225 }
1226 })
1227}
1228
1229pub fn glean_session_start() {
1234 launch_with_glean_mut(|glean| {
1235 if glean.session_manager.mode == session::SessionMode::Manual {
1236 glean.session_start();
1237 }
1238 });
1239}
1240
1241pub fn glean_session_end(reason: Option<String>) {
1249 launch_with_glean_mut(move |glean| {
1250 if glean.session_manager.mode == session::SessionMode::Manual {
1251 glean.session_end(reason.as_deref());
1252 }
1253 });
1254}
1255
1256pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1258 dispatcher::launch(|| {
1259 let sent =
1260 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1261
1262 if sent {
1263 let state = global_state().lock().unwrap();
1264 if let Err(e) = state.callbacks.trigger_upload() {
1265 log::error!("Triggering upload failed. Error: {}", e);
1266 }
1267 }
1268 })
1269}
1270
1271pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1275 if !was_initialize_called() {
1276 return false;
1277 }
1278
1279 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1280 .unwrap_or(false)
1281}
1282
1283pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1290 register_event_listener(tag, listener);
1291}
1292
1293pub fn glean_unregister_event_listener(tag: String) {
1301 unregister_event_listener(tag);
1302}
1303
1304pub fn glean_set_test_mode(enabled: bool) {
1308 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1309}
1310
1311pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1315 if was_initialize_called() {
1316 join_init();
1318
1319 dispatcher::reset_dispatcher();
1320
1321 let has_storage = core::with_opt_glean(|glean| {
1324 glean
1326 .storage_opt()
1327 .map(|storage| storage.persist_ping_lifetime_data())
1328 .is_some()
1329 })
1330 .unwrap_or(false);
1331 if has_storage {
1332 uploader_shutdown();
1333 }
1334
1335 if core::global_glean().is_some() {
1336 core::with_glean_mut(|glean| {
1337 if clear_stores {
1338 glean.test_clear_all_stores()
1339 }
1340 glean.destroy_db()
1341 });
1342 }
1343
1344 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1346 } else if clear_stores {
1347 if let Some(data_path) = data_path {
1348 let _ = std::fs::remove_dir_all(data_path).ok();
1349 } else {
1350 log::warn!("Asked to clear stores before initialization, but no data path given.");
1351 }
1352 }
1353}
1354
1355pub fn glean_get_upload_task() -> PingUploadTask {
1357 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1358}
1359
1360pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1362 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1363}
1364
1365pub fn glean_set_dirty_flag(new_value: bool) {
1369 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1370}
1371
1372pub fn glean_clear_attribution() {
1375 if was_initialize_called() && core::global_glean().is_some() {
1376 core::with_glean(|glean| glean.clear_attribution());
1377 } else {
1378 PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1379 _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1380 }
1381}
1382
1383pub fn glean_update_attribution(attribution: AttributionMetrics) {
1386 if was_initialize_called() && core::global_glean().is_some() {
1387 core::with_glean(|glean| glean.update_attribution(attribution));
1388 } else {
1389 PRE_INIT_ATTRIBUTION
1390 .lock()
1391 .unwrap()
1392 .get_or_insert(Default::default())
1393 .update(attribution);
1394 }
1395}
1396
1397pub fn glean_test_get_attribution() -> AttributionMetrics {
1402 join_init();
1403 core::with_glean(|glean| glean.test_get_attribution())
1404}
1405
1406pub fn glean_clear_distribution() {
1409 if was_initialize_called() && core::global_glean().is_some() {
1410 core::with_glean(|glean| glean.clear_distribution());
1411 } else {
1412 PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1413 _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1414 }
1415}
1416
1417pub fn glean_update_distribution(distribution: DistributionMetrics) {
1420 if was_initialize_called() && core::global_glean().is_some() {
1421 core::with_glean(|glean| glean.update_distribution(distribution));
1422 } else {
1423 PRE_INIT_DISTRIBUTION
1424 .lock()
1425 .unwrap()
1426 .get_or_insert(Default::default())
1427 .update(distribution);
1428 }
1429}
1430
1431pub fn glean_test_get_distribution() -> DistributionMetrics {
1436 join_init();
1437 core::with_glean(|glean| glean.test_get_distribution())
1438}
1439
1440#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1441static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1442
1443#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1456pub fn glean_enable_logging_to_fd(fd: u64) {
1457 unsafe {
1463 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1468 if log::set_logger(logger).is_ok() {
1472 log::set_max_level(log::LevelFilter::Debug);
1473 }
1474 }
1475}
1476
1477fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1479 let subdirs = ["db", "events", "pending_pings"];
1481 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1482 DataDirectoryInfoObject::with_capacity(subdirs.len());
1483
1484 for subdir in subdirs.iter() {
1485 let dir_path = path.join(subdir);
1486
1487 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1489 dir_name: Some(subdir.to_string()),
1490 dir_exists: None,
1491 dir_created: None,
1492 dir_modified: None,
1493 file_count: None,
1494 files: Vec::new(),
1495 error_message: None,
1496 };
1497
1498 if dir_path.is_dir() {
1500 directory_info.dir_exists = Some(true);
1501
1502 match fs::metadata(&dir_path) {
1504 Ok(metadata) => {
1505 if let Ok(created) = metadata.created() {
1506 directory_info.dir_created = Some(
1507 created
1508 .duration_since(UNIX_EPOCH)
1509 .unwrap_or(Duration::ZERO)
1510 .as_secs() as i64,
1511 );
1512 }
1513 if let Ok(modified) = metadata.modified() {
1514 directory_info.dir_modified = Some(
1515 modified
1516 .duration_since(UNIX_EPOCH)
1517 .unwrap_or(Duration::ZERO)
1518 .as_secs() as i64,
1519 );
1520 }
1521 }
1522 Err(error) => {
1523 let msg = format!("Unable to get metadata: {}", error.kind());
1524 directory_info.error_message = Some(msg.clone());
1525 log::warn!("{}", msg);
1526 continue;
1527 }
1528 }
1529
1530 let mut file_count = 0;
1532 let entries = match fs::read_dir(&dir_path) {
1533 Ok(entries) => entries,
1534 Err(error) => {
1535 let msg = format!("Unable to read subdir: {}", error.kind());
1536 directory_info.error_message = Some(msg.clone());
1537 log::warn!("{}", msg);
1538 continue;
1539 }
1540 };
1541 for entry in entries {
1542 directory_info.files.push(
1543 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1544 file_name: None,
1545 file_created: None,
1546 file_modified: None,
1547 file_size: None,
1548 error_message: None,
1549 },
1550 );
1551 let file_info = directory_info.files.last_mut().unwrap();
1553 let entry = match entry {
1554 Ok(entry) => entry,
1555 Err(error) => {
1556 let msg = format!("Unable to read file: {}", error.kind());
1557 file_info.error_message = Some(msg.clone());
1558 log::warn!("{}", msg);
1559 continue;
1560 }
1561 };
1562 let file_name = match entry.file_name().into_string() {
1563 Ok(file_name) => file_name,
1564 _ => {
1565 let msg = "Unable to convert file name to string".to_string();
1566 file_info.error_message = Some(msg.clone());
1567 log::warn!("{}", msg);
1568 continue;
1569 }
1570 };
1571 let metadata = match entry.metadata() {
1572 Ok(metadata) => metadata,
1573 Err(error) => {
1574 let msg = format!("Unable to read file metadata: {}", error.kind());
1575 file_info.file_name = Some(file_name);
1576 file_info.error_message = Some(msg.clone());
1577 log::warn!("{}", msg);
1578 continue;
1579 }
1580 };
1581
1582 if metadata.is_file() {
1584 file_count += 1;
1585
1586 file_info.file_name = Some(file_name);
1588 file_info.file_created = Some(
1589 metadata
1590 .created()
1591 .unwrap_or(UNIX_EPOCH)
1592 .duration_since(UNIX_EPOCH)
1593 .unwrap_or(Duration::ZERO)
1594 .as_secs() as i64,
1595 );
1596 file_info.file_modified = Some(
1597 metadata
1598 .modified()
1599 .unwrap_or(UNIX_EPOCH)
1600 .duration_since(UNIX_EPOCH)
1601 .unwrap_or(Duration::ZERO)
1602 .as_secs() as i64,
1603 );
1604 file_info.file_size = Some(metadata.len() as i64);
1605 } else {
1606 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1607 file_info.file_name = Some(file_name);
1608 file_info.error_message = Some(msg.clone());
1609 log::warn!("{}", msg);
1610 }
1611 }
1612
1613 directory_info.file_count = Some(file_count as i64);
1614 } else {
1615 directory_info.dir_exists = Some(false);
1616 }
1617
1618 directories_info.push(directory_info);
1620 }
1621
1622 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1623 Some(directories_info_json)
1624 } else {
1625 log::error!("Failed to serialize data directory info");
1626 None
1627 }
1628}
1629
1630fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1631 core::with_glean(|glean| {
1632 glean
1633 .health_metrics
1634 .data_directory_info
1635 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1636 glean.internal_pings.health.submit_sync(glean, Some(reason));
1637 });
1638}
1639
1640#[cfg(any(target_os = "android", target_os = "ios"))]
1642pub fn glean_enable_logging_to_fd(_fd: u64) {
1643 }
1645
1646#[allow(missing_docs)]
1647#[allow(clippy::all)]
1649mod ffi {
1650 use super::*;
1651 uniffi::include_scaffolding!("glean");
1652
1653 type CowString = Cow<'static, str>;
1654
1655 uniffi::custom_type!(CowString, String, {
1656 remote,
1657 lower: |s| s.into_owned(),
1658 try_lift: |s| Ok(Cow::from(s))
1659 });
1660
1661 type JsonValue = serde_json::Value;
1662
1663 uniffi::custom_type!(JsonValue, String, {
1664 remote,
1665 lower: |s| serde_json::to_string(&s).unwrap(),
1666 try_lift: |s| Ok(serde_json::from_str(&s)?)
1667 });
1668}
1669pub use ffi::*;
1670
1671#[cfg(test)]
1673#[path = "lib_unit_tests.rs"]
1674mod tests;