1#![allow(clippy::doc_overindented_list_items)]
6#![allow(clippy::large_const_arrays)] #![allow(clippy::significant_drop_in_scrutinee)]
8#![allow(clippy::uninlined_format_args)]
9#![deny(rustdoc::broken_intra_doc_links)]
10#![deny(missing_docs)]
11
12use std::borrow::Cow;
21use std::collections::HashMap;
22use std::path::Path;
23use std::sync::atomic::{AtomicBool, Ordering};
24use std::sync::{Arc, Mutex};
25use std::time::{Duration, UNIX_EPOCH};
26use std::{fmt, fs};
27
28use crossbeam_channel::unbounded;
29use log::LevelFilter;
30use malloc_size_of_derive::MallocSizeOf;
31use once_cell::sync::{Lazy, OnceCell};
32use uuid::Uuid;
33
34use metrics::RemoteSettingsConfig;
35
36mod common_metric_data;
37mod core;
38mod core_metrics;
39mod database;
40mod debug;
41#[cfg(feature = "benchmark")]
42#[doc(hidden)]
43pub mod dispatcher;
44#[cfg(not(feature = "benchmark"))]
45mod dispatcher;
46mod error;
47mod error_recording;
48mod event_database;
49mod glean_metrics;
50mod histogram;
51mod internal_metrics;
52mod internal_pings;
53pub mod metrics;
54pub mod ping;
55mod scheduler;
56pub(crate) mod session;
57pub mod storage;
58mod system;
59#[doc(hidden)]
60pub mod thread;
61pub mod traits;
62pub mod upload;
63mod util;
64
65#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
66mod fd_logger;
67
68pub use crate::common_metric_data::{CommonMetricData, Lifetime, MetricLabel};
69pub use crate::core::Glean;
70pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
71use crate::dispatcher::is_test_mode;
72pub use crate::error::{Error, ErrorKind, Result};
73pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
74pub use crate::histogram::HistogramType;
75use crate::internal_metrics::DataDirectoryInfoObject;
76pub use crate::metrics::labeled::{
77 AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
78 LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
79 LabeledTimingDistribution,
80};
81pub use crate::metrics::{
82 BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
83 DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
84 LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
85 MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
86 Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
87 TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
88 UrlMetric, UuidMetric,
89};
90pub use crate::session::{SessionManager, SessionMetadata, SessionMode};
91pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
92
93const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
94const GLEAN_SCHEMA_VERSION: u32 = 1;
95const DEFAULT_MAX_EVENTS: u32 = 500;
96static KNOWN_CLIENT_ID: Lazy<Uuid> =
97 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
98
99pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
101pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
102
103static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
107
108static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
110static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
111static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
112
113static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
115static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
116
117static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
119static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
120static PRE_INIT_ATTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
121static PRE_INIT_DISTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
122
123static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
127 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
128
129#[derive(Debug, Clone, MallocSizeOf)]
131pub struct InternalConfiguration {
132 pub upload_enabled: bool,
134 pub data_path: String,
136 pub application_id: String,
138 pub language_binding_name: String,
140 pub max_events: Option<u32>,
142 pub delay_ping_lifetime_io: bool,
144 pub app_build: String,
147 pub use_core_mps: bool,
149 pub trim_data_to_registered_pings: bool,
151 #[ignore_malloc_size_of = "external non-allocating type"]
154 pub log_level: Option<LevelFilter>,
155 pub rate_limit: Option<PingRateLimit>,
157 pub enable_event_timestamps: bool,
159 pub experimentation_id: Option<String>,
163 pub enable_internal_pings: bool,
165 pub ping_schedule: HashMap<String, Vec<String>>,
169
170 pub ping_lifetime_threshold: u64,
172 pub ping_lifetime_max_time: u64,
174 pub max_pending_pings_count: Option<u64>,
176 pub max_pending_pings_directory_size: Option<u64>,
178 pub session_mode: session::SessionMode,
180 pub session_sample_rate: f64,
182 pub session_inactivity_timeout_ms: u64,
185 pub events_ping_acceleration_factor: Option<u32>,
187}
188
189#[derive(Debug, Clone, MallocSizeOf)]
191pub struct PingRateLimit {
192 pub seconds_per_interval: u64,
194 pub pings_per_interval: u32,
196}
197
198fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
200 dispatcher::launch(|| core::with_glean(callback));
201}
202
203fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
206 dispatcher::launch(|| core::with_glean_mut(callback));
207}
208
209fn block_on_dispatcher() {
213 dispatcher::block_on_queue()
214}
215
216pub fn get_awake_timestamp_ms() -> u64 {
218 const NANOS_PER_MILLI: u64 = 1_000_000;
219 zeitstempel::now_awake() / NANOS_PER_MILLI
220}
221
222pub fn get_timestamp_ms() -> u64 {
224 const NANOS_PER_MILLI: u64 = 1_000_000;
225 zeitstempel::now() / NANOS_PER_MILLI
226}
227
228struct State {
233 client_info: ClientInfoMetrics,
235
236 callbacks: Box<dyn OnGleanEvents>,
237}
238
239static STATE: OnceCell<Mutex<State>> = OnceCell::new();
243
244#[track_caller] fn global_state() -> &'static Mutex<State> {
249 STATE.get().unwrap()
250}
251
252#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
257 STATE.get()
258}
259
260fn setup_state(state: State) {
262 if STATE.get().is_none() {
272 if STATE.set(Mutex::new(state)).is_err() {
273 log::error!(
274 "Global Glean state object is initialized already. This probably happened concurrently."
275 );
276 }
277 } else {
278 let mut lock = STATE.get().unwrap().lock().unwrap();
282 *lock = state;
283 }
284}
285
286static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
289 OnceCell::new();
290
291fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
292 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
293}
294
295fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
296 let mut lock = event_listeners().lock().unwrap();
297 lock.insert(tag, listener);
298}
299
300fn unregister_event_listener(tag: String) {
301 let mut lock = event_listeners().lock().unwrap();
302 lock.remove(&tag);
303}
304
305#[derive(Debug)]
307pub enum CallbackError {
308 UnexpectedError,
310}
311
312impl fmt::Display for CallbackError {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 write!(f, "Unexpected error")
315 }
316}
317
318impl std::error::Error for CallbackError {}
319
320impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
321 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
322 CallbackError::UnexpectedError
323 }
324}
325
326pub trait OnGleanEvents: Send {
330 fn initialize_finished(&self);
336
337 fn trigger_upload(&self) -> Result<(), CallbackError>;
342
343 fn start_metrics_ping_scheduler(&self) -> bool;
345
346 fn cancel_uploads(&self) -> Result<(), CallbackError>;
348
349 fn shutdown(&self) -> Result<(), CallbackError> {
356 Ok(())
358 }
359}
360
361pub trait GleanEventListener: Send {
364 fn on_event_recorded(&self, id: String);
366}
367
368pub fn glean_initialize(
377 cfg: InternalConfiguration,
378 client_info: ClientInfoMetrics,
379 callbacks: Box<dyn OnGleanEvents>,
380) {
381 initialize_inner(cfg, client_info, callbacks);
382}
383
384pub fn glean_shutdown() {
386 shutdown();
387}
388
389pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
394 let glean = match Glean::new_for_subprocess(&cfg, true) {
395 Ok(glean) => glean,
396 Err(err) => {
397 log::error!("Failed to initialize Glean: {}", err);
398 return false;
399 }
400 };
401 if core::setup_glean(glean).is_err() {
402 return false;
403 }
404 log::info!("Glean initialized for subprocess");
405 true
406}
407
408fn initialize_inner(
409 cfg: InternalConfiguration,
410 client_info: ClientInfoMetrics,
411 callbacks: Box<dyn OnGleanEvents>,
412) {
413 if was_initialize_called() {
414 log::error!("Glean should not be initialized multiple times");
415 return;
416 }
417
418 let init_handle = thread::spawn("glean.init", move || {
419 let upload_enabled = cfg.upload_enabled;
420 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
421
422 if let Some(level) = cfg.log_level {
424 log::set_max_level(level)
425 }
426
427 let data_path_str = cfg.data_path.clone();
428 let data_path = Path::new(&data_path_str);
429 let internal_pings_enabled = cfg.enable_internal_pings;
430 let dir_info = if !is_test_mode() && internal_pings_enabled {
431 collect_directory_info(Path::new(&data_path))
432 } else {
433 None
434 };
435
436 let glean = match Glean::new(cfg) {
437 Ok(glean) => glean,
438 Err(err) => {
439 log::error!("Failed to initialize Glean: {}", err);
440 return;
441 }
442 };
443 if core::setup_glean(glean).is_err() {
444 return;
445 }
446
447 log::info!("Glean initialized");
448
449 core::with_glean(|glean| {
450 glean.health_metrics.init_count.add_sync(glean, 1);
451 });
452
453 setup_state(State {
454 client_info,
455 callbacks,
456 });
457
458 let mut is_first_run = false;
459 let mut dirty_flag = false;
460 let mut pings_submitted = false;
461 core::with_glean_mut(|glean| {
462 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
465 if !debug_tag.is_empty() {
466 glean.set_debug_view_tag(&debug_tag);
467 }
468
469 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
472 if log_pigs {
473 glean.set_log_pings(log_pigs);
474 }
475
476 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
479 if !source_tags.is_empty() {
480 glean.set_source_tags(source_tags.to_vec());
481 }
482
483 dirty_flag = glean.is_dirty_flag_set();
488 glean.set_dirty_flag(false);
489
490 if dirty_flag {
494 glean.recover_session_on_dirty_flag();
495 }
496
497 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
500 for ping in pings.iter() {
501 glean.register_ping_type(ping);
502 }
503 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
504 for (ping, enabled) in pings.iter() {
505 glean.set_ping_enabled(ping, *enabled);
506 }
507
508 let clear_attribution = PRE_INIT_ATTRIBUTION_CLEARED.load(Ordering::SeqCst);
511 if clear_attribution {
512 glean.clear_attribution();
513 }
514 let clear_distribution = PRE_INIT_DISTRIBUTION_CLEARED.load(Ordering::SeqCst);
515 if clear_distribution {
516 glean.clear_distribution();
517 }
518 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
519 glean.update_attribution(attribution);
520 }
521 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
522 glean.update_distribution(distribution);
523 }
524
525 is_first_run = glean.is_first_run();
529 if is_first_run {
530 let state = global_state().lock().unwrap();
531 initialize_core_metrics(glean, &state.client_info);
532 }
533
534 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
536 });
537
538 {
539 let state = global_state().lock().unwrap();
540 if pings_submitted || !upload_enabled {
544 if let Err(e) = state.callbacks.trigger_upload() {
545 log::error!("Triggering upload failed. Error: {}", e);
546 }
547 }
548 }
549
550 core::with_glean(|glean| {
551 glean.start_metrics_ping_scheduler();
553 });
554
555 {
563 let state = global_state().lock().unwrap();
564
565 if state.callbacks.start_metrics_ping_scheduler() {
569 if let Err(e) = state.callbacks.trigger_upload() {
570 log::error!("Triggering upload failed. Error: {}", e);
571 }
572 }
573 }
574
575 core::with_glean_mut(|glean| {
576 let state = global_state().lock().unwrap();
577
578 if !is_first_run && dirty_flag {
582 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
588 if let Err(e) = state.callbacks.trigger_upload() {
589 log::error!("Triggering upload failed. Error: {}", e);
590 }
591 }
592 }
593
594 if !is_first_run {
598 glean.clear_application_lifetime_metrics();
599 initialize_core_metrics(glean, &state.client_info);
600 }
601 });
602
603 match dispatcher::flush_init() {
609 Ok(task_count) if task_count > 0 => {
610 core::with_glean(|glean| {
611 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
612 });
613 }
614 Ok(_) => {}
615 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
616 }
617
618 if !is_test_mode() && internal_pings_enabled {
619 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
622
623 let state = global_state().lock().unwrap();
624 if let Err(e) = state.callbacks.trigger_upload() {
625 log::error!("Triggering upload failed. Error: {}", e);
626 }
627 }
628 let state = global_state().lock().unwrap();
629 state.callbacks.initialize_finished();
630 })
631 .expect("Failed to spawn Glean's init thread");
632
633 INIT_HANDLES.lock().unwrap().push(init_handle);
635
636 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
639
640 if dispatcher::global::is_test_mode() {
643 join_init();
644 }
645}
646
647pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
651 use malloc_size_of::MallocSizeOf;
652 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
653}
654
655pub fn join_init() {
658 let mut handles = INIT_HANDLES.lock().unwrap();
659 for handle in handles.drain(..) {
660 handle.join().unwrap();
661 }
662}
663
664fn uploader_shutdown() {
672 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
673 let (tx, rx) = unbounded();
674
675 let handle = thread::spawn("glean.shutdown", move || {
676 let state = global_state().lock().unwrap();
677 if let Err(e) = state.callbacks.shutdown() {
678 log::error!("Shutdown callback failed: {e:?}");
679 }
680
681 let _ = tx.send(()).ok();
683 })
684 .expect("Unable to spawn thread to wait on shutdown");
685
686 let result = rx.recv_timeout(Duration::from_secs(30));
694
695 let stop_time = zeitstempel::now_awake();
696 core::with_glean(|glean| {
697 glean
698 .additional_metrics
699 .shutdown_wait
700 .set_stop_and_accumulate(glean, timer_id, stop_time);
701 });
702
703 if result.is_err() {
704 log::warn!("Waiting for upload failed. We're shutting down.");
705 } else {
706 let _ = handle.join().ok();
707 }
708}
709
710pub fn shutdown() {
712 if !was_initialize_called() {
722 log::warn!("Shutdown called before Glean is initialized");
723 if let Err(e) = dispatcher::kill() {
724 log::error!("Can't kill dispatcher thread: {:?}", e);
725 }
726 return;
727 }
728
729 if core::global_glean().is_none() {
731 log::warn!("Shutdown called before Glean is initialized. Waiting.");
732 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
740 }
741 if core::global_glean().is_none() {
743 log::warn!("Waiting for Glean initialization timed out. Exiting.");
744 if let Err(e) = dispatcher::kill() {
745 log::error!("Can't kill dispatcher thread: {:?}", e);
746 }
747 return;
748 }
749
750 crate::launch_with_glean_mut(|glean| {
752 glean.cancel_metrics_ping_scheduler();
753 glean.set_dirty_flag(false);
754 });
755
756 let timer_id = core::with_glean(|glean| {
763 glean
764 .additional_metrics
765 .shutdown_dispatcher_wait
766 .start_sync()
767 });
768 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
769
770 let stop_time = zeitstempel::now_awake();
772 core::with_glean(|glean| {
773 glean
774 .additional_metrics
775 .shutdown_dispatcher_wait
776 .set_stop_and_accumulate(glean, timer_id, stop_time);
777 });
778 if blocked.is_err() {
779 log::error!(
780 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
781 );
782 return;
783 }
784
785 if let Err(e) = dispatcher::shutdown() {
786 log::error!("Can't shutdown dispatcher thread: {:?}", e);
787 }
788
789 uploader_shutdown();
790
791 core::with_glean_mut(|glean| {
793 if let Err(e) = glean.persist_ping_lifetime_data() {
794 log::info!("Can't persist ping lifetime data: {:?}", e);
795 }
796
797 if let Some(database) = &glean.data_store {
798 if let Err(e) = database.run_maintenance(false) {
799 log::info!("Can't run database maintenance on shutdown: {:?}", e);
800 }
801 }
802
803 glean.close_db();
804 });
805}
806
807pub fn glean_persist_ping_lifetime_data() {
814 crate::launch_with_glean(|glean| {
816 let _ = glean.persist_ping_lifetime_data();
817 });
818}
819
820fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
821 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
822 core_metrics::internal_metrics::app_display_version
823 .set_sync(glean, &client_info.app_display_version[..]);
824 core_metrics::internal_metrics::app_build_date
825 .set_sync(glean, Some(client_info.app_build_date.clone()));
826 if let Some(app_channel) = client_info.channel.as_ref() {
827 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
828 }
829
830 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
831 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
832
833 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
834 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
835 }
836 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
837 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
838 }
839 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
840 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
841 }
842 if let Some(device_model) = client_info.device_model.as_ref() {
843 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
844 }
845 if let Some(locale) = client_info.locale.as_ref() {
846 core_metrics::internal_metrics::locale.set_sync(glean, locale);
847 }
848}
849
850fn was_initialize_called() -> bool {
856 INITIALIZE_CALLED.load(Ordering::SeqCst)
857}
858
859#[no_mangle]
862pub extern "C" fn glean_enable_logging() {
863 #[cfg(target_os = "android")]
864 {
865 let _ = std::panic::catch_unwind(|| {
866 let filter = android_logger::FilterBuilder::new()
867 .filter_module("glean_ffi", log::LevelFilter::Debug)
868 .filter_module("glean_core", log::LevelFilter::Debug)
869 .filter_module("glean", log::LevelFilter::Debug)
870 .filter_module("glean_core::ffi", log::LevelFilter::Info)
871 .build();
872 android_logger::init_once(
873 android_logger::Config::default()
874 .with_max_level(log::LevelFilter::Debug)
875 .with_filter(filter)
876 .with_tag("libglean_ffi"),
877 );
878 log::trace!("Android logging should be hooked up!")
879 });
880 }
881
882 #[cfg(target_os = "ios")]
884 {
885 #[cfg(debug_assertions)]
888 let level = log::LevelFilter::Debug;
889 #[cfg(not(debug_assertions))]
890 let level = log::LevelFilter::Info;
891
892 let logger = oslog::OsLogger::new("org.mozilla.glean")
893 .level_filter(level)
894 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
896
897 match logger.init() {
898 Ok(_) => log::trace!("os_log should be hooked up!"),
899 Err(_) => log::warn!("os_log was already initialized"),
903 };
904 }
905
906 #[cfg(all(
910 not(target_os = "android"),
911 not(target_os = "ios"),
912 feature = "enable_env_logger"
913 ))]
914 {
915 match env_logger::try_init() {
916 Ok(_) => log::trace!("stdout logging should be hooked up!"),
917 Err(_) => log::warn!("stdout logging was already initialized"),
921 };
922 }
923}
924
925pub fn glean_set_upload_enabled(enabled: bool) {
930 if !was_initialize_called() {
931 return;
932 }
933
934 crate::launch_with_glean_mut(move |glean| {
935 let state = global_state().lock().unwrap();
936 let original_enabled = glean.is_upload_enabled();
937
938 if !enabled {
939 glean.cancel_metrics_ping_scheduler();
941 if let Err(e) = state.callbacks.cancel_uploads() {
943 log::error!("Canceling upload failed. Error: {}", e);
944 }
945 }
946
947 glean.set_upload_enabled(enabled);
948
949 if !original_enabled && enabled {
950 initialize_core_metrics(glean, &state.client_info);
951 }
952
953 if original_enabled && !enabled {
954 if let Err(e) = state.callbacks.trigger_upload() {
955 log::error!("Triggering upload failed. Error: {}", e);
956 }
957 }
958 })
959}
960
961pub fn glean_set_collection_enabled(enabled: bool) {
965 glean_set_upload_enabled(enabled)
966}
967
968pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
973 let ping = ping.clone();
974 if was_initialize_called() && core::global_glean().is_some() {
975 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
976 } else {
977 let m = &PRE_INIT_PING_ENABLED;
978 let mut lock = m.lock().unwrap();
979 lock.push((ping, enabled));
980 }
981}
982
983pub(crate) fn register_ping_type(ping: &PingType) {
985 if was_initialize_called() && core::global_glean().is_some() {
990 let ping = ping.clone();
991 crate::launch_with_glean_mut(move |glean| {
992 glean.register_ping_type(&ping);
993 })
994 } else {
995 let m = &PRE_INIT_PING_REGISTRATION;
1000 let mut lock = m.lock().unwrap();
1001 lock.push(ping.clone());
1002 }
1003}
1004
1005pub fn glean_get_registered_ping_names() -> Vec<String> {
1011 block_on_dispatcher();
1012 core::with_glean(|glean| {
1013 glean
1014 .get_registered_ping_names()
1015 .iter()
1016 .map(|ping| ping.to_string())
1017 .collect()
1018 })
1019}
1020
1021pub fn glean_set_experiment_active(
1027 experiment_id: String,
1028 branch: String,
1029 extra: HashMap<String, String>,
1030) {
1031 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1032}
1033
1034pub fn glean_set_experiment_inactive(experiment_id: String) {
1038 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1039}
1040
1041pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1045 block_on_dispatcher();
1046 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1047}
1048
1049pub fn glean_set_experimentation_id(experimentation_id: String) {
1053 launch_with_glean(move |glean| {
1054 glean
1055 .additional_metrics
1056 .experimentation_id
1057 .set(experimentation_id);
1058 });
1059}
1060
1061pub fn glean_test_get_experimentation_id() -> Option<String> {
1064 block_on_dispatcher();
1065 core::with_glean(|glean| glean.test_get_experimentation_id())
1066}
1067
1068pub fn glean_apply_server_knobs_config(json: String) {
1073 if json.is_empty() {
1076 return;
1077 }
1078
1079 match RemoteSettingsConfig::try_from(json) {
1080 Ok(cfg) => launch_with_glean(|glean| {
1081 glean.apply_server_knobs_config(cfg);
1082 }),
1083 Err(e) => {
1084 log::error!("Error setting metrics feature config: {:?}", e);
1085 }
1086 }
1087}
1088
1089pub fn glean_set_debug_view_tag(tag: String) -> bool {
1103 if was_initialize_called() && core::global_glean().is_some() {
1104 crate::launch_with_glean_mut(move |glean| {
1105 glean.set_debug_view_tag(&tag);
1106 });
1107 true
1108 } else {
1109 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1111 let mut lock = m.lock().unwrap();
1112 *lock = tag;
1113 true
1116 }
1117}
1118
1119pub fn glean_get_debug_view_tag() -> Option<String> {
1125 block_on_dispatcher();
1126 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1127}
1128
1129pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1141 if was_initialize_called() && core::global_glean().is_some() {
1142 crate::launch_with_glean_mut(|glean| {
1143 glean.set_source_tags(tags);
1144 });
1145 true
1146 } else {
1147 let m = &PRE_INIT_SOURCE_TAGS;
1149 let mut lock = m.lock().unwrap();
1150 *lock = tags;
1151 true
1154 }
1155}
1156
1157pub fn glean_set_log_pings(value: bool) {
1166 if was_initialize_called() && core::global_glean().is_some() {
1167 crate::launch_with_glean_mut(move |glean| {
1168 glean.set_log_pings(value);
1169 });
1170 } else {
1171 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1172 }
1173}
1174
1175pub fn glean_get_log_pings() -> bool {
1181 block_on_dispatcher();
1182 core::with_glean(|glean| glean.log_pings())
1183}
1184
1185pub fn glean_handle_client_active() {
1192 dispatcher::launch(|| {
1193 core::with_glean_mut(|glean| {
1194 glean.handle_client_active();
1195 });
1196
1197 let state = global_state().lock().unwrap();
1201 if let Err(e) = state.callbacks.trigger_upload() {
1202 log::error!("Triggering upload failed. Error: {}", e);
1203 }
1204 });
1205
1206 core_metrics::internal_metrics::baseline_duration.start();
1211}
1212
1213pub fn glean_handle_client_inactive() {
1220 core_metrics::internal_metrics::baseline_duration.stop();
1224
1225 dispatcher::launch(|| {
1226 core::with_glean_mut(|glean| {
1227 glean.handle_client_inactive();
1228 });
1229
1230 let state = global_state().lock().unwrap();
1234 if let Err(e) = state.callbacks.trigger_upload() {
1235 log::error!("Triggering upload failed. Error: {}", e);
1236 }
1237 })
1238}
1239
1240pub fn glean_session_start() {
1245 launch_with_glean_mut(|glean| {
1246 if glean.session_manager.mode == session::SessionMode::Manual {
1247 glean.session_start();
1248 }
1249 });
1250}
1251
1252pub fn glean_session_end(reason: Option<String>) {
1260 launch_with_glean_mut(move |glean| {
1261 if glean.session_manager.mode == session::SessionMode::Manual {
1262 glean.session_end(reason.as_deref());
1263 }
1264 });
1265}
1266
1267pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1269 dispatcher::launch(|| {
1270 let sent =
1271 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1272
1273 if sent {
1274 let state = global_state().lock().unwrap();
1275 if let Err(e) = state.callbacks.trigger_upload() {
1276 log::error!("Triggering upload failed. Error: {}", e);
1277 }
1278 }
1279 })
1280}
1281
1282pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1286 if !was_initialize_called() {
1287 return false;
1288 }
1289
1290 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1291 .unwrap_or(false)
1292}
1293
1294pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1301 register_event_listener(tag, listener);
1302}
1303
1304pub fn glean_unregister_event_listener(tag: String) {
1312 unregister_event_listener(tag);
1313}
1314
1315pub fn glean_set_test_mode(enabled: bool) {
1319 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1320}
1321
1322pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1326 if was_initialize_called() {
1327 join_init();
1329
1330 dispatcher::reset_dispatcher();
1331
1332 let has_storage = core::with_opt_glean(|glean| {
1335 glean
1337 .storage_opt()
1338 .map(|storage| storage.persist_ping_lifetime_data())
1339 .is_some()
1340 })
1341 .unwrap_or(false);
1342 if has_storage {
1343 uploader_shutdown();
1344 }
1345
1346 if core::global_glean().is_some() {
1347 core::with_glean_mut(|glean| {
1348 if clear_stores {
1349 glean.test_clear_all_stores()
1350 }
1351 glean.close_db()
1352 });
1353 }
1354
1355 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1357 } else if clear_stores {
1358 if let Some(data_path) = data_path {
1359 let _ = std::fs::remove_dir_all(data_path).ok();
1360 } else {
1361 log::warn!("Asked to clear stores before initialization, but no data path given.");
1362 }
1363 }
1364}
1365
1366pub fn glean_get_upload_task() -> PingUploadTask {
1368 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1369}
1370
1371pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1373 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1374}
1375
1376pub fn glean_set_dirty_flag(new_value: bool) {
1380 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1381}
1382
1383pub fn glean_clear_attribution() {
1386 if was_initialize_called() && core::global_glean().is_some() {
1387 core::with_glean(|glean| glean.clear_attribution());
1388 } else {
1389 PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1390 _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1391 }
1392}
1393
1394pub fn glean_update_attribution(attribution: AttributionMetrics) {
1397 if was_initialize_called() && core::global_glean().is_some() {
1398 core::with_glean(|glean| glean.update_attribution(attribution));
1399 } else {
1400 PRE_INIT_ATTRIBUTION
1401 .lock()
1402 .unwrap()
1403 .get_or_insert(Default::default())
1404 .update(attribution);
1405 }
1406}
1407
1408pub fn glean_test_get_attribution() -> AttributionMetrics {
1413 join_init();
1414 core::with_glean(|glean| glean.test_get_attribution())
1415}
1416
1417pub fn glean_clear_distribution() {
1420 if was_initialize_called() && core::global_glean().is_some() {
1421 core::with_glean(|glean| glean.clear_distribution());
1422 } else {
1423 PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1424 _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1425 }
1426}
1427
1428pub fn glean_update_distribution(distribution: DistributionMetrics) {
1431 if was_initialize_called() && core::global_glean().is_some() {
1432 core::with_glean(|glean| glean.update_distribution(distribution));
1433 } else {
1434 PRE_INIT_DISTRIBUTION
1435 .lock()
1436 .unwrap()
1437 .get_or_insert(Default::default())
1438 .update(distribution);
1439 }
1440}
1441
1442pub fn glean_test_get_distribution() -> DistributionMetrics {
1447 join_init();
1448 core::with_glean(|glean| glean.test_get_distribution())
1449}
1450
1451#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1452static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1453
1454#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1467pub fn glean_enable_logging_to_fd(fd: u64) {
1468 unsafe {
1474 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1479 if log::set_logger(logger).is_ok() {
1483 log::set_max_level(log::LevelFilter::Debug);
1484 }
1485 }
1486}
1487
1488fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1490 let subdirs = ["db", "events", "pending_pings"];
1492 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1493 DataDirectoryInfoObject::with_capacity(subdirs.len());
1494
1495 for subdir in subdirs.iter() {
1496 let dir_path = path.join(subdir);
1497
1498 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1500 dir_name: Some(subdir.to_string()),
1501 dir_exists: None,
1502 dir_created: None,
1503 dir_modified: None,
1504 file_count: None,
1505 files: Vec::new(),
1506 error_message: None,
1507 };
1508
1509 if dir_path.is_dir() {
1511 directory_info.dir_exists = Some(true);
1512
1513 match fs::metadata(&dir_path) {
1515 Ok(metadata) => {
1516 if let Ok(created) = metadata.created() {
1517 directory_info.dir_created = Some(
1518 created
1519 .duration_since(UNIX_EPOCH)
1520 .unwrap_or(Duration::ZERO)
1521 .as_secs() as i64,
1522 );
1523 }
1524 if let Ok(modified) = metadata.modified() {
1525 directory_info.dir_modified = Some(
1526 modified
1527 .duration_since(UNIX_EPOCH)
1528 .unwrap_or(Duration::ZERO)
1529 .as_secs() as i64,
1530 );
1531 }
1532 }
1533 Err(error) => {
1534 let msg = format!("Unable to get metadata: {}", error.kind());
1535 directory_info.error_message = Some(msg.clone());
1536 log::warn!("{}", msg);
1537 continue;
1538 }
1539 }
1540
1541 let mut file_count = 0;
1543 let entries = match fs::read_dir(&dir_path) {
1544 Ok(entries) => entries,
1545 Err(error) => {
1546 let msg = format!("Unable to read subdir: {}", error.kind());
1547 directory_info.error_message = Some(msg.clone());
1548 log::warn!("{}", msg);
1549 continue;
1550 }
1551 };
1552 for entry in entries {
1553 directory_info.files.push(
1554 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1555 file_name: None,
1556 file_created: None,
1557 file_modified: None,
1558 file_size: None,
1559 error_message: None,
1560 },
1561 );
1562 let file_info = directory_info.files.last_mut().unwrap();
1564 let entry = match entry {
1565 Ok(entry) => entry,
1566 Err(error) => {
1567 let msg = format!("Unable to read file: {}", error.kind());
1568 file_info.error_message = Some(msg.clone());
1569 log::warn!("{}", msg);
1570 continue;
1571 }
1572 };
1573 let file_name = match entry.file_name().into_string() {
1574 Ok(file_name) => file_name,
1575 _ => {
1576 let msg = "Unable to convert file name to string".to_string();
1577 file_info.error_message = Some(msg.clone());
1578 log::warn!("{}", msg);
1579 continue;
1580 }
1581 };
1582 let metadata = match entry.metadata() {
1583 Ok(metadata) => metadata,
1584 Err(error) => {
1585 let msg = format!("Unable to read file metadata: {}", error.kind());
1586 file_info.file_name = Some(file_name);
1587 file_info.error_message = Some(msg.clone());
1588 log::warn!("{}", msg);
1589 continue;
1590 }
1591 };
1592
1593 if metadata.is_file() {
1595 file_count += 1;
1596
1597 file_info.file_name = Some(file_name);
1599 file_info.file_created = Some(
1600 metadata
1601 .created()
1602 .unwrap_or(UNIX_EPOCH)
1603 .duration_since(UNIX_EPOCH)
1604 .unwrap_or(Duration::ZERO)
1605 .as_secs() as i64,
1606 );
1607 file_info.file_modified = Some(
1608 metadata
1609 .modified()
1610 .unwrap_or(UNIX_EPOCH)
1611 .duration_since(UNIX_EPOCH)
1612 .unwrap_or(Duration::ZERO)
1613 .as_secs() as i64,
1614 );
1615 file_info.file_size = Some(metadata.len() as i64);
1616 } else {
1617 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1618 file_info.file_name = Some(file_name);
1619 file_info.error_message = Some(msg.clone());
1620 log::warn!("{}", msg);
1621 }
1622 }
1623
1624 directory_info.file_count = Some(file_count as i64);
1625 } else {
1626 directory_info.dir_exists = Some(false);
1627 }
1628
1629 directories_info.push(directory_info);
1631 }
1632
1633 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1634 Some(directories_info_json)
1635 } else {
1636 log::error!("Failed to serialize data directory info");
1637 None
1638 }
1639}
1640
1641fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1642 core::with_glean(|glean| {
1643 glean
1644 .health_metrics
1645 .data_directory_info
1646 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1647 glean.internal_pings.health.submit_sync(glean, Some(reason));
1648 });
1649}
1650
1651#[cfg(any(target_os = "android", target_os = "ios"))]
1653pub fn glean_enable_logging_to_fd(_fd: u64) {
1654 }
1656
1657uniffi::include_scaffolding!("glean");
1660
1661type CowString = Cow<'static, str>;
1662
1663uniffi::custom_type!(CowString, String, {
1664 remote,
1665 lower: |s| s.into_owned(),
1666 try_lift: |s| Ok(Cow::from(s))
1667});
1668
1669type JsonValue = serde_json::Value;
1670
1671uniffi::custom_type!(JsonValue, String, {
1672 remote,
1673 lower: |s| serde_json::to_string(&s).unwrap(),
1674 try_lift: |s| Ok(serde_json::from_str(&s)?)
1675});
1676
1677#[cfg(test)]
1681#[path = "lib_unit_tests.rs"]
1682mod tests;