forked from mirrors/gecko-dev
349 lines
11 KiB
Rust
349 lines
11 KiB
Rust
// 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()
|
|
};
|
|
|
|
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: 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(())
|
|
}
|