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);
119
120static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
124 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
125
126#[derive(Debug, Clone, MallocSizeOf)]
128pub struct InternalConfiguration {
129 pub upload_enabled: bool,
131 pub data_path: String,
133 pub application_id: String,
135 pub language_binding_name: String,
137 pub max_events: Option<u32>,
139 pub delay_ping_lifetime_io: bool,
141 pub app_build: String,
144 pub use_core_mps: bool,
146 pub trim_data_to_registered_pings: bool,
148 #[ignore_malloc_size_of = "external non-allocating type"]
151 pub log_level: Option<LevelFilter>,
152 pub rate_limit: Option<PingRateLimit>,
154 pub enable_event_timestamps: bool,
156 pub experimentation_id: Option<String>,
160 pub enable_internal_pings: bool,
162 pub ping_schedule: HashMap<String, Vec<String>>,
166
167 pub ping_lifetime_threshold: u64,
169 pub ping_lifetime_max_time: u64,
171 pub max_pending_pings_count: Option<u64>,
173 pub max_pending_pings_directory_size: Option<u64>,
175 pub session_mode: session::SessionMode,
177 pub session_sample_rate: f64,
179 pub session_inactivity_timeout_ms: u64,
182}
183
184#[derive(Debug, Clone, MallocSizeOf)]
186pub struct PingRateLimit {
187 pub seconds_per_interval: u64,
189 pub pings_per_interval: u32,
191}
192
193fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
195 dispatcher::launch(|| core::with_glean(callback));
196}
197
198fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
201 dispatcher::launch(|| core::with_glean_mut(callback));
202}
203
204fn block_on_dispatcher() {
208 dispatcher::block_on_queue()
209}
210
211pub fn get_awake_timestamp_ms() -> u64 {
213 const NANOS_PER_MILLI: u64 = 1_000_000;
214 zeitstempel::now_awake() / NANOS_PER_MILLI
215}
216
217pub fn get_timestamp_ms() -> u64 {
219 const NANOS_PER_MILLI: u64 = 1_000_000;
220 zeitstempel::now() / NANOS_PER_MILLI
221}
222
223struct State {
228 client_info: ClientInfoMetrics,
230
231 callbacks: Box<dyn OnGleanEvents>,
232}
233
234static STATE: OnceCell<Mutex<State>> = OnceCell::new();
238
239#[track_caller] fn global_state() -> &'static Mutex<State> {
244 STATE.get().unwrap()
245}
246
247#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
252 STATE.get()
253}
254
255fn setup_state(state: State) {
257 if STATE.get().is_none() {
267 if STATE.set(Mutex::new(state)).is_err() {
268 log::error!(
269 "Global Glean state object is initialized already. This probably happened concurrently."
270 );
271 }
272 } else {
273 let mut lock = STATE.get().unwrap().lock().unwrap();
277 *lock = state;
278 }
279}
280
281static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
284 OnceCell::new();
285
286fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
287 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
288}
289
290fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
291 let mut lock = event_listeners().lock().unwrap();
292 lock.insert(tag, listener);
293}
294
295fn unregister_event_listener(tag: String) {
296 let mut lock = event_listeners().lock().unwrap();
297 lock.remove(&tag);
298}
299
300#[derive(Debug)]
302pub enum CallbackError {
303 UnexpectedError,
305}
306
307impl fmt::Display for CallbackError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 write!(f, "Unexpected error")
310 }
311}
312
313impl std::error::Error for CallbackError {}
314
315impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
316 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
317 CallbackError::UnexpectedError
318 }
319}
320
321pub trait OnGleanEvents: Send {
325 fn initialize_finished(&self);
331
332 fn trigger_upload(&self) -> Result<(), CallbackError>;
337
338 fn start_metrics_ping_scheduler(&self) -> bool;
340
341 fn cancel_uploads(&self) -> Result<(), CallbackError>;
343
344 fn shutdown(&self) -> Result<(), CallbackError> {
351 Ok(())
353 }
354}
355
356pub trait GleanEventListener: Send {
359 fn on_event_recorded(&self, id: String);
361}
362
363pub fn glean_initialize(
372 cfg: InternalConfiguration,
373 client_info: ClientInfoMetrics,
374 callbacks: Box<dyn OnGleanEvents>,
375) {
376 initialize_inner(cfg, client_info, callbacks);
377}
378
379pub fn glean_shutdown() {
381 shutdown();
382}
383
384pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
389 let glean = match Glean::new_for_subprocess(&cfg, true) {
390 Ok(glean) => glean,
391 Err(err) => {
392 log::error!("Failed to initialize Glean: {}", err);
393 return false;
394 }
395 };
396 if core::setup_glean(glean).is_err() {
397 return false;
398 }
399 log::info!("Glean initialized for subprocess");
400 true
401}
402
403fn initialize_inner(
404 cfg: InternalConfiguration,
405 client_info: ClientInfoMetrics,
406 callbacks: Box<dyn OnGleanEvents>,
407) {
408 if was_initialize_called() {
409 log::error!("Glean should not be initialized multiple times");
410 return;
411 }
412
413 let init_handle = thread::spawn("glean.init", move || {
414 let upload_enabled = cfg.upload_enabled;
415 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
416
417 if let Some(level) = cfg.log_level {
419 log::set_max_level(level)
420 }
421
422 let data_path_str = cfg.data_path.clone();
423 let data_path = Path::new(&data_path_str);
424 let internal_pings_enabled = cfg.enable_internal_pings;
425 let dir_info = if !is_test_mode() && internal_pings_enabled {
426 collect_directory_info(Path::new(&data_path))
427 } else {
428 None
429 };
430
431 let glean = match Glean::new(cfg) {
432 Ok(glean) => glean,
433 Err(err) => {
434 log::error!("Failed to initialize Glean: {}", err);
435 return;
436 }
437 };
438 if core::setup_glean(glean).is_err() {
439 return;
440 }
441
442 log::info!("Glean initialized");
443
444 core::with_glean(|glean| {
445 glean.health_metrics.init_count.add_sync(glean, 1);
446 });
447
448 setup_state(State {
449 client_info,
450 callbacks,
451 });
452
453 let mut is_first_run = false;
454 let mut dirty_flag = false;
455 let mut pings_submitted = false;
456 core::with_glean_mut(|glean| {
457 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
460 if !debug_tag.is_empty() {
461 glean.set_debug_view_tag(&debug_tag);
462 }
463
464 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
467 if log_pigs {
468 glean.set_log_pings(log_pigs);
469 }
470
471 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
474 if !source_tags.is_empty() {
475 glean.set_source_tags(source_tags.to_vec());
476 }
477
478 dirty_flag = glean.is_dirty_flag_set();
483 glean.set_dirty_flag(false);
484
485 if dirty_flag {
489 glean.recover_session_on_dirty_flag();
490 }
491
492 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
495 for ping in pings.iter() {
496 glean.register_ping_type(ping);
497 }
498 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
499 for (ping, enabled) in pings.iter() {
500 glean.set_ping_enabled(ping, *enabled);
501 }
502
503 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
506 glean.update_attribution(attribution);
507 }
508 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
509 glean.update_distribution(distribution);
510 }
511
512 is_first_run = glean.is_first_run();
516 if is_first_run {
517 let state = global_state().lock().unwrap();
518 initialize_core_metrics(glean, &state.client_info);
519 }
520
521 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
523 });
524
525 {
526 let state = global_state().lock().unwrap();
527 if pings_submitted || !upload_enabled {
531 if let Err(e) = state.callbacks.trigger_upload() {
532 log::error!("Triggering upload failed. Error: {}", e);
533 }
534 }
535 }
536
537 core::with_glean(|glean| {
538 glean.start_metrics_ping_scheduler();
540 });
541
542 {
550 let state = global_state().lock().unwrap();
551
552 if state.callbacks.start_metrics_ping_scheduler() {
556 if let Err(e) = state.callbacks.trigger_upload() {
557 log::error!("Triggering upload failed. Error: {}", e);
558 }
559 }
560 }
561
562 core::with_glean_mut(|glean| {
563 let state = global_state().lock().unwrap();
564
565 if !is_first_run && dirty_flag {
569 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
575 if let Err(e) = state.callbacks.trigger_upload() {
576 log::error!("Triggering upload failed. Error: {}", e);
577 }
578 }
579 }
580
581 if !is_first_run {
585 glean.clear_application_lifetime_metrics();
586 initialize_core_metrics(glean, &state.client_info);
587 }
588 });
589
590 match dispatcher::flush_init() {
596 Ok(task_count) if task_count > 0 => {
597 core::with_glean(|glean| {
598 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
599 });
600 }
601 Ok(_) => {}
602 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
603 }
604
605 if !is_test_mode() && internal_pings_enabled {
606 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
609
610 let state = global_state().lock().unwrap();
611 if let Err(e) = state.callbacks.trigger_upload() {
612 log::error!("Triggering upload failed. Error: {}", e);
613 }
614 }
615 let state = global_state().lock().unwrap();
616 state.callbacks.initialize_finished();
617 })
618 .expect("Failed to spawn Glean's init thread");
619
620 INIT_HANDLES.lock().unwrap().push(init_handle);
622
623 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
626
627 if dispatcher::global::is_test_mode() {
630 join_init();
631 }
632}
633
634pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
638 use malloc_size_of::MallocSizeOf;
639 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
640}
641
642pub fn join_init() {
645 let mut handles = INIT_HANDLES.lock().unwrap();
646 for handle in handles.drain(..) {
647 handle.join().unwrap();
648 }
649}
650
651fn uploader_shutdown() {
659 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
660 let (tx, rx) = unbounded();
661
662 let handle = thread::spawn("glean.shutdown", move || {
663 let state = global_state().lock().unwrap();
664 if let Err(e) = state.callbacks.shutdown() {
665 log::error!("Shutdown callback failed: {e:?}");
666 }
667
668 let _ = tx.send(()).ok();
670 })
671 .expect("Unable to spawn thread to wait on shutdown");
672
673 let result = rx.recv_timeout(Duration::from_secs(30));
681
682 let stop_time = zeitstempel::now_awake();
683 core::with_glean(|glean| {
684 glean
685 .additional_metrics
686 .shutdown_wait
687 .set_stop_and_accumulate(glean, timer_id, stop_time);
688 });
689
690 if result.is_err() {
691 log::warn!("Waiting for upload failed. We're shutting down.");
692 } else {
693 let _ = handle.join().ok();
694 }
695}
696
697pub fn shutdown() {
699 if !was_initialize_called() {
709 log::warn!("Shutdown called before Glean is initialized");
710 if let Err(e) = dispatcher::kill() {
711 log::error!("Can't kill dispatcher thread: {:?}", e);
712 }
713 return;
714 }
715
716 if core::global_glean().is_none() {
718 log::warn!("Shutdown called before Glean is initialized. Waiting.");
719 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
727 }
728 if core::global_glean().is_none() {
730 log::warn!("Waiting for Glean initialization timed out. Exiting.");
731 if let Err(e) = dispatcher::kill() {
732 log::error!("Can't kill dispatcher thread: {:?}", e);
733 }
734 return;
735 }
736
737 crate::launch_with_glean_mut(|glean| {
739 glean.cancel_metrics_ping_scheduler();
740 glean.set_dirty_flag(false);
741 });
742
743 let timer_id = core::with_glean(|glean| {
750 glean
751 .additional_metrics
752 .shutdown_dispatcher_wait
753 .start_sync()
754 });
755 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
756
757 let stop_time = zeitstempel::now_awake();
759 core::with_glean(|glean| {
760 glean
761 .additional_metrics
762 .shutdown_dispatcher_wait
763 .set_stop_and_accumulate(glean, timer_id, stop_time);
764 });
765 if blocked.is_err() {
766 log::error!(
767 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
768 );
769 return;
770 }
771
772 if let Err(e) = dispatcher::shutdown() {
773 log::error!("Can't shutdown dispatcher thread: {:?}", e);
774 }
775
776 uploader_shutdown();
777
778 core::with_glean(|glean| {
780 if let Err(e) = glean.persist_ping_lifetime_data() {
781 log::info!("Can't persist ping lifetime data: {:?}", e);
782 }
783 });
784}
785
786pub fn glean_persist_ping_lifetime_data() {
793 crate::launch_with_glean(|glean| {
795 let _ = glean.persist_ping_lifetime_data();
796 });
797}
798
799fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
800 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
801 core_metrics::internal_metrics::app_display_version
802 .set_sync(glean, &client_info.app_display_version[..]);
803 core_metrics::internal_metrics::app_build_date
804 .set_sync(glean, Some(client_info.app_build_date.clone()));
805 if let Some(app_channel) = client_info.channel.as_ref() {
806 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
807 }
808
809 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
810 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
811
812 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
813 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
814 }
815 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
816 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
817 }
818 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
819 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
820 }
821 if let Some(device_model) = client_info.device_model.as_ref() {
822 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
823 }
824 if let Some(locale) = client_info.locale.as_ref() {
825 core_metrics::internal_metrics::locale.set_sync(glean, locale);
826 }
827}
828
829fn was_initialize_called() -> bool {
835 INITIALIZE_CALLED.load(Ordering::SeqCst)
836}
837
838#[no_mangle]
841pub extern "C" fn glean_enable_logging() {
842 #[cfg(target_os = "android")]
843 {
844 let _ = std::panic::catch_unwind(|| {
845 let filter = android_logger::FilterBuilder::new()
846 .filter_module("glean_ffi", log::LevelFilter::Debug)
847 .filter_module("glean_core", log::LevelFilter::Debug)
848 .filter_module("glean", log::LevelFilter::Debug)
849 .filter_module("glean_core::ffi", log::LevelFilter::Info)
850 .build();
851 android_logger::init_once(
852 android_logger::Config::default()
853 .with_max_level(log::LevelFilter::Debug)
854 .with_filter(filter)
855 .with_tag("libglean_ffi"),
856 );
857 log::trace!("Android logging should be hooked up!")
858 });
859 }
860
861 #[cfg(target_os = "ios")]
863 {
864 #[cfg(debug_assertions)]
867 let level = log::LevelFilter::Debug;
868 #[cfg(not(debug_assertions))]
869 let level = log::LevelFilter::Info;
870
871 let logger = oslog::OsLogger::new("org.mozilla.glean")
872 .level_filter(level)
873 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
875
876 match logger.init() {
877 Ok(_) => log::trace!("os_log should be hooked up!"),
878 Err(_) => log::warn!("os_log was already initialized"),
882 };
883 }
884
885 #[cfg(all(
889 not(target_os = "android"),
890 not(target_os = "ios"),
891 feature = "enable_env_logger"
892 ))]
893 {
894 match env_logger::try_init() {
895 Ok(_) => log::trace!("stdout logging should be hooked up!"),
896 Err(_) => log::warn!("stdout logging was already initialized"),
900 };
901 }
902}
903
904pub fn glean_set_upload_enabled(enabled: bool) {
909 if !was_initialize_called() {
910 return;
911 }
912
913 crate::launch_with_glean_mut(move |glean| {
914 let state = global_state().lock().unwrap();
915 let original_enabled = glean.is_upload_enabled();
916
917 if !enabled {
918 glean.cancel_metrics_ping_scheduler();
920 if let Err(e) = state.callbacks.cancel_uploads() {
922 log::error!("Canceling upload failed. Error: {}", e);
923 }
924 }
925
926 glean.set_upload_enabled(enabled);
927
928 if !original_enabled && enabled {
929 initialize_core_metrics(glean, &state.client_info);
930 }
931
932 if original_enabled && !enabled {
933 if let Err(e) = state.callbacks.trigger_upload() {
934 log::error!("Triggering upload failed. Error: {}", e);
935 }
936 }
937 })
938}
939
940pub fn glean_set_collection_enabled(enabled: bool) {
944 glean_set_upload_enabled(enabled)
945}
946
947pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
952 let ping = ping.clone();
953 if was_initialize_called() && core::global_glean().is_some() {
954 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
955 } else {
956 let m = &PRE_INIT_PING_ENABLED;
957 let mut lock = m.lock().unwrap();
958 lock.push((ping, enabled));
959 }
960}
961
962pub(crate) fn register_ping_type(ping: &PingType) {
964 if was_initialize_called() && core::global_glean().is_some() {
969 let ping = ping.clone();
970 crate::launch_with_glean_mut(move |glean| {
971 glean.register_ping_type(&ping);
972 })
973 } else {
974 let m = &PRE_INIT_PING_REGISTRATION;
979 let mut lock = m.lock().unwrap();
980 lock.push(ping.clone());
981 }
982}
983
984pub fn glean_get_registered_ping_names() -> Vec<String> {
990 block_on_dispatcher();
991 core::with_glean(|glean| {
992 glean
993 .get_registered_ping_names()
994 .iter()
995 .map(|ping| ping.to_string())
996 .collect()
997 })
998}
999
1000pub fn glean_set_experiment_active(
1006 experiment_id: String,
1007 branch: String,
1008 extra: HashMap<String, String>,
1009) {
1010 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1011}
1012
1013pub fn glean_set_experiment_inactive(experiment_id: String) {
1017 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1018}
1019
1020pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1024 block_on_dispatcher();
1025 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1026}
1027
1028pub fn glean_set_experimentation_id(experimentation_id: String) {
1032 launch_with_glean(move |glean| {
1033 glean
1034 .additional_metrics
1035 .experimentation_id
1036 .set(experimentation_id);
1037 });
1038}
1039
1040pub fn glean_test_get_experimentation_id() -> Option<String> {
1043 block_on_dispatcher();
1044 core::with_glean(|glean| glean.test_get_experimentation_id())
1045}
1046
1047pub fn glean_apply_server_knobs_config(json: String) {
1052 if json.is_empty() {
1055 return;
1056 }
1057
1058 match RemoteSettingsConfig::try_from(json) {
1059 Ok(cfg) => launch_with_glean(|glean| {
1060 glean.apply_server_knobs_config(cfg);
1061 }),
1062 Err(e) => {
1063 log::error!("Error setting metrics feature config: {:?}", e);
1064 }
1065 }
1066}
1067
1068pub fn glean_set_debug_view_tag(tag: String) -> bool {
1082 if was_initialize_called() && core::global_glean().is_some() {
1083 crate::launch_with_glean_mut(move |glean| {
1084 glean.set_debug_view_tag(&tag);
1085 });
1086 true
1087 } else {
1088 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1090 let mut lock = m.lock().unwrap();
1091 *lock = tag;
1092 true
1095 }
1096}
1097
1098pub fn glean_get_debug_view_tag() -> Option<String> {
1104 block_on_dispatcher();
1105 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1106}
1107
1108pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1120 if was_initialize_called() && core::global_glean().is_some() {
1121 crate::launch_with_glean_mut(|glean| {
1122 glean.set_source_tags(tags);
1123 });
1124 true
1125 } else {
1126 let m = &PRE_INIT_SOURCE_TAGS;
1128 let mut lock = m.lock().unwrap();
1129 *lock = tags;
1130 true
1133 }
1134}
1135
1136pub fn glean_set_log_pings(value: bool) {
1145 if was_initialize_called() && core::global_glean().is_some() {
1146 crate::launch_with_glean_mut(move |glean| {
1147 glean.set_log_pings(value);
1148 });
1149 } else {
1150 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1151 }
1152}
1153
1154pub fn glean_get_log_pings() -> bool {
1160 block_on_dispatcher();
1161 core::with_glean(|glean| glean.log_pings())
1162}
1163
1164pub fn glean_handle_client_active() {
1171 dispatcher::launch(|| {
1172 core::with_glean_mut(|glean| {
1173 glean.handle_client_active();
1174 });
1175
1176 let state = global_state().lock().unwrap();
1180 if let Err(e) = state.callbacks.trigger_upload() {
1181 log::error!("Triggering upload failed. Error: {}", e);
1182 }
1183 });
1184
1185 core_metrics::internal_metrics::baseline_duration.start();
1190}
1191
1192pub fn glean_handle_client_inactive() {
1199 core_metrics::internal_metrics::baseline_duration.stop();
1203
1204 dispatcher::launch(|| {
1205 core::with_glean_mut(|glean| {
1206 glean.handle_client_inactive();
1207 });
1208
1209 let state = global_state().lock().unwrap();
1213 if let Err(e) = state.callbacks.trigger_upload() {
1214 log::error!("Triggering upload failed. Error: {}", e);
1215 }
1216 })
1217}
1218
1219pub fn glean_session_start() {
1224 launch_with_glean_mut(|glean| {
1225 if glean.session_manager.mode == session::SessionMode::Manual {
1226 glean.session_start();
1227 }
1228 });
1229}
1230
1231pub fn glean_session_end(reason: Option<String>) {
1239 launch_with_glean_mut(move |glean| {
1240 if glean.session_manager.mode == session::SessionMode::Manual {
1241 glean.session_end(reason.as_deref());
1242 }
1243 });
1244}
1245
1246pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1248 dispatcher::launch(|| {
1249 let sent =
1250 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1251
1252 if sent {
1253 let state = global_state().lock().unwrap();
1254 if let Err(e) = state.callbacks.trigger_upload() {
1255 log::error!("Triggering upload failed. Error: {}", e);
1256 }
1257 }
1258 })
1259}
1260
1261pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1265 if !was_initialize_called() {
1266 return false;
1267 }
1268
1269 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1270 .unwrap_or(false)
1271}
1272
1273pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1280 register_event_listener(tag, listener);
1281}
1282
1283pub fn glean_unregister_event_listener(tag: String) {
1291 unregister_event_listener(tag);
1292}
1293
1294pub fn glean_set_test_mode(enabled: bool) {
1298 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1299}
1300
1301pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1305 if was_initialize_called() {
1306 join_init();
1308
1309 dispatcher::reset_dispatcher();
1310
1311 let has_storage = core::with_opt_glean(|glean| {
1314 glean
1316 .storage_opt()
1317 .map(|storage| storage.persist_ping_lifetime_data())
1318 .is_some()
1319 })
1320 .unwrap_or(false);
1321 if has_storage {
1322 uploader_shutdown();
1323 }
1324
1325 if core::global_glean().is_some() {
1326 core::with_glean_mut(|glean| {
1327 if clear_stores {
1328 glean.test_clear_all_stores()
1329 }
1330 glean.destroy_db()
1331 });
1332 }
1333
1334 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1336 } else if clear_stores {
1337 if let Some(data_path) = data_path {
1338 let _ = std::fs::remove_dir_all(data_path).ok();
1339 } else {
1340 log::warn!("Asked to clear stores before initialization, but no data path given.");
1341 }
1342 }
1343}
1344
1345pub fn glean_get_upload_task() -> PingUploadTask {
1347 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1348}
1349
1350pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1352 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1353}
1354
1355pub fn glean_set_dirty_flag(new_value: bool) {
1359 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1360}
1361
1362pub fn glean_update_attribution(attribution: AttributionMetrics) {
1365 if was_initialize_called() && core::global_glean().is_some() {
1366 core::with_glean(|glean| glean.update_attribution(attribution));
1367 } else {
1368 PRE_INIT_ATTRIBUTION
1369 .lock()
1370 .unwrap()
1371 .get_or_insert(Default::default())
1372 .update(attribution);
1373 }
1374}
1375
1376pub fn glean_test_get_attribution() -> AttributionMetrics {
1381 join_init();
1382 core::with_glean(|glean| glean.test_get_attribution())
1383}
1384
1385pub fn glean_update_distribution(distribution: DistributionMetrics) {
1388 if was_initialize_called() && core::global_glean().is_some() {
1389 core::with_glean(|glean| glean.update_distribution(distribution));
1390 } else {
1391 PRE_INIT_DISTRIBUTION
1392 .lock()
1393 .unwrap()
1394 .get_or_insert(Default::default())
1395 .update(distribution);
1396 }
1397}
1398
1399pub fn glean_test_get_distribution() -> DistributionMetrics {
1404 join_init();
1405 core::with_glean(|glean| glean.test_get_distribution())
1406}
1407
1408#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1409static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1410
1411#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1424pub fn glean_enable_logging_to_fd(fd: u64) {
1425 unsafe {
1431 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1436 if log::set_logger(logger).is_ok() {
1440 log::set_max_level(log::LevelFilter::Debug);
1441 }
1442 }
1443}
1444
1445fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1447 let subdirs = ["db", "events", "pending_pings"];
1449 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1450 DataDirectoryInfoObject::with_capacity(subdirs.len());
1451
1452 for subdir in subdirs.iter() {
1453 let dir_path = path.join(subdir);
1454
1455 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1457 dir_name: Some(subdir.to_string()),
1458 dir_exists: None,
1459 dir_created: None,
1460 dir_modified: None,
1461 file_count: None,
1462 files: Vec::new(),
1463 error_message: None,
1464 };
1465
1466 if dir_path.is_dir() {
1468 directory_info.dir_exists = Some(true);
1469
1470 match fs::metadata(&dir_path) {
1472 Ok(metadata) => {
1473 if let Ok(created) = metadata.created() {
1474 directory_info.dir_created = Some(
1475 created
1476 .duration_since(UNIX_EPOCH)
1477 .unwrap_or(Duration::ZERO)
1478 .as_secs() as i64,
1479 );
1480 }
1481 if let Ok(modified) = metadata.modified() {
1482 directory_info.dir_modified = Some(
1483 modified
1484 .duration_since(UNIX_EPOCH)
1485 .unwrap_or(Duration::ZERO)
1486 .as_secs() as i64,
1487 );
1488 }
1489 }
1490 Err(error) => {
1491 let msg = format!("Unable to get metadata: {}", error.kind());
1492 directory_info.error_message = Some(msg.clone());
1493 log::warn!("{}", msg);
1494 continue;
1495 }
1496 }
1497
1498 let mut file_count = 0;
1500 let entries = match fs::read_dir(&dir_path) {
1501 Ok(entries) => entries,
1502 Err(error) => {
1503 let msg = format!("Unable to read subdir: {}", error.kind());
1504 directory_info.error_message = Some(msg.clone());
1505 log::warn!("{}", msg);
1506 continue;
1507 }
1508 };
1509 for entry in entries {
1510 directory_info.files.push(
1511 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1512 file_name: None,
1513 file_created: None,
1514 file_modified: None,
1515 file_size: None,
1516 error_message: None,
1517 },
1518 );
1519 let file_info = directory_info.files.last_mut().unwrap();
1521 let entry = match entry {
1522 Ok(entry) => entry,
1523 Err(error) => {
1524 let msg = format!("Unable to read file: {}", error.kind());
1525 file_info.error_message = Some(msg.clone());
1526 log::warn!("{}", msg);
1527 continue;
1528 }
1529 };
1530 let file_name = match entry.file_name().into_string() {
1531 Ok(file_name) => file_name,
1532 _ => {
1533 let msg = "Unable to convert file name to string".to_string();
1534 file_info.error_message = Some(msg.clone());
1535 log::warn!("{}", msg);
1536 continue;
1537 }
1538 };
1539 let metadata = match entry.metadata() {
1540 Ok(metadata) => metadata,
1541 Err(error) => {
1542 let msg = format!("Unable to read file metadata: {}", error.kind());
1543 file_info.file_name = Some(file_name);
1544 file_info.error_message = Some(msg.clone());
1545 log::warn!("{}", msg);
1546 continue;
1547 }
1548 };
1549
1550 if metadata.is_file() {
1552 file_count += 1;
1553
1554 file_info.file_name = Some(file_name);
1556 file_info.file_created = Some(
1557 metadata
1558 .created()
1559 .unwrap_or(UNIX_EPOCH)
1560 .duration_since(UNIX_EPOCH)
1561 .unwrap_or(Duration::ZERO)
1562 .as_secs() as i64,
1563 );
1564 file_info.file_modified = Some(
1565 metadata
1566 .modified()
1567 .unwrap_or(UNIX_EPOCH)
1568 .duration_since(UNIX_EPOCH)
1569 .unwrap_or(Duration::ZERO)
1570 .as_secs() as i64,
1571 );
1572 file_info.file_size = Some(metadata.len() as i64);
1573 } else {
1574 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1575 file_info.file_name = Some(file_name);
1576 file_info.error_message = Some(msg.clone());
1577 log::warn!("{}", msg);
1578 }
1579 }
1580
1581 directory_info.file_count = Some(file_count as i64);
1582 } else {
1583 directory_info.dir_exists = Some(false);
1584 }
1585
1586 directories_info.push(directory_info);
1588 }
1589
1590 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1591 Some(directories_info_json)
1592 } else {
1593 log::error!("Failed to serialize data directory info");
1594 None
1595 }
1596}
1597
1598fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1599 core::with_glean(|glean| {
1600 glean
1601 .health_metrics
1602 .data_directory_info
1603 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1604 glean.internal_pings.health.submit_sync(glean, Some(reason));
1605 });
1606}
1607
1608#[cfg(any(target_os = "android", target_os = "ios"))]
1610pub fn glean_enable_logging_to_fd(_fd: u64) {
1611 }
1613
1614#[allow(missing_docs)]
1615#[allow(clippy::all)]
1617mod ffi {
1618 use super::*;
1619 uniffi::include_scaffolding!("glean");
1620
1621 type CowString = Cow<'static, str>;
1622
1623 uniffi::custom_type!(CowString, String, {
1624 remote,
1625 lower: |s| s.into_owned(),
1626 try_lift: |s| Ok(Cow::from(s))
1627 });
1628
1629 type JsonValue = serde_json::Value;
1630
1631 uniffi::custom_type!(JsonValue, String, {
1632 remote,
1633 lower: |s| serde_json::to_string(&s).unwrap(),
1634 try_lift: |s| Ok(serde_json::from_str(&s)?)
1635 });
1636}
1637pub use ffi::*;
1638
1639#[cfg(test)]
1641#[path = "lib_unit_tests.rs"]
1642mod tests;