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 coverage;
39mod database;
40mod debug;
41mod dispatcher;
42mod error;
43mod error_recording;
44mod event_database;
45mod glean_metrics;
46mod histogram;
47mod internal_metrics;
48mod internal_pings;
49pub mod metrics;
50pub mod ping;
51mod scheduler;
52pub mod storage;
53mod system;
54#[doc(hidden)]
55pub mod thread;
56pub mod traits;
57pub mod upload;
58mod util;
59
60#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
61mod fd_logger;
62
63pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
64pub use crate::core::Glean;
65pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
66use crate::dispatcher::is_test_mode;
67pub use crate::error::{Error, ErrorKind, Result};
68pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
69pub use crate::histogram::HistogramType;
70use crate::internal_metrics::DataDirectoryInfoObject;
71pub use crate::metrics::labeled::{
72 AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
73 LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
74 LabeledTimingDistribution,
75};
76pub use crate::metrics::{
77 BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
78 DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
79 LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
80 MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
81 Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
82 TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
83 UrlMetric, UuidMetric,
84};
85pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
86
87const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
88const GLEAN_SCHEMA_VERSION: u32 = 1;
89const DEFAULT_MAX_EVENTS: u32 = 500;
90static KNOWN_CLIENT_ID: Lazy<Uuid> =
91 Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
92
93pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
95pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
96
97static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
101
102static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
104static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
105static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
106
107static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
109static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
110
111static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
113static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
114
115static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
119 Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
120
121#[derive(Debug, Clone, MallocSizeOf)]
123pub struct InternalConfiguration {
124 pub upload_enabled: bool,
126 pub data_path: String,
128 pub application_id: String,
130 pub language_binding_name: String,
132 pub max_events: Option<u32>,
134 pub delay_ping_lifetime_io: bool,
136 pub app_build: String,
139 pub use_core_mps: bool,
141 pub trim_data_to_registered_pings: bool,
143 #[ignore_malloc_size_of = "external non-allocating type"]
146 pub log_level: Option<LevelFilter>,
147 pub rate_limit: Option<PingRateLimit>,
149 pub enable_event_timestamps: bool,
151 pub experimentation_id: Option<String>,
155 pub enable_internal_pings: bool,
157 pub ping_schedule: HashMap<String, Vec<String>>,
161
162 pub ping_lifetime_threshold: u64,
164 pub ping_lifetime_max_time: u64,
166}
167
168#[derive(Debug, Clone, MallocSizeOf)]
170pub struct PingRateLimit {
171 pub seconds_per_interval: u64,
173 pub pings_per_interval: u32,
175}
176
177fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
179 dispatcher::launch(|| core::with_glean(callback));
180}
181
182fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
185 dispatcher::launch(|| core::with_glean_mut(callback));
186}
187
188fn block_on_dispatcher() {
192 dispatcher::block_on_queue()
193}
194
195pub fn get_awake_timestamp_ms() -> u64 {
197 const NANOS_PER_MILLI: u64 = 1_000_000;
198 zeitstempel::now_awake() / NANOS_PER_MILLI
199}
200
201pub fn get_timestamp_ms() -> u64 {
203 const NANOS_PER_MILLI: u64 = 1_000_000;
204 zeitstempel::now() / NANOS_PER_MILLI
205}
206
207struct State {
212 client_info: ClientInfoMetrics,
214
215 callbacks: Box<dyn OnGleanEvents>,
216}
217
218static STATE: OnceCell<Mutex<State>> = OnceCell::new();
222
223#[track_caller] fn global_state() -> &'static Mutex<State> {
228 STATE.get().unwrap()
229}
230
231#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
236 STATE.get()
237}
238
239fn setup_state(state: State) {
241 if STATE.get().is_none() {
251 if STATE.set(Mutex::new(state)).is_err() {
252 log::error!(
253 "Global Glean state object is initialized already. This probably happened concurrently."
254 );
255 }
256 } else {
257 let mut lock = STATE.get().unwrap().lock().unwrap();
261 *lock = state;
262 }
263}
264
265static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
268 OnceCell::new();
269
270fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
271 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
272}
273
274fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
275 let mut lock = event_listeners().lock().unwrap();
276 lock.insert(tag, listener);
277}
278
279fn unregister_event_listener(tag: String) {
280 let mut lock = event_listeners().lock().unwrap();
281 lock.remove(&tag);
282}
283
284#[derive(Debug)]
286pub enum CallbackError {
287 UnexpectedError,
289}
290
291impl fmt::Display for CallbackError {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 write!(f, "Unexpected error")
294 }
295}
296
297impl std::error::Error for CallbackError {}
298
299impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
300 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
301 CallbackError::UnexpectedError
302 }
303}
304
305pub trait OnGleanEvents: Send {
309 fn initialize_finished(&self);
315
316 fn trigger_upload(&self) -> Result<(), CallbackError>;
321
322 fn start_metrics_ping_scheduler(&self) -> bool;
324
325 fn cancel_uploads(&self) -> Result<(), CallbackError>;
327
328 fn shutdown(&self) -> Result<(), CallbackError> {
335 Ok(())
337 }
338}
339
340pub trait GleanEventListener: Send {
343 fn on_event_recorded(&self, id: String);
345}
346
347pub fn glean_initialize(
356 cfg: InternalConfiguration,
357 client_info: ClientInfoMetrics,
358 callbacks: Box<dyn OnGleanEvents>,
359) {
360 initialize_inner(cfg, client_info, callbacks);
361}
362
363pub fn glean_shutdown() {
365 shutdown();
366}
367
368pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
373 let glean = match Glean::new_for_subprocess(&cfg, true) {
374 Ok(glean) => glean,
375 Err(err) => {
376 log::error!("Failed to initialize Glean: {}", err);
377 return false;
378 }
379 };
380 if core::setup_glean(glean).is_err() {
381 return false;
382 }
383 log::info!("Glean initialized for subprocess");
384 true
385}
386
387fn initialize_inner(
388 cfg: InternalConfiguration,
389 client_info: ClientInfoMetrics,
390 callbacks: Box<dyn OnGleanEvents>,
391) {
392 if was_initialize_called() {
393 log::error!("Glean should not be initialized multiple times");
394 return;
395 }
396
397 let init_handle = thread::spawn("glean.init", move || {
398 let upload_enabled = cfg.upload_enabled;
399 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
400
401 if let Some(level) = cfg.log_level {
403 log::set_max_level(level)
404 }
405
406 let data_path_str = cfg.data_path.clone();
407 let data_path = Path::new(&data_path_str);
408 let internal_pings_enabled = cfg.enable_internal_pings;
409 let dir_info = if !is_test_mode() && internal_pings_enabled {
410 collect_directory_info(Path::new(&data_path))
411 } else {
412 None
413 };
414
415 let glean = match Glean::new(cfg) {
416 Ok(glean) => glean,
417 Err(err) => {
418 log::error!("Failed to initialize Glean: {}", err);
419 return;
420 }
421 };
422 if core::setup_glean(glean).is_err() {
423 return;
424 }
425
426 log::info!("Glean initialized");
427
428 core::with_glean(|glean| {
429 glean.health_metrics.init_count.add_sync(glean, 1);
430 });
431
432 setup_state(State {
433 client_info,
434 callbacks,
435 });
436
437 let mut is_first_run = false;
438 let mut dirty_flag = false;
439 let mut pings_submitted = false;
440 core::with_glean_mut(|glean| {
441 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
444 if !debug_tag.is_empty() {
445 glean.set_debug_view_tag(&debug_tag);
446 }
447
448 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
451 if log_pigs {
452 glean.set_log_pings(log_pigs);
453 }
454
455 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
458 if !source_tags.is_empty() {
459 glean.set_source_tags(source_tags.to_vec());
460 }
461
462 dirty_flag = glean.is_dirty_flag_set();
467 glean.set_dirty_flag(false);
468
469 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
472 for ping in pings.iter() {
473 glean.register_ping_type(ping);
474 }
475 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
476 for (ping, enabled) in pings.iter() {
477 glean.set_ping_enabled(ping, *enabled);
478 }
479
480 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
483 glean.update_attribution(attribution);
484 }
485 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
486 glean.update_distribution(distribution);
487 }
488
489 is_first_run = glean.is_first_run();
493 if is_first_run {
494 let state = global_state().lock().unwrap();
495 initialize_core_metrics(glean, &state.client_info);
496 }
497
498 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
500 });
501
502 {
503 let state = global_state().lock().unwrap();
504 if pings_submitted || !upload_enabled {
508 if let Err(e) = state.callbacks.trigger_upload() {
509 log::error!("Triggering upload failed. Error: {}", e);
510 }
511 }
512 }
513
514 core::with_glean(|glean| {
515 glean.start_metrics_ping_scheduler();
517 });
518
519 {
527 let state = global_state().lock().unwrap();
528
529 if state.callbacks.start_metrics_ping_scheduler() {
533 if let Err(e) = state.callbacks.trigger_upload() {
534 log::error!("Triggering upload failed. Error: {}", e);
535 }
536 }
537 }
538
539 core::with_glean_mut(|glean| {
540 let state = global_state().lock().unwrap();
541
542 if !is_first_run && dirty_flag {
546 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
552 if let Err(e) = state.callbacks.trigger_upload() {
553 log::error!("Triggering upload failed. Error: {}", e);
554 }
555 }
556 }
557
558 if !is_first_run {
562 glean.clear_application_lifetime_metrics();
563 initialize_core_metrics(glean, &state.client_info);
564 }
565 });
566
567 match dispatcher::flush_init() {
573 Ok(task_count) if task_count > 0 => {
574 core::with_glean(|glean| {
575 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
576 });
577 }
578 Ok(_) => {}
579 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
580 }
581
582 if !is_test_mode() && internal_pings_enabled {
583 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
586
587 record_dir_info_and_submit_health_ping(collect_directory_info(data_path), "post_init");
590 }
591 let state = global_state().lock().unwrap();
592 state.callbacks.initialize_finished();
593 })
594 .expect("Failed to spawn Glean's init thread");
595
596 INIT_HANDLES.lock().unwrap().push(init_handle);
598
599 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
602
603 if dispatcher::global::is_test_mode() {
606 join_init();
607 }
608}
609
610pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
614 use malloc_size_of::MallocSizeOf;
615 core::with_glean(|glean| glean.size_of(ops))
616}
617
618pub fn join_init() {
621 let mut handles = INIT_HANDLES.lock().unwrap();
622 for handle in handles.drain(..) {
623 handle.join().unwrap();
624 }
625}
626
627fn uploader_shutdown() {
635 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
636 let (tx, rx) = unbounded();
637
638 let handle = thread::spawn("glean.shutdown", move || {
639 let state = global_state().lock().unwrap();
640 if let Err(e) = state.callbacks.shutdown() {
641 log::error!("Shutdown callback failed: {e:?}");
642 }
643
644 let _ = tx.send(()).ok();
646 })
647 .expect("Unable to spawn thread to wait on shutdown");
648
649 let result = rx.recv_timeout(Duration::from_secs(30));
657
658 let stop_time = zeitstempel::now_awake();
659 core::with_glean(|glean| {
660 glean
661 .additional_metrics
662 .shutdown_wait
663 .set_stop_and_accumulate(glean, timer_id, stop_time);
664 });
665
666 if result.is_err() {
667 log::warn!("Waiting for upload failed. We're shutting down.");
668 } else {
669 let _ = handle.join().ok();
670 }
671}
672
673pub fn shutdown() {
675 if !was_initialize_called() {
685 log::warn!("Shutdown called before Glean is initialized");
686 if let Err(e) = dispatcher::kill() {
687 log::error!("Can't kill dispatcher thread: {:?}", e);
688 }
689 return;
690 }
691
692 if core::global_glean().is_none() {
694 log::warn!("Shutdown called before Glean is initialized. Waiting.");
695 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
703 }
704 if core::global_glean().is_none() {
706 log::warn!("Waiting for Glean initialization timed out. Exiting.");
707 if let Err(e) = dispatcher::kill() {
708 log::error!("Can't kill dispatcher thread: {:?}", e);
709 }
710 return;
711 }
712
713 crate::launch_with_glean_mut(|glean| {
715 glean.cancel_metrics_ping_scheduler();
716 glean.set_dirty_flag(false);
717 });
718
719 let timer_id = core::with_glean(|glean| {
726 glean
727 .additional_metrics
728 .shutdown_dispatcher_wait
729 .start_sync()
730 });
731 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
732
733 let stop_time = zeitstempel::now_awake();
735 core::with_glean(|glean| {
736 glean
737 .additional_metrics
738 .shutdown_dispatcher_wait
739 .set_stop_and_accumulate(glean, timer_id, stop_time);
740 });
741 if blocked.is_err() {
742 log::error!(
743 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
744 );
745 return;
746 }
747
748 if let Err(e) = dispatcher::shutdown() {
749 log::error!("Can't shutdown dispatcher thread: {:?}", e);
750 }
751
752 uploader_shutdown();
753
754 core::with_glean(|glean| {
756 if let Err(e) = glean.persist_ping_lifetime_data() {
757 log::info!("Can't persist ping lifetime data: {:?}", e);
758 }
759 });
760}
761
762pub fn glean_persist_ping_lifetime_data() {
769 crate::launch_with_glean(|glean| {
771 let _ = glean.persist_ping_lifetime_data();
772 });
773}
774
775fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
776 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
777 core_metrics::internal_metrics::app_display_version
778 .set_sync(glean, &client_info.app_display_version[..]);
779 core_metrics::internal_metrics::app_build_date
780 .set_sync(glean, Some(client_info.app_build_date.clone()));
781 if let Some(app_channel) = client_info.channel.as_ref() {
782 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
783 }
784
785 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
786 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
787
788 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
789 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
790 }
791 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
792 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
793 }
794 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
795 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
796 }
797 if let Some(device_model) = client_info.device_model.as_ref() {
798 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
799 }
800 if let Some(locale) = client_info.locale.as_ref() {
801 core_metrics::internal_metrics::locale.set_sync(glean, locale);
802 }
803}
804
805fn was_initialize_called() -> bool {
811 INITIALIZE_CALLED.load(Ordering::SeqCst)
812}
813
814#[no_mangle]
817pub extern "C" fn glean_enable_logging() {
818 #[cfg(target_os = "android")]
819 {
820 let _ = std::panic::catch_unwind(|| {
821 let filter = android_logger::FilterBuilder::new()
822 .filter_module("glean_ffi", log::LevelFilter::Debug)
823 .filter_module("glean_core", log::LevelFilter::Debug)
824 .filter_module("glean", log::LevelFilter::Debug)
825 .filter_module("glean_core::ffi", log::LevelFilter::Info)
826 .build();
827 android_logger::init_once(
828 android_logger::Config::default()
829 .with_max_level(log::LevelFilter::Debug)
830 .with_filter(filter)
831 .with_tag("libglean_ffi"),
832 );
833 log::trace!("Android logging should be hooked up!")
834 });
835 }
836
837 #[cfg(target_os = "ios")]
839 {
840 #[cfg(debug_assertions)]
843 let level = log::LevelFilter::Debug;
844 #[cfg(not(debug_assertions))]
845 let level = log::LevelFilter::Info;
846
847 let logger = oslog::OsLogger::new("org.mozilla.glean")
848 .level_filter(level)
849 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
851
852 match logger.init() {
853 Ok(_) => log::trace!("os_log should be hooked up!"),
854 Err(_) => log::warn!("os_log was already initialized"),
858 };
859 }
860
861 #[cfg(all(
865 not(target_os = "android"),
866 not(target_os = "ios"),
867 feature = "enable_env_logger"
868 ))]
869 {
870 match env_logger::try_init() {
871 Ok(_) => log::trace!("stdout logging should be hooked up!"),
872 Err(_) => log::warn!("stdout logging was already initialized"),
876 };
877 }
878}
879
880pub fn glean_set_upload_enabled(enabled: bool) {
885 if !was_initialize_called() {
886 return;
887 }
888
889 crate::launch_with_glean_mut(move |glean| {
890 let state = global_state().lock().unwrap();
891 let original_enabled = glean.is_upload_enabled();
892
893 if !enabled {
894 glean.cancel_metrics_ping_scheduler();
896 if let Err(e) = state.callbacks.cancel_uploads() {
898 log::error!("Canceling upload failed. Error: {}", e);
899 }
900 }
901
902 glean.set_upload_enabled(enabled);
903
904 if !original_enabled && enabled {
905 initialize_core_metrics(glean, &state.client_info);
906 }
907
908 if original_enabled && !enabled {
909 if let Err(e) = state.callbacks.trigger_upload() {
910 log::error!("Triggering upload failed. Error: {}", e);
911 }
912 }
913 })
914}
915
916pub fn glean_set_collection_enabled(enabled: bool) {
920 glean_set_upload_enabled(enabled)
921}
922
923pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
928 let ping = ping.clone();
929 if was_initialize_called() && core::global_glean().is_some() {
930 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
931 } else {
932 let m = &PRE_INIT_PING_ENABLED;
933 let mut lock = m.lock().unwrap();
934 lock.push((ping, enabled));
935 }
936}
937
938pub(crate) fn register_ping_type(ping: &PingType) {
940 if was_initialize_called() && core::global_glean().is_some() {
945 let ping = ping.clone();
946 crate::launch_with_glean_mut(move |glean| {
947 glean.register_ping_type(&ping);
948 })
949 } else {
950 let m = &PRE_INIT_PING_REGISTRATION;
955 let mut lock = m.lock().unwrap();
956 lock.push(ping.clone());
957 }
958}
959
960pub fn glean_get_registered_ping_names() -> Vec<String> {
966 block_on_dispatcher();
967 core::with_glean(|glean| {
968 glean
969 .get_registered_ping_names()
970 .iter()
971 .map(|ping| ping.to_string())
972 .collect()
973 })
974}
975
976pub fn glean_set_experiment_active(
982 experiment_id: String,
983 branch: String,
984 extra: HashMap<String, String>,
985) {
986 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
987}
988
989pub fn glean_set_experiment_inactive(experiment_id: String) {
993 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
994}
995
996pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1000 block_on_dispatcher();
1001 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1002}
1003
1004pub fn glean_set_experimentation_id(experimentation_id: String) {
1008 launch_with_glean(move |glean| {
1009 glean
1010 .additional_metrics
1011 .experimentation_id
1012 .set(experimentation_id);
1013 });
1014}
1015
1016pub fn glean_test_get_experimentation_id() -> Option<String> {
1019 block_on_dispatcher();
1020 core::with_glean(|glean| glean.test_get_experimentation_id())
1021}
1022
1023pub fn glean_apply_server_knobs_config(json: String) {
1028 if json.is_empty() {
1031 return;
1032 }
1033
1034 match RemoteSettingsConfig::try_from(json) {
1035 Ok(cfg) => launch_with_glean(|glean| {
1036 glean.apply_server_knobs_config(cfg);
1037 }),
1038 Err(e) => {
1039 log::error!("Error setting metrics feature config: {:?}", e);
1040 }
1041 }
1042}
1043
1044pub fn glean_set_debug_view_tag(tag: String) -> bool {
1058 if was_initialize_called() && core::global_glean().is_some() {
1059 crate::launch_with_glean_mut(move |glean| {
1060 glean.set_debug_view_tag(&tag);
1061 });
1062 true
1063 } else {
1064 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1066 let mut lock = m.lock().unwrap();
1067 *lock = tag;
1068 true
1071 }
1072}
1073
1074pub fn glean_get_debug_view_tag() -> Option<String> {
1080 block_on_dispatcher();
1081 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1082}
1083
1084pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1096 if was_initialize_called() && core::global_glean().is_some() {
1097 crate::launch_with_glean_mut(|glean| {
1098 glean.set_source_tags(tags);
1099 });
1100 true
1101 } else {
1102 let m = &PRE_INIT_SOURCE_TAGS;
1104 let mut lock = m.lock().unwrap();
1105 *lock = tags;
1106 true
1109 }
1110}
1111
1112pub fn glean_set_log_pings(value: bool) {
1121 if was_initialize_called() && core::global_glean().is_some() {
1122 crate::launch_with_glean_mut(move |glean| {
1123 glean.set_log_pings(value);
1124 });
1125 } else {
1126 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1127 }
1128}
1129
1130pub fn glean_get_log_pings() -> bool {
1136 block_on_dispatcher();
1137 core::with_glean(|glean| glean.log_pings())
1138}
1139
1140pub fn glean_handle_client_active() {
1147 dispatcher::launch(|| {
1148 core::with_glean_mut(|glean| {
1149 glean.handle_client_active();
1150 });
1151
1152 let state = global_state().lock().unwrap();
1156 if let Err(e) = state.callbacks.trigger_upload() {
1157 log::error!("Triggering upload failed. Error: {}", e);
1158 }
1159 });
1160
1161 core_metrics::internal_metrics::baseline_duration.start();
1166}
1167
1168pub fn glean_handle_client_inactive() {
1175 core_metrics::internal_metrics::baseline_duration.stop();
1179
1180 dispatcher::launch(|| {
1181 core::with_glean_mut(|glean| {
1182 glean.handle_client_inactive();
1183 });
1184
1185 let state = global_state().lock().unwrap();
1189 if let Err(e) = state.callbacks.trigger_upload() {
1190 log::error!("Triggering upload failed. Error: {}", e);
1191 }
1192 })
1193}
1194
1195pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1197 dispatcher::launch(|| {
1198 let sent =
1199 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1200
1201 if sent {
1202 let state = global_state().lock().unwrap();
1203 if let Err(e) = state.callbacks.trigger_upload() {
1204 log::error!("Triggering upload failed. Error: {}", e);
1205 }
1206 }
1207 })
1208}
1209
1210pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1214 if !was_initialize_called() {
1215 return false;
1216 }
1217
1218 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1219 .unwrap_or(false)
1220}
1221
1222pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1229 register_event_listener(tag, listener);
1230}
1231
1232pub fn glean_unregister_event_listener(tag: String) {
1240 unregister_event_listener(tag);
1241}
1242
1243pub fn glean_set_test_mode(enabled: bool) {
1247 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1248}
1249
1250pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1254 if was_initialize_called() {
1255 join_init();
1257
1258 dispatcher::reset_dispatcher();
1259
1260 let has_storage = core::with_opt_glean(|glean| {
1263 glean
1265 .storage_opt()
1266 .map(|storage| storage.persist_ping_lifetime_data())
1267 .is_some()
1268 })
1269 .unwrap_or(false);
1270 if has_storage {
1271 uploader_shutdown();
1272 }
1273
1274 if core::global_glean().is_some() {
1275 core::with_glean_mut(|glean| {
1276 if clear_stores {
1277 glean.test_clear_all_stores()
1278 }
1279 glean.destroy_db()
1280 });
1281 }
1282
1283 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1285 } else if clear_stores {
1286 if let Some(data_path) = data_path {
1287 let _ = std::fs::remove_dir_all(data_path).ok();
1288 } else {
1289 log::warn!("Asked to clear stores before initialization, but no data path given.");
1290 }
1291 }
1292}
1293
1294pub fn glean_get_upload_task() -> PingUploadTask {
1296 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1297}
1298
1299pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1301 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1302}
1303
1304pub fn glean_set_dirty_flag(new_value: bool) {
1308 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1309}
1310
1311pub fn glean_update_attribution(attribution: AttributionMetrics) {
1314 if was_initialize_called() && core::global_glean().is_some() {
1315 core::with_glean(|glean| glean.update_attribution(attribution));
1316 } else {
1317 PRE_INIT_ATTRIBUTION
1318 .lock()
1319 .unwrap()
1320 .get_or_insert(Default::default())
1321 .update(attribution);
1322 }
1323}
1324
1325pub fn glean_test_get_attribution() -> AttributionMetrics {
1330 join_init();
1331 core::with_glean(|glean| glean.test_get_attribution())
1332}
1333
1334pub fn glean_update_distribution(distribution: DistributionMetrics) {
1337 if was_initialize_called() && core::global_glean().is_some() {
1338 core::with_glean(|glean| glean.update_distribution(distribution));
1339 } else {
1340 PRE_INIT_DISTRIBUTION
1341 .lock()
1342 .unwrap()
1343 .get_or_insert(Default::default())
1344 .update(distribution);
1345 }
1346}
1347
1348pub fn glean_test_get_distribution() -> DistributionMetrics {
1353 join_init();
1354 core::with_glean(|glean| glean.test_get_distribution())
1355}
1356
1357#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1358static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1359
1360#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1373pub fn glean_enable_logging_to_fd(fd: u64) {
1374 unsafe {
1380 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1385 if log::set_logger(logger).is_ok() {
1389 log::set_max_level(log::LevelFilter::Debug);
1390 }
1391 }
1392}
1393
1394fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1396 let subdirs = ["db", "events", "pending_pings"];
1398 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1399 DataDirectoryInfoObject::with_capacity(subdirs.len());
1400
1401 for subdir in subdirs.iter() {
1402 let dir_path = path.join(subdir);
1403
1404 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1406 dir_name: Some(subdir.to_string()),
1407 dir_exists: None,
1408 dir_created: None,
1409 dir_modified: None,
1410 file_count: None,
1411 files: Vec::new(),
1412 error_message: None,
1413 };
1414
1415 if dir_path.is_dir() {
1417 directory_info.dir_exists = Some(true);
1418
1419 match fs::metadata(&dir_path) {
1421 Ok(metadata) => {
1422 if let Ok(created) = metadata.created() {
1423 directory_info.dir_created = Some(
1424 created
1425 .duration_since(UNIX_EPOCH)
1426 .unwrap_or(Duration::ZERO)
1427 .as_secs() as i64,
1428 );
1429 }
1430 if let Ok(modified) = metadata.modified() {
1431 directory_info.dir_modified = Some(
1432 modified
1433 .duration_since(UNIX_EPOCH)
1434 .unwrap_or(Duration::ZERO)
1435 .as_secs() as i64,
1436 );
1437 }
1438 }
1439 Err(error) => {
1440 let msg = format!("Unable to get metadata: {}", error.kind());
1441 directory_info.error_message = Some(msg.clone());
1442 log::warn!("{}", msg);
1443 continue;
1444 }
1445 }
1446
1447 let mut file_count = 0;
1449 let entries = match fs::read_dir(&dir_path) {
1450 Ok(entries) => entries,
1451 Err(error) => {
1452 let msg = format!("Unable to read subdir: {}", error.kind());
1453 directory_info.error_message = Some(msg.clone());
1454 log::warn!("{}", msg);
1455 continue;
1456 }
1457 };
1458 for entry in entries {
1459 directory_info.files.push(
1460 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1461 file_name: None,
1462 file_created: None,
1463 file_modified: None,
1464 file_size: None,
1465 error_message: None,
1466 },
1467 );
1468 let file_info = directory_info.files.last_mut().unwrap();
1470 let entry = match entry {
1471 Ok(entry) => entry,
1472 Err(error) => {
1473 let msg = format!("Unable to read file: {}", error.kind());
1474 file_info.error_message = Some(msg.clone());
1475 log::warn!("{}", msg);
1476 continue;
1477 }
1478 };
1479 let file_name = match entry.file_name().into_string() {
1480 Ok(file_name) => file_name,
1481 _ => {
1482 let msg = "Unable to convert file name to string".to_string();
1483 file_info.error_message = Some(msg.clone());
1484 log::warn!("{}", msg);
1485 continue;
1486 }
1487 };
1488 let metadata = match entry.metadata() {
1489 Ok(metadata) => metadata,
1490 Err(error) => {
1491 let msg = format!("Unable to read file metadata: {}", error.kind());
1492 file_info.file_name = Some(file_name);
1493 file_info.error_message = Some(msg.clone());
1494 log::warn!("{}", msg);
1495 continue;
1496 }
1497 };
1498
1499 if metadata.is_file() {
1501 file_count += 1;
1502
1503 file_info.file_name = Some(file_name);
1505 file_info.file_created = Some(
1506 metadata
1507 .created()
1508 .unwrap_or(UNIX_EPOCH)
1509 .duration_since(UNIX_EPOCH)
1510 .unwrap_or(Duration::ZERO)
1511 .as_secs() as i64,
1512 );
1513 file_info.file_modified = Some(
1514 metadata
1515 .modified()
1516 .unwrap_or(UNIX_EPOCH)
1517 .duration_since(UNIX_EPOCH)
1518 .unwrap_or(Duration::ZERO)
1519 .as_secs() as i64,
1520 );
1521 file_info.file_size = Some(metadata.len() as i64);
1522 } else {
1523 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1524 file_info.file_name = Some(file_name);
1525 file_info.error_message = Some(msg.clone());
1526 log::warn!("{}", msg);
1527 }
1528 }
1529
1530 directory_info.file_count = Some(file_count as i64);
1531 } else {
1532 directory_info.dir_exists = Some(false);
1533 }
1534
1535 directories_info.push(directory_info);
1537 }
1538
1539 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1540 Some(directories_info_json)
1541 } else {
1542 log::error!("Failed to serialize data directory info");
1543 None
1544 }
1545}
1546
1547fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1548 core::with_glean(|glean| {
1549 glean
1550 .health_metrics
1551 .data_directory_info
1552 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1553 glean.internal_pings.health.submit_sync(glean, Some(reason));
1554 });
1555}
1556
1557#[cfg(any(target_os = "android", target_os = "ios"))]
1559pub fn glean_enable_logging_to_fd(_fd: u64) {
1560 }
1562
1563#[allow(missing_docs)]
1564#[allow(clippy::all)]
1566mod ffi {
1567 use super::*;
1568 uniffi::include_scaffolding!("glean");
1569
1570 type CowString = Cow<'static, str>;
1571
1572 uniffi::custom_type!(CowString, String, {
1573 remote,
1574 lower: |s| s.into_owned(),
1575 try_lift: |s| Ok(Cow::from(s))
1576 });
1577
1578 type JsonValue = serde_json::Value;
1579
1580 uniffi::custom_type!(JsonValue, String, {
1581 remote,
1582 lower: |s| serde_json::to_string(&s).unwrap(),
1583 try_lift: |s| Ok(serde_json::from_str(&s)?)
1584 });
1585}
1586pub use ffi::*;
1587
1588#[cfg(test)]
1590#[path = "lib_unit_tests.rs"]
1591mod tests;