#![allow(clippy::doc_overindented_list_items)]
#![allow(clippy::significant_drop_in_scrutinee)]
#![allow(clippy::uninlined_format_args)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, UNIX_EPOCH};
use std::{fmt, fs};
use crossbeam_channel::unbounded;
use log::LevelFilter;
use malloc_size_of_derive::MallocSizeOf;
use once_cell::sync::{Lazy, OnceCell};
use uuid::Uuid;
use metrics::RemoteSettingsConfig;
mod common_metric_data;
mod core;
mod core_metrics;
mod database;
mod debug;
#[cfg(feature = "benchmark")]
#[doc(hidden)]
pub mod dispatcher;
#[cfg(not(feature = "benchmark"))]
mod dispatcher;
mod error;
mod error_recording;
mod event_database;
mod glean_metrics;
mod histogram;
mod internal_metrics;
mod internal_pings;
pub mod metrics;
pub mod ping;
mod scheduler;
pub mod storage;
mod system;
#[doc(hidden)]
pub mod thread;
pub mod traits;
pub mod upload;
mod util;
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
mod fd_logger;
pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
pub use crate::core::Glean;
pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
use crate::dispatcher::is_test_mode;
pub use crate::error::{Error, ErrorKind, Result};
pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
pub use crate::histogram::HistogramType;
use crate::internal_metrics::DataDirectoryInfoObject;
pub use crate::metrics::labeled::{
AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
LabeledTimingDistribution,
};
pub use crate::metrics::{
BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
UrlMetric, UuidMetric,
};
pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
const GLEAN_SCHEMA_VERSION: u32 = 1;
const DEFAULT_MAX_EVENTS: u32 = 500;
static KNOWN_CLIENT_ID: Lazy<Uuid> =
Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
#[derive(Debug, Clone, MallocSizeOf)]
pub struct InternalConfiguration {
pub upload_enabled: bool,
pub data_path: String,
pub application_id: String,
pub language_binding_name: String,
pub max_events: Option<u32>,
pub delay_ping_lifetime_io: bool,
pub app_build: String,
pub use_core_mps: bool,
pub trim_data_to_registered_pings: bool,
#[ignore_malloc_size_of = "external non-allocating type"]
pub log_level: Option<LevelFilter>,
pub rate_limit: Option<PingRateLimit>,
pub enable_event_timestamps: bool,
pub experimentation_id: Option<String>,
pub enable_internal_pings: bool,
pub ping_schedule: HashMap<String, Vec<String>>,
pub ping_lifetime_threshold: u64,
pub ping_lifetime_max_time: u64,
}
#[derive(Debug, Clone, MallocSizeOf)]
pub struct PingRateLimit {
pub seconds_per_interval: u64,
pub pings_per_interval: u32,
}
fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
dispatcher::launch(|| core::with_glean(callback));
}
fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
dispatcher::launch(|| core::with_glean_mut(callback));
}
fn block_on_dispatcher() {
dispatcher::block_on_queue()
}
pub fn get_awake_timestamp_ms() -> u64 {
const NANOS_PER_MILLI: u64 = 1_000_000;
zeitstempel::now_awake() / NANOS_PER_MILLI
}
pub fn get_timestamp_ms() -> u64 {
const NANOS_PER_MILLI: u64 = 1_000_000;
zeitstempel::now() / NANOS_PER_MILLI
}
struct State {
client_info: ClientInfoMetrics,
callbacks: Box<dyn OnGleanEvents>,
}
static STATE: OnceCell<Mutex<State>> = OnceCell::new();
#[track_caller] fn global_state() -> &'static Mutex<State> {
STATE.get().unwrap()
}
#[track_caller] fn maybe_global_state() -> Option<&'static Mutex<State>> {
STATE.get()
}
fn setup_state(state: State) {
if STATE.get().is_none() {
if STATE.set(Mutex::new(state)).is_err() {
log::error!(
"Global Glean state object is initialized already. This probably happened concurrently."
);
}
} else {
let mut lock = STATE.get().unwrap().lock().unwrap();
*lock = state;
}
}
static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
OnceCell::new();
fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
}
fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
let mut lock = event_listeners().lock().unwrap();
lock.insert(tag, listener);
}
fn unregister_event_listener(tag: String) {
let mut lock = event_listeners().lock().unwrap();
lock.remove(&tag);
}
#[derive(Debug)]
pub enum CallbackError {
UnexpectedError,
}
impl fmt::Display for CallbackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unexpected error")
}
}
impl std::error::Error for CallbackError {}
impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
CallbackError::UnexpectedError
}
}
pub trait OnGleanEvents: Send {
fn initialize_finished(&self);
fn trigger_upload(&self) -> Result<(), CallbackError>;
fn start_metrics_ping_scheduler(&self) -> bool;
fn cancel_uploads(&self) -> Result<(), CallbackError>;
fn shutdown(&self) -> Result<(), CallbackError> {
Ok(())
}
}
pub trait GleanEventListener: Send {
fn on_event_recorded(&self, id: String);
}
pub fn glean_initialize(
cfg: InternalConfiguration,
client_info: ClientInfoMetrics,
callbacks: Box<dyn OnGleanEvents>,
) {
initialize_inner(cfg, client_info, callbacks);
}
pub fn glean_shutdown() {
shutdown();
}
pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
let glean = match Glean::new_for_subprocess(&cfg, true) {
Ok(glean) => glean,
Err(err) => {
log::error!("Failed to initialize Glean: {}", err);
return false;
}
};
if core::setup_glean(glean).is_err() {
return false;
}
log::info!("Glean initialized for subprocess");
true
}
fn initialize_inner(
cfg: InternalConfiguration,
client_info: ClientInfoMetrics,
callbacks: Box<dyn OnGleanEvents>,
) {
if was_initialize_called() {
log::error!("Glean should not be initialized multiple times");
return;
}
let init_handle = thread::spawn("glean.init", move || {
let upload_enabled = cfg.upload_enabled;
let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
if let Some(level) = cfg.log_level {
log::set_max_level(level)
}
let data_path_str = cfg.data_path.clone();
let data_path = Path::new(&data_path_str);
let internal_pings_enabled = cfg.enable_internal_pings;
let dir_info = if !is_test_mode() && internal_pings_enabled {
collect_directory_info(Path::new(&data_path))
} else {
None
};
let glean = match Glean::new(cfg) {
Ok(glean) => glean,
Err(err) => {
log::error!("Failed to initialize Glean: {}", err);
return;
}
};
if core::setup_glean(glean).is_err() {
return;
}
log::info!("Glean initialized");
core::with_glean(|glean| {
glean.health_metrics.init_count.add_sync(glean, 1);
});
setup_state(State {
client_info,
callbacks,
});
let mut is_first_run = false;
let mut dirty_flag = false;
let mut pings_submitted = false;
core::with_glean_mut(|glean| {
let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
if !debug_tag.is_empty() {
glean.set_debug_view_tag(&debug_tag);
}
let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
if log_pigs {
glean.set_log_pings(log_pigs);
}
let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
if !source_tags.is_empty() {
glean.set_source_tags(source_tags.to_vec());
}
dirty_flag = glean.is_dirty_flag_set();
glean.set_dirty_flag(false);
let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
for ping in pings.iter() {
glean.register_ping_type(ping);
}
let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
for (ping, enabled) in pings.iter() {
glean.set_ping_enabled(ping, *enabled);
}
if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
glean.update_attribution(attribution);
}
if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
glean.update_distribution(distribution);
}
is_first_run = glean.is_first_run();
if is_first_run {
let state = global_state().lock().unwrap();
initialize_core_metrics(glean, &state.client_info);
}
pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
});
{
let state = global_state().lock().unwrap();
if pings_submitted || !upload_enabled {
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
}
core::with_glean(|glean| {
glean.start_metrics_ping_scheduler();
});
{
let state = global_state().lock().unwrap();
if state.callbacks.start_metrics_ping_scheduler() {
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
}
core::with_glean_mut(|glean| {
let state = global_state().lock().unwrap();
if !is_first_run && dirty_flag {
if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
}
if !is_first_run {
glean.clear_application_lifetime_metrics();
initialize_core_metrics(glean, &state.client_info);
}
});
match dispatcher::flush_init() {
Ok(task_count) if task_count > 0 => {
core::with_glean(|glean| {
glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
});
}
Ok(_) => {}
Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
}
if !is_test_mode() && internal_pings_enabled {
record_dir_info_and_submit_health_ping(dir_info, "pre_init");
let state = global_state().lock().unwrap();
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
let state = global_state().lock().unwrap();
state.callbacks.initialize_finished();
})
.expect("Failed to spawn Glean's init thread");
INIT_HANDLES.lock().unwrap().push(init_handle);
INITIALIZE_CALLED.store(true, Ordering::SeqCst);
if dispatcher::global::is_test_mode() {
join_init();
}
}
pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
use malloc_size_of::MallocSizeOf;
core::with_glean(|glean| glean.size_of(ops))
}
pub fn join_init() {
let mut handles = INIT_HANDLES.lock().unwrap();
for handle in handles.drain(..) {
handle.join().unwrap();
}
}
fn uploader_shutdown() {
let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
let (tx, rx) = unbounded();
let handle = thread::spawn("glean.shutdown", move || {
let state = global_state().lock().unwrap();
if let Err(e) = state.callbacks.shutdown() {
log::error!("Shutdown callback failed: {e:?}");
}
let _ = tx.send(()).ok();
})
.expect("Unable to spawn thread to wait on shutdown");
let result = rx.recv_timeout(Duration::from_secs(30));
let stop_time = zeitstempel::now_awake();
core::with_glean(|glean| {
glean
.additional_metrics
.shutdown_wait
.set_stop_and_accumulate(glean, timer_id, stop_time);
});
if result.is_err() {
log::warn!("Waiting for upload failed. We're shutting down.");
} else {
let _ = handle.join().ok();
}
}
pub fn shutdown() {
if !was_initialize_called() {
log::warn!("Shutdown called before Glean is initialized");
if let Err(e) = dispatcher::kill() {
log::error!("Can't kill dispatcher thread: {:?}", e);
}
return;
}
if core::global_glean().is_none() {
log::warn!("Shutdown called before Glean is initialized. Waiting.");
let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
}
if core::global_glean().is_none() {
log::warn!("Waiting for Glean initialization timed out. Exiting.");
if let Err(e) = dispatcher::kill() {
log::error!("Can't kill dispatcher thread: {:?}", e);
}
return;
}
crate::launch_with_glean_mut(|glean| {
glean.cancel_metrics_ping_scheduler();
glean.set_dirty_flag(false);
});
let timer_id = core::with_glean(|glean| {
glean
.additional_metrics
.shutdown_dispatcher_wait
.start_sync()
});
let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
let stop_time = zeitstempel::now_awake();
core::with_glean(|glean| {
glean
.additional_metrics
.shutdown_dispatcher_wait
.set_stop_and_accumulate(glean, timer_id, stop_time);
});
if blocked.is_err() {
log::error!(
"Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
);
return;
}
if let Err(e) = dispatcher::shutdown() {
log::error!("Can't shutdown dispatcher thread: {:?}", e);
}
uploader_shutdown();
core::with_glean(|glean| {
if let Err(e) = glean.persist_ping_lifetime_data() {
log::info!("Can't persist ping lifetime data: {:?}", e);
}
});
}
pub fn glean_persist_ping_lifetime_data() {
crate::launch_with_glean(|glean| {
let _ = glean.persist_ping_lifetime_data();
});
}
fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
core_metrics::internal_metrics::app_display_version
.set_sync(glean, &client_info.app_display_version[..]);
core_metrics::internal_metrics::app_build_date
.set_sync(glean, Some(client_info.app_build_date.clone()));
if let Some(app_channel) = client_info.channel.as_ref() {
core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
}
core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
}
if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
}
if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
}
if let Some(device_model) = client_info.device_model.as_ref() {
core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
}
if let Some(locale) = client_info.locale.as_ref() {
core_metrics::internal_metrics::locale.set_sync(glean, locale);
}
}
fn was_initialize_called() -> bool {
INITIALIZE_CALLED.load(Ordering::SeqCst)
}
#[no_mangle]
pub extern "C" fn glean_enable_logging() {
#[cfg(target_os = "android")]
{
let _ = std::panic::catch_unwind(|| {
let filter = android_logger::FilterBuilder::new()
.filter_module("glean_ffi", log::LevelFilter::Debug)
.filter_module("glean_core", log::LevelFilter::Debug)
.filter_module("glean", log::LevelFilter::Debug)
.filter_module("glean_core::ffi", log::LevelFilter::Info)
.build();
android_logger::init_once(
android_logger::Config::default()
.with_max_level(log::LevelFilter::Debug)
.with_filter(filter)
.with_tag("libglean_ffi"),
);
log::trace!("Android logging should be hooked up!")
});
}
#[cfg(target_os = "ios")]
{
#[cfg(debug_assertions)]
let level = log::LevelFilter::Debug;
#[cfg(not(debug_assertions))]
let level = log::LevelFilter::Info;
let logger = oslog::OsLogger::new("org.mozilla.glean")
.level_filter(level)
.category_level_filter("glean_core::ffi", log::LevelFilter::Info);
match logger.init() {
Ok(_) => log::trace!("os_log should be hooked up!"),
Err(_) => log::warn!("os_log was already initialized"),
};
}
#[cfg(all(
not(target_os = "android"),
not(target_os = "ios"),
feature = "enable_env_logger"
))]
{
match env_logger::try_init() {
Ok(_) => log::trace!("stdout logging should be hooked up!"),
Err(_) => log::warn!("stdout logging was already initialized"),
};
}
}
pub fn glean_set_upload_enabled(enabled: bool) {
if !was_initialize_called() {
return;
}
crate::launch_with_glean_mut(move |glean| {
let state = global_state().lock().unwrap();
let original_enabled = glean.is_upload_enabled();
if !enabled {
glean.cancel_metrics_ping_scheduler();
if let Err(e) = state.callbacks.cancel_uploads() {
log::error!("Canceling upload failed. Error: {}", e);
}
}
glean.set_upload_enabled(enabled);
if !original_enabled && enabled {
initialize_core_metrics(glean, &state.client_info);
}
if original_enabled && !enabled {
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
})
}
pub fn glean_set_collection_enabled(enabled: bool) {
glean_set_upload_enabled(enabled)
}
pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
let ping = ping.clone();
if was_initialize_called() && core::global_glean().is_some() {
crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
} else {
let m = &PRE_INIT_PING_ENABLED;
let mut lock = m.lock().unwrap();
lock.push((ping, enabled));
}
}
pub(crate) fn register_ping_type(ping: &PingType) {
if was_initialize_called() && core::global_glean().is_some() {
let ping = ping.clone();
crate::launch_with_glean_mut(move |glean| {
glean.register_ping_type(&ping);
})
} else {
let m = &PRE_INIT_PING_REGISTRATION;
let mut lock = m.lock().unwrap();
lock.push(ping.clone());
}
}
pub fn glean_get_registered_ping_names() -> Vec<String> {
block_on_dispatcher();
core::with_glean(|glean| {
glean
.get_registered_ping_names()
.iter()
.map(|ping| ping.to_string())
.collect()
})
}
pub fn glean_set_experiment_active(
experiment_id: String,
branch: String,
extra: HashMap<String, String>,
) {
launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
}
pub fn glean_set_experiment_inactive(experiment_id: String) {
launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
}
pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
block_on_dispatcher();
core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
}
pub fn glean_set_experimentation_id(experimentation_id: String) {
launch_with_glean(move |glean| {
glean
.additional_metrics
.experimentation_id
.set(experimentation_id);
});
}
pub fn glean_test_get_experimentation_id() -> Option<String> {
block_on_dispatcher();
core::with_glean(|glean| glean.test_get_experimentation_id())
}
pub fn glean_apply_server_knobs_config(json: String) {
if json.is_empty() {
return;
}
match RemoteSettingsConfig::try_from(json) {
Ok(cfg) => launch_with_glean(|glean| {
glean.apply_server_knobs_config(cfg);
}),
Err(e) => {
log::error!("Error setting metrics feature config: {:?}", e);
}
}
}
pub fn glean_set_debug_view_tag(tag: String) -> bool {
if was_initialize_called() && core::global_glean().is_some() {
crate::launch_with_glean_mut(move |glean| {
glean.set_debug_view_tag(&tag);
});
true
} else {
let m = &PRE_INIT_DEBUG_VIEW_TAG;
let mut lock = m.lock().unwrap();
*lock = tag;
true
}
}
pub fn glean_get_debug_view_tag() -> Option<String> {
block_on_dispatcher();
core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
}
pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
if was_initialize_called() && core::global_glean().is_some() {
crate::launch_with_glean_mut(|glean| {
glean.set_source_tags(tags);
});
true
} else {
let m = &PRE_INIT_SOURCE_TAGS;
let mut lock = m.lock().unwrap();
*lock = tags;
true
}
}
pub fn glean_set_log_pings(value: bool) {
if was_initialize_called() && core::global_glean().is_some() {
crate::launch_with_glean_mut(move |glean| {
glean.set_log_pings(value);
});
} else {
PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
}
}
pub fn glean_get_log_pings() -> bool {
block_on_dispatcher();
core::with_glean(|glean| glean.log_pings())
}
pub fn glean_handle_client_active() {
dispatcher::launch(|| {
core::with_glean_mut(|glean| {
glean.handle_client_active();
});
let state = global_state().lock().unwrap();
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
});
core_metrics::internal_metrics::baseline_duration.start();
}
pub fn glean_handle_client_inactive() {
core_metrics::internal_metrics::baseline_duration.stop();
dispatcher::launch(|| {
core::with_glean_mut(|glean| {
glean.handle_client_inactive();
});
let state = global_state().lock().unwrap();
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
})
}
pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
dispatcher::launch(|| {
let sent =
core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
if sent {
let state = global_state().lock().unwrap();
if let Err(e) = state.callbacks.trigger_upload() {
log::error!("Triggering upload failed. Error: {}", e);
}
}
})
}
pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
if !was_initialize_called() {
return false;
}
core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
.unwrap_or(false)
}
pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
register_event_listener(tag, listener);
}
pub fn glean_unregister_event_listener(tag: String) {
unregister_event_listener(tag);
}
pub fn glean_set_test_mode(enabled: bool) {
dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
}
pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
if was_initialize_called() {
join_init();
dispatcher::reset_dispatcher();
let has_storage = core::with_opt_glean(|glean| {
glean
.storage_opt()
.map(|storage| storage.persist_ping_lifetime_data())
.is_some()
})
.unwrap_or(false);
if has_storage {
uploader_shutdown();
}
if core::global_glean().is_some() {
core::with_glean_mut(|glean| {
if clear_stores {
glean.test_clear_all_stores()
}
glean.destroy_db()
});
}
INITIALIZE_CALLED.store(false, Ordering::SeqCst);
} else if clear_stores {
if let Some(data_path) = data_path {
let _ = std::fs::remove_dir_all(data_path).ok();
} else {
log::warn!("Asked to clear stores before initialization, but no data path given.");
}
}
}
pub fn glean_get_upload_task() -> PingUploadTask {
core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
}
pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
}
pub fn glean_set_dirty_flag(new_value: bool) {
core::with_glean(|glean| glean.set_dirty_flag(new_value))
}
pub fn glean_update_attribution(attribution: AttributionMetrics) {
if was_initialize_called() && core::global_glean().is_some() {
core::with_glean(|glean| glean.update_attribution(attribution));
} else {
PRE_INIT_ATTRIBUTION
.lock()
.unwrap()
.get_or_insert(Default::default())
.update(attribution);
}
}
pub fn glean_test_get_attribution() -> AttributionMetrics {
join_init();
core::with_glean(|glean| glean.test_get_attribution())
}
pub fn glean_update_distribution(distribution: DistributionMetrics) {
if was_initialize_called() && core::global_glean().is_some() {
core::with_glean(|glean| glean.update_distribution(distribution));
} else {
PRE_INIT_DISTRIBUTION
.lock()
.unwrap()
.get_or_insert(Default::default())
.update(distribution);
}
}
pub fn glean_test_get_distribution() -> DistributionMetrics {
join_init();
core::with_glean(|glean| glean.test_get_distribution())
}
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
pub fn glean_enable_logging_to_fd(fd: u64) {
unsafe {
let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
if log::set_logger(logger).is_ok() {
log::set_max_level(log::LevelFilter::Debug);
}
}
}
fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
let subdirs = ["db", "events", "pending_pings"];
let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
DataDirectoryInfoObject::with_capacity(subdirs.len());
for subdir in subdirs.iter() {
let dir_path = path.join(subdir);
let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
dir_name: Some(subdir.to_string()),
dir_exists: None,
dir_created: None,
dir_modified: None,
file_count: None,
files: Vec::new(),
error_message: None,
};
if dir_path.is_dir() {
directory_info.dir_exists = Some(true);
match fs::metadata(&dir_path) {
Ok(metadata) => {
if let Ok(created) = metadata.created() {
directory_info.dir_created = Some(
created
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs() as i64,
);
}
if let Ok(modified) = metadata.modified() {
directory_info.dir_modified = Some(
modified
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs() as i64,
);
}
}
Err(error) => {
let msg = format!("Unable to get metadata: {}", error.kind());
directory_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
continue;
}
}
let mut file_count = 0;
let entries = match fs::read_dir(&dir_path) {
Ok(entries) => entries,
Err(error) => {
let msg = format!("Unable to read subdir: {}", error.kind());
directory_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
continue;
}
};
for entry in entries {
directory_info.files.push(
crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
file_name: None,
file_created: None,
file_modified: None,
file_size: None,
error_message: None,
},
);
let file_info = directory_info.files.last_mut().unwrap();
let entry = match entry {
Ok(entry) => entry,
Err(error) => {
let msg = format!("Unable to read file: {}", error.kind());
file_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
continue;
}
};
let file_name = match entry.file_name().into_string() {
Ok(file_name) => file_name,
_ => {
let msg = "Unable to convert file name to string".to_string();
file_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
continue;
}
};
let metadata = match entry.metadata() {
Ok(metadata) => metadata,
Err(error) => {
let msg = format!("Unable to read file metadata: {}", error.kind());
file_info.file_name = Some(file_name);
file_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
continue;
}
};
if metadata.is_file() {
file_count += 1;
file_info.file_name = Some(file_name);
file_info.file_created = Some(
metadata
.created()
.unwrap_or(UNIX_EPOCH)
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs() as i64,
);
file_info.file_modified = Some(
metadata
.modified()
.unwrap_or(UNIX_EPOCH)
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs() as i64,
);
file_info.file_size = Some(metadata.len() as i64);
} else {
let msg = format!("Skipping non-file entry: {}", file_name.clone());
file_info.file_name = Some(file_name);
file_info.error_message = Some(msg.clone());
log::warn!("{}", msg);
}
}
directory_info.file_count = Some(file_count as i64);
} else {
directory_info.dir_exists = Some(false);
}
directories_info.push(directory_info);
}
if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
Some(directories_info_json)
} else {
log::error!("Failed to serialize data directory info");
None
}
}
fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
core::with_glean(|glean| {
glean
.health_metrics
.data_directory_info
.set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
glean.internal_pings.health.submit_sync(glean, Some(reason));
});
}
#[cfg(any(target_os = "android", target_os = "ios"))]
pub fn glean_enable_logging_to_fd(_fd: u64) {
}
#[allow(missing_docs)]
#[allow(clippy::all)]
mod ffi {
use super::*;
uniffi::include_scaffolding!("glean");
type CowString = Cow<'static, str>;
uniffi::custom_type!(CowString, String, {
remote,
lower: |s| s.into_owned(),
try_lift: |s| Ok(Cow::from(s))
});
type JsonValue = serde_json::Value;
uniffi::custom_type!(JsonValue, String, {
remote,
lower: |s| serde_json::to_string(&s).unwrap(),
try_lift: |s| Ok(serde_json::from_str(&s)?)
});
}
pub use ffi::*;
#[cfg(test)]
#[path = "lib_unit_tests.rs"]
mod tests;