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 pub enable_store_submitted_pings: bool,
189}
190
191#[derive(Debug, Clone, MallocSizeOf)]
193pub struct PingRateLimit {
194 pub seconds_per_interval: u64,
196 pub pings_per_interval: u32,
198}
199
200fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
202 dispatcher::launch(|| core::with_glean(callback));
203}
204
205fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
208 dispatcher::launch(|| core::with_glean_mut(callback));
209}
210
211fn block_on_dispatcher() {
215 dispatcher::block_on_queue()
216}
217
218pub fn get_awake_timestamp_ms() -> u64 {
220 const NANOS_PER_MILLI: u64 = 1_000_000;
221 zeitstempel::now_awake() / NANOS_PER_MILLI
222}
223
224pub fn get_timestamp_ms() -> u64 {
226 const NANOS_PER_MILLI: u64 = 1_000_000;
227 zeitstempel::now() / NANOS_PER_MILLI
228}
229
230struct State {
235 client_info: ClientInfoMetrics,
237
238 callbacks: Box<dyn OnGleanEvents>,
239}
240
241static STATE: OnceCell<Mutex<State>> = OnceCell::new();
245
246#[track_caller] fn global_state() -> &'static Mutex<State> {
251 STATE.get().unwrap()
252}
253
254#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
259 STATE.get()
260}
261
262fn setup_state(state: State) {
264 if STATE.get().is_none() {
274 if STATE.set(Mutex::new(state)).is_err() {
275 log::error!(
276 "Global Glean state object is initialized already. This probably happened concurrently."
277 );
278 }
279 } else {
280 let mut lock = STATE.get().unwrap().lock().unwrap();
284 *lock = state;
285 }
286}
287
288static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
291 OnceCell::new();
292
293fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
294 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
295}
296
297fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
298 let mut lock = event_listeners().lock().unwrap();
299 lock.insert(tag, listener);
300}
301
302fn unregister_event_listener(tag: String) {
303 let mut lock = event_listeners().lock().unwrap();
304 lock.remove(&tag);
305}
306
307#[derive(Debug)]
309pub enum CallbackError {
310 UnexpectedError,
312}
313
314impl fmt::Display for CallbackError {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 write!(f, "Unexpected error")
317 }
318}
319
320impl std::error::Error for CallbackError {}
321
322impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
323 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
324 CallbackError::UnexpectedError
325 }
326}
327
328pub trait OnGleanEvents: Send {
332 fn initialize_finished(&self);
338
339 fn trigger_upload(&self) -> Result<(), CallbackError>;
344
345 fn start_metrics_ping_scheduler(&self) -> bool;
347
348 fn cancel_uploads(&self) -> Result<(), CallbackError>;
350
351 fn shutdown(&self) -> Result<(), CallbackError> {
358 Ok(())
360 }
361}
362
363pub trait GleanEventListener: Send {
366 fn on_event_recorded(&self, id: String);
368}
369
370pub fn glean_initialize(
379 cfg: InternalConfiguration,
380 client_info: ClientInfoMetrics,
381 callbacks: Box<dyn OnGleanEvents>,
382) {
383 initialize_inner(cfg, client_info, callbacks);
384}
385
386pub fn glean_shutdown() {
388 shutdown();
389}
390
391pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
396 let glean = match Glean::new_for_subprocess(&cfg, true) {
397 Ok(glean) => glean,
398 Err(err) => {
399 log::error!("Failed to initialize Glean: {}", err);
400 return false;
401 }
402 };
403 if core::setup_glean(glean).is_err() {
404 return false;
405 }
406 log::info!("Glean initialized for subprocess");
407 true
408}
409
410fn initialize_inner(
411 cfg: InternalConfiguration,
412 client_info: ClientInfoMetrics,
413 callbacks: Box<dyn OnGleanEvents>,
414) {
415 if was_initialize_called() {
416 log::error!("Glean should not be initialized multiple times");
417 return;
418 }
419
420 let init_handle = thread::spawn("glean.init", move || {
421 let upload_enabled = cfg.upload_enabled;
422 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
423
424 if let Some(level) = cfg.log_level {
426 log::set_max_level(level)
427 }
428
429 let data_path_str = cfg.data_path.clone();
430 let data_path = Path::new(&data_path_str);
431 let internal_pings_enabled = cfg.enable_internal_pings;
432 let dir_info = if !is_test_mode() && internal_pings_enabled {
433 collect_directory_info(Path::new(&data_path))
434 } else {
435 None
436 };
437
438 let glean = match Glean::new(cfg) {
439 Ok(glean) => glean,
440 Err(err) => {
441 log::error!("Failed to initialize Glean: {}", err);
442 return;
443 }
444 };
445 if core::setup_glean(glean).is_err() {
446 return;
447 }
448
449 log::info!("Glean initialized");
450
451 core::with_glean(|glean| {
452 glean.health_metrics.init_count.add_sync(glean, 1);
453 });
454
455 setup_state(State {
456 client_info,
457 callbacks,
458 });
459
460 let mut is_first_run = false;
461 let mut dirty_flag = false;
462 let mut pings_submitted = false;
463 core::with_glean_mut(|glean| {
464 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
467 if !debug_tag.is_empty() {
468 glean.set_debug_view_tag(&debug_tag);
469 }
470
471 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
474 if log_pigs {
475 glean.set_log_pings(log_pigs);
476 }
477
478 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
481 if !source_tags.is_empty() {
482 glean.set_source_tags(source_tags.to_vec());
483 }
484
485 dirty_flag = glean.is_dirty_flag_set();
490 glean.set_dirty_flag(false);
491
492 if dirty_flag {
496 glean.recover_session_on_dirty_flag();
497 }
498
499 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
502 for ping in pings.iter() {
503 glean.register_ping_type(ping);
504 }
505 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
506 for (ping, enabled) in pings.iter() {
507 glean.set_ping_enabled(ping, *enabled);
508 }
509
510 let clear_attribution = PRE_INIT_ATTRIBUTION_CLEARED.load(Ordering::SeqCst);
513 if clear_attribution {
514 glean.clear_attribution();
515 }
516 let clear_distribution = PRE_INIT_DISTRIBUTION_CLEARED.load(Ordering::SeqCst);
517 if clear_distribution {
518 glean.clear_distribution();
519 }
520 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
521 glean.update_attribution(attribution);
522 }
523 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
524 glean.update_distribution(distribution);
525 }
526
527 is_first_run = glean.is_first_run();
531 if is_first_run {
532 let state = global_state().lock().unwrap();
533 initialize_core_metrics(glean, &state.client_info);
534 }
535
536 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
538 });
539
540 {
541 let state = global_state().lock().unwrap();
542 if pings_submitted || !upload_enabled {
546 if let Err(e) = state.callbacks.trigger_upload() {
547 log::error!("Triggering upload failed. Error: {}", e);
548 }
549 }
550 }
551
552 core::with_glean(|glean| {
553 glean.start_metrics_ping_scheduler();
555 });
556
557 {
565 let state = global_state().lock().unwrap();
566
567 if state.callbacks.start_metrics_ping_scheduler() {
571 if let Err(e) = state.callbacks.trigger_upload() {
572 log::error!("Triggering upload failed. Error: {}", e);
573 }
574 }
575 }
576
577 core::with_glean_mut(|glean| {
578 let state = global_state().lock().unwrap();
579
580 if !is_first_run && dirty_flag {
584 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
590 if let Err(e) = state.callbacks.trigger_upload() {
591 log::error!("Triggering upload failed. Error: {}", e);
592 }
593 }
594 }
595
596 if !is_first_run {
600 glean.clear_application_lifetime_metrics();
601 initialize_core_metrics(glean, &state.client_info);
602 }
603 });
604
605 match dispatcher::flush_init() {
611 Ok(task_count) if task_count > 0 => {
612 core::with_glean(|glean| {
613 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
614 });
615 }
616 Ok(_) => {}
617 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
618 }
619
620 if !is_test_mode() && internal_pings_enabled {
621 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
624
625 let state = global_state().lock().unwrap();
626 if let Err(e) = state.callbacks.trigger_upload() {
627 log::error!("Triggering upload failed. Error: {}", e);
628 }
629 }
630 let state = global_state().lock().unwrap();
631 state.callbacks.initialize_finished();
632 })
633 .expect("Failed to spawn Glean's init thread");
634
635 INIT_HANDLES.lock().unwrap().push(init_handle);
637
638 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
641
642 if dispatcher::global::is_test_mode() {
645 join_init();
646 }
647}
648
649pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
653 use malloc_size_of::MallocSizeOf;
654 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
655}
656
657pub fn join_init() {
660 let mut handles = INIT_HANDLES.lock().unwrap();
661 for handle in handles.drain(..) {
662 handle.join().unwrap();
663 }
664}
665
666fn uploader_shutdown() {
674 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
675 let (tx, rx) = unbounded();
676
677 let handle = thread::spawn("glean.shutdown", move || {
678 let state = global_state().lock().unwrap();
679 if let Err(e) = state.callbacks.shutdown() {
680 log::error!("Shutdown callback failed: {e:?}");
681 }
682
683 let _ = tx.send(()).ok();
685 })
686 .expect("Unable to spawn thread to wait on shutdown");
687
688 let result = rx.recv_timeout(Duration::from_secs(30));
696
697 let stop_time = zeitstempel::now_awake();
698 core::with_glean(|glean| {
699 glean
700 .additional_metrics
701 .shutdown_wait
702 .set_stop_and_accumulate(glean, timer_id, stop_time);
703 });
704
705 if result.is_err() {
706 log::warn!("Waiting for upload failed. We're shutting down.");
707 } else {
708 let _ = handle.join().ok();
709 }
710}
711
712pub fn shutdown() {
714 if !was_initialize_called() {
724 log::warn!("Shutdown called before Glean is initialized");
725 if let Err(e) = dispatcher::kill() {
726 log::error!("Can't kill dispatcher thread: {:?}", e);
727 }
728 return;
729 }
730
731 if core::global_glean().is_none() {
733 log::warn!("Shutdown called before Glean is initialized. Waiting.");
734 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
742 }
743 if core::global_glean().is_none() {
745 log::warn!("Waiting for Glean initialization timed out. Exiting.");
746 if let Err(e) = dispatcher::kill() {
747 log::error!("Can't kill dispatcher thread: {:?}", e);
748 }
749 return;
750 }
751
752 crate::launch_with_glean_mut(|glean| {
754 glean.cancel_metrics_ping_scheduler();
755 glean.set_dirty_flag(false);
756 });
757
758 let timer_id = core::with_glean(|glean| {
765 glean
766 .additional_metrics
767 .shutdown_dispatcher_wait
768 .start_sync()
769 });
770 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
771
772 let stop_time = zeitstempel::now_awake();
774 core::with_glean(|glean| {
775 glean
776 .additional_metrics
777 .shutdown_dispatcher_wait
778 .set_stop_and_accumulate(glean, timer_id, stop_time);
779 });
780 if blocked.is_err() {
781 log::error!(
782 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
783 );
784 return;
785 }
786
787 if let Err(e) = dispatcher::shutdown() {
788 log::error!("Can't shutdown dispatcher thread: {:?}", e);
789 }
790
791 uploader_shutdown();
792
793 core::with_glean_mut(|glean| {
795 if let Err(e) = glean.persist_ping_lifetime_data() {
796 log::info!("Can't persist ping lifetime data: {:?}", e);
797 }
798
799 if let Some(database) = &glean.data_store {
800 if let Err(e) = database.cleanup_submitted_pings(None) {
801 log::info!("Could not clean up submitted_pings table: {:?}", e);
802 }
803 if let Err(e) = database.run_maintenance(false) {
804 log::info!("Can't run database maintenance on shutdown: {:?}", e);
805 }
806 }
807
808 glean.close_db();
809 });
810}
811
812pub fn glean_persist_ping_lifetime_data() {
819 crate::launch_with_glean(|glean| {
821 let _ = glean.persist_ping_lifetime_data();
822 });
823}
824
825fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
826 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
827 core_metrics::internal_metrics::app_display_version
828 .set_sync(glean, &client_info.app_display_version[..]);
829 core_metrics::internal_metrics::app_build_date
830 .set_sync(glean, Some(client_info.app_build_date.clone()));
831 if let Some(app_channel) = client_info.channel.as_ref() {
832 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
833 }
834
835 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
836 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
837
838 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
839 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
840 }
841 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
842 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
843 }
844 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
845 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
846 }
847 if let Some(device_model) = client_info.device_model.as_ref() {
848 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
849 }
850 if let Some(locale) = client_info.locale.as_ref() {
851 core_metrics::internal_metrics::locale.set_sync(glean, locale);
852 }
853}
854
855fn was_initialize_called() -> bool {
861 INITIALIZE_CALLED.load(Ordering::SeqCst)
862}
863
864#[no_mangle]
867pub extern "C" fn glean_enable_logging() {
868 #[cfg(target_os = "android")]
869 {
870 let _ = std::panic::catch_unwind(|| {
871 let filter = android_logger::FilterBuilder::new()
872 .filter_module("glean_ffi", log::LevelFilter::Debug)
873 .filter_module("glean_core", log::LevelFilter::Debug)
874 .filter_module("glean", log::LevelFilter::Debug)
875 .filter_module("glean_core::ffi", log::LevelFilter::Info)
876 .build();
877 android_logger::init_once(
878 android_logger::Config::default()
879 .with_max_level(log::LevelFilter::Debug)
880 .with_filter(filter)
881 .with_tag("libglean_ffi"),
882 );
883 log::trace!("Android logging should be hooked up!")
884 });
885 }
886
887 #[cfg(target_os = "ios")]
889 {
890 #[cfg(debug_assertions)]
893 let level = log::LevelFilter::Debug;
894 #[cfg(not(debug_assertions))]
895 let level = log::LevelFilter::Info;
896
897 let logger = oslog::OsLogger::new("org.mozilla.glean")
898 .level_filter(level)
899 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
901
902 match logger.init() {
903 Ok(_) => log::trace!("os_log should be hooked up!"),
904 Err(_) => log::warn!("os_log was already initialized"),
908 };
909 }
910
911 #[cfg(all(
915 not(target_os = "android"),
916 not(target_os = "ios"),
917 feature = "enable_env_logger"
918 ))]
919 {
920 match env_logger::try_init() {
921 Ok(_) => log::trace!("stdout logging should be hooked up!"),
922 Err(_) => log::warn!("stdout logging was already initialized"),
926 };
927 }
928}
929
930pub fn glean_set_upload_enabled(enabled: bool) {
935 if !was_initialize_called() {
936 return;
937 }
938
939 crate::launch_with_glean_mut(move |glean| {
940 let state = global_state().lock().unwrap();
941 let original_enabled = glean.is_upload_enabled();
942
943 if !enabled {
944 glean.cancel_metrics_ping_scheduler();
946 if let Err(e) = state.callbacks.cancel_uploads() {
948 log::error!("Canceling upload failed. Error: {}", e);
949 }
950 }
951
952 glean.set_upload_enabled(enabled);
953
954 if !original_enabled && enabled {
955 initialize_core_metrics(glean, &state.client_info);
956 }
957
958 if original_enabled && !enabled {
959 if let Err(e) = state.callbacks.trigger_upload() {
960 log::error!("Triggering upload failed. Error: {}", e);
961 }
962 }
963 })
964}
965
966pub fn glean_set_collection_enabled(enabled: bool) {
970 glean_set_upload_enabled(enabled)
971}
972
973pub fn glean_set_store_submitted_pings_enabled(enabled: bool) {
975 if !was_initialize_called() {
976 return;
977 }
978
979 launch_with_glean_mut(move |glean| {
980 glean.store_submitted_pings_enabled = enabled;
981 });
982}
983
984pub struct SubmittedPing {
986 pub document_id: String,
988 pub ping: String,
990 pub submitted_date: String,
992 pub uploaded_date: Option<String>,
994 pub upload_failed: Option<String>,
996 pub payload: Option<JsonValue>,
998}
999
1000impl From<database::sqlite::SubmittedPing> for SubmittedPing {
1001 fn from(value: database::sqlite::SubmittedPing) -> Self {
1002 SubmittedPing {
1003 document_id: value.document_id.clone(),
1004 ping: value.ping.clone(),
1005 submitted_date: value.submitted_date.0.to_rfc3339(),
1006 uploaded_date: value.uploaded_date.as_ref().map(|d| d.0.to_rfc3339()),
1007 upload_failed: value.upload_failed.as_ref().map(|d| d.0.to_rfc3339()),
1008 payload: value.payload(),
1009 }
1010 }
1011}
1012
1013pub fn glean_get_all_stored_submitted_pings() -> Vec<SubmittedPing> {
1015 core::with_glean(|glean| glean.storage().get_all_submitted_pings())
1016 .into_iter()
1017 .map(|p| p.into())
1018 .collect()
1019}
1020
1021pub fn glean_get_stored_submitted_pings_by_name(ping: String) -> Vec<SubmittedPing> {
1027 core::with_glean(|glean| glean.storage().get_submitted_pings_by_name(&ping))
1028 .into_iter()
1029 .map(|p| p.into())
1030 .collect()
1031}
1032
1033pub fn glean_clear_stored_submitted_pings() {
1035 launch_with_glean(|glean| {
1036 if let Err(e) = glean
1037 .storage()
1038 .cleanup_submitted_pings(Some(chrono::Utc::now()))
1039 {
1040 log::warn!("Unable to clear stored submitted pings: {:?}", e);
1041 }
1042 });
1043}
1044
1045pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
1050 let ping = ping.clone();
1051 if was_initialize_called() && core::global_glean().is_some() {
1052 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
1053 } else {
1054 let m = &PRE_INIT_PING_ENABLED;
1055 let mut lock = m.lock().unwrap();
1056 lock.push((ping, enabled));
1057 }
1058}
1059
1060pub(crate) fn register_ping_type(ping: &PingType) {
1062 if was_initialize_called() && core::global_glean().is_some() {
1067 let ping = ping.clone();
1068 crate::launch_with_glean_mut(move |glean| {
1069 glean.register_ping_type(&ping);
1070 })
1071 } else {
1072 let m = &PRE_INIT_PING_REGISTRATION;
1077 let mut lock = m.lock().unwrap();
1078 lock.push(ping.clone());
1079 }
1080}
1081
1082pub fn glean_get_registered_ping_names() -> Vec<String> {
1088 block_on_dispatcher();
1089 core::with_glean(|glean| {
1090 glean
1091 .get_registered_ping_names()
1092 .iter()
1093 .map(|ping| ping.to_string())
1094 .collect()
1095 })
1096}
1097
1098pub fn glean_set_experiment_active(
1104 experiment_id: String,
1105 branch: String,
1106 extra: HashMap<String, String>,
1107) {
1108 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1109}
1110
1111pub fn glean_set_experiment_inactive(experiment_id: String) {
1115 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1116}
1117
1118pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1122 block_on_dispatcher();
1123 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1124}
1125
1126pub fn glean_set_experimentation_id(experimentation_id: String) {
1130 launch_with_glean(move |glean| {
1131 glean
1132 .additional_metrics
1133 .experimentation_id
1134 .set(experimentation_id);
1135 });
1136}
1137
1138pub fn glean_test_get_experimentation_id() -> Option<String> {
1141 block_on_dispatcher();
1142 core::with_glean(|glean| glean.test_get_experimentation_id())
1143}
1144
1145pub fn glean_apply_server_knobs_config(json: String) {
1150 if json.is_empty() {
1153 return;
1154 }
1155
1156 match RemoteSettingsConfig::try_from(json) {
1157 Ok(cfg) => launch_with_glean(|glean| {
1158 glean.apply_server_knobs_config(cfg);
1159 }),
1160 Err(e) => {
1161 log::error!("Error setting metrics feature config: {:?}", e);
1162 }
1163 }
1164}
1165
1166pub fn glean_set_debug_view_tag(tag: String) -> bool {
1180 if was_initialize_called() && core::global_glean().is_some() {
1181 crate::launch_with_glean_mut(move |glean| {
1182 glean.set_debug_view_tag(&tag);
1183 });
1184 true
1185 } else {
1186 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1188 let mut lock = m.lock().unwrap();
1189 *lock = tag;
1190 true
1193 }
1194}
1195
1196pub fn glean_get_debug_view_tag() -> Option<String> {
1202 block_on_dispatcher();
1203 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1204}
1205
1206pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1218 if was_initialize_called() && core::global_glean().is_some() {
1219 crate::launch_with_glean_mut(|glean| {
1220 glean.set_source_tags(tags);
1221 });
1222 true
1223 } else {
1224 let m = &PRE_INIT_SOURCE_TAGS;
1226 let mut lock = m.lock().unwrap();
1227 *lock = tags;
1228 true
1231 }
1232}
1233
1234pub fn glean_set_log_pings(value: bool) {
1243 if was_initialize_called() && core::global_glean().is_some() {
1244 crate::launch_with_glean_mut(move |glean| {
1245 glean.set_log_pings(value);
1246 });
1247 } else {
1248 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1249 }
1250}
1251
1252pub fn glean_get_log_pings() -> bool {
1258 block_on_dispatcher();
1259 core::with_glean(|glean| glean.log_pings())
1260}
1261
1262pub fn glean_handle_client_active() {
1269 dispatcher::launch(|| {
1270 core::with_glean_mut(|glean| {
1271 glean.handle_client_active();
1272 });
1273
1274 let state = global_state().lock().unwrap();
1278 if let Err(e) = state.callbacks.trigger_upload() {
1279 log::error!("Triggering upload failed. Error: {}", e);
1280 }
1281 });
1282
1283 core_metrics::internal_metrics::baseline_duration.start();
1288}
1289
1290pub fn glean_handle_client_inactive() {
1297 core_metrics::internal_metrics::baseline_duration.stop();
1301
1302 dispatcher::launch(|| {
1303 core::with_glean_mut(|glean| {
1304 glean.handle_client_inactive();
1305 });
1306
1307 let state = global_state().lock().unwrap();
1311 if let Err(e) = state.callbacks.trigger_upload() {
1312 log::error!("Triggering upload failed. Error: {}", e);
1313 }
1314 })
1315}
1316
1317pub fn glean_session_start() {
1322 launch_with_glean_mut(|glean| {
1323 if glean.session_manager.mode == session::SessionMode::Manual {
1324 glean.session_start();
1325 }
1326 });
1327}
1328
1329pub fn glean_session_end(reason: Option<String>) {
1337 launch_with_glean_mut(move |glean| {
1338 if glean.session_manager.mode == session::SessionMode::Manual {
1339 glean.session_end(reason.as_deref());
1340 }
1341 });
1342}
1343
1344pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1346 dispatcher::launch(|| {
1347 let sent =
1348 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1349
1350 if sent {
1351 let state = global_state().lock().unwrap();
1352 if let Err(e) = state.callbacks.trigger_upload() {
1353 log::error!("Triggering upload failed. Error: {}", e);
1354 }
1355 }
1356 })
1357}
1358
1359pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1363 if !was_initialize_called() {
1364 return false;
1365 }
1366
1367 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1368 .unwrap_or(false)
1369}
1370
1371pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1378 register_event_listener(tag, listener);
1379}
1380
1381pub fn glean_unregister_event_listener(tag: String) {
1389 unregister_event_listener(tag);
1390}
1391
1392pub fn glean_set_test_mode(enabled: bool) {
1396 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1397}
1398
1399pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1403 if was_initialize_called() {
1404 join_init();
1406
1407 dispatcher::reset_dispatcher();
1408
1409 let has_storage = core::with_opt_glean(|glean| {
1412 glean
1414 .storage_opt()
1415 .map(|storage| storage.persist_ping_lifetime_data())
1416 .is_some()
1417 })
1418 .unwrap_or(false);
1419 if has_storage {
1420 uploader_shutdown();
1421 }
1422
1423 if core::global_glean().is_some() {
1424 core::with_glean_mut(|glean| {
1425 if clear_stores {
1426 glean.test_clear_all_stores()
1427 }
1428 glean.close_db()
1429 });
1430 }
1431
1432 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1434 } else if clear_stores {
1435 if let Some(data_path) = data_path {
1436 let _ = std::fs::remove_dir_all(data_path).ok();
1437 } else {
1438 log::warn!("Asked to clear stores before initialization, but no data path given.");
1439 }
1440 }
1441}
1442
1443pub fn glean_get_upload_task() -> PingUploadTask {
1445 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1446}
1447
1448pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1450 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1451}
1452
1453pub fn glean_set_dirty_flag(new_value: bool) {
1457 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1458}
1459
1460pub fn glean_clear_attribution() {
1463 if was_initialize_called() && core::global_glean().is_some() {
1464 core::with_glean(|glean| glean.clear_attribution());
1465 } else {
1466 PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1467 _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1468 }
1469}
1470
1471pub fn glean_update_attribution(attribution: AttributionMetrics) {
1474 if was_initialize_called() && core::global_glean().is_some() {
1475 core::with_glean(|glean| glean.update_attribution(attribution));
1476 } else {
1477 PRE_INIT_ATTRIBUTION
1478 .lock()
1479 .unwrap()
1480 .get_or_insert(Default::default())
1481 .update(attribution);
1482 }
1483}
1484
1485pub fn glean_test_get_attribution() -> AttributionMetrics {
1490 join_init();
1491 core::with_glean(|glean| glean.test_get_attribution())
1492}
1493
1494pub fn glean_clear_distribution() {
1497 if was_initialize_called() && core::global_glean().is_some() {
1498 core::with_glean(|glean| glean.clear_distribution());
1499 } else {
1500 PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1501 _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1502 }
1503}
1504
1505pub fn glean_update_distribution(distribution: DistributionMetrics) {
1508 if was_initialize_called() && core::global_glean().is_some() {
1509 core::with_glean(|glean| glean.update_distribution(distribution));
1510 } else {
1511 PRE_INIT_DISTRIBUTION
1512 .lock()
1513 .unwrap()
1514 .get_or_insert(Default::default())
1515 .update(distribution);
1516 }
1517}
1518
1519pub fn glean_test_get_distribution() -> DistributionMetrics {
1524 join_init();
1525 core::with_glean(|glean| glean.test_get_distribution())
1526}
1527
1528#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1529static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1530
1531#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1544pub fn glean_enable_logging_to_fd(fd: u64) {
1545 unsafe {
1551 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1556 if log::set_logger(logger).is_ok() {
1560 log::set_max_level(log::LevelFilter::Debug);
1561 }
1562 }
1563}
1564
1565fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1567 let subdirs = ["db", "events", "pending_pings"];
1569 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1570 DataDirectoryInfoObject::with_capacity(subdirs.len());
1571
1572 for subdir in subdirs.iter() {
1573 let dir_path = path.join(subdir);
1574
1575 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1577 dir_name: Some(subdir.to_string()),
1578 dir_exists: None,
1579 dir_created: None,
1580 dir_modified: None,
1581 file_count: None,
1582 files: Vec::new(),
1583 error_message: None,
1584 };
1585
1586 if dir_path.is_dir() {
1588 directory_info.dir_exists = Some(true);
1589
1590 match fs::metadata(&dir_path) {
1592 Ok(metadata) => {
1593 if let Ok(created) = metadata.created() {
1594 directory_info.dir_created = Some(
1595 created
1596 .duration_since(UNIX_EPOCH)
1597 .unwrap_or(Duration::ZERO)
1598 .as_secs() as i64,
1599 );
1600 }
1601 if let Ok(modified) = metadata.modified() {
1602 directory_info.dir_modified = Some(
1603 modified
1604 .duration_since(UNIX_EPOCH)
1605 .unwrap_or(Duration::ZERO)
1606 .as_secs() as i64,
1607 );
1608 }
1609 }
1610 Err(error) => {
1611 let msg = format!("Unable to get metadata: {}", error.kind());
1612 directory_info.error_message = Some(msg.clone());
1613 log::warn!("{}", msg);
1614 continue;
1615 }
1616 }
1617
1618 let mut file_count = 0;
1620 let entries = match fs::read_dir(&dir_path) {
1621 Ok(entries) => entries,
1622 Err(error) => {
1623 let msg = format!("Unable to read subdir: {}", error.kind());
1624 directory_info.error_message = Some(msg.clone());
1625 log::warn!("{}", msg);
1626 continue;
1627 }
1628 };
1629 for entry in entries {
1630 directory_info.files.push(
1631 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1632 file_name: None,
1633 file_created: None,
1634 file_modified: None,
1635 file_size: None,
1636 error_message: None,
1637 },
1638 );
1639 let file_info = directory_info.files.last_mut().unwrap();
1641 let entry = match entry {
1642 Ok(entry) => entry,
1643 Err(error) => {
1644 let msg = format!("Unable to read file: {}", error.kind());
1645 file_info.error_message = Some(msg.clone());
1646 log::warn!("{}", msg);
1647 continue;
1648 }
1649 };
1650 let file_name = match entry.file_name().into_string() {
1651 Ok(file_name) => file_name,
1652 _ => {
1653 let msg = "Unable to convert file name to string".to_string();
1654 file_info.error_message = Some(msg.clone());
1655 log::warn!("{}", msg);
1656 continue;
1657 }
1658 };
1659 let metadata = match entry.metadata() {
1660 Ok(metadata) => metadata,
1661 Err(error) => {
1662 let msg = format!("Unable to read file metadata: {}", error.kind());
1663 file_info.file_name = Some(file_name);
1664 file_info.error_message = Some(msg.clone());
1665 log::warn!("{}", msg);
1666 continue;
1667 }
1668 };
1669
1670 if metadata.is_file() {
1672 file_count += 1;
1673
1674 file_info.file_name = Some(file_name);
1676 file_info.file_created = Some(
1677 metadata
1678 .created()
1679 .unwrap_or(UNIX_EPOCH)
1680 .duration_since(UNIX_EPOCH)
1681 .unwrap_or(Duration::ZERO)
1682 .as_secs() as i64,
1683 );
1684 file_info.file_modified = Some(
1685 metadata
1686 .modified()
1687 .unwrap_or(UNIX_EPOCH)
1688 .duration_since(UNIX_EPOCH)
1689 .unwrap_or(Duration::ZERO)
1690 .as_secs() as i64,
1691 );
1692 file_info.file_size = Some(metadata.len() as i64);
1693 } else {
1694 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1695 file_info.file_name = Some(file_name);
1696 file_info.error_message = Some(msg.clone());
1697 log::warn!("{}", msg);
1698 }
1699 }
1700
1701 directory_info.file_count = Some(file_count as i64);
1702 } else {
1703 directory_info.dir_exists = Some(false);
1704 }
1705
1706 directories_info.push(directory_info);
1708 }
1709
1710 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1711 Some(directories_info_json)
1712 } else {
1713 log::error!("Failed to serialize data directory info");
1714 None
1715 }
1716}
1717
1718fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1719 core::with_glean(|glean| {
1720 glean
1721 .health_metrics
1722 .data_directory_info
1723 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1724 glean.internal_pings.health.submit_sync(glean, Some(reason));
1725 });
1726}
1727
1728#[cfg(any(target_os = "android", target_os = "ios"))]
1730pub fn glean_enable_logging_to_fd(_fd: u64) {
1731 }
1733
1734uniffi::include_scaffolding!("glean");
1737
1738type CowString = Cow<'static, str>;
1739
1740uniffi::custom_type!(CowString, String, {
1741 remote,
1742 lower: |s| s.into_owned(),
1743 try_lift: |s| Ok(Cow::from(s))
1744});
1745
1746type JsonValue = serde_json::Value;
1747
1748uniffi::custom_type!(JsonValue, String, {
1749 remote,
1750 lower: |s| serde_json::to_string(&s).unwrap(),
1751 try_lift: |s| Ok(serde_json::from_str(&s)?)
1752});
1753
1754#[cfg(test)]
1758#[path = "lib_unit_tests.rs"]
1759mod tests;