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 coverage;
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 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::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
90
91const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
92const GLEAN_SCHEMA_VERSION: u32 = 1;
93const DEFAULT_MAX_EVENTS: u32 = 500;
94static KNOWN_CLIENT_ID: Lazy<Uuid> =
95 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
96
97pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
99pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
100
101static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
105
106static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
108static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
109static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
110
111static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
113static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
114
115static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
117static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
118
119static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
123 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
124
125#[derive(Debug, Clone, MallocSizeOf)]
127pub struct InternalConfiguration {
128 pub upload_enabled: bool,
130 pub data_path: String,
132 pub application_id: String,
134 pub language_binding_name: String,
136 pub max_events: Option<u32>,
138 pub delay_ping_lifetime_io: bool,
140 pub app_build: String,
143 pub use_core_mps: bool,
145 pub trim_data_to_registered_pings: bool,
147 #[ignore_malloc_size_of = "external non-allocating type"]
150 pub log_level: Option<LevelFilter>,
151 pub rate_limit: Option<PingRateLimit>,
153 pub enable_event_timestamps: bool,
155 pub experimentation_id: Option<String>,
159 pub enable_internal_pings: bool,
161 pub ping_schedule: HashMap<String, Vec<String>>,
165
166 pub ping_lifetime_threshold: u64,
168 pub ping_lifetime_max_time: u64,
170}
171
172#[derive(Debug, Clone, MallocSizeOf)]
174pub struct PingRateLimit {
175 pub seconds_per_interval: u64,
177 pub pings_per_interval: u32,
179}
180
181fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
183 dispatcher::launch(|| core::with_glean(callback));
184}
185
186fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
189 dispatcher::launch(|| core::with_glean_mut(callback));
190}
191
192fn block_on_dispatcher() {
196 dispatcher::block_on_queue()
197}
198
199pub fn get_awake_timestamp_ms() -> u64 {
201 const NANOS_PER_MILLI: u64 = 1_000_000;
202 zeitstempel::now_awake() / NANOS_PER_MILLI
203}
204
205pub fn get_timestamp_ms() -> u64 {
207 const NANOS_PER_MILLI: u64 = 1_000_000;
208 zeitstempel::now() / NANOS_PER_MILLI
209}
210
211struct State {
216 client_info: ClientInfoMetrics,
218
219 callbacks: Box<dyn OnGleanEvents>,
220}
221
222static STATE: OnceCell<Mutex<State>> = OnceCell::new();
226
227#[track_caller] fn global_state() -> &'static Mutex<State> {
232 STATE.get().unwrap()
233}
234
235#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
240 STATE.get()
241}
242
243fn setup_state(state: State) {
245 if STATE.get().is_none() {
255 if STATE.set(Mutex::new(state)).is_err() {
256 log::error!(
257 "Global Glean state object is initialized already. This probably happened concurrently."
258 );
259 }
260 } else {
261 let mut lock = STATE.get().unwrap().lock().unwrap();
265 *lock = state;
266 }
267}
268
269static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
272 OnceCell::new();
273
274fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
275 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
276}
277
278fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
279 let mut lock = event_listeners().lock().unwrap();
280 lock.insert(tag, listener);
281}
282
283fn unregister_event_listener(tag: String) {
284 let mut lock = event_listeners().lock().unwrap();
285 lock.remove(&tag);
286}
287
288#[derive(Debug)]
290pub enum CallbackError {
291 UnexpectedError,
293}
294
295impl fmt::Display for CallbackError {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 write!(f, "Unexpected error")
298 }
299}
300
301impl std::error::Error for CallbackError {}
302
303impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
304 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
305 CallbackError::UnexpectedError
306 }
307}
308
309pub trait OnGleanEvents: Send {
313 fn initialize_finished(&self);
319
320 fn trigger_upload(&self) -> Result<(), CallbackError>;
325
326 fn start_metrics_ping_scheduler(&self) -> bool;
328
329 fn cancel_uploads(&self) -> Result<(), CallbackError>;
331
332 fn shutdown(&self) -> Result<(), CallbackError> {
339 Ok(())
341 }
342}
343
344pub trait GleanEventListener: Send {
347 fn on_event_recorded(&self, id: String);
349}
350
351pub fn glean_initialize(
360 cfg: InternalConfiguration,
361 client_info: ClientInfoMetrics,
362 callbacks: Box<dyn OnGleanEvents>,
363) {
364 initialize_inner(cfg, client_info, callbacks);
365}
366
367pub fn glean_shutdown() {
369 shutdown();
370}
371
372pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
377 let glean = match Glean::new_for_subprocess(&cfg, true) {
378 Ok(glean) => glean,
379 Err(err) => {
380 log::error!("Failed to initialize Glean: {}", err);
381 return false;
382 }
383 };
384 if core::setup_glean(glean).is_err() {
385 return false;
386 }
387 log::info!("Glean initialized for subprocess");
388 true
389}
390
391fn initialize_inner(
392 cfg: InternalConfiguration,
393 client_info: ClientInfoMetrics,
394 callbacks: Box<dyn OnGleanEvents>,
395) {
396 if was_initialize_called() {
397 log::error!("Glean should not be initialized multiple times");
398 return;
399 }
400
401 let init_handle = thread::spawn("glean.init", move || {
402 let upload_enabled = cfg.upload_enabled;
403 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
404
405 if let Some(level) = cfg.log_level {
407 log::set_max_level(level)
408 }
409
410 let data_path_str = cfg.data_path.clone();
411 let data_path = Path::new(&data_path_str);
412 let internal_pings_enabled = cfg.enable_internal_pings;
413 let dir_info = if !is_test_mode() && internal_pings_enabled {
414 collect_directory_info(Path::new(&data_path))
415 } else {
416 None
417 };
418
419 let glean = match Glean::new(cfg) {
420 Ok(glean) => glean,
421 Err(err) => {
422 log::error!("Failed to initialize Glean: {}", err);
423 return;
424 }
425 };
426 if core::setup_glean(glean).is_err() {
427 return;
428 }
429
430 log::info!("Glean initialized");
431
432 core::with_glean(|glean| {
433 glean.health_metrics.init_count.add_sync(glean, 1);
434 });
435
436 setup_state(State {
437 client_info,
438 callbacks,
439 });
440
441 let mut is_first_run = false;
442 let mut dirty_flag = false;
443 let mut pings_submitted = false;
444 core::with_glean_mut(|glean| {
445 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
448 if !debug_tag.is_empty() {
449 glean.set_debug_view_tag(&debug_tag);
450 }
451
452 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
455 if log_pigs {
456 glean.set_log_pings(log_pigs);
457 }
458
459 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
462 if !source_tags.is_empty() {
463 glean.set_source_tags(source_tags.to_vec());
464 }
465
466 dirty_flag = glean.is_dirty_flag_set();
471 glean.set_dirty_flag(false);
472
473 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
476 for ping in pings.iter() {
477 glean.register_ping_type(ping);
478 }
479 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
480 for (ping, enabled) in pings.iter() {
481 glean.set_ping_enabled(ping, *enabled);
482 }
483
484 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
487 glean.update_attribution(attribution);
488 }
489 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
490 glean.update_distribution(distribution);
491 }
492
493 is_first_run = glean.is_first_run();
497 if is_first_run {
498 let state = global_state().lock().unwrap();
499 initialize_core_metrics(glean, &state.client_info);
500 }
501
502 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
504 });
505
506 {
507 let state = global_state().lock().unwrap();
508 if pings_submitted || !upload_enabled {
512 if let Err(e) = state.callbacks.trigger_upload() {
513 log::error!("Triggering upload failed. Error: {}", e);
514 }
515 }
516 }
517
518 core::with_glean(|glean| {
519 glean.start_metrics_ping_scheduler();
521 });
522
523 {
531 let state = global_state().lock().unwrap();
532
533 if state.callbacks.start_metrics_ping_scheduler() {
537 if let Err(e) = state.callbacks.trigger_upload() {
538 log::error!("Triggering upload failed. Error: {}", e);
539 }
540 }
541 }
542
543 core::with_glean_mut(|glean| {
544 let state = global_state().lock().unwrap();
545
546 if !is_first_run && dirty_flag {
550 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
556 if let Err(e) = state.callbacks.trigger_upload() {
557 log::error!("Triggering upload failed. Error: {}", e);
558 }
559 }
560 }
561
562 if !is_first_run {
566 glean.clear_application_lifetime_metrics();
567 initialize_core_metrics(glean, &state.client_info);
568 }
569 });
570
571 match dispatcher::flush_init() {
577 Ok(task_count) if task_count > 0 => {
578 core::with_glean(|glean| {
579 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
580 });
581 }
582 Ok(_) => {}
583 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
584 }
585
586 if !is_test_mode() && internal_pings_enabled {
587 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
590
591 let state = global_state().lock().unwrap();
592 if let Err(e) = state.callbacks.trigger_upload() {
593 log::error!("Triggering upload failed. Error: {}", e);
594 }
595 }
596 let state = global_state().lock().unwrap();
597 state.callbacks.initialize_finished();
598 })
599 .expect("Failed to spawn Glean's init thread");
600
601 INIT_HANDLES.lock().unwrap().push(init_handle);
603
604 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
607
608 if dispatcher::global::is_test_mode() {
611 join_init();
612 }
613}
614
615pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
619 use malloc_size_of::MallocSizeOf;
620 core::with_glean(|glean| glean.size_of(ops))
621}
622
623pub fn join_init() {
626 let mut handles = INIT_HANDLES.lock().unwrap();
627 for handle in handles.drain(..) {
628 handle.join().unwrap();
629 }
630}
631
632fn uploader_shutdown() {
640 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
641 let (tx, rx) = unbounded();
642
643 let handle = thread::spawn("glean.shutdown", move || {
644 let state = global_state().lock().unwrap();
645 if let Err(e) = state.callbacks.shutdown() {
646 log::error!("Shutdown callback failed: {e:?}");
647 }
648
649 let _ = tx.send(()).ok();
651 })
652 .expect("Unable to spawn thread to wait on shutdown");
653
654 let result = rx.recv_timeout(Duration::from_secs(30));
662
663 let stop_time = zeitstempel::now_awake();
664 core::with_glean(|glean| {
665 glean
666 .additional_metrics
667 .shutdown_wait
668 .set_stop_and_accumulate(glean, timer_id, stop_time);
669 });
670
671 if result.is_err() {
672 log::warn!("Waiting for upload failed. We're shutting down.");
673 } else {
674 let _ = handle.join().ok();
675 }
676}
677
678pub fn shutdown() {
680 if !was_initialize_called() {
690 log::warn!("Shutdown called before Glean is initialized");
691 if let Err(e) = dispatcher::kill() {
692 log::error!("Can't kill dispatcher thread: {:?}", e);
693 }
694 return;
695 }
696
697 if core::global_glean().is_none() {
699 log::warn!("Shutdown called before Glean is initialized. Waiting.");
700 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
708 }
709 if core::global_glean().is_none() {
711 log::warn!("Waiting for Glean initialization timed out. Exiting.");
712 if let Err(e) = dispatcher::kill() {
713 log::error!("Can't kill dispatcher thread: {:?}", e);
714 }
715 return;
716 }
717
718 crate::launch_with_glean_mut(|glean| {
720 glean.cancel_metrics_ping_scheduler();
721 glean.set_dirty_flag(false);
722 });
723
724 let timer_id = core::with_glean(|glean| {
731 glean
732 .additional_metrics
733 .shutdown_dispatcher_wait
734 .start_sync()
735 });
736 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
737
738 let stop_time = zeitstempel::now_awake();
740 core::with_glean(|glean| {
741 glean
742 .additional_metrics
743 .shutdown_dispatcher_wait
744 .set_stop_and_accumulate(glean, timer_id, stop_time);
745 });
746 if blocked.is_err() {
747 log::error!(
748 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
749 );
750 return;
751 }
752
753 if let Err(e) = dispatcher::shutdown() {
754 log::error!("Can't shutdown dispatcher thread: {:?}", e);
755 }
756
757 uploader_shutdown();
758
759 core::with_glean(|glean| {
761 if let Err(e) = glean.persist_ping_lifetime_data() {
762 log::info!("Can't persist ping lifetime data: {:?}", e);
763 }
764 });
765}
766
767pub fn glean_persist_ping_lifetime_data() {
774 crate::launch_with_glean(|glean| {
776 let _ = glean.persist_ping_lifetime_data();
777 });
778}
779
780fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
781 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
782 core_metrics::internal_metrics::app_display_version
783 .set_sync(glean, &client_info.app_display_version[..]);
784 core_metrics::internal_metrics::app_build_date
785 .set_sync(glean, Some(client_info.app_build_date.clone()));
786 if let Some(app_channel) = client_info.channel.as_ref() {
787 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
788 }
789
790 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
791 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
792
793 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
794 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
795 }
796 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
797 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
798 }
799 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
800 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
801 }
802 if let Some(device_model) = client_info.device_model.as_ref() {
803 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
804 }
805 if let Some(locale) = client_info.locale.as_ref() {
806 core_metrics::internal_metrics::locale.set_sync(glean, locale);
807 }
808}
809
810fn was_initialize_called() -> bool {
816 INITIALIZE_CALLED.load(Ordering::SeqCst)
817}
818
819#[no_mangle]
822pub extern "C" fn glean_enable_logging() {
823 #[cfg(target_os = "android")]
824 {
825 let _ = std::panic::catch_unwind(|| {
826 let filter = android_logger::FilterBuilder::new()
827 .filter_module("glean_ffi", log::LevelFilter::Debug)
828 .filter_module("glean_core", log::LevelFilter::Debug)
829 .filter_module("glean", log::LevelFilter::Debug)
830 .filter_module("glean_core::ffi", log::LevelFilter::Info)
831 .build();
832 android_logger::init_once(
833 android_logger::Config::default()
834 .with_max_level(log::LevelFilter::Debug)
835 .with_filter(filter)
836 .with_tag("libglean_ffi"),
837 );
838 log::trace!("Android logging should be hooked up!")
839 });
840 }
841
842 #[cfg(target_os = "ios")]
844 {
845 #[cfg(debug_assertions)]
848 let level = log::LevelFilter::Debug;
849 #[cfg(not(debug_assertions))]
850 let level = log::LevelFilter::Info;
851
852 let logger = oslog::OsLogger::new("org.mozilla.glean")
853 .level_filter(level)
854 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
856
857 match logger.init() {
858 Ok(_) => log::trace!("os_log should be hooked up!"),
859 Err(_) => log::warn!("os_log was already initialized"),
863 };
864 }
865
866 #[cfg(all(
870 not(target_os = "android"),
871 not(target_os = "ios"),
872 feature = "enable_env_logger"
873 ))]
874 {
875 match env_logger::try_init() {
876 Ok(_) => log::trace!("stdout logging should be hooked up!"),
877 Err(_) => log::warn!("stdout logging was already initialized"),
881 };
882 }
883}
884
885pub fn glean_set_upload_enabled(enabled: bool) {
890 if !was_initialize_called() {
891 return;
892 }
893
894 crate::launch_with_glean_mut(move |glean| {
895 let state = global_state().lock().unwrap();
896 let original_enabled = glean.is_upload_enabled();
897
898 if !enabled {
899 glean.cancel_metrics_ping_scheduler();
901 if let Err(e) = state.callbacks.cancel_uploads() {
903 log::error!("Canceling upload failed. Error: {}", e);
904 }
905 }
906
907 glean.set_upload_enabled(enabled);
908
909 if !original_enabled && enabled {
910 initialize_core_metrics(glean, &state.client_info);
911 }
912
913 if original_enabled && !enabled {
914 if let Err(e) = state.callbacks.trigger_upload() {
915 log::error!("Triggering upload failed. Error: {}", e);
916 }
917 }
918 })
919}
920
921pub fn glean_set_collection_enabled(enabled: bool) {
925 glean_set_upload_enabled(enabled)
926}
927
928pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
933 let ping = ping.clone();
934 if was_initialize_called() && core::global_glean().is_some() {
935 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
936 } else {
937 let m = &PRE_INIT_PING_ENABLED;
938 let mut lock = m.lock().unwrap();
939 lock.push((ping, enabled));
940 }
941}
942
943pub(crate) fn register_ping_type(ping: &PingType) {
945 if was_initialize_called() && core::global_glean().is_some() {
950 let ping = ping.clone();
951 crate::launch_with_glean_mut(move |glean| {
952 glean.register_ping_type(&ping);
953 })
954 } else {
955 let m = &PRE_INIT_PING_REGISTRATION;
960 let mut lock = m.lock().unwrap();
961 lock.push(ping.clone());
962 }
963}
964
965pub fn glean_get_registered_ping_names() -> Vec<String> {
971 block_on_dispatcher();
972 core::with_glean(|glean| {
973 glean
974 .get_registered_ping_names()
975 .iter()
976 .map(|ping| ping.to_string())
977 .collect()
978 })
979}
980
981pub fn glean_set_experiment_active(
987 experiment_id: String,
988 branch: String,
989 extra: HashMap<String, String>,
990) {
991 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
992}
993
994pub fn glean_set_experiment_inactive(experiment_id: String) {
998 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
999}
1000
1001pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1005 block_on_dispatcher();
1006 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1007}
1008
1009pub fn glean_set_experimentation_id(experimentation_id: String) {
1013 launch_with_glean(move |glean| {
1014 glean
1015 .additional_metrics
1016 .experimentation_id
1017 .set(experimentation_id);
1018 });
1019}
1020
1021pub fn glean_test_get_experimentation_id() -> Option<String> {
1024 block_on_dispatcher();
1025 core::with_glean(|glean| glean.test_get_experimentation_id())
1026}
1027
1028pub fn glean_apply_server_knobs_config(json: String) {
1033 if json.is_empty() {
1036 return;
1037 }
1038
1039 match RemoteSettingsConfig::try_from(json) {
1040 Ok(cfg) => launch_with_glean(|glean| {
1041 glean.apply_server_knobs_config(cfg);
1042 }),
1043 Err(e) => {
1044 log::error!("Error setting metrics feature config: {:?}", e);
1045 }
1046 }
1047}
1048
1049pub fn glean_set_debug_view_tag(tag: String) -> bool {
1063 if was_initialize_called() && core::global_glean().is_some() {
1064 crate::launch_with_glean_mut(move |glean| {
1065 glean.set_debug_view_tag(&tag);
1066 });
1067 true
1068 } else {
1069 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1071 let mut lock = m.lock().unwrap();
1072 *lock = tag;
1073 true
1076 }
1077}
1078
1079pub fn glean_get_debug_view_tag() -> Option<String> {
1085 block_on_dispatcher();
1086 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1087}
1088
1089pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1101 if was_initialize_called() && core::global_glean().is_some() {
1102 crate::launch_with_glean_mut(|glean| {
1103 glean.set_source_tags(tags);
1104 });
1105 true
1106 } else {
1107 let m = &PRE_INIT_SOURCE_TAGS;
1109 let mut lock = m.lock().unwrap();
1110 *lock = tags;
1111 true
1114 }
1115}
1116
1117pub fn glean_set_log_pings(value: bool) {
1126 if was_initialize_called() && core::global_glean().is_some() {
1127 crate::launch_with_glean_mut(move |glean| {
1128 glean.set_log_pings(value);
1129 });
1130 } else {
1131 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1132 }
1133}
1134
1135pub fn glean_get_log_pings() -> bool {
1141 block_on_dispatcher();
1142 core::with_glean(|glean| glean.log_pings())
1143}
1144
1145pub fn glean_handle_client_active() {
1152 dispatcher::launch(|| {
1153 core::with_glean_mut(|glean| {
1154 glean.handle_client_active();
1155 });
1156
1157 let state = global_state().lock().unwrap();
1161 if let Err(e) = state.callbacks.trigger_upload() {
1162 log::error!("Triggering upload failed. Error: {}", e);
1163 }
1164 });
1165
1166 core_metrics::internal_metrics::baseline_duration.start();
1171}
1172
1173pub fn glean_handle_client_inactive() {
1180 core_metrics::internal_metrics::baseline_duration.stop();
1184
1185 dispatcher::launch(|| {
1186 core::with_glean_mut(|glean| {
1187 glean.handle_client_inactive();
1188 });
1189
1190 let state = global_state().lock().unwrap();
1194 if let Err(e) = state.callbacks.trigger_upload() {
1195 log::error!("Triggering upload failed. Error: {}", e);
1196 }
1197 })
1198}
1199
1200pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1202 dispatcher::launch(|| {
1203 let sent =
1204 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1205
1206 if sent {
1207 let state = global_state().lock().unwrap();
1208 if let Err(e) = state.callbacks.trigger_upload() {
1209 log::error!("Triggering upload failed. Error: {}", e);
1210 }
1211 }
1212 })
1213}
1214
1215pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1219 if !was_initialize_called() {
1220 return false;
1221 }
1222
1223 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1224 .unwrap_or(false)
1225}
1226
1227pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1234 register_event_listener(tag, listener);
1235}
1236
1237pub fn glean_unregister_event_listener(tag: String) {
1245 unregister_event_listener(tag);
1246}
1247
1248pub fn glean_set_test_mode(enabled: bool) {
1252 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1253}
1254
1255pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1259 if was_initialize_called() {
1260 join_init();
1262
1263 dispatcher::reset_dispatcher();
1264
1265 let has_storage = core::with_opt_glean(|glean| {
1268 glean
1270 .storage_opt()
1271 .map(|storage| storage.persist_ping_lifetime_data())
1272 .is_some()
1273 })
1274 .unwrap_or(false);
1275 if has_storage {
1276 uploader_shutdown();
1277 }
1278
1279 if core::global_glean().is_some() {
1280 core::with_glean_mut(|glean| {
1281 if clear_stores {
1282 glean.test_clear_all_stores()
1283 }
1284 glean.destroy_db()
1285 });
1286 }
1287
1288 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1290 } else if clear_stores {
1291 if let Some(data_path) = data_path {
1292 let _ = std::fs::remove_dir_all(data_path).ok();
1293 } else {
1294 log::warn!("Asked to clear stores before initialization, but no data path given.");
1295 }
1296 }
1297}
1298
1299pub fn glean_get_upload_task() -> PingUploadTask {
1301 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1302}
1303
1304pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1306 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1307}
1308
1309pub fn glean_set_dirty_flag(new_value: bool) {
1313 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1314}
1315
1316pub fn glean_update_attribution(attribution: AttributionMetrics) {
1319 if was_initialize_called() && core::global_glean().is_some() {
1320 core::with_glean(|glean| glean.update_attribution(attribution));
1321 } else {
1322 PRE_INIT_ATTRIBUTION
1323 .lock()
1324 .unwrap()
1325 .get_or_insert(Default::default())
1326 .update(attribution);
1327 }
1328}
1329
1330pub fn glean_test_get_attribution() -> AttributionMetrics {
1335 join_init();
1336 core::with_glean(|glean| glean.test_get_attribution())
1337}
1338
1339pub fn glean_update_distribution(distribution: DistributionMetrics) {
1342 if was_initialize_called() && core::global_glean().is_some() {
1343 core::with_glean(|glean| glean.update_distribution(distribution));
1344 } else {
1345 PRE_INIT_DISTRIBUTION
1346 .lock()
1347 .unwrap()
1348 .get_or_insert(Default::default())
1349 .update(distribution);
1350 }
1351}
1352
1353pub fn glean_test_get_distribution() -> DistributionMetrics {
1358 join_init();
1359 core::with_glean(|glean| glean.test_get_distribution())
1360}
1361
1362#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1363static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1364
1365#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1378pub fn glean_enable_logging_to_fd(fd: u64) {
1379 unsafe {
1385 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1390 if log::set_logger(logger).is_ok() {
1394 log::set_max_level(log::LevelFilter::Debug);
1395 }
1396 }
1397}
1398
1399fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1401 let subdirs = ["db", "events", "pending_pings"];
1403 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1404 DataDirectoryInfoObject::with_capacity(subdirs.len());
1405
1406 for subdir in subdirs.iter() {
1407 let dir_path = path.join(subdir);
1408
1409 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1411 dir_name: Some(subdir.to_string()),
1412 dir_exists: None,
1413 dir_created: None,
1414 dir_modified: None,
1415 file_count: None,
1416 files: Vec::new(),
1417 error_message: None,
1418 };
1419
1420 if dir_path.is_dir() {
1422 directory_info.dir_exists = Some(true);
1423
1424 match fs::metadata(&dir_path) {
1426 Ok(metadata) => {
1427 if let Ok(created) = metadata.created() {
1428 directory_info.dir_created = Some(
1429 created
1430 .duration_since(UNIX_EPOCH)
1431 .unwrap_or(Duration::ZERO)
1432 .as_secs() as i64,
1433 );
1434 }
1435 if let Ok(modified) = metadata.modified() {
1436 directory_info.dir_modified = Some(
1437 modified
1438 .duration_since(UNIX_EPOCH)
1439 .unwrap_or(Duration::ZERO)
1440 .as_secs() as i64,
1441 );
1442 }
1443 }
1444 Err(error) => {
1445 let msg = format!("Unable to get metadata: {}", error.kind());
1446 directory_info.error_message = Some(msg.clone());
1447 log::warn!("{}", msg);
1448 continue;
1449 }
1450 }
1451
1452 let mut file_count = 0;
1454 let entries = match fs::read_dir(&dir_path) {
1455 Ok(entries) => entries,
1456 Err(error) => {
1457 let msg = format!("Unable to read subdir: {}", error.kind());
1458 directory_info.error_message = Some(msg.clone());
1459 log::warn!("{}", msg);
1460 continue;
1461 }
1462 };
1463 for entry in entries {
1464 directory_info.files.push(
1465 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1466 file_name: None,
1467 file_created: None,
1468 file_modified: None,
1469 file_size: None,
1470 error_message: None,
1471 },
1472 );
1473 let file_info = directory_info.files.last_mut().unwrap();
1475 let entry = match entry {
1476 Ok(entry) => entry,
1477 Err(error) => {
1478 let msg = format!("Unable to read file: {}", error.kind());
1479 file_info.error_message = Some(msg.clone());
1480 log::warn!("{}", msg);
1481 continue;
1482 }
1483 };
1484 let file_name = match entry.file_name().into_string() {
1485 Ok(file_name) => file_name,
1486 _ => {
1487 let msg = "Unable to convert file name to string".to_string();
1488 file_info.error_message = Some(msg.clone());
1489 log::warn!("{}", msg);
1490 continue;
1491 }
1492 };
1493 let metadata = match entry.metadata() {
1494 Ok(metadata) => metadata,
1495 Err(error) => {
1496 let msg = format!("Unable to read file metadata: {}", error.kind());
1497 file_info.file_name = Some(file_name);
1498 file_info.error_message = Some(msg.clone());
1499 log::warn!("{}", msg);
1500 continue;
1501 }
1502 };
1503
1504 if metadata.is_file() {
1506 file_count += 1;
1507
1508 file_info.file_name = Some(file_name);
1510 file_info.file_created = Some(
1511 metadata
1512 .created()
1513 .unwrap_or(UNIX_EPOCH)
1514 .duration_since(UNIX_EPOCH)
1515 .unwrap_or(Duration::ZERO)
1516 .as_secs() as i64,
1517 );
1518 file_info.file_modified = Some(
1519 metadata
1520 .modified()
1521 .unwrap_or(UNIX_EPOCH)
1522 .duration_since(UNIX_EPOCH)
1523 .unwrap_or(Duration::ZERO)
1524 .as_secs() as i64,
1525 );
1526 file_info.file_size = Some(metadata.len() as i64);
1527 } else {
1528 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1529 file_info.file_name = Some(file_name);
1530 file_info.error_message = Some(msg.clone());
1531 log::warn!("{}", msg);
1532 }
1533 }
1534
1535 directory_info.file_count = Some(file_count as i64);
1536 } else {
1537 directory_info.dir_exists = Some(false);
1538 }
1539
1540 directories_info.push(directory_info);
1542 }
1543
1544 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1545 Some(directories_info_json)
1546 } else {
1547 log::error!("Failed to serialize data directory info");
1548 None
1549 }
1550}
1551
1552fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1553 core::with_glean(|glean| {
1554 glean
1555 .health_metrics
1556 .data_directory_info
1557 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1558 glean.internal_pings.health.submit_sync(glean, Some(reason));
1559 });
1560}
1561
1562#[cfg(any(target_os = "android", target_os = "ios"))]
1564pub fn glean_enable_logging_to_fd(_fd: u64) {
1565 }
1567
1568#[allow(missing_docs)]
1569#[allow(clippy::all)]
1571mod ffi {
1572 use super::*;
1573 uniffi::include_scaffolding!("glean");
1574
1575 type CowString = Cow<'static, str>;
1576
1577 uniffi::custom_type!(CowString, String, {
1578 remote,
1579 lower: |s| s.into_owned(),
1580 try_lift: |s| Ok(Cow::from(s))
1581 });
1582
1583 type JsonValue = serde_json::Value;
1584
1585 uniffi::custom_type!(JsonValue, String, {
1586 remote,
1587 lower: |s| serde_json::to_string(&s).unwrap(),
1588 try_lift: |s| Ok(serde_json::from_str(&s)?)
1589 });
1590}
1591pub use ffi::*;
1592
1593#[cfg(test)]
1595#[path = "lib_unit_tests.rs"]
1596mod tests;