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 chrono::{DateTime, Utc};
29use crossbeam_channel::unbounded;
30use log::LevelFilter;
31use malloc_size_of_derive::MallocSizeOf;
32use once_cell::sync::{Lazy, OnceCell};
33use uuid::Uuid;
34
35use metrics::RemoteSettingsConfig;
36
37mod common_metric_data;
38mod core;
39mod core_metrics;
40mod database;
41mod debug;
42#[cfg(feature = "benchmark")]
43#[doc(hidden)]
44pub mod dispatcher;
45#[cfg(not(feature = "benchmark"))]
46mod dispatcher;
47mod error;
48mod error_recording;
49mod event_database;
50mod glean_metrics;
51mod histogram;
52mod internal_metrics;
53mod internal_pings;
54pub mod metrics;
55pub mod ping;
56mod scheduler;
57pub(crate) mod session;
58pub mod storage;
59mod system;
60#[doc(hidden)]
61pub mod thread;
62pub mod traits;
63pub mod upload;
64mod util;
65
66#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
67mod fd_logger;
68
69pub use crate::common_metric_data::{CommonMetricData, Lifetime, MetricLabel};
70pub use crate::core::Glean;
71pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
72use crate::database::StoredSubmittedPingHandler;
73use crate::dispatcher::is_test_mode;
74pub use crate::error::{Error, ErrorKind, Result};
75pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
76pub use crate::histogram::HistogramType;
77use crate::internal_metrics::DataDirectoryInfoObject;
78pub use crate::metrics::labeled::{
79 AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
80 LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
81 LabeledTimingDistribution,
82};
83pub use crate::metrics::{
84 BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
85 DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
86 LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
87 MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
88 Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
89 TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
90 UrlMetric, UuidMetric,
91};
92pub use crate::session::{SessionManager, SessionMetadata, SessionMode};
93pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
94
95const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
96const GLEAN_SCHEMA_VERSION: u32 = 1;
97const DEFAULT_MAX_EVENTS: u32 = 500;
98static KNOWN_CLIENT_ID: Lazy<Uuid> =
99 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
100
101pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
103pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
104
105static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
109
110static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
112static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
113static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
114
115static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
117static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
118
119static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
121static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
122static PRE_INIT_ATTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
123static PRE_INIT_DISTRIBUTION_CLEARED: AtomicBool = AtomicBool::new(false);
124
125static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
129 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
130
131#[derive(Debug, Clone, MallocSizeOf)]
133pub struct InternalConfiguration {
134 pub upload_enabled: bool,
136 pub data_path: String,
138 pub application_id: String,
140 pub language_binding_name: String,
142 pub max_events: Option<u32>,
144 pub delay_ping_lifetime_io: bool,
146 pub app_build: String,
149 pub use_core_mps: bool,
151 pub trim_data_to_registered_pings: bool,
153 #[ignore_malloc_size_of = "external non-allocating type"]
156 pub log_level: Option<LevelFilter>,
157 pub rate_limit: Option<PingRateLimit>,
159 pub enable_event_timestamps: bool,
161 pub experimentation_id: Option<String>,
165 pub enable_internal_pings: bool,
167 pub ping_schedule: HashMap<String, Vec<String>>,
171
172 pub ping_lifetime_threshold: u64,
174 pub ping_lifetime_max_time: u64,
176 pub max_pending_pings_count: Option<u64>,
178 pub max_pending_pings_directory_size: Option<u64>,
180 pub session_mode: session::SessionMode,
182 pub session_sample_rate: f64,
184 pub session_inactivity_timeout_ms: u64,
187 pub events_ping_acceleration_factor: Option<u32>,
189 pub enable_store_submitted_pings: bool,
191}
192
193#[derive(Debug, Clone, MallocSizeOf)]
195pub struct PingRateLimit {
196 pub seconds_per_interval: u64,
198 pub pings_per_interval: u32,
200}
201
202fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
204 dispatcher::launch(|| core::with_glean(callback));
205}
206
207fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
210 dispatcher::launch(|| core::with_glean_mut(callback));
211}
212
213fn block_on_dispatcher() {
217 dispatcher::block_on_queue()
218}
219
220pub fn get_awake_timestamp_ms() -> u64 {
222 const NANOS_PER_MILLI: u64 = 1_000_000;
223 zeitstempel::now_awake() / NANOS_PER_MILLI
224}
225
226pub fn get_timestamp_ms() -> u64 {
228 const NANOS_PER_MILLI: u64 = 1_000_000;
229 zeitstempel::now() / NANOS_PER_MILLI
230}
231
232struct State {
237 client_info: ClientInfoMetrics,
239
240 callbacks: Box<dyn OnGleanEvents>,
241}
242
243static STATE: OnceCell<Mutex<State>> = OnceCell::new();
247
248#[track_caller] fn global_state() -> &'static Mutex<State> {
253 STATE.get().unwrap()
254}
255
256#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
261 STATE.get()
262}
263
264fn setup_state(state: State) {
266 if STATE.get().is_none() {
276 if STATE.set(Mutex::new(state)).is_err() {
277 log::error!(
278 "Global Glean state object is initialized already. This probably happened concurrently."
279 );
280 }
281 } else {
282 let mut lock = STATE.get().unwrap().lock().unwrap();
286 *lock = state;
287 }
288}
289
290static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
293 OnceCell::new();
294
295fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
296 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
297}
298
299fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
300 let mut lock = event_listeners().lock().unwrap();
301 lock.insert(tag, listener);
302}
303
304fn unregister_event_listener(tag: String) {
305 let mut lock = event_listeners().lock().unwrap();
306 lock.remove(&tag);
307}
308
309#[derive(Debug)]
311pub enum CallbackError {
312 UnexpectedError,
314}
315
316impl fmt::Display for CallbackError {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 write!(f, "Unexpected error")
319 }
320}
321
322impl std::error::Error for CallbackError {}
323
324impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
325 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
326 CallbackError::UnexpectedError
327 }
328}
329
330pub trait OnGleanEvents: Send {
334 fn initialize_finished(&self);
340
341 fn trigger_upload(&self) -> Result<(), CallbackError>;
346
347 fn start_metrics_ping_scheduler(&self) -> bool;
349
350 fn cancel_uploads(&self) -> Result<(), CallbackError>;
352
353 fn shutdown(&self) -> Result<(), CallbackError> {
360 Ok(())
362 }
363}
364
365pub trait GleanEventListener: Send {
368 fn on_event_recorded(&self, id: String);
370}
371
372pub fn glean_initialize(
381 cfg: InternalConfiguration,
382 client_info: ClientInfoMetrics,
383 callbacks: Box<dyn OnGleanEvents>,
384) {
385 initialize_inner(cfg, client_info, callbacks);
386}
387
388pub fn glean_shutdown() {
390 shutdown();
391}
392
393pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
398 let glean = match Glean::new_for_subprocess(&cfg, true) {
399 Ok(glean) => glean,
400 Err(err) => {
401 log::error!("Failed to initialize Glean: {}", err);
402 return false;
403 }
404 };
405 if core::setup_glean(glean).is_err() {
406 return false;
407 }
408 log::info!("Glean initialized for subprocess");
409 true
410}
411
412fn initialize_inner(
413 cfg: InternalConfiguration,
414 client_info: ClientInfoMetrics,
415 callbacks: Box<dyn OnGleanEvents>,
416) {
417 if was_initialize_called() {
418 log::error!("Glean should not be initialized multiple times");
419 return;
420 }
421
422 let init_handle = thread::spawn("glean.init", move || {
423 let upload_enabled = cfg.upload_enabled;
424 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
425
426 if let Some(level) = cfg.log_level {
428 log::set_max_level(level)
429 }
430
431 let data_path_str = cfg.data_path.clone();
432 let data_path = Path::new(&data_path_str);
433 let internal_pings_enabled = cfg.enable_internal_pings;
434 let dir_info = if !is_test_mode() && internal_pings_enabled {
435 collect_directory_info(Path::new(&data_path))
436 } else {
437 None
438 };
439
440 let glean = match Glean::new(cfg) {
441 Ok(glean) => glean,
442 Err(err) => {
443 log::error!("Failed to initialize Glean: {}", err);
444 return;
445 }
446 };
447 if core::setup_glean(glean).is_err() {
448 return;
449 }
450
451 log::info!("Glean initialized");
452
453 core::with_glean(|glean| {
454 glean.health_metrics.init_count.add_sync(glean, 1);
455 });
456
457 setup_state(State {
458 client_info,
459 callbacks,
460 });
461
462 let mut is_first_run = false;
463 let mut dirty_flag = false;
464 let mut pings_submitted = false;
465 core::with_glean_mut(|glean| {
466 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
469 if !debug_tag.is_empty() {
470 glean.set_debug_view_tag(&debug_tag);
471 }
472
473 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
476 if log_pigs {
477 glean.set_log_pings(log_pigs);
478 }
479
480 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
483 if !source_tags.is_empty() {
484 glean.set_source_tags(source_tags.to_vec());
485 }
486
487 dirty_flag = glean.is_dirty_flag_set();
492 glean.set_dirty_flag(false);
493
494 if dirty_flag {
498 glean.recover_session_on_dirty_flag();
499 }
500
501 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
504 for ping in pings.iter() {
505 glean.register_ping_type(ping);
506 }
507 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
508 for (ping, enabled) in pings.iter() {
509 glean.set_ping_enabled(ping, *enabled);
510 }
511
512 let clear_attribution = PRE_INIT_ATTRIBUTION_CLEARED.load(Ordering::SeqCst);
515 if clear_attribution {
516 glean.clear_attribution();
517 }
518 let clear_distribution = PRE_INIT_DISTRIBUTION_CLEARED.load(Ordering::SeqCst);
519 if clear_distribution {
520 glean.clear_distribution();
521 }
522 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
523 glean.update_attribution(attribution);
524 }
525 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
526 glean.update_distribution(distribution);
527 }
528
529 is_first_run = glean.is_first_run();
533 if is_first_run {
534 let state = global_state().lock().unwrap();
535 initialize_core_metrics(glean, &state.client_info);
536 }
537
538 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
540 });
541
542 {
543 let state = global_state().lock().unwrap();
544 if pings_submitted || !upload_enabled {
548 if let Err(e) = state.callbacks.trigger_upload() {
549 log::error!("Triggering upload failed. Error: {}", e);
550 }
551 }
552 }
553
554 core::with_glean(|glean| {
555 glean.start_metrics_ping_scheduler();
557 });
558
559 {
567 let state = global_state().lock().unwrap();
568
569 if state.callbacks.start_metrics_ping_scheduler() {
573 if let Err(e) = state.callbacks.trigger_upload() {
574 log::error!("Triggering upload failed. Error: {}", e);
575 }
576 }
577 }
578
579 core::with_glean_mut(|glean| {
580 let state = global_state().lock().unwrap();
581
582 if !is_first_run && dirty_flag {
586 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
592 if let Err(e) = state.callbacks.trigger_upload() {
593 log::error!("Triggering upload failed. Error: {}", e);
594 }
595 }
596 }
597
598 if !is_first_run {
602 glean.clear_application_lifetime_metrics();
603 initialize_core_metrics(glean, &state.client_info);
604 }
605 });
606
607 let submit_health_ping = !is_test_mode() && internal_pings_enabled;
618 let flushed = dispatcher::flush_init(move |overflow_count| {
619 let sent = core::with_glean(|glean| {
620 if overflow_count > 0 {
622 glean_metrics::error::preinit_tasks_overflow
623 .add_sync(glean, overflow_count as i32);
624 }
625
626 submit_health_ping
629 && record_dir_info_and_submit_health_ping(glean, dir_info, "pre_init")
630 });
631
632 if sent {
635 let state = global_state().lock().unwrap();
636 if let Err(e) = state.callbacks.trigger_upload() {
637 log::error!("Triggering upload failed. Error: {}", e);
638 }
639 }
640 });
641 if let Err(err) = flushed {
642 log::error!("Unable to flush the preinit queue: {}", err);
643 }
644
645 let state = global_state().lock().unwrap();
646 state.callbacks.initialize_finished();
647 })
648 .expect("Failed to spawn Glean's init thread");
649
650 INIT_HANDLES.lock().unwrap().push(init_handle);
652
653 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
656
657 if dispatcher::global::is_test_mode() {
660 join_init();
661 }
662}
663
664pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
668 use malloc_size_of::MallocSizeOf;
669 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
670}
671
672pub fn join_init() {
675 let mut handles = INIT_HANDLES.lock().unwrap();
676 for handle in handles.drain(..) {
677 handle.join().unwrap();
678 }
679}
680
681fn uploader_shutdown() {
689 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
690 let (tx, rx) = unbounded();
691
692 let handle = thread::spawn("glean.shutdown", move || {
693 let state = global_state().lock().unwrap();
694 if let Err(e) = state.callbacks.shutdown() {
695 log::error!("Shutdown callback failed: {e:?}");
696 }
697
698 let _ = tx.send(()).ok();
700 })
701 .expect("Unable to spawn thread to wait on shutdown");
702
703 let result = rx.recv_timeout(Duration::from_secs(30));
711
712 let stop_time = zeitstempel::now_awake();
713 core::with_glean(|glean| {
714 glean
715 .additional_metrics
716 .shutdown_wait
717 .set_stop_and_accumulate(glean, timer_id, stop_time);
718 });
719
720 if result.is_err() {
721 log::warn!("Waiting for upload failed. We're shutting down.");
722 } else {
723 let _ = handle.join().ok();
724 }
725}
726
727pub fn shutdown() {
729 if !was_initialize_called() {
739 log::warn!("Shutdown called before Glean is initialized");
740 if let Err(e) = dispatcher::kill() {
741 log::error!("Can't kill dispatcher thread: {:?}", e);
742 }
743 return;
744 }
745
746 if core::global_glean().is_none() {
748 log::warn!("Shutdown called before Glean is initialized. Waiting.");
749 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
757 }
758 if core::global_glean().is_none() {
760 log::warn!("Waiting for Glean initialization timed out. Exiting.");
761 if let Err(e) = dispatcher::kill() {
762 log::error!("Can't kill dispatcher thread: {:?}", e);
763 }
764 return;
765 }
766
767 crate::launch_with_glean_mut(|glean| {
769 glean.cancel_metrics_ping_scheduler();
770 glean.set_dirty_flag(false);
771 });
772
773 let timer_id = core::with_glean(|glean| {
780 glean
781 .additional_metrics
782 .shutdown_dispatcher_wait
783 .start_sync()
784 });
785 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
786
787 let stop_time = zeitstempel::now_awake();
789 core::with_glean(|glean| {
790 glean
791 .additional_metrics
792 .shutdown_dispatcher_wait
793 .set_stop_and_accumulate(glean, timer_id, stop_time);
794 });
795 if blocked.is_err() {
796 log::error!(
797 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
798 );
799 return;
800 }
801
802 if let Err(e) = dispatcher::shutdown() {
803 log::error!("Can't shutdown dispatcher thread: {:?}", e);
804 }
805
806 uploader_shutdown();
807
808 core::with_glean_mut(|glean| {
810 if let Err(e) = glean.persist_ping_lifetime_data() {
811 log::info!("Can't persist ping lifetime data: {:?}", e);
812 }
813
814 #[cfg(feature = "sqlite")]
815 if let Some(database) = &glean.data_store {
816 if let Err(e) = database.cleanup_submitted_pings(None) {
817 log::info!("Could not clean up submitted_pings table: {:?}", e);
818 }
819 if let Err(e) = database.run_maintenance(false) {
820 log::info!("Can't run database maintenance on shutdown: {:?}", e);
821 }
822 }
823
824 glean.close_db();
825 });
826}
827
828pub fn glean_persist_ping_lifetime_data() {
835 crate::launch_with_glean(|glean| {
837 let _ = glean.persist_ping_lifetime_data();
838 });
839}
840
841fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
842 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
843 core_metrics::internal_metrics::app_display_version
844 .set_sync(glean, &client_info.app_display_version[..]);
845 core_metrics::internal_metrics::app_build_date
846 .set_sync(glean, Some(client_info.app_build_date.clone()));
847 if let Some(app_channel) = client_info.channel.as_ref() {
848 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
849 }
850
851 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
852 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
853
854 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
855 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
856 }
857 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
858 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
859 }
860 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
861 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
862 }
863 if let Some(device_model) = client_info.device_model.as_ref() {
864 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
865 }
866 if let Some(locale) = client_info.locale.as_ref() {
867 core_metrics::internal_metrics::locale.set_sync(glean, locale);
868 }
869}
870
871fn was_initialize_called() -> bool {
877 INITIALIZE_CALLED.load(Ordering::SeqCst)
878}
879
880#[no_mangle]
883pub extern "C" fn glean_enable_logging() {
884 #[cfg(target_os = "android")]
885 {
886 let _ = std::panic::catch_unwind(|| {
887 let filter = android_logger::FilterBuilder::new()
888 .filter_module("glean_ffi", log::LevelFilter::Debug)
889 .filter_module("glean_core", log::LevelFilter::Debug)
890 .filter_module("glean", log::LevelFilter::Debug)
891 .filter_module("glean_core::ffi", log::LevelFilter::Info)
892 .build();
893 android_logger::init_once(
894 android_logger::Config::default()
895 .with_max_level(log::LevelFilter::Debug)
896 .with_filter(filter)
897 .with_tag("libglean_ffi"),
898 );
899 log::trace!("Android logging should be hooked up!")
900 });
901 }
902
903 #[cfg(target_os = "ios")]
905 {
906 #[cfg(debug_assertions)]
909 let level = log::LevelFilter::Debug;
910 #[cfg(not(debug_assertions))]
911 let level = log::LevelFilter::Info;
912
913 let logger = oslog::OsLogger::new("org.mozilla.glean")
914 .level_filter(level)
915 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
917
918 match logger.init() {
919 Ok(_) => log::trace!("os_log should be hooked up!"),
920 Err(_) => log::warn!("os_log was already initialized"),
924 };
925 }
926
927 #[cfg(all(
931 not(target_os = "android"),
932 not(target_os = "ios"),
933 feature = "enable_env_logger"
934 ))]
935 {
936 match env_logger::try_init() {
937 Ok(_) => log::trace!("stdout logging should be hooked up!"),
938 Err(_) => log::warn!("stdout logging was already initialized"),
942 };
943 }
944}
945
946pub fn glean_set_upload_enabled(enabled: bool) {
951 if !was_initialize_called() {
952 return;
953 }
954
955 crate::launch_with_glean_mut(move |glean| {
956 let state = global_state().lock().unwrap();
957 let original_enabled = glean.is_upload_enabled();
958
959 if !enabled {
960 glean.cancel_metrics_ping_scheduler();
962 if let Err(e) = state.callbacks.cancel_uploads() {
964 log::error!("Canceling upload failed. Error: {}", e);
965 }
966 }
967
968 glean.set_upload_enabled(enabled);
969
970 if !original_enabled && enabled {
971 initialize_core_metrics(glean, &state.client_info);
972 }
973
974 if original_enabled && !enabled {
975 if let Err(e) = state.callbacks.trigger_upload() {
976 log::error!("Triggering upload failed. Error: {}", e);
977 }
978 }
979 })
980}
981
982pub fn glean_set_collection_enabled(enabled: bool) {
986 glean_set_upload_enabled(enabled)
987}
988
989pub fn glean_set_store_submitted_pings_enabled(enabled: bool) {
991 if !was_initialize_called() {
992 return;
993 }
994
995 launch_with_glean_mut(move |glean| {
996 glean.store_submitted_pings_enabled = enabled;
997 });
998}
999
1000#[derive(Clone)]
1002pub struct SubmittedPing {
1003 pub document_id: String,
1005 pub ping: String,
1007 pub submitted_date: String,
1009 pub uploaded_date: Option<String>,
1011 pub upload_failed: Option<String>,
1013 pub payload: Option<JsonValue>,
1015}
1016
1017impl SubmittedPing {
1018 pub fn submitted_date(&self) -> DateTime<Utc> {
1020 DateTime::parse_from_rfc3339(&self.submitted_date)
1021 .map(|d| d.to_utc())
1022 .unwrap()
1023 }
1024
1025 pub fn uploaded_date(&self) -> Option<DateTime<Utc>> {
1027 self.uploaded_date.as_ref().map(|uploaded_date| {
1028 DateTime::parse_from_rfc3339(uploaded_date)
1029 .map(|d| d.to_utc())
1030 .unwrap()
1031 })
1032 }
1033
1034 pub fn upload_failed(&self) -> Option<DateTime<Utc>> {
1036 self.upload_failed.as_ref().map(|upload_failed| {
1037 DateTime::parse_from_rfc3339(upload_failed)
1038 .map(|d| d.to_utc())
1039 .unwrap()
1040 })
1041 }
1042}
1043
1044#[cfg(feature = "sqlite")]
1045impl From<database::sqlite::SubmittedPing> for SubmittedPing {
1046 fn from(value: database::sqlite::SubmittedPing) -> Self {
1047 SubmittedPing {
1048 document_id: value.document_id.clone(),
1049 ping: value.ping.clone(),
1050 submitted_date: value.submitted_date.0.to_rfc3339(),
1051 uploaded_date: value.uploaded_date.as_ref().map(|d| d.0.to_rfc3339()),
1052 upload_failed: value.upload_failed.as_ref().map(|d| d.0.to_rfc3339()),
1053 payload: value.payload(),
1054 }
1055 }
1056}
1057
1058pub fn glean_get_all_stored_submitted_pings() -> Vec<SubmittedPing> {
1060 block_on_dispatcher();
1061 core::with_glean(|glean| glean.storage().get_all_submitted_pings())
1062}
1063
1064pub fn glean_get_stored_submitted_pings_by_name(ping: String) -> Vec<SubmittedPing> {
1070 block_on_dispatcher();
1071 core::with_glean(|glean| glean.storage().get_submitted_pings_by_name(&ping))
1072}
1073
1074pub fn glean_clear_stored_submitted_pings() {
1076 launch_with_glean(|glean| {
1077 if let Err(e) = glean
1078 .storage()
1079 .cleanup_submitted_pings(Some(chrono::Utc::now()))
1080 {
1081 log::warn!("Unable to clear stored submitted pings: {:?}", e);
1082 }
1083 });
1084}
1085
1086pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
1091 let ping = ping.clone();
1092 if was_initialize_called() && core::global_glean().is_some() {
1093 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
1094 } else {
1095 let m = &PRE_INIT_PING_ENABLED;
1096 let mut lock = m.lock().unwrap();
1097 lock.push((ping, enabled));
1098 }
1099}
1100
1101pub(crate) fn register_ping_type(ping: &PingType) {
1103 if was_initialize_called() && core::global_glean().is_some() {
1108 let ping = ping.clone();
1109 crate::launch_with_glean_mut(move |glean| {
1110 glean.register_ping_type(&ping);
1111 })
1112 } else {
1113 let m = &PRE_INIT_PING_REGISTRATION;
1118 let mut lock = m.lock().unwrap();
1119 lock.push(ping.clone());
1120 }
1121}
1122
1123pub fn glean_get_registered_ping_names() -> Vec<String> {
1129 block_on_dispatcher();
1130 core::with_glean(|glean| {
1131 glean
1132 .get_registered_ping_names()
1133 .iter()
1134 .map(|ping| ping.to_string())
1135 .collect()
1136 })
1137}
1138
1139pub fn glean_set_experiment_active(
1145 experiment_id: String,
1146 branch: String,
1147 extra: HashMap<String, String>,
1148) {
1149 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1150}
1151
1152pub fn glean_set_experiment_inactive(experiment_id: String) {
1156 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1157}
1158
1159pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1163 block_on_dispatcher();
1164 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1165}
1166
1167pub fn glean_set_experimentation_id(experimentation_id: String) {
1171 launch_with_glean(move |glean| {
1172 glean
1173 .additional_metrics
1174 .experimentation_id
1175 .set(experimentation_id);
1176 });
1177}
1178
1179pub fn glean_test_get_experimentation_id() -> Option<String> {
1182 block_on_dispatcher();
1183 core::with_glean(|glean| glean.test_get_experimentation_id())
1184}
1185
1186pub fn glean_apply_server_knobs_config(json: String) {
1191 if json.is_empty() {
1194 return;
1195 }
1196
1197 match RemoteSettingsConfig::try_from(json) {
1198 Ok(cfg) => launch_with_glean(|glean| {
1199 glean.apply_server_knobs_config(cfg);
1200 }),
1201 Err(e) => {
1202 log::error!("Error setting metrics feature config: {:?}", e);
1203 }
1204 }
1205}
1206
1207pub fn glean_set_debug_view_tag(tag: String) -> bool {
1221 if was_initialize_called() && core::global_glean().is_some() {
1222 crate::launch_with_glean_mut(move |glean| {
1223 glean.set_debug_view_tag(&tag);
1224 });
1225 true
1226 } else {
1227 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1229 let mut lock = m.lock().unwrap();
1230 *lock = tag;
1231 true
1234 }
1235}
1236
1237pub fn glean_get_debug_view_tag() -> Option<String> {
1243 block_on_dispatcher();
1244 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1245}
1246
1247pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1259 if was_initialize_called() && core::global_glean().is_some() {
1260 crate::launch_with_glean_mut(|glean| {
1261 glean.set_source_tags(tags);
1262 });
1263 true
1264 } else {
1265 let m = &PRE_INIT_SOURCE_TAGS;
1267 let mut lock = m.lock().unwrap();
1268 *lock = tags;
1269 true
1272 }
1273}
1274
1275pub fn glean_set_log_pings(value: bool) {
1284 if was_initialize_called() && core::global_glean().is_some() {
1285 crate::launch_with_glean_mut(move |glean| {
1286 glean.set_log_pings(value);
1287 });
1288 } else {
1289 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1290 }
1291}
1292
1293pub fn glean_get_log_pings() -> bool {
1299 block_on_dispatcher();
1300 core::with_glean(|glean| glean.log_pings())
1301}
1302
1303pub fn glean_handle_client_active() {
1310 dispatcher::launch(|| {
1311 core::with_glean_mut(|glean| {
1312 glean.handle_client_active();
1313 });
1314
1315 let state = global_state().lock().unwrap();
1319 if let Err(e) = state.callbacks.trigger_upload() {
1320 log::error!("Triggering upload failed. Error: {}", e);
1321 }
1322 });
1323
1324 core_metrics::internal_metrics::baseline_duration.start();
1329}
1330
1331pub fn glean_handle_client_inactive() {
1338 core_metrics::internal_metrics::baseline_duration.stop();
1342
1343 dispatcher::launch(|| {
1344 core::with_glean_mut(|glean| {
1345 glean.handle_client_inactive();
1346 });
1347
1348 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
1358pub fn glean_session_start() {
1363 launch_with_glean_mut(|glean| {
1364 if glean.session_manager.mode == session::SessionMode::Manual {
1365 glean.session_start();
1366 }
1367 });
1368}
1369
1370pub fn glean_session_end(reason: Option<String>) {
1378 launch_with_glean_mut(move |glean| {
1379 if glean.session_manager.mode == session::SessionMode::Manual {
1380 glean.session_end(reason.as_deref());
1381 }
1382 });
1383}
1384
1385pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1387 dispatcher::launch(|| {
1388 let sent =
1389 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1390
1391 if sent {
1392 let state = global_state().lock().unwrap();
1393 if let Err(e) = state.callbacks.trigger_upload() {
1394 log::error!("Triggering upload failed. Error: {}", e);
1395 }
1396 }
1397 })
1398}
1399
1400pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1404 if !was_initialize_called() {
1405 return false;
1406 }
1407
1408 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1409 .unwrap_or(false)
1410}
1411
1412pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1419 register_event_listener(tag, listener);
1420}
1421
1422pub fn glean_unregister_event_listener(tag: String) {
1430 unregister_event_listener(tag);
1431}
1432
1433pub fn glean_set_test_mode(enabled: bool) {
1437 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1438}
1439
1440pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1444 if was_initialize_called() {
1445 join_init();
1447
1448 dispatcher::reset_dispatcher();
1449
1450 let has_storage = core::with_opt_glean(|glean| {
1453 glean
1455 .storage_opt()
1456 .map(|storage| storage.persist_ping_lifetime_data())
1457 .is_some()
1458 })
1459 .unwrap_or(false);
1460 if has_storage {
1461 uploader_shutdown();
1462 }
1463
1464 if core::global_glean().is_some() {
1465 core::with_glean_mut(|glean| {
1466 if clear_stores {
1467 glean.test_clear_all_stores()
1468 }
1469 glean.close_db()
1470 });
1471 }
1472
1473 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1475 } else if clear_stores {
1476 if let Some(data_path) = data_path {
1477 let _ = std::fs::remove_dir_all(data_path).ok();
1478 } else {
1479 log::warn!("Asked to clear stores before initialization, but no data path given.");
1480 }
1481 }
1482}
1483
1484pub fn glean_get_upload_task() -> PingUploadTask {
1486 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1487}
1488
1489pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1491 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1492}
1493
1494pub fn glean_set_dirty_flag(new_value: bool) {
1498 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1499}
1500
1501pub fn glean_clear_attribution() {
1504 if was_initialize_called() && core::global_glean().is_some() {
1505 core::with_glean(|glean| glean.clear_attribution());
1506 } else {
1507 PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1508 _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1509 }
1510}
1511
1512pub fn glean_update_attribution(attribution: AttributionMetrics) {
1515 if was_initialize_called() && core::global_glean().is_some() {
1516 core::with_glean(|glean| glean.update_attribution(attribution));
1517 } else {
1518 PRE_INIT_ATTRIBUTION
1519 .lock()
1520 .unwrap()
1521 .get_or_insert(Default::default())
1522 .update(attribution);
1523 }
1524}
1525
1526pub fn glean_test_get_attribution() -> AttributionMetrics {
1531 join_init();
1532 core::with_glean(|glean| glean.test_get_attribution())
1533}
1534
1535pub fn glean_clear_distribution() {
1538 if was_initialize_called() && core::global_glean().is_some() {
1539 core::with_glean(|glean| glean.clear_distribution());
1540 } else {
1541 PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1542 _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1543 }
1544}
1545
1546pub fn glean_update_distribution(distribution: DistributionMetrics) {
1549 if was_initialize_called() && core::global_glean().is_some() {
1550 core::with_glean(|glean| glean.update_distribution(distribution));
1551 } else {
1552 PRE_INIT_DISTRIBUTION
1553 .lock()
1554 .unwrap()
1555 .get_or_insert(Default::default())
1556 .update(distribution);
1557 }
1558}
1559
1560pub fn glean_test_get_distribution() -> DistributionMetrics {
1565 join_init();
1566 core::with_glean(|glean| glean.test_get_distribution())
1567}
1568
1569#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1570static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1571
1572#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1585pub fn glean_enable_logging_to_fd(fd: u64) {
1586 unsafe {
1592 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1597 if log::set_logger(logger).is_ok() {
1601 log::set_max_level(log::LevelFilter::Debug);
1602 }
1603 }
1604}
1605
1606fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1608 let subdirs = ["db", "events", "pending_pings"];
1610 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1611 DataDirectoryInfoObject::with_capacity(subdirs.len());
1612
1613 for subdir in subdirs.iter() {
1614 let dir_path = path.join(subdir);
1615
1616 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1618 dir_name: Some(subdir.to_string()),
1619 dir_exists: None,
1620 dir_created: None,
1621 dir_modified: None,
1622 file_count: None,
1623 files: Vec::new(),
1624 error_message: None,
1625 };
1626
1627 if dir_path.is_dir() {
1629 directory_info.dir_exists = Some(true);
1630
1631 match fs::metadata(&dir_path) {
1633 Ok(metadata) => {
1634 if let Ok(created) = metadata.created() {
1635 directory_info.dir_created = Some(
1636 created
1637 .duration_since(UNIX_EPOCH)
1638 .unwrap_or(Duration::ZERO)
1639 .as_secs() as i64,
1640 );
1641 }
1642 if let Ok(modified) = metadata.modified() {
1643 directory_info.dir_modified = Some(
1644 modified
1645 .duration_since(UNIX_EPOCH)
1646 .unwrap_or(Duration::ZERO)
1647 .as_secs() as i64,
1648 );
1649 }
1650 }
1651 Err(error) => {
1652 let msg = format!("Unable to get metadata: {}", error.kind());
1653 directory_info.error_message = Some(msg.clone());
1654 log::warn!("{}", msg);
1655 continue;
1656 }
1657 }
1658
1659 let mut file_count = 0;
1661 let entries = match fs::read_dir(&dir_path) {
1662 Ok(entries) => entries,
1663 Err(error) => {
1664 let msg = format!("Unable to read subdir: {}", error.kind());
1665 directory_info.error_message = Some(msg.clone());
1666 log::warn!("{}", msg);
1667 continue;
1668 }
1669 };
1670 for entry in entries {
1671 directory_info.files.push(
1672 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1673 file_name: None,
1674 file_created: None,
1675 file_modified: None,
1676 file_size: None,
1677 error_message: None,
1678 },
1679 );
1680 let file_info = directory_info.files.last_mut().unwrap();
1682 let entry = match entry {
1683 Ok(entry) => entry,
1684 Err(error) => {
1685 let msg = format!("Unable to read file: {}", error.kind());
1686 file_info.error_message = Some(msg.clone());
1687 log::warn!("{}", msg);
1688 continue;
1689 }
1690 };
1691 let file_name = match entry.file_name().into_string() {
1692 Ok(file_name) => file_name,
1693 _ => {
1694 let msg = "Unable to convert file name to string".to_string();
1695 file_info.error_message = Some(msg.clone());
1696 log::warn!("{}", msg);
1697 continue;
1698 }
1699 };
1700 let metadata = match entry.metadata() {
1701 Ok(metadata) => metadata,
1702 Err(error) => {
1703 let msg = format!("Unable to read file metadata: {}", error.kind());
1704 file_info.file_name = Some(file_name);
1705 file_info.error_message = Some(msg.clone());
1706 log::warn!("{}", msg);
1707 continue;
1708 }
1709 };
1710
1711 if metadata.is_file() {
1713 file_count += 1;
1714
1715 file_info.file_name = Some(file_name);
1717 file_info.file_created = Some(
1718 metadata
1719 .created()
1720 .unwrap_or(UNIX_EPOCH)
1721 .duration_since(UNIX_EPOCH)
1722 .unwrap_or(Duration::ZERO)
1723 .as_secs() as i64,
1724 );
1725 file_info.file_modified = Some(
1726 metadata
1727 .modified()
1728 .unwrap_or(UNIX_EPOCH)
1729 .duration_since(UNIX_EPOCH)
1730 .unwrap_or(Duration::ZERO)
1731 .as_secs() as i64,
1732 );
1733 file_info.file_size = Some(metadata.len() as i64);
1734 } else {
1735 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1736 file_info.file_name = Some(file_name);
1737 file_info.error_message = Some(msg.clone());
1738 log::warn!("{}", msg);
1739 }
1740 }
1741
1742 directory_info.file_count = Some(file_count as i64);
1743 } else {
1744 directory_info.dir_exists = Some(false);
1745 }
1746
1747 directories_info.push(directory_info);
1749 }
1750
1751 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1752 Some(directories_info_json)
1753 } else {
1754 log::error!("Failed to serialize data directory info");
1755 None
1756 }
1757}
1758
1759fn record_dir_info_and_submit_health_ping(
1760 glean: &Glean,
1761 dir_info: Option<serde_json::Value>,
1762 reason: &str,
1763) -> bool {
1764 glean
1765 .health_metrics
1766 .data_directory_info
1767 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1768 glean.internal_pings.health.submit_sync(glean, Some(reason))
1769}
1770
1771#[cfg(any(target_os = "android", target_os = "ios"))]
1773pub fn glean_enable_logging_to_fd(_fd: u64) {
1774 }
1776
1777uniffi::include_scaffolding!("glean");
1780
1781type CowString = Cow<'static, str>;
1782
1783uniffi::custom_type!(CowString, String, {
1784 remote,
1785 lower: |s| s.into_owned(),
1786 try_lift: |s| Ok(Cow::from(s))
1787});
1788
1789type JsonValue = serde_json::Value;
1790
1791uniffi::custom_type!(JsonValue, String, {
1792 remote,
1793 lower: |s| serde_json::to_string(&s).unwrap(),
1794 try_lift: |s| Ok(serde_json::from_str(&s)?)
1795});
1796
1797#[cfg(test)]
1801#[path = "lib_unit_tests.rs"]
1802mod tests;