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_timestamp_ms() -> u64 {
197 const NANOS_PER_MILLI: u64 = 1_000_000;
198 zeitstempel::now() / NANOS_PER_MILLI
199}
200
201struct State {
206 client_info: ClientInfoMetrics,
208
209 callbacks: Box<dyn OnGleanEvents>,
210}
211
212static STATE: OnceCell<Mutex<State>> = OnceCell::new();
216
217#[track_caller] fn global_state() -> &'static Mutex<State> {
222 STATE.get().unwrap()
223}
224
225#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
230 STATE.get()
231}
232
233fn setup_state(state: State) {
235 if STATE.get().is_none() {
245 if STATE.set(Mutex::new(state)).is_err() {
246 log::error!(
247 "Global Glean state object is initialized already. This probably happened concurrently."
248 );
249 }
250 } else {
251 let mut lock = STATE.get().unwrap().lock().unwrap();
255 *lock = state;
256 }
257}
258
259static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
262 OnceCell::new();
263
264fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
265 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
266}
267
268fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
269 let mut lock = event_listeners().lock().unwrap();
270 lock.insert(tag, listener);
271}
272
273fn unregister_event_listener(tag: String) {
274 let mut lock = event_listeners().lock().unwrap();
275 lock.remove(&tag);
276}
277
278#[derive(Debug)]
280pub enum CallbackError {
281 UnexpectedError,
283}
284
285impl fmt::Display for CallbackError {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 write!(f, "Unexpected error")
288 }
289}
290
291impl std::error::Error for CallbackError {}
292
293impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
294 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
295 CallbackError::UnexpectedError
296 }
297}
298
299pub trait OnGleanEvents: Send {
303 fn initialize_finished(&self);
309
310 fn trigger_upload(&self) -> Result<(), CallbackError>;
315
316 fn start_metrics_ping_scheduler(&self) -> bool;
318
319 fn cancel_uploads(&self) -> Result<(), CallbackError>;
321
322 fn shutdown(&self) -> Result<(), CallbackError> {
329 Ok(())
331 }
332}
333
334pub trait GleanEventListener: Send {
337 fn on_event_recorded(&self, id: String);
339}
340
341pub fn glean_initialize(
350 cfg: InternalConfiguration,
351 client_info: ClientInfoMetrics,
352 callbacks: Box<dyn OnGleanEvents>,
353) {
354 initialize_inner(cfg, client_info, callbacks);
355}
356
357pub fn glean_shutdown() {
359 shutdown();
360}
361
362pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
367 let glean = match Glean::new_for_subprocess(&cfg, true) {
368 Ok(glean) => glean,
369 Err(err) => {
370 log::error!("Failed to initialize Glean: {}", err);
371 return false;
372 }
373 };
374 if core::setup_glean(glean).is_err() {
375 return false;
376 }
377 log::info!("Glean initialized for subprocess");
378 true
379}
380
381fn initialize_inner(
382 cfg: InternalConfiguration,
383 client_info: ClientInfoMetrics,
384 callbacks: Box<dyn OnGleanEvents>,
385) {
386 if was_initialize_called() {
387 log::error!("Glean should not be initialized multiple times");
388 return;
389 }
390
391 let init_handle = thread::spawn("glean.init", move || {
392 let upload_enabled = cfg.upload_enabled;
393 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
394
395 if let Some(level) = cfg.log_level {
397 log::set_max_level(level)
398 }
399
400 let data_path_str = cfg.data_path.clone();
401 let data_path = Path::new(&data_path_str);
402 let internal_pings_enabled = cfg.enable_internal_pings;
403 let dir_info = if !is_test_mode() && internal_pings_enabled {
404 collect_directory_info(Path::new(&data_path))
405 } else {
406 None
407 };
408
409 let glean = match Glean::new(cfg) {
410 Ok(glean) => glean,
411 Err(err) => {
412 log::error!("Failed to initialize Glean: {}", err);
413 return;
414 }
415 };
416 if core::setup_glean(glean).is_err() {
417 return;
418 }
419
420 log::info!("Glean initialized");
421
422 core::with_glean(|glean| {
423 glean.health_metrics.init_count.add_sync(glean, 1);
424 });
425
426 setup_state(State {
427 client_info,
428 callbacks,
429 });
430
431 let mut is_first_run = false;
432 let mut dirty_flag = false;
433 let mut pings_submitted = false;
434 core::with_glean_mut(|glean| {
435 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
438 if !debug_tag.is_empty() {
439 glean.set_debug_view_tag(&debug_tag);
440 }
441
442 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
445 if log_pigs {
446 glean.set_log_pings(log_pigs);
447 }
448
449 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
452 if !source_tags.is_empty() {
453 glean.set_source_tags(source_tags.to_vec());
454 }
455
456 dirty_flag = glean.is_dirty_flag_set();
461 glean.set_dirty_flag(false);
462
463 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
466 for ping in pings.iter() {
467 glean.register_ping_type(ping);
468 }
469 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
470 for (ping, enabled) in pings.iter() {
471 glean.set_ping_enabled(ping, *enabled);
472 }
473
474 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
477 glean.update_attribution(attribution);
478 }
479 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
480 glean.update_distribution(distribution);
481 }
482
483 is_first_run = glean.is_first_run();
487 if is_first_run {
488 let state = global_state().lock().unwrap();
489 initialize_core_metrics(glean, &state.client_info);
490 }
491
492 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
494 });
495
496 {
497 let state = global_state().lock().unwrap();
498 if pings_submitted || !upload_enabled {
502 if let Err(e) = state.callbacks.trigger_upload() {
503 log::error!("Triggering upload failed. Error: {}", e);
504 }
505 }
506 }
507
508 core::with_glean(|glean| {
509 glean.start_metrics_ping_scheduler();
511 });
512
513 {
521 let state = global_state().lock().unwrap();
522
523 if state.callbacks.start_metrics_ping_scheduler() {
527 if let Err(e) = state.callbacks.trigger_upload() {
528 log::error!("Triggering upload failed. Error: {}", e);
529 }
530 }
531 }
532
533 core::with_glean_mut(|glean| {
534 let state = global_state().lock().unwrap();
535
536 if !is_first_run && dirty_flag {
540 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
546 if let Err(e) = state.callbacks.trigger_upload() {
547 log::error!("Triggering upload failed. Error: {}", e);
548 }
549 }
550 }
551
552 if !is_first_run {
556 glean.clear_application_lifetime_metrics();
557 initialize_core_metrics(glean, &state.client_info);
558 }
559 });
560
561 match dispatcher::flush_init() {
567 Ok(task_count) if task_count > 0 => {
568 core::with_glean(|glean| {
569 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
570 });
571 }
572 Ok(_) => {}
573 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
574 }
575
576 if !is_test_mode() && internal_pings_enabled {
577 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
580
581 record_dir_info_and_submit_health_ping(collect_directory_info(data_path), "post_init");
584 }
585 let state = global_state().lock().unwrap();
586 state.callbacks.initialize_finished();
587 })
588 .expect("Failed to spawn Glean's init thread");
589
590 INIT_HANDLES.lock().unwrap().push(init_handle);
592
593 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
596
597 if dispatcher::global::is_test_mode() {
600 join_init();
601 }
602}
603
604pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
608 use malloc_size_of::MallocSizeOf;
609 core::with_glean(|glean| glean.size_of(ops))
610}
611
612pub fn join_init() {
615 let mut handles = INIT_HANDLES.lock().unwrap();
616 for handle in handles.drain(..) {
617 handle.join().unwrap();
618 }
619}
620
621fn uploader_shutdown() {
629 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
630 let (tx, rx) = unbounded();
631
632 let handle = thread::spawn("glean.shutdown", move || {
633 let state = global_state().lock().unwrap();
634 if let Err(e) = state.callbacks.shutdown() {
635 log::error!("Shutdown callback failed: {e:?}");
636 }
637
638 let _ = tx.send(()).ok();
640 })
641 .expect("Unable to spawn thread to wait on shutdown");
642
643 let result = rx.recv_timeout(Duration::from_secs(30));
651
652 let stop_time = zeitstempel::now();
653 core::with_glean(|glean| {
654 glean
655 .additional_metrics
656 .shutdown_wait
657 .set_stop_and_accumulate(glean, timer_id, stop_time);
658 });
659
660 if result.is_err() {
661 log::warn!("Waiting for upload failed. We're shutting down.");
662 } else {
663 let _ = handle.join().ok();
664 }
665}
666
667pub fn shutdown() {
669 if !was_initialize_called() {
679 log::warn!("Shutdown called before Glean is initialized");
680 if let Err(e) = dispatcher::kill() {
681 log::error!("Can't kill dispatcher thread: {:?}", e);
682 }
683 return;
684 }
685
686 if core::global_glean().is_none() {
688 log::warn!("Shutdown called before Glean is initialized. Waiting.");
689 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
697 }
698 if core::global_glean().is_none() {
700 log::warn!("Waiting for Glean initialization timed out. Exiting.");
701 if let Err(e) = dispatcher::kill() {
702 log::error!("Can't kill dispatcher thread: {:?}", e);
703 }
704 return;
705 }
706
707 crate::launch_with_glean_mut(|glean| {
709 glean.cancel_metrics_ping_scheduler();
710 glean.set_dirty_flag(false);
711 });
712
713 let timer_id = core::with_glean(|glean| {
720 glean
721 .additional_metrics
722 .shutdown_dispatcher_wait
723 .start_sync()
724 });
725 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
726
727 let stop_time = zeitstempel::now();
729 core::with_glean(|glean| {
730 glean
731 .additional_metrics
732 .shutdown_dispatcher_wait
733 .set_stop_and_accumulate(glean, timer_id, stop_time);
734 });
735 if blocked.is_err() {
736 log::error!(
737 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
738 );
739 return;
740 }
741
742 if let Err(e) = dispatcher::shutdown() {
743 log::error!("Can't shutdown dispatcher thread: {:?}", e);
744 }
745
746 uploader_shutdown();
747
748 core::with_glean(|glean| {
750 if let Err(e) = glean.persist_ping_lifetime_data() {
751 log::error!("Can't persist ping lifetime data: {:?}", e);
752 }
753 });
754}
755
756pub fn glean_persist_ping_lifetime_data() {
763 crate::launch_with_glean(|glean| {
765 let _ = glean.persist_ping_lifetime_data();
766 });
767}
768
769fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
770 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
771 core_metrics::internal_metrics::app_display_version
772 .set_sync(glean, &client_info.app_display_version[..]);
773 core_metrics::internal_metrics::app_build_date
774 .set_sync(glean, Some(client_info.app_build_date.clone()));
775 if let Some(app_channel) = client_info.channel.as_ref() {
776 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
777 }
778
779 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
780 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
781
782 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
783 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
784 }
785 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
786 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
787 }
788 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
789 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
790 }
791 if let Some(device_model) = client_info.device_model.as_ref() {
792 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
793 }
794 if let Some(locale) = client_info.locale.as_ref() {
795 core_metrics::internal_metrics::locale.set_sync(glean, locale);
796 }
797}
798
799fn was_initialize_called() -> bool {
805 INITIALIZE_CALLED.load(Ordering::SeqCst)
806}
807
808#[no_mangle]
811pub extern "C" fn glean_enable_logging() {
812 #[cfg(target_os = "android")]
813 {
814 let _ = std::panic::catch_unwind(|| {
815 let filter = android_logger::FilterBuilder::new()
816 .filter_module("glean_ffi", log::LevelFilter::Debug)
817 .filter_module("glean_core", log::LevelFilter::Debug)
818 .filter_module("glean", log::LevelFilter::Debug)
819 .filter_module("glean_core::ffi", log::LevelFilter::Info)
820 .build();
821 android_logger::init_once(
822 android_logger::Config::default()
823 .with_max_level(log::LevelFilter::Debug)
824 .with_filter(filter)
825 .with_tag("libglean_ffi"),
826 );
827 log::trace!("Android logging should be hooked up!")
828 });
829 }
830
831 #[cfg(target_os = "ios")]
833 {
834 #[cfg(debug_assertions)]
837 let level = log::LevelFilter::Debug;
838 #[cfg(not(debug_assertions))]
839 let level = log::LevelFilter::Info;
840
841 let logger = oslog::OsLogger::new("org.mozilla.glean")
842 .level_filter(level)
843 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
845
846 match logger.init() {
847 Ok(_) => log::trace!("os_log should be hooked up!"),
848 Err(_) => log::warn!("os_log was already initialized"),
852 };
853 }
854
855 #[cfg(all(
859 not(target_os = "android"),
860 not(target_os = "ios"),
861 feature = "enable_env_logger"
862 ))]
863 {
864 match env_logger::try_init() {
865 Ok(_) => log::trace!("stdout logging should be hooked up!"),
866 Err(_) => log::warn!("stdout logging was already initialized"),
870 };
871 }
872}
873
874pub fn glean_set_upload_enabled(enabled: bool) {
879 if !was_initialize_called() {
880 return;
881 }
882
883 crate::launch_with_glean_mut(move |glean| {
884 let state = global_state().lock().unwrap();
885 let original_enabled = glean.is_upload_enabled();
886
887 if !enabled {
888 glean.cancel_metrics_ping_scheduler();
890 if let Err(e) = state.callbacks.cancel_uploads() {
892 log::error!("Canceling upload failed. Error: {}", e);
893 }
894 }
895
896 glean.set_upload_enabled(enabled);
897
898 if !original_enabled && enabled {
899 initialize_core_metrics(glean, &state.client_info);
900 }
901
902 if original_enabled && !enabled {
903 if let Err(e) = state.callbacks.trigger_upload() {
904 log::error!("Triggering upload failed. Error: {}", e);
905 }
906 }
907 })
908}
909
910pub fn glean_set_collection_enabled(enabled: bool) {
914 glean_set_upload_enabled(enabled)
915}
916
917pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
922 let ping = ping.clone();
923 if was_initialize_called() && core::global_glean().is_some() {
924 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
925 } else {
926 let m = &PRE_INIT_PING_ENABLED;
927 let mut lock = m.lock().unwrap();
928 lock.push((ping, enabled));
929 }
930}
931
932pub(crate) fn register_ping_type(ping: &PingType) {
934 if was_initialize_called() && core::global_glean().is_some() {
939 let ping = ping.clone();
940 crate::launch_with_glean_mut(move |glean| {
941 glean.register_ping_type(&ping);
942 })
943 } else {
944 let m = &PRE_INIT_PING_REGISTRATION;
949 let mut lock = m.lock().unwrap();
950 lock.push(ping.clone());
951 }
952}
953
954pub fn glean_get_registered_ping_names() -> Vec<String> {
960 block_on_dispatcher();
961 core::with_glean(|glean| {
962 glean
963 .get_registered_ping_names()
964 .iter()
965 .map(|ping| ping.to_string())
966 .collect()
967 })
968}
969
970pub fn glean_set_experiment_active(
976 experiment_id: String,
977 branch: String,
978 extra: HashMap<String, String>,
979) {
980 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
981}
982
983pub fn glean_set_experiment_inactive(experiment_id: String) {
987 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
988}
989
990pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
994 block_on_dispatcher();
995 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
996}
997
998pub fn glean_set_experimentation_id(experimentation_id: String) {
1002 launch_with_glean(move |glean| {
1003 glean
1004 .additional_metrics
1005 .experimentation_id
1006 .set(experimentation_id);
1007 });
1008}
1009
1010pub fn glean_test_get_experimentation_id() -> Option<String> {
1013 block_on_dispatcher();
1014 core::with_glean(|glean| glean.test_get_experimentation_id())
1015}
1016
1017pub fn glean_apply_server_knobs_config(json: String) {
1022 if json.is_empty() {
1025 return;
1026 }
1027
1028 match RemoteSettingsConfig::try_from(json) {
1029 Ok(cfg) => launch_with_glean(|glean| {
1030 glean.apply_server_knobs_config(cfg);
1031 }),
1032 Err(e) => {
1033 log::error!("Error setting metrics feature config: {:?}", e);
1034 }
1035 }
1036}
1037
1038pub fn glean_set_debug_view_tag(tag: String) -> bool {
1052 if was_initialize_called() && core::global_glean().is_some() {
1053 crate::launch_with_glean_mut(move |glean| {
1054 glean.set_debug_view_tag(&tag);
1055 });
1056 true
1057 } else {
1058 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1060 let mut lock = m.lock().unwrap();
1061 *lock = tag;
1062 true
1065 }
1066}
1067
1068pub fn glean_get_debug_view_tag() -> Option<String> {
1074 block_on_dispatcher();
1075 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1076}
1077
1078pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1090 if was_initialize_called() && core::global_glean().is_some() {
1091 crate::launch_with_glean_mut(|glean| {
1092 glean.set_source_tags(tags);
1093 });
1094 true
1095 } else {
1096 let m = &PRE_INIT_SOURCE_TAGS;
1098 let mut lock = m.lock().unwrap();
1099 *lock = tags;
1100 true
1103 }
1104}
1105
1106pub fn glean_set_log_pings(value: bool) {
1115 if was_initialize_called() && core::global_glean().is_some() {
1116 crate::launch_with_glean_mut(move |glean| {
1117 glean.set_log_pings(value);
1118 });
1119 } else {
1120 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1121 }
1122}
1123
1124pub fn glean_get_log_pings() -> bool {
1130 block_on_dispatcher();
1131 core::with_glean(|glean| glean.log_pings())
1132}
1133
1134pub fn glean_handle_client_active() {
1141 dispatcher::launch(|| {
1142 core::with_glean_mut(|glean| {
1143 glean.handle_client_active();
1144 });
1145
1146 let state = global_state().lock().unwrap();
1150 if let Err(e) = state.callbacks.trigger_upload() {
1151 log::error!("Triggering upload failed. Error: {}", e);
1152 }
1153 });
1154
1155 core_metrics::internal_metrics::baseline_duration.start();
1160}
1161
1162pub fn glean_handle_client_inactive() {
1169 core_metrics::internal_metrics::baseline_duration.stop();
1173
1174 dispatcher::launch(|| {
1175 core::with_glean_mut(|glean| {
1176 glean.handle_client_inactive();
1177 });
1178
1179 let state = global_state().lock().unwrap();
1183 if let Err(e) = state.callbacks.trigger_upload() {
1184 log::error!("Triggering upload failed. Error: {}", e);
1185 }
1186 })
1187}
1188
1189pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1191 dispatcher::launch(|| {
1192 let sent =
1193 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1194
1195 if sent {
1196 let state = global_state().lock().unwrap();
1197 if let Err(e) = state.callbacks.trigger_upload() {
1198 log::error!("Triggering upload failed. Error: {}", e);
1199 }
1200 }
1201 })
1202}
1203
1204pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1208 if !was_initialize_called() {
1209 return false;
1210 }
1211
1212 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1213 .unwrap_or(false)
1214}
1215
1216pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1223 register_event_listener(tag, listener);
1224}
1225
1226pub fn glean_unregister_event_listener(tag: String) {
1234 unregister_event_listener(tag);
1235}
1236
1237pub fn glean_set_test_mode(enabled: bool) {
1241 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1242}
1243
1244pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1248 if was_initialize_called() {
1249 join_init();
1251
1252 dispatcher::reset_dispatcher();
1253
1254 let has_storage = core::with_opt_glean(|glean| {
1257 glean
1259 .storage_opt()
1260 .map(|storage| storage.persist_ping_lifetime_data())
1261 .is_some()
1262 })
1263 .unwrap_or(false);
1264 if has_storage {
1265 uploader_shutdown();
1266 }
1267
1268 if core::global_glean().is_some() {
1269 core::with_glean_mut(|glean| {
1270 if clear_stores {
1271 glean.test_clear_all_stores()
1272 }
1273 glean.destroy_db()
1274 });
1275 }
1276
1277 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1279 } else if clear_stores {
1280 if let Some(data_path) = data_path {
1281 let _ = std::fs::remove_dir_all(data_path).ok();
1282 } else {
1283 log::warn!("Asked to clear stores before initialization, but no data path given.");
1284 }
1285 }
1286}
1287
1288pub fn glean_get_upload_task() -> PingUploadTask {
1290 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1291}
1292
1293pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1295 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1296}
1297
1298pub fn glean_set_dirty_flag(new_value: bool) {
1302 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1303}
1304
1305pub fn glean_update_attribution(attribution: AttributionMetrics) {
1308 if was_initialize_called() && core::global_glean().is_some() {
1309 core::with_glean(|glean| glean.update_attribution(attribution));
1310 } else {
1311 PRE_INIT_ATTRIBUTION
1312 .lock()
1313 .unwrap()
1314 .get_or_insert(Default::default())
1315 .update(attribution);
1316 }
1317}
1318
1319pub fn glean_test_get_attribution() -> AttributionMetrics {
1324 join_init();
1325 core::with_glean(|glean| glean.test_get_attribution())
1326}
1327
1328pub fn glean_update_distribution(distribution: DistributionMetrics) {
1331 if was_initialize_called() && core::global_glean().is_some() {
1332 core::with_glean(|glean| glean.update_distribution(distribution));
1333 } else {
1334 PRE_INIT_DISTRIBUTION
1335 .lock()
1336 .unwrap()
1337 .get_or_insert(Default::default())
1338 .update(distribution);
1339 }
1340}
1341
1342pub fn glean_test_get_distribution() -> DistributionMetrics {
1347 join_init();
1348 core::with_glean(|glean| glean.test_get_distribution())
1349}
1350
1351#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1352static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1353
1354#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1367pub fn glean_enable_logging_to_fd(fd: u64) {
1368 unsafe {
1374 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1379 if log::set_logger(logger).is_ok() {
1383 log::set_max_level(log::LevelFilter::Debug);
1384 }
1385 }
1386}
1387
1388fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1390 let subdirs = ["db", "events", "pending_pings"];
1392 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1393 DataDirectoryInfoObject::with_capacity(subdirs.len());
1394
1395 for subdir in subdirs.iter() {
1396 let dir_path = path.join(subdir);
1397
1398 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1400 dir_name: Some(subdir.to_string()),
1401 dir_exists: None,
1402 dir_created: None,
1403 dir_modified: None,
1404 file_count: None,
1405 files: Vec::new(),
1406 error_message: None,
1407 };
1408
1409 if dir_path.is_dir() {
1411 directory_info.dir_exists = Some(true);
1412
1413 match fs::metadata(&dir_path) {
1415 Ok(metadata) => {
1416 if let Ok(created) = metadata.created() {
1417 directory_info.dir_created = Some(
1418 created
1419 .duration_since(UNIX_EPOCH)
1420 .unwrap_or(Duration::ZERO)
1421 .as_secs() as i64,
1422 );
1423 }
1424 if let Ok(modified) = metadata.modified() {
1425 directory_info.dir_modified = Some(
1426 modified
1427 .duration_since(UNIX_EPOCH)
1428 .unwrap_or(Duration::ZERO)
1429 .as_secs() as i64,
1430 );
1431 }
1432 }
1433 Err(error) => {
1434 let msg = format!("Unable to get metadata: {}", error.kind());
1435 directory_info.error_message = Some(msg.clone());
1436 log::warn!("{}", msg);
1437 continue;
1438 }
1439 }
1440
1441 let mut file_count = 0;
1443 let entries = match fs::read_dir(&dir_path) {
1444 Ok(entries) => entries,
1445 Err(error) => {
1446 let msg = format!("Unable to read subdir: {}", error.kind());
1447 directory_info.error_message = Some(msg.clone());
1448 log::warn!("{}", msg);
1449 continue;
1450 }
1451 };
1452 for entry in entries {
1453 directory_info.files.push(
1454 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1455 file_name: None,
1456 file_created: None,
1457 file_modified: None,
1458 file_size: None,
1459 error_message: None,
1460 },
1461 );
1462 let file_info = directory_info.files.last_mut().unwrap();
1464 let entry = match entry {
1465 Ok(entry) => entry,
1466 Err(error) => {
1467 let msg = format!("Unable to read file: {}", error.kind());
1468 file_info.error_message = Some(msg.clone());
1469 log::warn!("{}", msg);
1470 continue;
1471 }
1472 };
1473 let file_name = match entry.file_name().into_string() {
1474 Ok(file_name) => file_name,
1475 _ => {
1476 let msg = "Unable to convert file name to string".to_string();
1477 file_info.error_message = Some(msg.clone());
1478 log::warn!("{}", msg);
1479 continue;
1480 }
1481 };
1482 let metadata = match entry.metadata() {
1483 Ok(metadata) => metadata,
1484 Err(error) => {
1485 let msg = format!("Unable to read file metadata: {}", error.kind());
1486 file_info.file_name = Some(file_name);
1487 file_info.error_message = Some(msg.clone());
1488 log::warn!("{}", msg);
1489 continue;
1490 }
1491 };
1492
1493 if metadata.is_file() {
1495 file_count += 1;
1496
1497 file_info.file_name = Some(file_name);
1499 file_info.file_created = Some(
1500 metadata
1501 .created()
1502 .unwrap_or(UNIX_EPOCH)
1503 .duration_since(UNIX_EPOCH)
1504 .unwrap_or(Duration::ZERO)
1505 .as_secs() as i64,
1506 );
1507 file_info.file_modified = Some(
1508 metadata
1509 .modified()
1510 .unwrap_or(UNIX_EPOCH)
1511 .duration_since(UNIX_EPOCH)
1512 .unwrap_or(Duration::ZERO)
1513 .as_secs() as i64,
1514 );
1515 file_info.file_size = Some(metadata.len() as i64);
1516 } else {
1517 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1518 file_info.file_name = Some(file_name);
1519 file_info.error_message = Some(msg.clone());
1520 log::warn!("{}", msg);
1521 }
1522 }
1523
1524 directory_info.file_count = Some(file_count as i64);
1525 } else {
1526 directory_info.dir_exists = Some(false);
1527 }
1528
1529 directories_info.push(directory_info);
1531 }
1532
1533 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1534 Some(directories_info_json)
1535 } else {
1536 log::error!("Failed to serialize data directory info");
1537 None
1538 }
1539}
1540
1541fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1542 core::with_glean(|glean| {
1543 glean
1544 .health_metrics
1545 .data_directory_info
1546 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1547 glean.internal_pings.health.submit_sync(glean, Some(reason));
1548 });
1549}
1550
1551#[cfg(any(target_os = "android", target_os = "ios"))]
1553pub fn glean_enable_logging_to_fd(_fd: u64) {
1554 }
1556
1557#[allow(missing_docs)]
1558#[allow(clippy::all)]
1560mod ffi {
1561 use super::*;
1562 uniffi::include_scaffolding!("glean");
1563
1564 type CowString = Cow<'static, str>;
1565
1566 uniffi::custom_type!(CowString, String, {
1567 remote,
1568 lower: |s| s.into_owned(),
1569 try_lift: |s| Ok(Cow::from(s))
1570 });
1571
1572 type JsonValue = serde_json::Value;
1573
1574 uniffi::custom_type!(JsonValue, String, {
1575 remote,
1576 lower: |s| serde_json::to_string(&s).unwrap(),
1577 try_lift: |s| Ok(serde_json::from_str(&s)?)
1578 });
1579}
1580pub use ffi::*;
1581
1582#[cfg(test)]
1584#[path = "lib_unit_tests.rs"]
1585mod tests;