1#![allow(clippy::doc_overindented_list_items)]
6#![allow(clippy::significant_drop_in_scrutinee)]
7#![allow(clippy::uninlined_format_args)]
8#![deny(rustdoc::broken_intra_doc_links)]
9#![deny(missing_docs)]
10
11use std::borrow::Cow;
20use std::collections::HashMap;
21use std::path::Path;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex};
24use std::time::{Duration, UNIX_EPOCH};
25use std::{fmt, fs};
26
27use crossbeam_channel::unbounded;
28use log::LevelFilter;
29use malloc_size_of_derive::MallocSizeOf;
30use once_cell::sync::{Lazy, OnceCell};
31use uuid::Uuid;
32
33use metrics::RemoteSettingsConfig;
34
35mod common_metric_data;
36mod core;
37mod core_metrics;
38mod database;
39mod debug;
40#[cfg(feature = "benchmark")]
41#[doc(hidden)]
42pub mod dispatcher;
43#[cfg(not(feature = "benchmark"))]
44mod dispatcher;
45mod error;
46mod error_recording;
47mod event_database;
48mod glean_metrics;
49mod histogram;
50mod internal_metrics;
51mod internal_pings;
52pub mod metrics;
53pub mod ping;
54mod scheduler;
55pub mod storage;
56mod system;
57#[doc(hidden)]
58pub mod thread;
59pub mod traits;
60pub mod upload;
61mod util;
62
63#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
64mod fd_logger;
65
66pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
67pub use crate::core::Glean;
68pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
69use crate::dispatcher::is_test_mode;
70pub use crate::error::{Error, ErrorKind, Result};
71pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
72pub use crate::histogram::HistogramType;
73use crate::internal_metrics::DataDirectoryInfoObject;
74pub use crate::metrics::labeled::{
75 AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
76 LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
77 LabeledTimingDistribution,
78};
79pub use crate::metrics::{
80 BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
81 DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
82 LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
83 MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
84 Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
85 TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
86 UrlMetric, UuidMetric,
87};
88pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
89
90const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
91const GLEAN_SCHEMA_VERSION: u32 = 1;
92const DEFAULT_MAX_EVENTS: u32 = 500;
93static KNOWN_CLIENT_ID: Lazy<Uuid> =
94 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
95
96pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
98pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
99
100static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
104
105static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
107static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
108static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
109
110static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
112static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
113
114static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
116static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
117
118static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
122 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
123
124#[derive(Debug, Clone, MallocSizeOf)]
126pub struct InternalConfiguration {
127 pub upload_enabled: bool,
129 pub data_path: String,
131 pub application_id: String,
133 pub language_binding_name: String,
135 pub max_events: Option<u32>,
137 pub delay_ping_lifetime_io: bool,
139 pub app_build: String,
142 pub use_core_mps: bool,
144 pub trim_data_to_registered_pings: bool,
146 #[ignore_malloc_size_of = "external non-allocating type"]
149 pub log_level: Option<LevelFilter>,
150 pub rate_limit: Option<PingRateLimit>,
152 pub enable_event_timestamps: bool,
154 pub experimentation_id: Option<String>,
158 pub enable_internal_pings: bool,
160 pub ping_schedule: HashMap<String, Vec<String>>,
164
165 pub ping_lifetime_threshold: u64,
167 pub ping_lifetime_max_time: u64,
169}
170
171#[derive(Debug, Clone, MallocSizeOf)]
173pub struct PingRateLimit {
174 pub seconds_per_interval: u64,
176 pub pings_per_interval: u32,
178}
179
180fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
182 dispatcher::launch(|| core::with_glean(callback));
183}
184
185fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
188 dispatcher::launch(|| core::with_glean_mut(callback));
189}
190
191fn block_on_dispatcher() {
195 dispatcher::block_on_queue()
196}
197
198pub fn get_awake_timestamp_ms() -> u64 {
200 const NANOS_PER_MILLI: u64 = 1_000_000;
201 zeitstempel::now_awake() / NANOS_PER_MILLI
202}
203
204pub fn get_timestamp_ms() -> u64 {
206 const NANOS_PER_MILLI: u64 = 1_000_000;
207 zeitstempel::now() / NANOS_PER_MILLI
208}
209
210struct State {
215 client_info: ClientInfoMetrics,
217
218 callbacks: Box<dyn OnGleanEvents>,
219}
220
221static STATE: OnceCell<Mutex<State>> = OnceCell::new();
225
226#[track_caller] fn global_state() -> &'static Mutex<State> {
231 STATE.get().unwrap()
232}
233
234#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
239 STATE.get()
240}
241
242fn setup_state(state: State) {
244 if STATE.get().is_none() {
254 if STATE.set(Mutex::new(state)).is_err() {
255 log::error!(
256 "Global Glean state object is initialized already. This probably happened concurrently."
257 );
258 }
259 } else {
260 let mut lock = STATE.get().unwrap().lock().unwrap();
264 *lock = state;
265 }
266}
267
268static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
271 OnceCell::new();
272
273fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
274 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
275}
276
277fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
278 let mut lock = event_listeners().lock().unwrap();
279 lock.insert(tag, listener);
280}
281
282fn unregister_event_listener(tag: String) {
283 let mut lock = event_listeners().lock().unwrap();
284 lock.remove(&tag);
285}
286
287#[derive(Debug)]
289pub enum CallbackError {
290 UnexpectedError,
292}
293
294impl fmt::Display for CallbackError {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 write!(f, "Unexpected error")
297 }
298}
299
300impl std::error::Error for CallbackError {}
301
302impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
303 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
304 CallbackError::UnexpectedError
305 }
306}
307
308pub trait OnGleanEvents: Send {
312 fn initialize_finished(&self);
318
319 fn trigger_upload(&self) -> Result<(), CallbackError>;
324
325 fn start_metrics_ping_scheduler(&self) -> bool;
327
328 fn cancel_uploads(&self) -> Result<(), CallbackError>;
330
331 fn shutdown(&self) -> Result<(), CallbackError> {
338 Ok(())
340 }
341}
342
343pub trait GleanEventListener: Send {
346 fn on_event_recorded(&self, id: String);
348}
349
350pub fn glean_initialize(
359 cfg: InternalConfiguration,
360 client_info: ClientInfoMetrics,
361 callbacks: Box<dyn OnGleanEvents>,
362) {
363 initialize_inner(cfg, client_info, callbacks);
364}
365
366pub fn glean_shutdown() {
368 shutdown();
369}
370
371pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
376 let glean = match Glean::new_for_subprocess(&cfg, true) {
377 Ok(glean) => glean,
378 Err(err) => {
379 log::error!("Failed to initialize Glean: {}", err);
380 return false;
381 }
382 };
383 if core::setup_glean(glean).is_err() {
384 return false;
385 }
386 log::info!("Glean initialized for subprocess");
387 true
388}
389
390fn initialize_inner(
391 cfg: InternalConfiguration,
392 client_info: ClientInfoMetrics,
393 callbacks: Box<dyn OnGleanEvents>,
394) {
395 if was_initialize_called() {
396 log::error!("Glean should not be initialized multiple times");
397 return;
398 }
399
400 let init_handle = thread::spawn("glean.init", move || {
401 let upload_enabled = cfg.upload_enabled;
402 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
403
404 if let Some(level) = cfg.log_level {
406 log::set_max_level(level)
407 }
408
409 let data_path_str = cfg.data_path.clone();
410 let data_path = Path::new(&data_path_str);
411 let internal_pings_enabled = cfg.enable_internal_pings;
412 let dir_info = if !is_test_mode() && internal_pings_enabled {
413 collect_directory_info(Path::new(&data_path))
414 } else {
415 None
416 };
417
418 let glean = match Glean::new(cfg) {
419 Ok(glean) => glean,
420 Err(err) => {
421 log::error!("Failed to initialize Glean: {}", err);
422 return;
423 }
424 };
425 if core::setup_glean(glean).is_err() {
426 return;
427 }
428
429 log::info!("Glean initialized");
430
431 core::with_glean(|glean| {
432 glean.health_metrics.init_count.add_sync(glean, 1);
433 });
434
435 setup_state(State {
436 client_info,
437 callbacks,
438 });
439
440 let mut is_first_run = false;
441 let mut dirty_flag = false;
442 let mut pings_submitted = false;
443 core::with_glean_mut(|glean| {
444 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
447 if !debug_tag.is_empty() {
448 glean.set_debug_view_tag(&debug_tag);
449 }
450
451 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
454 if log_pigs {
455 glean.set_log_pings(log_pigs);
456 }
457
458 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
461 if !source_tags.is_empty() {
462 glean.set_source_tags(source_tags.to_vec());
463 }
464
465 dirty_flag = glean.is_dirty_flag_set();
470 glean.set_dirty_flag(false);
471
472 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
475 for ping in pings.iter() {
476 glean.register_ping_type(ping);
477 }
478 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
479 for (ping, enabled) in pings.iter() {
480 glean.set_ping_enabled(ping, *enabled);
481 }
482
483 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
486 glean.update_attribution(attribution);
487 }
488 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
489 glean.update_distribution(distribution);
490 }
491
492 is_first_run = glean.is_first_run();
496 if is_first_run {
497 let state = global_state().lock().unwrap();
498 initialize_core_metrics(glean, &state.client_info);
499 }
500
501 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
503 });
504
505 {
506 let state = global_state().lock().unwrap();
507 if pings_submitted || !upload_enabled {
511 if let Err(e) = state.callbacks.trigger_upload() {
512 log::error!("Triggering upload failed. Error: {}", e);
513 }
514 }
515 }
516
517 core::with_glean(|glean| {
518 glean.start_metrics_ping_scheduler();
520 });
521
522 {
530 let state = global_state().lock().unwrap();
531
532 if state.callbacks.start_metrics_ping_scheduler() {
536 if let Err(e) = state.callbacks.trigger_upload() {
537 log::error!("Triggering upload failed. Error: {}", e);
538 }
539 }
540 }
541
542 core::with_glean_mut(|glean| {
543 let state = global_state().lock().unwrap();
544
545 if !is_first_run && dirty_flag {
549 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
555 if let Err(e) = state.callbacks.trigger_upload() {
556 log::error!("Triggering upload failed. Error: {}", e);
557 }
558 }
559 }
560
561 if !is_first_run {
565 glean.clear_application_lifetime_metrics();
566 initialize_core_metrics(glean, &state.client_info);
567 }
568 });
569
570 match dispatcher::flush_init() {
576 Ok(task_count) if task_count > 0 => {
577 core::with_glean(|glean| {
578 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
579 });
580 }
581 Ok(_) => {}
582 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
583 }
584
585 if !is_test_mode() && internal_pings_enabled {
586 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
589
590 let state = global_state().lock().unwrap();
591 if let Err(e) = state.callbacks.trigger_upload() {
592 log::error!("Triggering upload failed. Error: {}", e);
593 }
594 }
595 let state = global_state().lock().unwrap();
596 state.callbacks.initialize_finished();
597 })
598 .expect("Failed to spawn Glean's init thread");
599
600 INIT_HANDLES.lock().unwrap().push(init_handle);
602
603 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
606
607 if dispatcher::global::is_test_mode() {
610 join_init();
611 }
612}
613
614pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
618 use malloc_size_of::MallocSizeOf;
619 core::with_glean(|glean| glean.size_of(ops))
620}
621
622pub fn join_init() {
625 let mut handles = INIT_HANDLES.lock().unwrap();
626 for handle in handles.drain(..) {
627 handle.join().unwrap();
628 }
629}
630
631fn uploader_shutdown() {
639 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
640 let (tx, rx) = unbounded();
641
642 let handle = thread::spawn("glean.shutdown", move || {
643 let state = global_state().lock().unwrap();
644 if let Err(e) = state.callbacks.shutdown() {
645 log::error!("Shutdown callback failed: {e:?}");
646 }
647
648 let _ = tx.send(()).ok();
650 })
651 .expect("Unable to spawn thread to wait on shutdown");
652
653 let result = rx.recv_timeout(Duration::from_secs(30));
661
662 let stop_time = zeitstempel::now_awake();
663 core::with_glean(|glean| {
664 glean
665 .additional_metrics
666 .shutdown_wait
667 .set_stop_and_accumulate(glean, timer_id, stop_time);
668 });
669
670 if result.is_err() {
671 log::warn!("Waiting for upload failed. We're shutting down.");
672 } else {
673 let _ = handle.join().ok();
674 }
675}
676
677pub fn shutdown() {
679 if !was_initialize_called() {
689 log::warn!("Shutdown called before Glean is initialized");
690 if let Err(e) = dispatcher::kill() {
691 log::error!("Can't kill dispatcher thread: {:?}", e);
692 }
693 return;
694 }
695
696 if core::global_glean().is_none() {
698 log::warn!("Shutdown called before Glean is initialized. Waiting.");
699 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
707 }
708 if core::global_glean().is_none() {
710 log::warn!("Waiting for Glean initialization timed out. Exiting.");
711 if let Err(e) = dispatcher::kill() {
712 log::error!("Can't kill dispatcher thread: {:?}", e);
713 }
714 return;
715 }
716
717 crate::launch_with_glean_mut(|glean| {
719 glean.cancel_metrics_ping_scheduler();
720 glean.set_dirty_flag(false);
721 });
722
723 let timer_id = core::with_glean(|glean| {
730 glean
731 .additional_metrics
732 .shutdown_dispatcher_wait
733 .start_sync()
734 });
735 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
736
737 let stop_time = zeitstempel::now_awake();
739 core::with_glean(|glean| {
740 glean
741 .additional_metrics
742 .shutdown_dispatcher_wait
743 .set_stop_and_accumulate(glean, timer_id, stop_time);
744 });
745 if blocked.is_err() {
746 log::error!(
747 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
748 );
749 return;
750 }
751
752 if let Err(e) = dispatcher::shutdown() {
753 log::error!("Can't shutdown dispatcher thread: {:?}", e);
754 }
755
756 uploader_shutdown();
757
758 core::with_glean(|glean| {
760 if let Err(e) = glean.persist_ping_lifetime_data() {
761 log::info!("Can't persist ping lifetime data: {:?}", e);
762 }
763 });
764}
765
766pub fn glean_persist_ping_lifetime_data() {
773 crate::launch_with_glean(|glean| {
775 let _ = glean.persist_ping_lifetime_data();
776 });
777}
778
779fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
780 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
781 core_metrics::internal_metrics::app_display_version
782 .set_sync(glean, &client_info.app_display_version[..]);
783 core_metrics::internal_metrics::app_build_date
784 .set_sync(glean, Some(client_info.app_build_date.clone()));
785 if let Some(app_channel) = client_info.channel.as_ref() {
786 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
787 }
788
789 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
790 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
791
792 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
793 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
794 }
795 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
796 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
797 }
798 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
799 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
800 }
801 if let Some(device_model) = client_info.device_model.as_ref() {
802 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
803 }
804 if let Some(locale) = client_info.locale.as_ref() {
805 core_metrics::internal_metrics::locale.set_sync(glean, locale);
806 }
807}
808
809fn was_initialize_called() -> bool {
815 INITIALIZE_CALLED.load(Ordering::SeqCst)
816}
817
818#[no_mangle]
821pub extern "C" fn glean_enable_logging() {
822 #[cfg(target_os = "android")]
823 {
824 let _ = std::panic::catch_unwind(|| {
825 let filter = android_logger::FilterBuilder::new()
826 .filter_module("glean_ffi", log::LevelFilter::Debug)
827 .filter_module("glean_core", log::LevelFilter::Debug)
828 .filter_module("glean", log::LevelFilter::Debug)
829 .filter_module("glean_core::ffi", log::LevelFilter::Info)
830 .build();
831 android_logger::init_once(
832 android_logger::Config::default()
833 .with_max_level(log::LevelFilter::Debug)
834 .with_filter(filter)
835 .with_tag("libglean_ffi"),
836 );
837 log::trace!("Android logging should be hooked up!")
838 });
839 }
840
841 #[cfg(target_os = "ios")]
843 {
844 #[cfg(debug_assertions)]
847 let level = log::LevelFilter::Debug;
848 #[cfg(not(debug_assertions))]
849 let level = log::LevelFilter::Info;
850
851 let logger = oslog::OsLogger::new("org.mozilla.glean")
852 .level_filter(level)
853 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
855
856 match logger.init() {
857 Ok(_) => log::trace!("os_log should be hooked up!"),
858 Err(_) => log::warn!("os_log was already initialized"),
862 };
863 }
864
865 #[cfg(all(
869 not(target_os = "android"),
870 not(target_os = "ios"),
871 feature = "enable_env_logger"
872 ))]
873 {
874 match env_logger::try_init() {
875 Ok(_) => log::trace!("stdout logging should be hooked up!"),
876 Err(_) => log::warn!("stdout logging was already initialized"),
880 };
881 }
882}
883
884pub fn glean_set_upload_enabled(enabled: bool) {
889 if !was_initialize_called() {
890 return;
891 }
892
893 crate::launch_with_glean_mut(move |glean| {
894 let state = global_state().lock().unwrap();
895 let original_enabled = glean.is_upload_enabled();
896
897 if !enabled {
898 glean.cancel_metrics_ping_scheduler();
900 if let Err(e) = state.callbacks.cancel_uploads() {
902 log::error!("Canceling upload failed. Error: {}", e);
903 }
904 }
905
906 glean.set_upload_enabled(enabled);
907
908 if !original_enabled && enabled {
909 initialize_core_metrics(glean, &state.client_info);
910 }
911
912 if original_enabled && !enabled {
913 if let Err(e) = state.callbacks.trigger_upload() {
914 log::error!("Triggering upload failed. Error: {}", e);
915 }
916 }
917 })
918}
919
920pub fn glean_set_collection_enabled(enabled: bool) {
924 glean_set_upload_enabled(enabled)
925}
926
927pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
932 let ping = ping.clone();
933 if was_initialize_called() && core::global_glean().is_some() {
934 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
935 } else {
936 let m = &PRE_INIT_PING_ENABLED;
937 let mut lock = m.lock().unwrap();
938 lock.push((ping, enabled));
939 }
940}
941
942pub(crate) fn register_ping_type(ping: &PingType) {
944 if was_initialize_called() && core::global_glean().is_some() {
949 let ping = ping.clone();
950 crate::launch_with_glean_mut(move |glean| {
951 glean.register_ping_type(&ping);
952 })
953 } else {
954 let m = &PRE_INIT_PING_REGISTRATION;
959 let mut lock = m.lock().unwrap();
960 lock.push(ping.clone());
961 }
962}
963
964pub fn glean_get_registered_ping_names() -> Vec<String> {
970 block_on_dispatcher();
971 core::with_glean(|glean| {
972 glean
973 .get_registered_ping_names()
974 .iter()
975 .map(|ping| ping.to_string())
976 .collect()
977 })
978}
979
980pub fn glean_set_experiment_active(
986 experiment_id: String,
987 branch: String,
988 extra: HashMap<String, String>,
989) {
990 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
991}
992
993pub fn glean_set_experiment_inactive(experiment_id: String) {
997 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
998}
999
1000pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1004 block_on_dispatcher();
1005 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1006}
1007
1008pub fn glean_set_experimentation_id(experimentation_id: String) {
1012 launch_with_glean(move |glean| {
1013 glean
1014 .additional_metrics
1015 .experimentation_id
1016 .set(experimentation_id);
1017 });
1018}
1019
1020pub fn glean_test_get_experimentation_id() -> Option<String> {
1023 block_on_dispatcher();
1024 core::with_glean(|glean| glean.test_get_experimentation_id())
1025}
1026
1027pub fn glean_apply_server_knobs_config(json: String) {
1032 if json.is_empty() {
1035 return;
1036 }
1037
1038 match RemoteSettingsConfig::try_from(json) {
1039 Ok(cfg) => launch_with_glean(|glean| {
1040 glean.apply_server_knobs_config(cfg);
1041 }),
1042 Err(e) => {
1043 log::error!("Error setting metrics feature config: {:?}", e);
1044 }
1045 }
1046}
1047
1048pub fn glean_set_debug_view_tag(tag: String) -> bool {
1062 if was_initialize_called() && core::global_glean().is_some() {
1063 crate::launch_with_glean_mut(move |glean| {
1064 glean.set_debug_view_tag(&tag);
1065 });
1066 true
1067 } else {
1068 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1070 let mut lock = m.lock().unwrap();
1071 *lock = tag;
1072 true
1075 }
1076}
1077
1078pub fn glean_get_debug_view_tag() -> Option<String> {
1084 block_on_dispatcher();
1085 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1086}
1087
1088pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1100 if was_initialize_called() && core::global_glean().is_some() {
1101 crate::launch_with_glean_mut(|glean| {
1102 glean.set_source_tags(tags);
1103 });
1104 true
1105 } else {
1106 let m = &PRE_INIT_SOURCE_TAGS;
1108 let mut lock = m.lock().unwrap();
1109 *lock = tags;
1110 true
1113 }
1114}
1115
1116pub fn glean_set_log_pings(value: bool) {
1125 if was_initialize_called() && core::global_glean().is_some() {
1126 crate::launch_with_glean_mut(move |glean| {
1127 glean.set_log_pings(value);
1128 });
1129 } else {
1130 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1131 }
1132}
1133
1134pub fn glean_get_log_pings() -> bool {
1140 block_on_dispatcher();
1141 core::with_glean(|glean| glean.log_pings())
1142}
1143
1144pub fn glean_handle_client_active() {
1151 dispatcher::launch(|| {
1152 core::with_glean_mut(|glean| {
1153 glean.handle_client_active();
1154 });
1155
1156 let state = global_state().lock().unwrap();
1160 if let Err(e) = state.callbacks.trigger_upload() {
1161 log::error!("Triggering upload failed. Error: {}", e);
1162 }
1163 });
1164
1165 core_metrics::internal_metrics::baseline_duration.start();
1170}
1171
1172pub fn glean_handle_client_inactive() {
1179 core_metrics::internal_metrics::baseline_duration.stop();
1183
1184 dispatcher::launch(|| {
1185 core::with_glean_mut(|glean| {
1186 glean.handle_client_inactive();
1187 });
1188
1189 let state = global_state().lock().unwrap();
1193 if let Err(e) = state.callbacks.trigger_upload() {
1194 log::error!("Triggering upload failed. Error: {}", e);
1195 }
1196 })
1197}
1198
1199pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1201 dispatcher::launch(|| {
1202 let sent =
1203 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1204
1205 if sent {
1206 let state = global_state().lock().unwrap();
1207 if let Err(e) = state.callbacks.trigger_upload() {
1208 log::error!("Triggering upload failed. Error: {}", e);
1209 }
1210 }
1211 })
1212}
1213
1214pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1218 if !was_initialize_called() {
1219 return false;
1220 }
1221
1222 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1223 .unwrap_or(false)
1224}
1225
1226pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1233 register_event_listener(tag, listener);
1234}
1235
1236pub fn glean_unregister_event_listener(tag: String) {
1244 unregister_event_listener(tag);
1245}
1246
1247pub fn glean_set_test_mode(enabled: bool) {
1251 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1252}
1253
1254pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1258 if was_initialize_called() {
1259 join_init();
1261
1262 dispatcher::reset_dispatcher();
1263
1264 let has_storage = core::with_opt_glean(|glean| {
1267 glean
1269 .storage_opt()
1270 .map(|storage| storage.persist_ping_lifetime_data())
1271 .is_some()
1272 })
1273 .unwrap_or(false);
1274 if has_storage {
1275 uploader_shutdown();
1276 }
1277
1278 if core::global_glean().is_some() {
1279 core::with_glean_mut(|glean| {
1280 if clear_stores {
1281 glean.test_clear_all_stores()
1282 }
1283 glean.destroy_db()
1284 });
1285 }
1286
1287 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1289 } else if clear_stores {
1290 if let Some(data_path) = data_path {
1291 let _ = std::fs::remove_dir_all(data_path).ok();
1292 } else {
1293 log::warn!("Asked to clear stores before initialization, but no data path given.");
1294 }
1295 }
1296}
1297
1298pub fn glean_get_upload_task() -> PingUploadTask {
1300 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1301}
1302
1303pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1305 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1306}
1307
1308pub fn glean_set_dirty_flag(new_value: bool) {
1312 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1313}
1314
1315pub fn glean_update_attribution(attribution: AttributionMetrics) {
1318 if was_initialize_called() && core::global_glean().is_some() {
1319 core::with_glean(|glean| glean.update_attribution(attribution));
1320 } else {
1321 PRE_INIT_ATTRIBUTION
1322 .lock()
1323 .unwrap()
1324 .get_or_insert(Default::default())
1325 .update(attribution);
1326 }
1327}
1328
1329pub fn glean_test_get_attribution() -> AttributionMetrics {
1334 join_init();
1335 core::with_glean(|glean| glean.test_get_attribution())
1336}
1337
1338pub fn glean_update_distribution(distribution: DistributionMetrics) {
1341 if was_initialize_called() && core::global_glean().is_some() {
1342 core::with_glean(|glean| glean.update_distribution(distribution));
1343 } else {
1344 PRE_INIT_DISTRIBUTION
1345 .lock()
1346 .unwrap()
1347 .get_or_insert(Default::default())
1348 .update(distribution);
1349 }
1350}
1351
1352pub fn glean_test_get_distribution() -> DistributionMetrics {
1357 join_init();
1358 core::with_glean(|glean| glean.test_get_distribution())
1359}
1360
1361#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1362static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1363
1364#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1377pub fn glean_enable_logging_to_fd(fd: u64) {
1378 unsafe {
1384 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1389 if log::set_logger(logger).is_ok() {
1393 log::set_max_level(log::LevelFilter::Debug);
1394 }
1395 }
1396}
1397
1398fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1400 let subdirs = ["db", "events", "pending_pings"];
1402 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1403 DataDirectoryInfoObject::with_capacity(subdirs.len());
1404
1405 for subdir in subdirs.iter() {
1406 let dir_path = path.join(subdir);
1407
1408 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1410 dir_name: Some(subdir.to_string()),
1411 dir_exists: None,
1412 dir_created: None,
1413 dir_modified: None,
1414 file_count: None,
1415 files: Vec::new(),
1416 error_message: None,
1417 };
1418
1419 if dir_path.is_dir() {
1421 directory_info.dir_exists = Some(true);
1422
1423 match fs::metadata(&dir_path) {
1425 Ok(metadata) => {
1426 if let Ok(created) = metadata.created() {
1427 directory_info.dir_created = Some(
1428 created
1429 .duration_since(UNIX_EPOCH)
1430 .unwrap_or(Duration::ZERO)
1431 .as_secs() as i64,
1432 );
1433 }
1434 if let Ok(modified) = metadata.modified() {
1435 directory_info.dir_modified = Some(
1436 modified
1437 .duration_since(UNIX_EPOCH)
1438 .unwrap_or(Duration::ZERO)
1439 .as_secs() as i64,
1440 );
1441 }
1442 }
1443 Err(error) => {
1444 let msg = format!("Unable to get metadata: {}", error.kind());
1445 directory_info.error_message = Some(msg.clone());
1446 log::warn!("{}", msg);
1447 continue;
1448 }
1449 }
1450
1451 let mut file_count = 0;
1453 let entries = match fs::read_dir(&dir_path) {
1454 Ok(entries) => entries,
1455 Err(error) => {
1456 let msg = format!("Unable to read subdir: {}", error.kind());
1457 directory_info.error_message = Some(msg.clone());
1458 log::warn!("{}", msg);
1459 continue;
1460 }
1461 };
1462 for entry in entries {
1463 directory_info.files.push(
1464 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1465 file_name: None,
1466 file_created: None,
1467 file_modified: None,
1468 file_size: None,
1469 error_message: None,
1470 },
1471 );
1472 let file_info = directory_info.files.last_mut().unwrap();
1474 let entry = match entry {
1475 Ok(entry) => entry,
1476 Err(error) => {
1477 let msg = format!("Unable to read file: {}", error.kind());
1478 file_info.error_message = Some(msg.clone());
1479 log::warn!("{}", msg);
1480 continue;
1481 }
1482 };
1483 let file_name = match entry.file_name().into_string() {
1484 Ok(file_name) => file_name,
1485 _ => {
1486 let msg = "Unable to convert file name to string".to_string();
1487 file_info.error_message = Some(msg.clone());
1488 log::warn!("{}", msg);
1489 continue;
1490 }
1491 };
1492 let metadata = match entry.metadata() {
1493 Ok(metadata) => metadata,
1494 Err(error) => {
1495 let msg = format!("Unable to read file metadata: {}", error.kind());
1496 file_info.file_name = Some(file_name);
1497 file_info.error_message = Some(msg.clone());
1498 log::warn!("{}", msg);
1499 continue;
1500 }
1501 };
1502
1503 if metadata.is_file() {
1505 file_count += 1;
1506
1507 file_info.file_name = Some(file_name);
1509 file_info.file_created = Some(
1510 metadata
1511 .created()
1512 .unwrap_or(UNIX_EPOCH)
1513 .duration_since(UNIX_EPOCH)
1514 .unwrap_or(Duration::ZERO)
1515 .as_secs() as i64,
1516 );
1517 file_info.file_modified = Some(
1518 metadata
1519 .modified()
1520 .unwrap_or(UNIX_EPOCH)
1521 .duration_since(UNIX_EPOCH)
1522 .unwrap_or(Duration::ZERO)
1523 .as_secs() as i64,
1524 );
1525 file_info.file_size = Some(metadata.len() as i64);
1526 } else {
1527 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1528 file_info.file_name = Some(file_name);
1529 file_info.error_message = Some(msg.clone());
1530 log::warn!("{}", msg);
1531 }
1532 }
1533
1534 directory_info.file_count = Some(file_count as i64);
1535 } else {
1536 directory_info.dir_exists = Some(false);
1537 }
1538
1539 directories_info.push(directory_info);
1541 }
1542
1543 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1544 Some(directories_info_json)
1545 } else {
1546 log::error!("Failed to serialize data directory info");
1547 None
1548 }
1549}
1550
1551fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1552 core::with_glean(|glean| {
1553 glean
1554 .health_metrics
1555 .data_directory_info
1556 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1557 glean.internal_pings.health.submit_sync(glean, Some(reason));
1558 });
1559}
1560
1561#[cfg(any(target_os = "android", target_os = "ios"))]
1563pub fn glean_enable_logging_to_fd(_fd: u64) {
1564 }
1566
1567#[allow(missing_docs)]
1568#[allow(clippy::all)]
1570mod ffi {
1571 use super::*;
1572 uniffi::include_scaffolding!("glean");
1573
1574 type CowString = Cow<'static, str>;
1575
1576 uniffi::custom_type!(CowString, String, {
1577 remote,
1578 lower: |s| s.into_owned(),
1579 try_lift: |s| Ok(Cow::from(s))
1580 });
1581
1582 type JsonValue = serde_json::Value;
1583
1584 uniffi::custom_type!(JsonValue, String, {
1585 remote,
1586 lower: |s| serde_json::to_string(&s).unwrap(),
1587 try_lift: |s| Ok(serde_json::from_str(&s)?)
1588 });
1589}
1590pub use ffi::*;
1591
1592#[cfg(test)]
1594#[path = "lib_unit_tests.rs"]
1595mod tests;