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 #[cfg(feature = "sqlite")]
800 if let Some(database) = &glean.data_store {
801 if let Err(e) = database.cleanup_submitted_pings(None) {
802 log::info!("Could not clean up submitted_pings table: {:?}", e);
803 }
804 if let Err(e) = database.run_maintenance(false) {
805 log::info!("Can't run database maintenance on shutdown: {:?}", e);
806 }
807 }
808
809 glean.close_db();
810 });
811}
812
813pub fn glean_persist_ping_lifetime_data() {
820 crate::launch_with_glean(|glean| {
822 let _ = glean.persist_ping_lifetime_data();
823 });
824}
825
826fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
827 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
828 core_metrics::internal_metrics::app_display_version
829 .set_sync(glean, &client_info.app_display_version[..]);
830 core_metrics::internal_metrics::app_build_date
831 .set_sync(glean, Some(client_info.app_build_date.clone()));
832 if let Some(app_channel) = client_info.channel.as_ref() {
833 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
834 }
835
836 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
837 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
838
839 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
840 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
841 }
842 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
843 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
844 }
845 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
846 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
847 }
848 if let Some(device_model) = client_info.device_model.as_ref() {
849 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
850 }
851 if let Some(locale) = client_info.locale.as_ref() {
852 core_metrics::internal_metrics::locale.set_sync(glean, locale);
853 }
854}
855
856fn was_initialize_called() -> bool {
862 INITIALIZE_CALLED.load(Ordering::SeqCst)
863}
864
865#[no_mangle]
868pub extern "C" fn glean_enable_logging() {
869 #[cfg(target_os = "android")]
870 {
871 let _ = std::panic::catch_unwind(|| {
872 let filter = android_logger::FilterBuilder::new()
873 .filter_module("glean_ffi", log::LevelFilter::Debug)
874 .filter_module("glean_core", log::LevelFilter::Debug)
875 .filter_module("glean", log::LevelFilter::Debug)
876 .filter_module("glean_core::ffi", log::LevelFilter::Info)
877 .build();
878 android_logger::init_once(
879 android_logger::Config::default()
880 .with_max_level(log::LevelFilter::Debug)
881 .with_filter(filter)
882 .with_tag("libglean_ffi"),
883 );
884 log::trace!("Android logging should be hooked up!")
885 });
886 }
887
888 #[cfg(target_os = "ios")]
890 {
891 #[cfg(debug_assertions)]
894 let level = log::LevelFilter::Debug;
895 #[cfg(not(debug_assertions))]
896 let level = log::LevelFilter::Info;
897
898 let logger = oslog::OsLogger::new("org.mozilla.glean")
899 .level_filter(level)
900 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
902
903 match logger.init() {
904 Ok(_) => log::trace!("os_log should be hooked up!"),
905 Err(_) => log::warn!("os_log was already initialized"),
909 };
910 }
911
912 #[cfg(all(
916 not(target_os = "android"),
917 not(target_os = "ios"),
918 feature = "enable_env_logger"
919 ))]
920 {
921 match env_logger::try_init() {
922 Ok(_) => log::trace!("stdout logging should be hooked up!"),
923 Err(_) => log::warn!("stdout logging was already initialized"),
927 };
928 }
929}
930
931pub fn glean_set_upload_enabled(enabled: bool) {
936 if !was_initialize_called() {
937 return;
938 }
939
940 crate::launch_with_glean_mut(move |glean| {
941 let state = global_state().lock().unwrap();
942 let original_enabled = glean.is_upload_enabled();
943
944 if !enabled {
945 glean.cancel_metrics_ping_scheduler();
947 if let Err(e) = state.callbacks.cancel_uploads() {
949 log::error!("Canceling upload failed. Error: {}", e);
950 }
951 }
952
953 glean.set_upload_enabled(enabled);
954
955 if !original_enabled && enabled {
956 initialize_core_metrics(glean, &state.client_info);
957 }
958
959 if original_enabled && !enabled {
960 if let Err(e) = state.callbacks.trigger_upload() {
961 log::error!("Triggering upload failed. Error: {}", e);
962 }
963 }
964 })
965}
966
967pub fn glean_set_collection_enabled(enabled: bool) {
971 glean_set_upload_enabled(enabled)
972}
973
974pub fn glean_set_store_submitted_pings_enabled(enabled: bool) {
976 if !was_initialize_called() {
977 return;
978 }
979
980 launch_with_glean_mut(move |glean| {
981 glean.store_submitted_pings_enabled = enabled;
982 });
983}
984
985pub struct SubmittedPing {
987 pub document_id: String,
989 pub ping: String,
991 pub submitted_date: String,
993 pub uploaded_date: Option<String>,
995 pub upload_failed: Option<String>,
997 pub payload: Option<JsonValue>,
999}
1000
1001#[cfg(feature = "sqlite")]
1002impl From<database::sqlite::SubmittedPing> for SubmittedPing {
1003 fn from(value: database::sqlite::SubmittedPing) -> Self {
1004 SubmittedPing {
1005 document_id: value.document_id.clone(),
1006 ping: value.ping.clone(),
1007 submitted_date: value.submitted_date.0.to_rfc3339(),
1008 uploaded_date: value.uploaded_date.as_ref().map(|d| d.0.to_rfc3339()),
1009 upload_failed: value.upload_failed.as_ref().map(|d| d.0.to_rfc3339()),
1010 payload: value.payload(),
1011 }
1012 }
1013}
1014
1015pub fn glean_get_all_stored_submitted_pings() -> Vec<SubmittedPing> {
1017 #[cfg(feature = "sqlite")]
1018 {
1019 core::with_glean(|glean| glean.storage().get_all_submitted_pings())
1020 .into_iter()
1021 .map(|p| p.into())
1022 .collect()
1023 }
1024
1025 #[cfg(not(feature = "sqlite"))]
1026 Vec::new()
1027}
1028
1029pub fn glean_get_stored_submitted_pings_by_name(ping: String) -> Vec<SubmittedPing> {
1035 #[cfg(feature = "sqlite")]
1036 {
1037 core::with_glean(|glean| glean.storage().get_submitted_pings_by_name(&ping))
1038 .into_iter()
1039 .map(|p| p.into())
1040 .collect()
1041 }
1042
1043 #[cfg(not(feature = "sqlite"))]
1044 {
1045 _ = ping;
1046 Vec::new()
1047 }
1048}
1049
1050pub fn glean_clear_stored_submitted_pings() {
1052 #[cfg(feature = "sqlite")]
1053 launch_with_glean(|glean| {
1054 if let Err(e) = glean
1055 .storage()
1056 .cleanup_submitted_pings(Some(chrono::Utc::now()))
1057 {
1058 log::warn!("Unable to clear stored submitted pings: {:?}", e);
1059 }
1060 });
1061}
1062
1063pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
1068 let ping = ping.clone();
1069 if was_initialize_called() && core::global_glean().is_some() {
1070 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
1071 } else {
1072 let m = &PRE_INIT_PING_ENABLED;
1073 let mut lock = m.lock().unwrap();
1074 lock.push((ping, enabled));
1075 }
1076}
1077
1078pub(crate) fn register_ping_type(ping: &PingType) {
1080 if was_initialize_called() && core::global_glean().is_some() {
1085 let ping = ping.clone();
1086 crate::launch_with_glean_mut(move |glean| {
1087 glean.register_ping_type(&ping);
1088 })
1089 } else {
1090 let m = &PRE_INIT_PING_REGISTRATION;
1095 let mut lock = m.lock().unwrap();
1096 lock.push(ping.clone());
1097 }
1098}
1099
1100pub fn glean_get_registered_ping_names() -> Vec<String> {
1106 block_on_dispatcher();
1107 core::with_glean(|glean| {
1108 glean
1109 .get_registered_ping_names()
1110 .iter()
1111 .map(|ping| ping.to_string())
1112 .collect()
1113 })
1114}
1115
1116pub fn glean_set_experiment_active(
1122 experiment_id: String,
1123 branch: String,
1124 extra: HashMap<String, String>,
1125) {
1126 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1127}
1128
1129pub fn glean_set_experiment_inactive(experiment_id: String) {
1133 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1134}
1135
1136pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1140 block_on_dispatcher();
1141 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1142}
1143
1144pub fn glean_set_experimentation_id(experimentation_id: String) {
1148 launch_with_glean(move |glean| {
1149 glean
1150 .additional_metrics
1151 .experimentation_id
1152 .set(experimentation_id);
1153 });
1154}
1155
1156pub fn glean_test_get_experimentation_id() -> Option<String> {
1159 block_on_dispatcher();
1160 core::with_glean(|glean| glean.test_get_experimentation_id())
1161}
1162
1163pub fn glean_apply_server_knobs_config(json: String) {
1168 if json.is_empty() {
1171 return;
1172 }
1173
1174 match RemoteSettingsConfig::try_from(json) {
1175 Ok(cfg) => launch_with_glean(|glean| {
1176 glean.apply_server_knobs_config(cfg);
1177 }),
1178 Err(e) => {
1179 log::error!("Error setting metrics feature config: {:?}", e);
1180 }
1181 }
1182}
1183
1184pub fn glean_set_debug_view_tag(tag: String) -> bool {
1198 if was_initialize_called() && core::global_glean().is_some() {
1199 crate::launch_with_glean_mut(move |glean| {
1200 glean.set_debug_view_tag(&tag);
1201 });
1202 true
1203 } else {
1204 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1206 let mut lock = m.lock().unwrap();
1207 *lock = tag;
1208 true
1211 }
1212}
1213
1214pub fn glean_get_debug_view_tag() -> Option<String> {
1220 block_on_dispatcher();
1221 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1222}
1223
1224pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1236 if was_initialize_called() && core::global_glean().is_some() {
1237 crate::launch_with_glean_mut(|glean| {
1238 glean.set_source_tags(tags);
1239 });
1240 true
1241 } else {
1242 let m = &PRE_INIT_SOURCE_TAGS;
1244 let mut lock = m.lock().unwrap();
1245 *lock = tags;
1246 true
1249 }
1250}
1251
1252pub fn glean_set_log_pings(value: bool) {
1261 if was_initialize_called() && core::global_glean().is_some() {
1262 crate::launch_with_glean_mut(move |glean| {
1263 glean.set_log_pings(value);
1264 });
1265 } else {
1266 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1267 }
1268}
1269
1270pub fn glean_get_log_pings() -> bool {
1276 block_on_dispatcher();
1277 core::with_glean(|glean| glean.log_pings())
1278}
1279
1280pub fn glean_handle_client_active() {
1287 dispatcher::launch(|| {
1288 core::with_glean_mut(|glean| {
1289 glean.handle_client_active();
1290 });
1291
1292 let state = global_state().lock().unwrap();
1296 if let Err(e) = state.callbacks.trigger_upload() {
1297 log::error!("Triggering upload failed. Error: {}", e);
1298 }
1299 });
1300
1301 core_metrics::internal_metrics::baseline_duration.start();
1306}
1307
1308pub fn glean_handle_client_inactive() {
1315 core_metrics::internal_metrics::baseline_duration.stop();
1319
1320 dispatcher::launch(|| {
1321 core::with_glean_mut(|glean| {
1322 glean.handle_client_inactive();
1323 });
1324
1325 let state = global_state().lock().unwrap();
1329 if let Err(e) = state.callbacks.trigger_upload() {
1330 log::error!("Triggering upload failed. Error: {}", e);
1331 }
1332 })
1333}
1334
1335pub fn glean_session_start() {
1340 launch_with_glean_mut(|glean| {
1341 if glean.session_manager.mode == session::SessionMode::Manual {
1342 glean.session_start();
1343 }
1344 });
1345}
1346
1347pub fn glean_session_end(reason: Option<String>) {
1355 launch_with_glean_mut(move |glean| {
1356 if glean.session_manager.mode == session::SessionMode::Manual {
1357 glean.session_end(reason.as_deref());
1358 }
1359 });
1360}
1361
1362pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1364 dispatcher::launch(|| {
1365 let sent =
1366 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1367
1368 if sent {
1369 let state = global_state().lock().unwrap();
1370 if let Err(e) = state.callbacks.trigger_upload() {
1371 log::error!("Triggering upload failed. Error: {}", e);
1372 }
1373 }
1374 })
1375}
1376
1377pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1381 if !was_initialize_called() {
1382 return false;
1383 }
1384
1385 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1386 .unwrap_or(false)
1387}
1388
1389pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1396 register_event_listener(tag, listener);
1397}
1398
1399pub fn glean_unregister_event_listener(tag: String) {
1407 unregister_event_listener(tag);
1408}
1409
1410pub fn glean_set_test_mode(enabled: bool) {
1414 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1415}
1416
1417pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1421 if was_initialize_called() {
1422 join_init();
1424
1425 dispatcher::reset_dispatcher();
1426
1427 let has_storage = core::with_opt_glean(|glean| {
1430 glean
1432 .storage_opt()
1433 .map(|storage| storage.persist_ping_lifetime_data())
1434 .is_some()
1435 })
1436 .unwrap_or(false);
1437 if has_storage {
1438 uploader_shutdown();
1439 }
1440
1441 if core::global_glean().is_some() {
1442 core::with_glean_mut(|glean| {
1443 if clear_stores {
1444 glean.test_clear_all_stores()
1445 }
1446 glean.close_db()
1447 });
1448 }
1449
1450 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1452 } else if clear_stores {
1453 if let Some(data_path) = data_path {
1454 let _ = std::fs::remove_dir_all(data_path).ok();
1455 } else {
1456 log::warn!("Asked to clear stores before initialization, but no data path given.");
1457 }
1458 }
1459}
1460
1461pub fn glean_get_upload_task() -> PingUploadTask {
1463 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1464}
1465
1466pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1468 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1469}
1470
1471pub fn glean_set_dirty_flag(new_value: bool) {
1475 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1476}
1477
1478pub fn glean_clear_attribution() {
1481 if was_initialize_called() && core::global_glean().is_some() {
1482 core::with_glean(|glean| glean.clear_attribution());
1483 } else {
1484 PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1485 _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1486 }
1487}
1488
1489pub fn glean_update_attribution(attribution: AttributionMetrics) {
1492 if was_initialize_called() && core::global_glean().is_some() {
1493 core::with_glean(|glean| glean.update_attribution(attribution));
1494 } else {
1495 PRE_INIT_ATTRIBUTION
1496 .lock()
1497 .unwrap()
1498 .get_or_insert(Default::default())
1499 .update(attribution);
1500 }
1501}
1502
1503pub fn glean_test_get_attribution() -> AttributionMetrics {
1508 join_init();
1509 core::with_glean(|glean| glean.test_get_attribution())
1510}
1511
1512pub fn glean_clear_distribution() {
1515 if was_initialize_called() && core::global_glean().is_some() {
1516 core::with_glean(|glean| glean.clear_distribution());
1517 } else {
1518 PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1519 _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1520 }
1521}
1522
1523pub fn glean_update_distribution(distribution: DistributionMetrics) {
1526 if was_initialize_called() && core::global_glean().is_some() {
1527 core::with_glean(|glean| glean.update_distribution(distribution));
1528 } else {
1529 PRE_INIT_DISTRIBUTION
1530 .lock()
1531 .unwrap()
1532 .get_or_insert(Default::default())
1533 .update(distribution);
1534 }
1535}
1536
1537pub fn glean_test_get_distribution() -> DistributionMetrics {
1542 join_init();
1543 core::with_glean(|glean| glean.test_get_distribution())
1544}
1545
1546#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1547static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1548
1549#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1562pub fn glean_enable_logging_to_fd(fd: u64) {
1563 unsafe {
1569 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1574 if log::set_logger(logger).is_ok() {
1578 log::set_max_level(log::LevelFilter::Debug);
1579 }
1580 }
1581}
1582
1583fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1585 let subdirs = ["db", "events", "pending_pings"];
1587 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1588 DataDirectoryInfoObject::with_capacity(subdirs.len());
1589
1590 for subdir in subdirs.iter() {
1591 let dir_path = path.join(subdir);
1592
1593 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1595 dir_name: Some(subdir.to_string()),
1596 dir_exists: None,
1597 dir_created: None,
1598 dir_modified: None,
1599 file_count: None,
1600 files: Vec::new(),
1601 error_message: None,
1602 };
1603
1604 if dir_path.is_dir() {
1606 directory_info.dir_exists = Some(true);
1607
1608 match fs::metadata(&dir_path) {
1610 Ok(metadata) => {
1611 if let Ok(created) = metadata.created() {
1612 directory_info.dir_created = Some(
1613 created
1614 .duration_since(UNIX_EPOCH)
1615 .unwrap_or(Duration::ZERO)
1616 .as_secs() as i64,
1617 );
1618 }
1619 if let Ok(modified) = metadata.modified() {
1620 directory_info.dir_modified = Some(
1621 modified
1622 .duration_since(UNIX_EPOCH)
1623 .unwrap_or(Duration::ZERO)
1624 .as_secs() as i64,
1625 );
1626 }
1627 }
1628 Err(error) => {
1629 let msg = format!("Unable to get metadata: {}", error.kind());
1630 directory_info.error_message = Some(msg.clone());
1631 log::warn!("{}", msg);
1632 continue;
1633 }
1634 }
1635
1636 let mut file_count = 0;
1638 let entries = match fs::read_dir(&dir_path) {
1639 Ok(entries) => entries,
1640 Err(error) => {
1641 let msg = format!("Unable to read subdir: {}", error.kind());
1642 directory_info.error_message = Some(msg.clone());
1643 log::warn!("{}", msg);
1644 continue;
1645 }
1646 };
1647 for entry in entries {
1648 directory_info.files.push(
1649 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1650 file_name: None,
1651 file_created: None,
1652 file_modified: None,
1653 file_size: None,
1654 error_message: None,
1655 },
1656 );
1657 let file_info = directory_info.files.last_mut().unwrap();
1659 let entry = match entry {
1660 Ok(entry) => entry,
1661 Err(error) => {
1662 let msg = format!("Unable to read file: {}", error.kind());
1663 file_info.error_message = Some(msg.clone());
1664 log::warn!("{}", msg);
1665 continue;
1666 }
1667 };
1668 let file_name = match entry.file_name().into_string() {
1669 Ok(file_name) => file_name,
1670 _ => {
1671 let msg = "Unable to convert file name to string".to_string();
1672 file_info.error_message = Some(msg.clone());
1673 log::warn!("{}", msg);
1674 continue;
1675 }
1676 };
1677 let metadata = match entry.metadata() {
1678 Ok(metadata) => metadata,
1679 Err(error) => {
1680 let msg = format!("Unable to read file metadata: {}", error.kind());
1681 file_info.file_name = Some(file_name);
1682 file_info.error_message = Some(msg.clone());
1683 log::warn!("{}", msg);
1684 continue;
1685 }
1686 };
1687
1688 if metadata.is_file() {
1690 file_count += 1;
1691
1692 file_info.file_name = Some(file_name);
1694 file_info.file_created = Some(
1695 metadata
1696 .created()
1697 .unwrap_or(UNIX_EPOCH)
1698 .duration_since(UNIX_EPOCH)
1699 .unwrap_or(Duration::ZERO)
1700 .as_secs() as i64,
1701 );
1702 file_info.file_modified = Some(
1703 metadata
1704 .modified()
1705 .unwrap_or(UNIX_EPOCH)
1706 .duration_since(UNIX_EPOCH)
1707 .unwrap_or(Duration::ZERO)
1708 .as_secs() as i64,
1709 );
1710 file_info.file_size = Some(metadata.len() as i64);
1711 } else {
1712 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1713 file_info.file_name = Some(file_name);
1714 file_info.error_message = Some(msg.clone());
1715 log::warn!("{}", msg);
1716 }
1717 }
1718
1719 directory_info.file_count = Some(file_count as i64);
1720 } else {
1721 directory_info.dir_exists = Some(false);
1722 }
1723
1724 directories_info.push(directory_info);
1726 }
1727
1728 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1729 Some(directories_info_json)
1730 } else {
1731 log::error!("Failed to serialize data directory info");
1732 None
1733 }
1734}
1735
1736fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1737 core::with_glean(|glean| {
1738 glean
1739 .health_metrics
1740 .data_directory_info
1741 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1742 glean.internal_pings.health.submit_sync(glean, Some(reason));
1743 });
1744}
1745
1746#[cfg(any(target_os = "android", target_os = "ios"))]
1748pub fn glean_enable_logging_to_fd(_fd: u64) {
1749 }
1751
1752uniffi::include_scaffolding!("glean");
1755
1756type CowString = Cow<'static, str>;
1757
1758uniffi::custom_type!(CowString, String, {
1759 remote,
1760 lower: |s| s.into_owned(),
1761 try_lift: |s| Ok(Cow::from(s))
1762});
1763
1764type JsonValue = serde_json::Value;
1765
1766uniffi::custom_type!(JsonValue, String, {
1767 remote,
1768 lower: |s| serde_json::to_string(&s).unwrap(),
1769 try_lift: |s| Ok(serde_json::from_str(&s)?)
1770});
1771
1772#[cfg(test)]
1776#[path = "lib_unit_tests.rs"]
1777mod tests;