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 pub max_pending_pings_count: Option<u64>,
171 pub max_pending_pings_directory_size: Option<u64>,
173}
174
175#[derive(Debug, Clone, MallocSizeOf)]
177pub struct PingRateLimit {
178 pub seconds_per_interval: u64,
180 pub pings_per_interval: u32,
182}
183
184fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
186 dispatcher::launch(|| core::with_glean(callback));
187}
188
189fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
192 dispatcher::launch(|| core::with_glean_mut(callback));
193}
194
195fn block_on_dispatcher() {
199 dispatcher::block_on_queue()
200}
201
202pub fn get_awake_timestamp_ms() -> u64 {
204 const NANOS_PER_MILLI: u64 = 1_000_000;
205 zeitstempel::now_awake() / NANOS_PER_MILLI
206}
207
208pub fn get_timestamp_ms() -> u64 {
210 const NANOS_PER_MILLI: u64 = 1_000_000;
211 zeitstempel::now() / NANOS_PER_MILLI
212}
213
214struct State {
219 client_info: ClientInfoMetrics,
221
222 callbacks: Box<dyn OnGleanEvents>,
223}
224
225static STATE: OnceCell<Mutex<State>> = OnceCell::new();
229
230#[track_caller] fn global_state() -> &'static Mutex<State> {
235 STATE.get().unwrap()
236}
237
238#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
243 STATE.get()
244}
245
246fn setup_state(state: State) {
248 if STATE.get().is_none() {
258 if STATE.set(Mutex::new(state)).is_err() {
259 log::error!(
260 "Global Glean state object is initialized already. This probably happened concurrently."
261 );
262 }
263 } else {
264 let mut lock = STATE.get().unwrap().lock().unwrap();
268 *lock = state;
269 }
270}
271
272static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
275 OnceCell::new();
276
277fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
278 EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
279}
280
281fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
282 let mut lock = event_listeners().lock().unwrap();
283 lock.insert(tag, listener);
284}
285
286fn unregister_event_listener(tag: String) {
287 let mut lock = event_listeners().lock().unwrap();
288 lock.remove(&tag);
289}
290
291#[derive(Debug)]
293pub enum CallbackError {
294 UnexpectedError,
296}
297
298impl fmt::Display for CallbackError {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "Unexpected error")
301 }
302}
303
304impl std::error::Error for CallbackError {}
305
306impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
307 fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
308 CallbackError::UnexpectedError
309 }
310}
311
312pub trait OnGleanEvents: Send {
316 fn initialize_finished(&self);
322
323 fn trigger_upload(&self) -> Result<(), CallbackError>;
328
329 fn start_metrics_ping_scheduler(&self) -> bool;
331
332 fn cancel_uploads(&self) -> Result<(), CallbackError>;
334
335 fn shutdown(&self) -> Result<(), CallbackError> {
342 Ok(())
344 }
345}
346
347pub trait GleanEventListener: Send {
350 fn on_event_recorded(&self, id: String);
352}
353
354pub fn glean_initialize(
363 cfg: InternalConfiguration,
364 client_info: ClientInfoMetrics,
365 callbacks: Box<dyn OnGleanEvents>,
366) {
367 initialize_inner(cfg, client_info, callbacks);
368}
369
370pub fn glean_shutdown() {
372 shutdown();
373}
374
375pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
380 let glean = match Glean::new_for_subprocess(&cfg, true) {
381 Ok(glean) => glean,
382 Err(err) => {
383 log::error!("Failed to initialize Glean: {}", err);
384 return false;
385 }
386 };
387 if core::setup_glean(glean).is_err() {
388 return false;
389 }
390 log::info!("Glean initialized for subprocess");
391 true
392}
393
394fn initialize_inner(
395 cfg: InternalConfiguration,
396 client_info: ClientInfoMetrics,
397 callbacks: Box<dyn OnGleanEvents>,
398) {
399 if was_initialize_called() {
400 log::error!("Glean should not be initialized multiple times");
401 return;
402 }
403
404 let init_handle = thread::spawn("glean.init", move || {
405 let upload_enabled = cfg.upload_enabled;
406 let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
407
408 if let Some(level) = cfg.log_level {
410 log::set_max_level(level)
411 }
412
413 let data_path_str = cfg.data_path.clone();
414 let data_path = Path::new(&data_path_str);
415 let internal_pings_enabled = cfg.enable_internal_pings;
416 let dir_info = if !is_test_mode() && internal_pings_enabled {
417 collect_directory_info(Path::new(&data_path))
418 } else {
419 None
420 };
421
422 let glean = match Glean::new(cfg) {
423 Ok(glean) => glean,
424 Err(err) => {
425 log::error!("Failed to initialize Glean: {}", err);
426 return;
427 }
428 };
429 if core::setup_glean(glean).is_err() {
430 return;
431 }
432
433 log::info!("Glean initialized");
434
435 core::with_glean(|glean| {
436 glean.health_metrics.init_count.add_sync(glean, 1);
437 });
438
439 setup_state(State {
440 client_info,
441 callbacks,
442 });
443
444 let mut is_first_run = false;
445 let mut dirty_flag = false;
446 let mut pings_submitted = false;
447 core::with_glean_mut(|glean| {
448 let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
451 if !debug_tag.is_empty() {
452 glean.set_debug_view_tag(&debug_tag);
453 }
454
455 let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
458 if log_pigs {
459 glean.set_log_pings(log_pigs);
460 }
461
462 let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
465 if !source_tags.is_empty() {
466 glean.set_source_tags(source_tags.to_vec());
467 }
468
469 dirty_flag = glean.is_dirty_flag_set();
474 glean.set_dirty_flag(false);
475
476 let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
479 for ping in pings.iter() {
480 glean.register_ping_type(ping);
481 }
482 let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
483 for (ping, enabled) in pings.iter() {
484 glean.set_ping_enabled(ping, *enabled);
485 }
486
487 if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
490 glean.update_attribution(attribution);
491 }
492 if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
493 glean.update_distribution(distribution);
494 }
495
496 is_first_run = glean.is_first_run();
500 if is_first_run {
501 let state = global_state().lock().unwrap();
502 initialize_core_metrics(glean, &state.client_info);
503 }
504
505 pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
507 });
508
509 {
510 let state = global_state().lock().unwrap();
511 if pings_submitted || !upload_enabled {
515 if let Err(e) = state.callbacks.trigger_upload() {
516 log::error!("Triggering upload failed. Error: {}", e);
517 }
518 }
519 }
520
521 core::with_glean(|glean| {
522 glean.start_metrics_ping_scheduler();
524 });
525
526 {
534 let state = global_state().lock().unwrap();
535
536 if state.callbacks.start_metrics_ping_scheduler() {
540 if let Err(e) = state.callbacks.trigger_upload() {
541 log::error!("Triggering upload failed. Error: {}", e);
542 }
543 }
544 }
545
546 core::with_glean_mut(|glean| {
547 let state = global_state().lock().unwrap();
548
549 if !is_first_run && dirty_flag {
553 if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
559 if let Err(e) = state.callbacks.trigger_upload() {
560 log::error!("Triggering upload failed. Error: {}", e);
561 }
562 }
563 }
564
565 if !is_first_run {
569 glean.clear_application_lifetime_metrics();
570 initialize_core_metrics(glean, &state.client_info);
571 }
572 });
573
574 match dispatcher::flush_init() {
580 Ok(task_count) if task_count > 0 => {
581 core::with_glean(|glean| {
582 glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
583 });
584 }
585 Ok(_) => {}
586 Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
587 }
588
589 if !is_test_mode() && internal_pings_enabled {
590 record_dir_info_and_submit_health_ping(dir_info, "pre_init");
593
594 let state = global_state().lock().unwrap();
595 if let Err(e) = state.callbacks.trigger_upload() {
596 log::error!("Triggering upload failed. Error: {}", e);
597 }
598 }
599 let state = global_state().lock().unwrap();
600 state.callbacks.initialize_finished();
601 })
602 .expect("Failed to spawn Glean's init thread");
603
604 INIT_HANDLES.lock().unwrap().push(init_handle);
606
607 INITIALIZE_CALLED.store(true, Ordering::SeqCst);
610
611 if dispatcher::global::is_test_mode() {
614 join_init();
615 }
616}
617
618pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
622 use malloc_size_of::MallocSizeOf;
623 core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
624}
625
626pub fn join_init() {
629 let mut handles = INIT_HANDLES.lock().unwrap();
630 for handle in handles.drain(..) {
631 handle.join().unwrap();
632 }
633}
634
635fn uploader_shutdown() {
643 let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
644 let (tx, rx) = unbounded();
645
646 let handle = thread::spawn("glean.shutdown", move || {
647 let state = global_state().lock().unwrap();
648 if let Err(e) = state.callbacks.shutdown() {
649 log::error!("Shutdown callback failed: {e:?}");
650 }
651
652 let _ = tx.send(()).ok();
654 })
655 .expect("Unable to spawn thread to wait on shutdown");
656
657 let result = rx.recv_timeout(Duration::from_secs(30));
665
666 let stop_time = zeitstempel::now_awake();
667 core::with_glean(|glean| {
668 glean
669 .additional_metrics
670 .shutdown_wait
671 .set_stop_and_accumulate(glean, timer_id, stop_time);
672 });
673
674 if result.is_err() {
675 log::warn!("Waiting for upload failed. We're shutting down.");
676 } else {
677 let _ = handle.join().ok();
678 }
679}
680
681pub fn shutdown() {
683 if !was_initialize_called() {
693 log::warn!("Shutdown called before Glean is initialized");
694 if let Err(e) = dispatcher::kill() {
695 log::error!("Can't kill dispatcher thread: {:?}", e);
696 }
697 return;
698 }
699
700 if core::global_glean().is_none() {
702 log::warn!("Shutdown called before Glean is initialized. Waiting.");
703 let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
711 }
712 if core::global_glean().is_none() {
714 log::warn!("Waiting for Glean initialization timed out. Exiting.");
715 if let Err(e) = dispatcher::kill() {
716 log::error!("Can't kill dispatcher thread: {:?}", e);
717 }
718 return;
719 }
720
721 crate::launch_with_glean_mut(|glean| {
723 glean.cancel_metrics_ping_scheduler();
724 glean.set_dirty_flag(false);
725 });
726
727 let timer_id = core::with_glean(|glean| {
734 glean
735 .additional_metrics
736 .shutdown_dispatcher_wait
737 .start_sync()
738 });
739 let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
740
741 let stop_time = zeitstempel::now_awake();
743 core::with_glean(|glean| {
744 glean
745 .additional_metrics
746 .shutdown_dispatcher_wait
747 .set_stop_and_accumulate(glean, timer_id, stop_time);
748 });
749 if blocked.is_err() {
750 log::error!(
751 "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
752 );
753 return;
754 }
755
756 if let Err(e) = dispatcher::shutdown() {
757 log::error!("Can't shutdown dispatcher thread: {:?}", e);
758 }
759
760 uploader_shutdown();
761
762 core::with_glean(|glean| {
764 if let Err(e) = glean.persist_ping_lifetime_data() {
765 log::info!("Can't persist ping lifetime data: {:?}", e);
766 }
767 });
768}
769
770pub fn glean_persist_ping_lifetime_data() {
777 crate::launch_with_glean(|glean| {
779 let _ = glean.persist_ping_lifetime_data();
780 });
781}
782
783fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
784 core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
785 core_metrics::internal_metrics::app_display_version
786 .set_sync(glean, &client_info.app_display_version[..]);
787 core_metrics::internal_metrics::app_build_date
788 .set_sync(glean, Some(client_info.app_build_date.clone()));
789 if let Some(app_channel) = client_info.channel.as_ref() {
790 core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
791 }
792
793 core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
794 core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
795
796 if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
797 core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
798 }
799 if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
800 core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
801 }
802 if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
803 core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
804 }
805 if let Some(device_model) = client_info.device_model.as_ref() {
806 core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
807 }
808 if let Some(locale) = client_info.locale.as_ref() {
809 core_metrics::internal_metrics::locale.set_sync(glean, locale);
810 }
811}
812
813fn was_initialize_called() -> bool {
819 INITIALIZE_CALLED.load(Ordering::SeqCst)
820}
821
822#[no_mangle]
825pub extern "C" fn glean_enable_logging() {
826 #[cfg(target_os = "android")]
827 {
828 let _ = std::panic::catch_unwind(|| {
829 let filter = android_logger::FilterBuilder::new()
830 .filter_module("glean_ffi", log::LevelFilter::Debug)
831 .filter_module("glean_core", log::LevelFilter::Debug)
832 .filter_module("glean", log::LevelFilter::Debug)
833 .filter_module("glean_core::ffi", log::LevelFilter::Info)
834 .build();
835 android_logger::init_once(
836 android_logger::Config::default()
837 .with_max_level(log::LevelFilter::Debug)
838 .with_filter(filter)
839 .with_tag("libglean_ffi"),
840 );
841 log::trace!("Android logging should be hooked up!")
842 });
843 }
844
845 #[cfg(target_os = "ios")]
847 {
848 #[cfg(debug_assertions)]
851 let level = log::LevelFilter::Debug;
852 #[cfg(not(debug_assertions))]
853 let level = log::LevelFilter::Info;
854
855 let logger = oslog::OsLogger::new("org.mozilla.glean")
856 .level_filter(level)
857 .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
859
860 match logger.init() {
861 Ok(_) => log::trace!("os_log should be hooked up!"),
862 Err(_) => log::warn!("os_log was already initialized"),
866 };
867 }
868
869 #[cfg(all(
873 not(target_os = "android"),
874 not(target_os = "ios"),
875 feature = "enable_env_logger"
876 ))]
877 {
878 match env_logger::try_init() {
879 Ok(_) => log::trace!("stdout logging should be hooked up!"),
880 Err(_) => log::warn!("stdout logging was already initialized"),
884 };
885 }
886}
887
888pub fn glean_set_upload_enabled(enabled: bool) {
893 if !was_initialize_called() {
894 return;
895 }
896
897 crate::launch_with_glean_mut(move |glean| {
898 let state = global_state().lock().unwrap();
899 let original_enabled = glean.is_upload_enabled();
900
901 if !enabled {
902 glean.cancel_metrics_ping_scheduler();
904 if let Err(e) = state.callbacks.cancel_uploads() {
906 log::error!("Canceling upload failed. Error: {}", e);
907 }
908 }
909
910 glean.set_upload_enabled(enabled);
911
912 if !original_enabled && enabled {
913 initialize_core_metrics(glean, &state.client_info);
914 }
915
916 if original_enabled && !enabled {
917 if let Err(e) = state.callbacks.trigger_upload() {
918 log::error!("Triggering upload failed. Error: {}", e);
919 }
920 }
921 })
922}
923
924pub fn glean_set_collection_enabled(enabled: bool) {
928 glean_set_upload_enabled(enabled)
929}
930
931pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
936 let ping = ping.clone();
937 if was_initialize_called() && core::global_glean().is_some() {
938 crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
939 } else {
940 let m = &PRE_INIT_PING_ENABLED;
941 let mut lock = m.lock().unwrap();
942 lock.push((ping, enabled));
943 }
944}
945
946pub(crate) fn register_ping_type(ping: &PingType) {
948 if was_initialize_called() && core::global_glean().is_some() {
953 let ping = ping.clone();
954 crate::launch_with_glean_mut(move |glean| {
955 glean.register_ping_type(&ping);
956 })
957 } else {
958 let m = &PRE_INIT_PING_REGISTRATION;
963 let mut lock = m.lock().unwrap();
964 lock.push(ping.clone());
965 }
966}
967
968pub fn glean_get_registered_ping_names() -> Vec<String> {
974 block_on_dispatcher();
975 core::with_glean(|glean| {
976 glean
977 .get_registered_ping_names()
978 .iter()
979 .map(|ping| ping.to_string())
980 .collect()
981 })
982}
983
984pub fn glean_set_experiment_active(
990 experiment_id: String,
991 branch: String,
992 extra: HashMap<String, String>,
993) {
994 launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
995}
996
997pub fn glean_set_experiment_inactive(experiment_id: String) {
1001 launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1002}
1003
1004pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1008 block_on_dispatcher();
1009 core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1010}
1011
1012pub fn glean_set_experimentation_id(experimentation_id: String) {
1016 launch_with_glean(move |glean| {
1017 glean
1018 .additional_metrics
1019 .experimentation_id
1020 .set(experimentation_id);
1021 });
1022}
1023
1024pub fn glean_test_get_experimentation_id() -> Option<String> {
1027 block_on_dispatcher();
1028 core::with_glean(|glean| glean.test_get_experimentation_id())
1029}
1030
1031pub fn glean_apply_server_knobs_config(json: String) {
1036 if json.is_empty() {
1039 return;
1040 }
1041
1042 match RemoteSettingsConfig::try_from(json) {
1043 Ok(cfg) => launch_with_glean(|glean| {
1044 glean.apply_server_knobs_config(cfg);
1045 }),
1046 Err(e) => {
1047 log::error!("Error setting metrics feature config: {:?}", e);
1048 }
1049 }
1050}
1051
1052pub fn glean_set_debug_view_tag(tag: String) -> bool {
1066 if was_initialize_called() && core::global_glean().is_some() {
1067 crate::launch_with_glean_mut(move |glean| {
1068 glean.set_debug_view_tag(&tag);
1069 });
1070 true
1071 } else {
1072 let m = &PRE_INIT_DEBUG_VIEW_TAG;
1074 let mut lock = m.lock().unwrap();
1075 *lock = tag;
1076 true
1079 }
1080}
1081
1082pub fn glean_get_debug_view_tag() -> Option<String> {
1088 block_on_dispatcher();
1089 core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1090}
1091
1092pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1104 if was_initialize_called() && core::global_glean().is_some() {
1105 crate::launch_with_glean_mut(|glean| {
1106 glean.set_source_tags(tags);
1107 });
1108 true
1109 } else {
1110 let m = &PRE_INIT_SOURCE_TAGS;
1112 let mut lock = m.lock().unwrap();
1113 *lock = tags;
1114 true
1117 }
1118}
1119
1120pub fn glean_set_log_pings(value: bool) {
1129 if was_initialize_called() && core::global_glean().is_some() {
1130 crate::launch_with_glean_mut(move |glean| {
1131 glean.set_log_pings(value);
1132 });
1133 } else {
1134 PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1135 }
1136}
1137
1138pub fn glean_get_log_pings() -> bool {
1144 block_on_dispatcher();
1145 core::with_glean(|glean| glean.log_pings())
1146}
1147
1148pub fn glean_handle_client_active() {
1155 dispatcher::launch(|| {
1156 core::with_glean_mut(|glean| {
1157 glean.handle_client_active();
1158 });
1159
1160 let state = global_state().lock().unwrap();
1164 if let Err(e) = state.callbacks.trigger_upload() {
1165 log::error!("Triggering upload failed. Error: {}", e);
1166 }
1167 });
1168
1169 core_metrics::internal_metrics::baseline_duration.start();
1174}
1175
1176pub fn glean_handle_client_inactive() {
1183 core_metrics::internal_metrics::baseline_duration.stop();
1187
1188 dispatcher::launch(|| {
1189 core::with_glean_mut(|glean| {
1190 glean.handle_client_inactive();
1191 });
1192
1193 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
1203pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1205 dispatcher::launch(|| {
1206 let sent =
1207 core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1208
1209 if sent {
1210 let state = global_state().lock().unwrap();
1211 if let Err(e) = state.callbacks.trigger_upload() {
1212 log::error!("Triggering upload failed. Error: {}", e);
1213 }
1214 }
1215 })
1216}
1217
1218pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1222 if !was_initialize_called() {
1223 return false;
1224 }
1225
1226 core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1227 .unwrap_or(false)
1228}
1229
1230pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1237 register_event_listener(tag, listener);
1238}
1239
1240pub fn glean_unregister_event_listener(tag: String) {
1248 unregister_event_listener(tag);
1249}
1250
1251pub fn glean_set_test_mode(enabled: bool) {
1255 dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1256}
1257
1258pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1262 if was_initialize_called() {
1263 join_init();
1265
1266 dispatcher::reset_dispatcher();
1267
1268 let has_storage = core::with_opt_glean(|glean| {
1271 glean
1273 .storage_opt()
1274 .map(|storage| storage.persist_ping_lifetime_data())
1275 .is_some()
1276 })
1277 .unwrap_or(false);
1278 if has_storage {
1279 uploader_shutdown();
1280 }
1281
1282 if core::global_glean().is_some() {
1283 core::with_glean_mut(|glean| {
1284 if clear_stores {
1285 glean.test_clear_all_stores()
1286 }
1287 glean.destroy_db()
1288 });
1289 }
1290
1291 INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1293 } else if clear_stores {
1294 if let Some(data_path) = data_path {
1295 let _ = std::fs::remove_dir_all(data_path).ok();
1296 } else {
1297 log::warn!("Asked to clear stores before initialization, but no data path given.");
1298 }
1299 }
1300}
1301
1302pub fn glean_get_upload_task() -> PingUploadTask {
1304 core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1305}
1306
1307pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1309 core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1310}
1311
1312pub fn glean_set_dirty_flag(new_value: bool) {
1316 core::with_glean(|glean| glean.set_dirty_flag(new_value))
1317}
1318
1319pub fn glean_update_attribution(attribution: AttributionMetrics) {
1322 if was_initialize_called() && core::global_glean().is_some() {
1323 core::with_glean(|glean| glean.update_attribution(attribution));
1324 } else {
1325 PRE_INIT_ATTRIBUTION
1326 .lock()
1327 .unwrap()
1328 .get_or_insert(Default::default())
1329 .update(attribution);
1330 }
1331}
1332
1333pub fn glean_test_get_attribution() -> AttributionMetrics {
1338 join_init();
1339 core::with_glean(|glean| glean.test_get_attribution())
1340}
1341
1342pub fn glean_update_distribution(distribution: DistributionMetrics) {
1345 if was_initialize_called() && core::global_glean().is_some() {
1346 core::with_glean(|glean| glean.update_distribution(distribution));
1347 } else {
1348 PRE_INIT_DISTRIBUTION
1349 .lock()
1350 .unwrap()
1351 .get_or_insert(Default::default())
1352 .update(distribution);
1353 }
1354}
1355
1356pub fn glean_test_get_distribution() -> DistributionMetrics {
1361 join_init();
1362 core::with_glean(|glean| glean.test_get_distribution())
1363}
1364
1365#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1366static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1367
1368#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1381pub fn glean_enable_logging_to_fd(fd: u64) {
1382 unsafe {
1388 let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1393 if log::set_logger(logger).is_ok() {
1397 log::set_max_level(log::LevelFilter::Debug);
1398 }
1399 }
1400}
1401
1402fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1404 let subdirs = ["db", "events", "pending_pings"];
1406 let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1407 DataDirectoryInfoObject::with_capacity(subdirs.len());
1408
1409 for subdir in subdirs.iter() {
1410 let dir_path = path.join(subdir);
1411
1412 let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1414 dir_name: Some(subdir.to_string()),
1415 dir_exists: None,
1416 dir_created: None,
1417 dir_modified: None,
1418 file_count: None,
1419 files: Vec::new(),
1420 error_message: None,
1421 };
1422
1423 if dir_path.is_dir() {
1425 directory_info.dir_exists = Some(true);
1426
1427 match fs::metadata(&dir_path) {
1429 Ok(metadata) => {
1430 if let Ok(created) = metadata.created() {
1431 directory_info.dir_created = Some(
1432 created
1433 .duration_since(UNIX_EPOCH)
1434 .unwrap_or(Duration::ZERO)
1435 .as_secs() as i64,
1436 );
1437 }
1438 if let Ok(modified) = metadata.modified() {
1439 directory_info.dir_modified = Some(
1440 modified
1441 .duration_since(UNIX_EPOCH)
1442 .unwrap_or(Duration::ZERO)
1443 .as_secs() as i64,
1444 );
1445 }
1446 }
1447 Err(error) => {
1448 let msg = format!("Unable to get metadata: {}", error.kind());
1449 directory_info.error_message = Some(msg.clone());
1450 log::warn!("{}", msg);
1451 continue;
1452 }
1453 }
1454
1455 let mut file_count = 0;
1457 let entries = match fs::read_dir(&dir_path) {
1458 Ok(entries) => entries,
1459 Err(error) => {
1460 let msg = format!("Unable to read subdir: {}", error.kind());
1461 directory_info.error_message = Some(msg.clone());
1462 log::warn!("{}", msg);
1463 continue;
1464 }
1465 };
1466 for entry in entries {
1467 directory_info.files.push(
1468 crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1469 file_name: None,
1470 file_created: None,
1471 file_modified: None,
1472 file_size: None,
1473 error_message: None,
1474 },
1475 );
1476 let file_info = directory_info.files.last_mut().unwrap();
1478 let entry = match entry {
1479 Ok(entry) => entry,
1480 Err(error) => {
1481 let msg = format!("Unable to read file: {}", error.kind());
1482 file_info.error_message = Some(msg.clone());
1483 log::warn!("{}", msg);
1484 continue;
1485 }
1486 };
1487 let file_name = match entry.file_name().into_string() {
1488 Ok(file_name) => file_name,
1489 _ => {
1490 let msg = "Unable to convert file name to string".to_string();
1491 file_info.error_message = Some(msg.clone());
1492 log::warn!("{}", msg);
1493 continue;
1494 }
1495 };
1496 let metadata = match entry.metadata() {
1497 Ok(metadata) => metadata,
1498 Err(error) => {
1499 let msg = format!("Unable to read file metadata: {}", error.kind());
1500 file_info.file_name = Some(file_name);
1501 file_info.error_message = Some(msg.clone());
1502 log::warn!("{}", msg);
1503 continue;
1504 }
1505 };
1506
1507 if metadata.is_file() {
1509 file_count += 1;
1510
1511 file_info.file_name = Some(file_name);
1513 file_info.file_created = Some(
1514 metadata
1515 .created()
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_modified = Some(
1522 metadata
1523 .modified()
1524 .unwrap_or(UNIX_EPOCH)
1525 .duration_since(UNIX_EPOCH)
1526 .unwrap_or(Duration::ZERO)
1527 .as_secs() as i64,
1528 );
1529 file_info.file_size = Some(metadata.len() as i64);
1530 } else {
1531 let msg = format!("Skipping non-file entry: {}", file_name.clone());
1532 file_info.file_name = Some(file_name);
1533 file_info.error_message = Some(msg.clone());
1534 log::warn!("{}", msg);
1535 }
1536 }
1537
1538 directory_info.file_count = Some(file_count as i64);
1539 } else {
1540 directory_info.dir_exists = Some(false);
1541 }
1542
1543 directories_info.push(directory_info);
1545 }
1546
1547 if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1548 Some(directories_info_json)
1549 } else {
1550 log::error!("Failed to serialize data directory info");
1551 None
1552 }
1553}
1554
1555fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1556 core::with_glean(|glean| {
1557 glean
1558 .health_metrics
1559 .data_directory_info
1560 .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1561 glean.internal_pings.health.submit_sync(glean, Some(reason));
1562 });
1563}
1564
1565#[cfg(any(target_os = "android", target_os = "ios"))]
1567pub fn glean_enable_logging_to_fd(_fd: u64) {
1568 }
1570
1571#[allow(missing_docs)]
1572#[allow(clippy::all)]
1574mod ffi {
1575 use super::*;
1576 uniffi::include_scaffolding!("glean");
1577
1578 type CowString = Cow<'static, str>;
1579
1580 uniffi::custom_type!(CowString, String, {
1581 remote,
1582 lower: |s| s.into_owned(),
1583 try_lift: |s| Ok(Cow::from(s))
1584 });
1585
1586 type JsonValue = serde_json::Value;
1587
1588 uniffi::custom_type!(JsonValue, String, {
1589 remote,
1590 lower: |s| serde_json::to_string(&s).unwrap(),
1591 try_lift: |s| Ok(serde_json::from_str(&s)?)
1592 });
1593}
1594pub use ffi::*;
1595
1596#[cfg(test)]
1598#[path = "lib_unit_tests.rs"]
1599mod tests;