/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! The crashping crate allows populating and sending the crash ping, which contains all //! ping-scoped crash annotations.
pubuse glean::net; pubuse glean::ClientInfoMetrics; use glean::{Configuration, ConfigurationBuilder}; use std::path::PathBuf;
mod glean_metrics { // Env variable set to the file generated by glean_rust.py (by build.rs).
include!(env!("GLEAN_METRICS_FILE"));
} mod annotations; mod single_instance;
/// Initialize the Glean ping. This must be called _before_ Glean initialization. /// /// Since Glean v63.0.0, custom pings are required to be instantiated prior to Glean init /// in order to ensure they are enabled and able to collect data. This is due to the data /// collection state being determined at the ping level now instead of just by the global /// Glean collection enabled flag. See Bug 1934931 for more information. pubfn init() {
_ = &*glean_metrics::crash;
}
/// Initialize Glean and the Glean ping. /// /// If using this type, you do not need to call `init()`. This is intended for use in runtimes that /// are only using Glean to send the crash ping. /// /// You should be sure to set an `uploader` on the `configuration` before initializing. It is /// recommended to set the `ClientInfoMetrics` fields to more useful values as well. pubstruct InitGlean { pub configuration: Configuration, pub client_info_metrics: ClientInfoMetrics, pub clear_uploader_for_tests: bool,
}
/// A handle taking ownership of the Glean store. pubstruct GleanHandle {
single_instance: single_instance::SingleInstance,
}
impl GleanHandle { /// Own the Glean store for the lifetime of the application. pubfn application_lifetime(self) { self.single_instance.retain_until_application_exit();
}
}
impl InitGlean { /// The data_dir should be a dedicated directory for use by Glean. pubfn new(data_dir: PathBuf, app_id: &str, client_info_metrics: ClientInfoMetrics) -> Self {
InitGlean {
configuration: ConfigurationBuilder::new(true, data_dir, app_id)
.with_server_endpoint(TELEMETRY_SERVER)
.with_use_core_mps(false)
.with_internal_pings(false)
.build(),
client_info_metrics,
clear_uploader_for_tests: true,
}
}
/// Initialize using glean::test_reset_glean. /// /// This will not do any process locking of the Glean store. pubfn test_reset_glean(self, clear_stores: bool) { self.init_with_no_lock(move |c, m| glean::test_reset_glean(c, m, clear_stores))
}
/// Initialize with the given function. /// /// This will take exclusive ownership of the Glean store for the current process (potentially /// blocking). The returned GleanHandle tracks ownership. pubfn init_with<F: FnOnce(Configuration, ClientInfoMetrics)>( self,
f: F,
) -> std::io::Result<GleanHandle> {
std::fs::create_dir_all(&self.configuration.data_path)?; let handle = GleanHandle {
single_instance: single_instance::SingleInstance::acquire(
&self.configuration.data_path.join("crashping.pid"),
)?,
}; self.init_with_no_lock(f);
Ok(handle)
}
pubfn init_with_no_lock<F: FnOnce(Configuration, ClientInfoMetrics)>(mutself, f: F) { // Clear the uploader for tests, if configured. // No need to check `cfg!(test)`, since we don't set an uploader in unit tests (and if we // did, it would be test-specific). let is_test = std::env::var_os("XPCSHELL_TEST_PROFILE_DIR").is_some()
|| std::env::var_os("MOZ_AUTOMATION").is_some()
|| std::env::var_os("MOZ_DISABLE_NONLOCAL_CONNECTIONS") == Some("1".into()); ifself.clear_uploader_for_tests && is_test { self.configuration.uploader = None; self.configuration.server_endpoint = None;
}
init();
f(self.configuration, self.client_info_metrics);
}
}
/// Send the Glean crash ping. pubfn send(annotations: &serde_json::Value, reason: Option<&str>) -> anyhow::Result<()> { // The crash.time metric may be overwritten if a CrashTime annotation is present.
glean_metrics::crash::time.set(None);
set_metrics_from_annotations(annotations)?;
log::debug!("submitting Glean crash ping");
glean_metrics::crash.submit(reason);
Ok(())
}
/// Set whether upload is enabled or not. pubfn set_collection_enabled(enabled: bool) {
glean::set_collection_enabled(enabled);
}
/// **Test-only API** /// /// Register a callback that will be called before the next ping is sent. pubfn test_before_next_send<F: FnOnce(Option<&str>) + Send + 'static>(cb: F) {
glean_metrics::crash.test_before_next_submit(cb);
}
/// **Test-only API** /// /// Get all metric values as a JSON object. pubfn test_get_metric_values() -> serde_json::Value { letmut ret: serde_json::Map<String, serde_json::Value> = Default::default(); for annotation in ANNOTATIONS { iflet Some(value) = (annotation.test_get_glean_value)() {
ret.insert(annotation.glean_key.into(), value);
}
}
ret.into()
}
/// Set Glean metrics from the given annotations. fn set_metrics_from_annotations(annotations: &serde_json::Value) -> anyhow::Result<()> { for annotation in ANNOTATIONS { iflet Some(value) = annotations.get(annotation.key) {
(annotation.set_glean_metric)(value)?;
}
}
Ok(())
}
#[cfg(test)] mod test { usesuper::{send, test_before_next_send, ANNOTATIONS}; use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
Arc, Mutex,
};
/// Run a test that uses Glean. /// /// This function ensures that Glean tests run sequentially. fn glean_test<F: FnOnce() + std::panic::UnwindSafe>(f: F) { let _ = env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.is_test(true)
.try_init(); let res = { let _guard = test_init_glean(); // Catch panics so that we don't poison the mutex (so other tests can run).
std::panic::catch_unwind(f)
}; iflet Err(e) = res {
std::panic::resume_unwind(e);
}
}
// For convenience, automatically populate example values for the simple cases. for annotation in ANNOTATIONS { if !annotations
.as_object()
.unwrap()
.contains_key(annotation.key)
{ let default_val: Option<serde_json::Value> = match annotation.convert_fn { "convert_boolean_to_boolean" => Some("1".into()), "convert_string_to_string" => Some("some_string".into()), "convert_u64_to_quantity" => Some("42".into()),
_ => None,
}; iflet Some(val) = default_val {
annotations
.as_object_mut()
.unwrap()
.insert(annotation.key.to_owned(), val);
}
}
}
let success = SoftAssert::new(false, "one or more failures occurred");
// Ensure all annotations have a test value. for annotation in ANNOTATIONS {
success.assert(
annotations
.as_object()
.unwrap()
.contains_key(annotation.key),
format!("{} test value is not set", annotation.key),
);
}
// Ensure all metrics are set. let metrics_tested = SoftAssert::new(true, "test_before_next_send did not run");
{ let success = success.clone(); let metrics_tested = metrics_tested.clone();
test_before_next_send(move |_| { for annotation in ANNOTATIONS {
success.assert(
(annotation.test_get_glean_value)().is_some(),
format!("{} not set", annotation.glean_key),
);
}
metrics_tested.clear();
});
}
send(&annotations, Some("crash")).expect("failed to set metrics");
});
}
let sent = SoftAssert::new(true, "test_before_next_send did not run"); let check = SoftAssert::new(false, "annotation check failed"); let sent_inner = sent.clone(); let check_inner = check.clone(); let input_str = annotations.to_string();
test_before_next_send(move |_| {
sent_inner.clear(); // Use a SoftAssert rather than `assert_eq!` so that we don't panic in the callback // (which will poison the mutex that Glean uses, making other tests fail // unnecessarily). let actual = (annotation.test_get_glean_value)(); iflet Some(actual) = actual {
check_inner.assert(actual == expected, "value mismatch");
} else {
check_inner.assert( false,
format!( "missing value for {} with input {}",
annotation.glean_key, input_str,
),
);
}
});
send(&annotations, Some("crash")).expect("failed to set metrics");
});
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.