summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/src/init/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/src/init/mod.rs')
-rw-r--r--toolkit/components/glean/src/init/mod.rs368
1 files changed, 368 insertions, 0 deletions
diff --git a/toolkit/components/glean/src/init/mod.rs b/toolkit/components/glean/src/init/mod.rs
new file mode 100644
index 0000000000..f430cd7384
--- /dev/null
+++ b/toolkit/components/glean/src/init/mod.rs
@@ -0,0 +1,368 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::env;
+use std::ffi::CString;
+use std::ops::DerefMut;
+use std::path::PathBuf;
+
+use firefox_on_glean::{metrics, pings};
+use nserror::{nsresult, NS_ERROR_FAILURE};
+use nsstring::{nsACString, nsCString, nsString};
+use xpcom::interfaces::{
+ mozILocaleService, nsIFile, nsIPrefService, nsIProperties, nsIXULAppInfo, nsIXULRuntime,
+};
+use xpcom::{RefPtr, XpCom};
+
+use glean::{ClientInfoMetrics, Configuration};
+
+#[cfg(not(target_os = "android"))]
+mod upload_pref;
+#[cfg(not(target_os = "android"))]
+mod user_activity;
+mod viaduct_uploader;
+
+#[cfg(not(target_os = "android"))]
+use upload_pref::UploadPrefObserver;
+#[cfg(not(target_os = "android"))]
+use user_activity::UserActivityObserver;
+use viaduct_uploader::ViaductUploader;
+
+/// Project FOG's entry point.
+///
+/// This assembles client information and the Glean configuration and then initializes the global
+/// Glean instance.
+#[cfg(not(target_os = "android"))]
+#[no_mangle]
+pub extern "C" fn fog_init(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> nsresult {
+ let upload_enabled = static_prefs::pref!("datareporting.healthreport.uploadEnabled");
+ let recording_enabled = static_prefs::pref!("telemetry.fog.test.localhost_port") < 0;
+ let uploader = Some(Box::new(ViaductUploader) as Box<dyn glean::net::PingUploader>);
+
+ fog_init_internal(
+ data_path_override,
+ app_id_override,
+ upload_enabled || recording_enabled,
+ uploader,
+ )
+ .into()
+}
+
+/// Project FOG's entry point on Android.
+///
+/// This assembles client information and the Glean configuration and then initializes the global
+/// Glean instance.
+/// It always enables upload and set no uploader.
+/// This should only be called in test scenarios.
+/// In normal use Glean should be initialized and controlled by the Glean Kotlin SDK.
+#[cfg(target_os = "android")]
+#[no_mangle]
+pub extern "C" fn fog_init(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> nsresult {
+ // On Android always enable Glean upload.
+ let upload_enabled = true;
+ // Don't set up an uploader.
+ let uploader = None;
+
+ fog_init_internal(
+ data_path_override,
+ app_id_override,
+ upload_enabled,
+ uploader,
+ )
+ .into()
+}
+
+fn fog_init_internal(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+ upload_enabled: bool,
+ uploader: Option<Box<dyn glean::net::PingUploader>>,
+) -> Result<(), nsresult> {
+ metrics::fog::initialization.start();
+
+ log::debug!("Initializing FOG.");
+
+ setup_observers()?;
+
+ let (mut conf, client_info) = build_configuration(data_path_override, app_id_override)?;
+
+ conf.upload_enabled = upload_enabled;
+ conf.uploader = uploader;
+
+ // If we're operating in automation without any specific source tags to set,
+ // set the tag "automation" so any pings that escape don't clutter the tables.
+ // See https://mozilla.github.io/glean/book/user/debugging/index.html#enabling-debugging-features-through-environment-variables
+ if env::var("MOZ_AUTOMATION").is_ok() && env::var("GLEAN_SOURCE_TAGS").is_err() {
+ log::info!("In automation, setting 'automation' source tag.");
+ glean::set_source_tags(vec!["automation".to_string()]);
+ log::info!("In automation, disabling MPS to avoid 4am issues.");
+ conf.use_core_mps = false;
+ }
+
+ log::debug!("Configuration: {:#?}", conf);
+
+ // Register all custom pings before we initialize.
+ pings::register_pings(Some(&conf.application_id));
+
+ glean::initialize(conf, client_info);
+
+ metrics::fog::initialization.stop();
+
+ Ok(())
+}
+
+fn build_configuration(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> Result<(Configuration, ClientInfoMetrics), nsresult> {
+ let data_path_str = if data_path_override.is_empty() {
+ get_data_path()?
+ } else {
+ data_path_override.to_utf8().to_string()
+ };
+ let data_path = PathBuf::from(&data_path_str);
+
+ let (app_build, app_display_version, channel, locale) = get_app_info()?;
+
+ let client_info = ClientInfoMetrics {
+ app_build,
+ app_display_version,
+ channel: Some(channel),
+ locale: Some(locale),
+ };
+ log::debug!("Client Info: {:#?}", client_info);
+
+ const SERVER: &str = "https://incoming.telemetry.mozilla.org";
+ let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
+ let server = if localhost_port > 0 {
+ format!("http://localhost:{}", localhost_port)
+ } else {
+ String::from(SERVER)
+ };
+
+ let application_id = if app_id_override.is_empty() {
+ "firefox.desktop".to_string()
+ } else {
+ app_id_override.to_utf8().to_string()
+ };
+
+ extern "C" {
+ fn FOG_MaxPingLimit() -> u32;
+ fn FOG_EventTimestampsEnabled() -> bool;
+ }
+
+ // SAFETY NOTE: Safe because it returns a primitive by value.
+ let pings_per_interval = unsafe { FOG_MaxPingLimit() };
+ metrics::fog::max_pings_per_minute.set(pings_per_interval.into());
+
+ // SAFETY NOTE: Safe because it returns a primitive by value.
+ let enable_event_timestamps = unsafe { FOG_EventTimestampsEnabled() };
+
+ let rate_limit = Some(glean::PingRateLimit {
+ seconds_per_interval: 60,
+ pings_per_interval,
+ });
+
+ let configuration = Configuration {
+ upload_enabled: false,
+ data_path,
+ application_id,
+ max_events: None,
+ delay_ping_lifetime_io: true,
+ server_endpoint: Some(server),
+ uploader: None,
+ use_core_mps: true,
+ trim_data_to_registered_pings: true,
+ log_level: None,
+ rate_limit,
+ enable_event_timestamps,
+ experimentation_id: None,
+ };
+
+ Ok((configuration, client_info))
+}
+
+#[cfg(not(target_os = "android"))]
+fn setup_observers() -> Result<(), nsresult> {
+ if let Err(e) = UploadPrefObserver::begin_observing() {
+ log::error!(
+ "Could not observe data upload pref. Abandoning FOG init due to {:?}",
+ e
+ );
+ return Err(e);
+ }
+
+ if let Err(e) = UserActivityObserver::begin_observing() {
+ log::error!(
+ "Could not observe user activity. Abandoning FOG init due to {:?}",
+ e
+ );
+ return Err(e);
+ }
+
+ Ok(())
+}
+
+#[cfg(target_os = "android")]
+fn setup_observers() -> Result<(), nsresult> {
+ // No observers are set up on Android.
+ Ok(())
+}
+
+/// Construct and return the data_path from the profile dir, or return an error.
+fn get_data_path() -> Result<String, nsresult> {
+ let dir_svc: RefPtr<nsIProperties> = match xpcom::components::Directory::service() {
+ Ok(ds) => ds,
+ _ => return Err(NS_ERROR_FAILURE),
+ };
+ let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new();
+ unsafe {
+ dir_svc
+ .Get(
+ cstr!("ProfD").as_ptr(),
+ &nsIFile::IID,
+ profile_dir.void_ptr(),
+ )
+ .to_result()?;
+ }
+ let profile_dir = profile_dir.refptr().ok_or(NS_ERROR_FAILURE)?;
+ let mut profile_path = nsString::new();
+ unsafe {
+ (*profile_dir).GetPath(&mut *profile_path).to_result()?;
+ }
+ let profile_path = String::from_utf16(&profile_path[..]).map_err(|_| NS_ERROR_FAILURE)?;
+ let data_path = profile_path + "/datareporting/glean";
+ Ok(data_path)
+}
+
+/// Return a tuple of the build_id, app version, build channel, and locale.
+/// If the XUL Runtime isn't a XULAppInfo (e.g. in xpcshell),
+/// build_id ad app_version will be "unknown".
+/// Other problems result in an error being returned instead.
+fn get_app_info() -> Result<(String, String, String, String), nsresult> {
+ let xul: RefPtr<nsIXULRuntime> =
+ xpcom::components::XULRuntime::service().map_err(|_| NS_ERROR_FAILURE)?;
+
+ let pref_service: RefPtr<nsIPrefService> =
+ xpcom::components::Preferences::service().map_err(|_| NS_ERROR_FAILURE)?;
+ let locale_service: RefPtr<mozILocaleService> =
+ xpcom::components::Locale::service().map_err(|_| NS_ERROR_FAILURE)?;
+ let branch = xpcom::getter_addrefs(|p| {
+ // Safe because:
+ // * `null` is explicitly allowed per documentation
+ // * `p` is a valid outparam guaranteed by `getter_addrefs`
+ unsafe { pref_service.GetDefaultBranch(std::ptr::null(), p) }
+ })?;
+ let pref_name = CString::new("app.update.channel").map_err(|_| NS_ERROR_FAILURE)?;
+ let mut channel = nsCString::new();
+ // Safe because:
+ // * `branch` is non-null (otherwise `getter_addrefs` would've been `Err`
+ // * `pref_name` exists so a pointer to it is valid for the life of the function
+ // * `channel` exists so a pointer to it is valid, and it can be written to
+ unsafe {
+ if (*branch)
+ .GetCharPref(pref_name.as_ptr(), channel.deref_mut() as *mut nsACString)
+ .to_result()
+ .is_err()
+ {
+ channel = "unknown".into();
+ }
+ }
+
+ let app_info = match xul.query_interface::<nsIXULAppInfo>() {
+ Some(ai) => ai,
+ // In e.g. xpcshell the XULRuntime isn't XULAppInfo.
+ // We still want to return sensible values so tests don't explode.
+ _ => {
+ return Ok((
+ "unknown".to_owned(),
+ "unknown".to_owned(),
+ channel.to_string(),
+ "unknown".to_owned(),
+ ))
+ }
+ };
+
+ let mut build_id = nsCString::new();
+ unsafe {
+ app_info.GetAppBuildID(&mut *build_id).to_result()?;
+ }
+
+ let mut version = nsCString::new();
+ unsafe {
+ app_info.GetVersion(&mut *version).to_result()?;
+ }
+
+ let mut locale = nsCString::new();
+ unsafe {
+ locale_service
+ .GetAppLocaleAsBCP47(&mut *locale)
+ .to_result()?;
+ }
+
+ Ok((
+ build_id.to_string(),
+ version.to_string(),
+ channel.to_string(),
+ locale.to_string(),
+ ))
+}
+
+/// **TEST-ONLY METHOD**
+/// Resets FOG and the underlying Glean SDK, clearing stores.
+#[no_mangle]
+pub extern "C" fn fog_test_reset(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> nsresult {
+ fog_test_reset_internal(data_path_override, app_id_override).into()
+}
+
+// Split out into its own function so I could use `?`
+#[cfg(not(target_os = "android"))]
+fn fog_test_reset_internal(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> Result<(), nsresult> {
+ let (mut conf, client_info) = build_configuration(data_path_override, app_id_override)?;
+
+ let upload_enabled = static_prefs::pref!("datareporting.healthreport.uploadEnabled");
+ let recording_enabled = static_prefs::pref!("telemetry.fog.test.localhost_port") < 0;
+ conf.upload_enabled = upload_enabled || recording_enabled;
+
+ // Don't accidentally send "main" pings during tests.
+ conf.use_core_mps = false;
+
+ // I'd prefer to reuse the uploader, but it gets moved into Glean so we build anew.
+ conf.uploader = Some(Box::new(ViaductUploader) as Box<dyn glean::net::PingUploader>);
+
+ glean::test_reset_glean(conf, client_info, true);
+ Ok(())
+}
+
+#[cfg(target_os = "android")]
+fn fog_test_reset_internal(
+ data_path_override: &nsACString,
+ app_id_override: &nsACString,
+) -> Result<(), nsresult> {
+ let (mut conf, client_info) = build_configuration(data_path_override, app_id_override)?;
+
+ // On Android always enable Glean upload.
+ conf.upload_enabled = true;
+
+ // Don't accidentally send "main" pings during tests.
+ conf.use_core_mps = false;
+
+ // Same as before, would prefer to reuse, but it gets moved into Glean so we build anew.
+ conf.uploader = Some(Box::new(ViaductUploader) as Box<dyn glean::net::PingUploader>);
+
+ glean::test_reset_glean(conf, client_info, true);
+ Ok(())
+}